Web 组件

¥Web Components

Preact 的微小尺寸和标准优先的方法使其成为构建 Web 组件的绝佳选择。

¥Preact's tiny size and standards-first approach make it a great choice for building web components.

Web 组件是一组标准,可以构建新的 HTML 元素类型 - 自定义元素,例如 <material-card><tab-bar>。Preact 完全支持这些标准,允许无缝使用自定义元素生命周期、属性和事件。

¥Web Components are a set of standards that make it possible to build new HTML element types - Custom Elements like <material-card> or <tab-bar>. Preact fully supports these standards, allowing seamless use of Custom Element lifecycles, properties and events.

Preact 旨在渲染完整的应用和页面的各个部分,使其非常适合构建 Web 组件。许多公司使用它来构建组件或设计系统,然后将其封装到一组 Web 组件中,从而实现跨多个项目和其他框架的重用。

¥Preact is designed to render both full applications and individual parts of a page, making it a natural fit for building Web Components. Many companies use it to build component or design systems that are then wrapped up into a set of Web Components, enabling re-use across multiple projects and within other frameworks.

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-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')} />

创建 Web 组件

¥Creating a Web Component

任何 Preact 组件都可以通过 preact-custom-element 转换为 Web 组件,preact-custom-element 是一个遵循 Custom Elements v1 规范的非常薄的封装器。

¥Any Preact component can be turned into a web component with preact-custom-element, a very thin wrapper that adheres to the Custom Elements v1 spec.

import register from 'preact-custom-element';

const Greeting = ({ name = 'World' }) => (
  <p>Hello, {name}!</p>
);

register(Greeting, 'x-greeting', ['name'], { shadow: false });
//          ^            ^           ^             ^
//          |      HTML tag name     |       use shadow-dom
//   Component definition      Observed attributes

注意:根据 自定义元素规范,标签名称必须包含连字符 (-)。

¥Note: As per the Custom Element Specification, the tag name must contain a hyphen (-).

在 HTML 中使用新的标签名称,属性键和值将作为 props 传入:

¥Use the new tag name in HTML, attribute keys and values will be passed in as props:

<x-greeting name="Billy Jo"></x-greeting>

输出:

¥Output:

<p>Hello, Billy Jo!</p>

观察到的属性

¥Observed Attributes

Web 组件需要显式列出你想要观察的属性名称,以便在其值更改时做出响应。这些可以通过传递给 register() 函数的第三个参数来指定:

¥Web Components require explicitly listing the names of attributes you want to observe in order to respond when their values are changed. These can be specified via the third parameter that's passed to the register() function:

// Listen to changes to the `name` attribute
register(Greeting, 'x-greeting', ['name']);

如果省略 register() 的第三个参数,则可以使用组件上的静态 observedAttributes 属性来指定要观察的属性列表。这也适用于自定义元素的名称,可以使用 tagName 静态属性指定:

¥If you omit the third parameter to register(), the list of attributes to observe can be specified using a static observedAttributes property on your Component. This also works for the Custom Element's name, which can be specified using a tagName static property:

import register from 'preact-custom-element';

// <x-greeting name="Bo"></x-greeting>
class Greeting extends Component {
  // Register as <x-greeting>:
  static tagName = 'x-greeting';

  // Track these attributes:
  static observedAttributes = ['name'];

  render({ name }) {
    return <p>Hello, {name}!</p>;
  }
}
register(Greeting);

如果未指定 observedAttributes,则将从 propTypes 的键(如果存在于组件上)推断它们:

¥If no observedAttributes are specified, they will be inferred from the keys of propTypes if present on the Component:

// Other option: use PropTypes:
function FullName({ first, last }) {
  return <span>{first} {last}</span>
}

FullName.propTypes = {
  first: Object,   // you can use PropTypes, or this
  last: Object     // trick to define un-typed props.
};

register(FullName, 'full-name');

将插槽作为属性传递

¥Passing slots as props

register() 函数有第四个参数来传递选项;目前,仅支持 shadow 选项,它将影子 DOM 树附加到指定元素。启用后,这允许使用命名的 <slot> 元素将自定义元素的子元素转发到影子树中的特定位置。

¥The register() function has a fourth parameter to pass options; currently, only the shadow option is supported, which attaches a shadow DOM tree to the specified element. When enabled, this allows the use of named <slot> elements to forward the Custom Element's children to specific places in the shadow tree.

function TextSection({ heading, content }) {
    return (
        <div>
            <h1>{heading}</h1>
            <p>{content}</p>
        </div>
    );
}

register(TextSection, 'text-section', [], { shadow: true });

用法:

¥Usage:

<text-section>
  <span slot="heading">Nice heading</span>
  <span slot="content">Great content</span>
</text-section>
Preact 中文网 - 粤ICP备13048890号