Web 组件

¥Web Components

Web 组件是一组不同的技术,允许你创建可重用的、封装的自定义 HTML 元素,这些元素完全与框架无关。Web 组件的示例包括 <material-card><tab-bar> 等元素。

¥Web Components are a set of different technologies that allow you to create reusable, encapsulated custom HTML elements that are entirely framework-agnostic. Examples of Web Components include elements like <material-card> or <tab-bar>.

作为平台原语,Preact 完全支持 Web 组件 允许在你的 Preact 应用中无缝使用自定义元素的生命周期、属性和事件。

¥As a platform primitive, Preact fully supports Web Components, allowing seamless use of Custom Element lifecycles, properties, and events in your Preact apps.

Preact 和 Web Components 是互补技术:Web 组件提供了一组用于扩展浏览器的底层原语,而 Preact 提供了可以位于这些原语之上的高级组件模型。

¥Preact and Web Components are complementary technologies: Web Components provide a set of low-level primitives for extending the browser, and Preact provides a high-level component model that can sit atop those primitives.



渲染 Web 组件

¥Rendering Web Components

在 Preact 中,Web 组件的工作方式就像其他 DOM 元素一样。可以使用其注册的标签名称来渲染它们:

¥In Preact, web components work just like other DOM Elements. They can be rendered using their registered tag name:

customElements.define(
	'x-foo',
	class extends HTMLElement {
		// ...
	}
);

function Foo() {
	return <x-foo />;
}

特性和属性

¥Properties and Attributes

JSX 不提供区分属性和特性的方法。自定义元素通常依赖于自定义属性来支持设置无法表示为属性的复杂值。这在 Preact 中效果很好,因为渲染器通过检查受影响的 DOM 元素自动确定是否使用属性或特性设置值。当自定义元素为给定属性定义 setter 时,Preact 会检测到其存在并使用 setter 而不是属性。

¥JSX does not provide a way to differentiate between properties and attributes. Custom Elements generally rely on custom properties in order to support setting complex values that can't be expressed as attributes. This works well in Preact, because the renderer automatically determines whether to set values using a property or attribute by inspecting the affected DOM element. When a Custom Element defines a setter for a given property, Preact detects its existence and will use the setter instead of an attribute.

customElements.define(
	'context-menu',
	class extends HTMLElement {
		set position({ x, y }) {
			this.style.cssText = `left:${x}px; top:${y}px;`;
		}
	}
);

function Foo() {
	return <context-menu position={{ x: 10, y: 20 }}> ... </context-menu>;
}

注意:Preact 不对命名方案做出任何假设,也不会尝试将名称(在 JSX 或其他方式中)强制转换为 DOM 属性。如果自定义元素具有属性名 someProperty,则需要使用完全相同的大小写和拼写 (someProperty=...) 进行设置。someproperty=...some-property=... 不起作用。

¥Note: Preact makes no assumptions over naming schemes and will not attempt to coerce names, in JSX or otherwise, to DOM properties. If a custom element has a property name someProperty, then it will need to be set using that exact same capitalization and spelling (someProperty=...). someproperty=... or some-property=... will not work.

当使用 preact-render-to-string ("SSR") 渲染静态 HTML 时,像上面的对象这样的复杂属性值不会自动序列化。一旦静态 HTML 在客户端上水合,就会应用它们。

¥When rendering static HTML using preact-render-to-string ("SSR"), complex property values like the object above are not automatically serialized. They are applied once the static HTML is hydrated on the client.

访问实例方法

¥Accessing Instance Methods

为了能够访问自定义 Web 组件的实例,我们可以利用 refs

¥To be able to access the instance of your custom web component, we can leverage refs:

function Foo() {
	const myRef = useRef(null);

	useEffect(() => {
		if (myRef.current) {
			myRef.current.doSomething();
		}
	}, []);

	return <x-foo ref={myRef} />;
}

触发自定义事件

¥Triggering custom events

Preact 规范了标准内置 DOM 事件的大小写,这些事件通常区分大小写。这就是可以将 onChange 属性传递给 <input> 的原因,尽管实际事件名称是 "change"。自定义元素通常会触发自定义事件作为其公共 API 的一部分,但是无法知道可能会触发哪些自定义事件。为了确保 Preact 中无缝支持自定义元素,传递给 DOM 元素的无法识别的事件处理程序 props 会按照指定的大小写进行注册。

¥Preact normalizes the casing of standard built-in DOM Events, which are normally case-sensitive. This is the reason it's possible to pass an onChange prop to <input>, despite the actual event name being "change". Custom Elements often fire custom events as part of their public API, however there is no way to know what custom events might be fired. In order to ensure Custom Elements are seamlessly supported in Preact, unrecognized event handler props passed to a DOM Element are registered using their casing exactly as specified.

// Built-in DOM event: listens for a "click" event
<input onClick={() => console.log('click')} />

// Custom Element: listens for "TabChange" event (case-sensitive!)
<tab-bar onTabChange={() => console.log('tab change')} />

// Corrected: listens for "tabchange" event (lower-case)
<tab-bar ontabchange={() => console.log('tab change')} />