Web Components
Preact の小さなサイズと標準に準拠したアプローチは、Web コンポーネントを構築するのに最適な選択肢です。
Web Components は、新しい HTML 要素タイプ (<material-card>
や <tab-bar>
などのカスタム要素) を構築できるようにする一連の標準です。Preact は これらの標準を完全にサポートしており、カスタム要素のライフサイクル、プロパティ、およびイベントをシームレスに使用できます。
Preact は、完全なアプリケーションとページの一部をレンダリングするように設計されており、Web コンポーネントを構築するのに最適です。多くの企業が、複数のプロジェクトや他のフレームワーク内で再利用できるように、コンポーネントやデザインシステムを構築し、それを一連の Web コンポーネントにラップしています。
Preact と Web コンポーネントは補完的な技術です。Web コンポーネントはブラウザを拡張するための低レベルのプリミティブセットを提供し、Preact はそれらのプリミティブの上に配置できる高レベルのコンポーネントモデルを提供します。
Web コンポーネントのレンダリング
Preact では、Web コンポーネントは他の 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 がハイドレーションされると、それらが適用されます。
インスタンスメソッドへのアクセス
カスタム Web コンポーネントのインスタンスにアクセスできるようにするために、refs
を活用できます
function Foo() {
const myRef = useRef(null);
useEffect(() => {
if (myRef.current) {
myRef.current.doSomething();
}
}, []);
return <x-foo ref={myRef} />;
}
カスタムイベントのトリガー
Preact は、通常は大文字と小文字を区別する標準の組み込み DOM イベントの大文字と小文字を正規化します。これが、実際のイベント名が "change"
であるにもかかわらず、<input>
に onChange
プロップを渡すことができる理由です。カスタム要素は、パブリック API の一部としてカスタムイベントを発生させることがよくありますが、どのようなカスタムイベントが発生する可能性があるかを知る方法はありません。カスタム要素が Preact でシームレスにサポートされるようにするために、DOM 要素に渡される認識されないイベントハンドラープロップは、指定されたとおりに大文字と小文字を区別して登録されます。
// 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 コンポーネントの作成
Preact コンポーネントは、すべて preact-custom-element を使用して Web コンポーネントに変換できます。これは、カスタム要素 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 で新しいタグ名を使用します。属性キーと値は props として渡されます
<x-greeting name="Billy Jo"></x-greeting>
出力
<p>Hello, Billy Jo!</p>
監視対象の属性
Web コンポーネントでは、値が変更されたときに応答するために、監視する属性の名前を明示的にリストする必要があります。これらは、register()
関数に渡される 3 番目のパラメーターを介して指定できます
// Listen to changes to the `name` attribute
register(Greeting, 'x-greeting', ['name']);
register()
の 3 番目のパラメーターを省略した場合、監視する属性のリストは、コンポーネントの静的な 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');
スロットを props として渡す
register()
関数には、オプションを渡すための 4 番目のパラメーターがあります。現在、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>