說明
支持我們

Web 組件

Preact 的小巧尺寸和以標準為優先的方法,使其成為建置 Web 組件的絕佳選擇。

Web 組件是一組標準,讓建置新的 HTML 元素類型成為可能,例如 <material-card><tab-bar> 等自訂元素。Preact 完全支援這些標準,允許無縫使用自訂元素的生命週期、屬性和事件。

Preact 設計用於呈現完整的應用程式和網頁的個別部分,使其成為建置網頁元件的自然選擇。許多公司使用它來建置元件或設計系統,然後將其包裝成一組網頁元件,讓多個專案和在其他架構中重複使用。

Preact 和網頁元件是互補的技術:網頁元件提供一組用於擴充瀏覽器的低階原語,而 Preact 提供一個可以建立在這些原語之上的高階元件模型。



呈現網頁元件

在 Preact 中,網頁元件就像其他 DOM 元素一樣運作。它們可以使用已註冊的標籤名稱呈現

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

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

屬性和特徵

JSX 沒有提供區分屬性和特徵的方法。自訂元素通常依賴自訂屬性,以支援設定無法表示為特徵的複雜值。這在 Preact 中運作良好,因為呈現器會自動檢查受影響的 DOM 元素,以確定是否使用屬性或特徵設定值。當自訂元素為特定屬性定義設定器時,Preact 會偵測其存在,並使用設定器而不是特徵。

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 在用戶端水化後套用。

存取執行個體方法

若要存取自訂網路元件的執行個體,我們可以利用 refs

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

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

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

觸發自訂事件

Preact 會將標準內建 DOM 事件的大小寫標準化,這些事件通常會區分大小寫。這就是可以將 onChange prop 傳遞給 <input> 的原因,儘管實際的事件名稱為 "change"。自訂元素通常會在其公開 API 中觸發自訂事件,但無法得知可能觸發哪些自訂事件。為了確保 Preact 中無縫支援自訂元素,會使用與指定內容完全相同的大小寫,來註冊傳遞給 DOM 元素的未辨識事件處理常式 prop。

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

建立網路元件

任何 Preact 元件都可以透過 preact-custom-element 轉換為網路元件,這是一個非常精簡的包裝器,符合自訂元素 v1 規格。

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

注意:根據 自訂元素規格,標籤名稱必須包含連字號 (-)。

在 HTML 中使用新的標籤名稱,屬性鍵和值將傳遞為 prop

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

輸出

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

觀察的屬性

網路元件需要明確列出您想要觀察的屬性名稱,以便在變更其值時做出回應。這些屬性可以透過傳遞給 register() 函式的第三個參數來指定

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

如果您省略 register() 的第三個參數,可以使用元件上的靜態 observedAttributes 屬性來指定要觀察的屬性清單。這也適用於自訂元素的名稱,可以使用 tagName 靜態屬性來指定

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 鍵推論。

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

將槽傳遞為道具

register() 函式有第四個參數來傳遞選項。目前僅支援 shadow 選項,它會將影子 DOM 樹附加到指定的元素。啟用後,這允許使用命名 <slot> 元素將自訂元素的子項轉發到影子樹中的特定位置。

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

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

用法

<text-section>
  <span slot="heading">Nice heading</span>
  <span slot="content">Great content</span>
</text-section>