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>