API リファレンス
Interactivity API は WordPress 6.5以降でのみ利用できます。
Interactivity API を使用してブロックにインタラクションを追加するには、開発者は以下を利用します。
- ディレクティブ: マークアップに追加され、ブロックの DOM 要素に特定の動作を追加します。
- ストア: 動作に必要なロジックとデータ (ステート、アクション、副作用など) を格納します。
DOM 要素は、ディレクティブを通して、ステートやコンテキストに格納されたデータと接続されます。ステートやコンテキストのデータが変更されると、ディレクティブはその変更に反応し、DOM を更新します (図 を参照)。
ディレクティブとは ?
ディレクティブはブロックのマークアップに追加されるカスタム属性で、ブロックの DOM 要素に動作を追加します。これは render.php
ファイル (動的ブロックの場合)、または save.js
ファイル (静的ブロックの場合) の中で行われます。
Interactivity API ディレクティブは data-
接頭辞を使用します。以下は HTML マークアップで使用されるディレクティブの例です。
<div
data-wp-interactive="myPlugin"
data-wp-context='{ "isOpen": false }'
data-wp-watch="callbacks.logIsOpen"
>
<button
data-wp-on--click="actions.toggle"
data-wp-bind--aria-expanded="context.isOpen"
aria-controls="p-1"
>
Toggle
</button>
<p id="p-1" data-wp-bind--hidden="!context.isOpen">
This element is now visible!
</p>
</div>
ディレクティブは HTML タグプロセッサ を使用して動的にも注入できます。
ディレクティブを使用すると、副作用、ステート、イベントハンドラ、属性、コンテンツなどのインタラクションを直接管理できます。
ディレクティブ一覧
wp-interactive
wp-interactive
ディレクティブは Interactivity API (ディレクティブとストア) を通して、DOM 要素とその子要素のインタラクティブ機能を「有効化」します。ディレクティブは特定のストアを参照する名前空間を含み、 string
または object
として設定されます。
<!-- この要素とその子をインタラクティブにして、名前空間を設定する -->
<div
data-wp-interactive="myPlugin"
data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
<p>I'm interactive now, <span data-wp-style--background-color="context.myBgColor">and I can use directives!</span></p>
<div>
<p>I'm also interactive, <span data-wp-style--color="context.myColor">and I can also use directives!</span></p>
</div>
</div>
<!-- これも有効 -->
<div
data-wp-interactive='{ "namespace": "myPlugin" }'
data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
<p>I'm interactive now, <span data-wp-style--background-color="context.myBgColor">and I can use directives!</span></p>
<div>
<p>I'm also interactive, <span data-wp-style--color="context.myColor">and I can also use directives!</span></p>
</div>
</div>
data-wp-interactive
の使用は、Interactivity API「エンジン」の動作ための必須条件です。ただしこの文書の後続の例では簡便のためdata-wp-interactive
を省略します。なお、将来的には、data-wp-interactive
ディレクティブは自動的に注入される予定です。
wp-context
特定の HTML ノードとその子ノードで利用可能な ローカル のステートを提供します。
wp-context
ディレクティブは、値として文字列化された JSON を受け取ります。
// render.php
<div data-wp-context='{ "post": { "id": <?php echo $post->ID; ?> } }' >
<button data-wp-on--click="actions.logId" >
Click Me!
</button>
</div>
// view.js
store( "myPlugin", {
actions: {
logId: () => {
const { post } = getContext();
console.log( post.id );
},
},
} );
異なるレベルで異なるコンテキストを定義することができ、より深いレベルでは、自身のコンテキストを親のコンテキストにマージします。
<div data-wp-context='{ "foo": "bar" }'>
<span data-wp-text="context.foo"><!-- 出力: "bar" --></span>
<div data-wp-context='{ "bar": "baz" }'>
<span data-wp-text="context.foo"><!-- 出力: "bar" --></span>
<div data-wp-context='{ "foo": "bob" }'>
<span data-wp-text="context.foo"><!-- 出力: "bob" --></span>
</div>
</div>
</div>
wp-bind
このディレクティブは、真偽値や文字列値によって、要素に HTML 属性を設定できます。構文は data-wp-bind--attribute
です。
<li data-wp-context='{ "isMenuOpen": false }'>
<button
data-wp-on--click="actions.toggleMenu"
data-wp-bind--aria-expanded="context.isMenuOpen"
>
Toggle
</button>
<div data-wp-bind--hidden="!context.isMenuOpen">
<span>Title</span>
<ul>
SUBMENU ITEMS
</ul>
</div>
</li>
// view.js
store( "myPlugin", {
actions: {
toggleMenu: () => {
const context = getContext();
context.isMenuOpen = !context.isMenuOpen;
},
},
} );
wp-bind
ディレクティブは以下のタイミングで実行されます。
- 要素が作成されたとき
- ディレクティブの最終的な値を取得するために関係する、
state
またはcontext
のプロパティに変更があるたび (参照として渡されるコールバックまたは式の内部で)
wp-bind
ディレクティブが最終的な値を得るためにコールバックを参照するとき、
- このコールバック内で使用される
state
またはcontext
のプロパティが変更されるたびに、wp-bind
ディレクティブが実行されます。 - コールバック関数の戻り値は、関連付けられた属性の値を変更するために使用されます。
wp-bind
は DOM 要素が適用されたとき、その値によって異なる動作をします。
- 値が
true
なら、属性が追加されます:<div attribute>
- 値が
false
なら、属性は削除されます:<div>
- 値が文字列なら、属性が追加され、値が割り当てられます: <div attribute=”value”`
- 属性名が
aria-
またはdata-
で始まり、値が真偽値 (true
またはfalse
) なら、属性が DOM に追加され、真偽値は文字列として割り当てられます:<div aria-attribute="true">
wp-class
このディレクティブは、真偽値によって、HTML 要素にクラスを追加または削除します。構文は data-wp-class--classname
です。
<div>
<li
data-wp-context='{ "isSelected": false }'
data-wp-on--click="actions.toggleSelection"
data-wp-class--selected="context.isSelected"
>
Option 1
</li>
<li
data-wp-context='{ "isSelected": false }'
data-wp-on--click="actions.toggleSelection"
data-wp-class--selected="context.isSelected"
>
Option 2
</li>
</div>
// view.js
store( "myPlugin", {
actions: {
toggleSelection: () => {
const context = getContext();
context.isSelected = !context.isSelected
}
}
} );
wp-class
ディレクティブは以下のタイミングで実行されます。
- 要素が作成されたとき
- ディレクティブの最終的な値を取得するために関係する、
state
またはcontext
のプロパティに変更があるたび (参照として渡されるコールバックまたは式の内部で)
ディレクティブが受け取った真偽値は class
属性の、関連するクラス名のトグル (true
なら追加、false
なら削除) に使用されます。
重要な注意点として、wp-class
ディレクティブを使用する場合、クラス名にはキャメルケース (camelCase) ではなくケバブケース (kebab-case) を使用してください。これは HTML の属性が大文字小文字を区別せず、data-wp-class--isDark
は data-wp-class--isdark
や DATA-WP-CLASS--ISDARK
と同じように扱われるためです。
このため、例えば、クラス名には isDark
ではなく is-dark
を、data-wp-class--isDark
ではなく data-wp-class--is-dark
を使用してください。
<!-- 推奨 -->
<div data-wp-class--is-dark="context.isDarkMode">
<!-- ... -->
</div>
<!-- 非推奨 -->
<div data-wp-class--isDark="context.isDarkMode">
<!-- ... -->
</div>
/* 推奨 */
.is-dark {
/* ... */
}
/* 非推奨 */
.isDark {
/* ... */
}
wp-style
このディレクティブは、その値によって、HTML 要素に インラインスタイルを追加または削除します。構文は data-wp-style--css-property
です。
<div data-wp-context='{ "color": "red" }' >
<button data-wp-on--click="actions.toggleContextColor">Toggle Color Text</button>
<p data-wp-style--color="context.color">Hello World!</p>
</div>
>
store( "myPlugin", {
// view.js
actions: {
toggleContextColor: () => {
const context = getContext();
context.color = context.color === 'red' ? 'blue' : 'red';
},
},
} );
wp-style
ディレクティブは以下のタイミングで実行されます。
- 要素が作成されたとき
- ディレクティブの最終的な値を取得するために関係する、
state
またはcontext
のプロパティに変更があるたび (参照として渡されるコールバックまたは式の内部で)
ディレクティブが受け取った値は、関連する CSS プロパティで style 属性の追加、または削除に使われます。
- 値が
false
なら、style 属性は削除されます:<div>
- 値が文字列なら、属性が追加され、その値が割り当てられます:
<div style="css-property: value;">
wp-text
HTML 要素の内部テキストを設定します。
<div data-wp-context='{ "text": "Text 1" }'>
<span data-wp-text="context.text"></span>
<button data-wp-on--click="actions.toggleContextText">
Toggle Context Text
</button>
</div>
// view.js
store( "myPlugin", {
actions: {
toggleContextText: () => {
const context = getContext();
context.text = context.text === 'Text 1' ? 'Text 2' : 'Text 1';
},
},
} );
wp-text
ディレクティブは以下のタイミングで実行されます。
- 要素が作成されたとき
- ディレクティブの最終的な値を取得するために関係する、
state
またはcontext
のプロパティに変更があるたび (参照として渡されるコールバックまたは式の内部で)
戻り値は、要素の内部コンテンツの変更に使用されます: <div>value</div>
wp-on
もしもディレクティブのコードがイベントオブジェクトへの同期アクセスを必要としなければ、パフォーマンスに優れた
wp-on-async
の使用を検討してください。また同期アクセスが必要な場合も、同期 API を呼び出した後にメインスレッドへ yield する「async アクション
」 (後述) の実装を検討してください。
このディレクティブはディスパッチされた click
や keyup
などの DOM イベントに対してコードを実行します。構文は data-wp-on--[event]
です (例: data-wp-on--click
や data-wp-on--keyup
)。
<button data-wp-on--click="actions.logTime" >
Click Me!
</button>
// view.js
store( "myPlugin", {
actions: {
logTime: ( event ) => {
console.log( new Date() )
},
},
} );
wp-on
ディレクティブは、関連するイベントがトリガーされるたびに実行されます。
コールバックは参照として渡され、イベント (event
) を受け取ります。コールバックが返す値は無視されます。
wp-on-async
このディレクティブは wp-on
よりもパフォーマンスに優れたアプローチです。直ちにメインスレッドに yield して、時間のかかるタスクの待ちを回避し、メインスレッドで待機している他の処理がより早く実行できるようにします。event
オブジェクトに同期的にアクセスする必要がない場合、特に event.preventDefault()
、event.stopPropagation()
、event.stopImmediatePropagation()
メソッドでは、この非同期バージョンを使用してください。
wp-on-window
もしもディレクティブのコードがイベントオブジェクトへの同期アクセスを必要としなければ、パフォーマンスに優れた
wp-on-async-window
の使用を検討してください。また同期アクセスが必要な場合も、同期 API を呼び出した後にメインスレッドへ yield する「async アクション
」 (後述) の実装を検討してください。
このディレクティブを使用すると、resize
、copy
、focus
などのグローバルウィンドウイベントにアタッチし、それらが起きたときに、定義されたコールバックを実行できます。
リンク: サポートするウィンドウイベントのリスト
このディレクティブの構文は data-wp-on-window--[window-event]
です (data-wp-on-window--resize
or data-wp-on-window--languagechange
)。
<div data-wp-on-window--resize="callbacks.logWidth"></div>
// view.js
store( "myPlugin", {
callbacks: {
logWidth() {
console.log( 'Window width: ', window.innerWidth );
},
},
} );
コールバックは参照として渡され、イベント (event
) を受け取ります。コールバックが返す値は無視されます。要素が DOM から削除されると、イベントリスナーも削除されます。
wp-on-async-window
wp-on-async
と同じように、このディレクティブも wp-on-window
の最適化バージョンで、直ちにメインスレッドに yield して、時間のかかるタスクの待ちを回避します。event
オブジェクトに同期的にアクセスする必要がない場合、特に event.preventDefault()
、event.stopPropagation()
、event.stopImmediatePropagation()
メソッドでは、この非同期バージョンを使用してください。またこのイベントリスナーは passive
として追加されます。
wp-on-document
もしもディレクティブのコードがイベントオブジェクトへの同期アクセスを必要としなければ、パフォーマンスに優れた
wp-on-async-document
の使用を検討してください。また同期アクセスが必要な場合も、同期 API を呼び出した後にメインスレッドへ yield する「async アクション
」 (後述) の実装を検討してください。
このディレクティブを使用すると、scroll
、mousemove
、keydown
などのグローバルドキュメントイベントにアタッチし、それらが起きたときに、定義されたコールバックを実行できます。
リンク: サポートするドキュメントイベントのリスト
このディレクティブの構文は data-wp-on-document--[document-event]
です (例: data-wp-on-document--keydown
、data-wp-on-document--selectionchange
)
<div data-wp-on-document--keydown="callbacks.logKeydown"></div>
// view.js
store( "myPlugin", {
callbacks: {
logKeydown(event) {
console.log( 'Key pressed: ', event.key );
},
},
} );
コールバックは参照として渡され、イベント (event
) を受け取ります。コールバックが返す値は無視されます。要素が DOM から削除されると、イベントリスナーも削除されます。
wp-on-async-document
wp-on-async
と同じように、このディレクティブも wp-on-document
の最適化バージョンで、直ちにメインスレッドに yield して、時間のかかるタスクの待ちを回避します。event
オブジェクトに同期的にアクセスする必要がない場合、特に event.preventDefault()
、event.stopPropagation()
、event.stopImmediatePropagation()
メソッドでは、この非同期バージョンを使用してください。またこのイベントリスナーは passive
として追加されます。
wp-watch
このディレクティブは、ノードが作成されるときに コールバックを実行し、ステートやコンテキストが変わったとき に再度実行します。
構文 data-wp-watch--[unique-id]
を使うことで、同じ DOM 要素に対して、複数の副作用をアタッチできます。
unique-id
がグローバルに一意である必要はありません。その DOM 要素の他の wp-watch
ディレクティブの ID と異なりさえすれば十分です。
<div
data-wp-context='{ "counter": 0 }'
data-wp-watch="callbacks.logCounter"
>
<p>Counter: <span data-wp-text="context.counter"></span></p>
<button data-wp-on--click="actions.increaseCounter">+</button>
<button data-wp-on--click="actions.decreaseCounter">-</button>
</div>
// view.js
store( "myPlugin", {
actions: {
increaseCounter: () => {
const context = getContext();
context.counter++;
},
decreaseCounter: () => {
const context = getContext();
context.counter--;
},
},
callbacks: {
logCounter: () => {
const { counter } = getContext();
console.log("Counter is " + counter + " at " + new Date() );
},
},
} );
wp-watch
ディレクティブは以下のタイミングで実行されます。
- 要素が作成されたとき
- コールバックの内部で使用されている
state
またはcontext
の任意のプロパティのいずれかが変更されるたび
wp-watch
ディレクティブは関数を返すことができます。このとき、返された関数はクリーンアップロジックとして使用されます。すなわち、コールバックが再び実行される直前と、要素が DOM から削除されるときに実行されます。
参考として、このディレクティブの使用例をいくつか挙げます。
- ロギング
- ページのタイトルの変更
- 要素に
.focus()
でフォーカスを設定 - 特定の条件が満たされたときにステートやコンテキストを変更
wp-init
このディレクティブは ノードが作成されたときのみ コールバックを実行します。
構文 data-wp-init--[unique-id]
を使うことで、同じ DOM 要素に対して、複数の wp-init
をアタッチできます。 です。
unique-id
がグローバルに一意である必要はありません。その DOM 要素の他の wp-init
ディレクティブの ID と異なりさえすれば十分です。
<div data-wp-init="callbacks.logTimeInit">
<p>Hi!</p>
</div>
同じ DOM 要素に複数の wp-init
ディレクティブをアタッチする例です。
<form
data-wp-init--log="callbacks.logTimeInit"
data-wp-init--focus="callbacks.focusFirstElement"
>
<input type="text">
</form>
// view.js
import { store, getElement } from '@wordpress/interactivity';
store( "myPlugin", {
callbacks: {
logTimeInit: () => console.log( `Init at ` + new Date() ),
focusFirstElement: () => {
const { ref } = getElement();
ref.querySelector( 'input:first-child' ).focus(),
},
},
} );
wp-init
は関数を返すことができます。このとき、返された関数は、要素が DOM から削除されたときに実行されます。
wp-run
このディレクティブは ノードのレンダー実行中に 渡されたコールバックを実行します。
渡されるコールバックの中では useState
、useWatch
、useEffect
などのフックを使用し、組み合わせて、独自のロジックを作成できます。このため、これまでのディレクティブよりも柔軟性に優れます。
構文 data-wp-run--[unique-id]
を使うことで、同じ DOM 要素に対して、複数の wp-run
をアタッチできます。
unique-id
がグローバルに一意である必要はありません。その DOM 要素の他の wp-run
ディレクティブの ID と異なりさえすれば十分です。
<div data-wp-run="callbacks.logInView">
<p>Hi!</p>
</div>
// view.js
import { getElement, store, useState, useEffect } from '@wordpress/interactivity';
// `data-wp-init` や `data-wp-watch` と異なり、`data-wp-run` コールバック内では、
// 任意のフックを使用できます。
const useInView = () => {
const [ inView, setInView ] = useState( false );
useEffect( () => {
const { ref } = getElement();
const observer = new IntersectionObserver( ( [ entry ] ) => {
setInView( entry.isIntersecting );
} );
observer.observe( ref );
return () => ref && observer.unobserve( ref );
}, []);
return inView;
};
store( 'myPlugin', {
callbacks: {
logInView: () => {
const isInView = useInView();
useEffect( () => {
if ( isInView ) {
console.log( 'Inside' );
} else {
console.log( 'Outside' );
}
});
}
},
} );
重要な注意点として、(P)Reactコンポーネント同様、最初のレンダーでは getElement()
の ref
は null
です。DOM 要素の参照に適切にアクセスするには、通常 useEffect
、useInit
、useWatch
などのエフェクト系のフックを使用する必要があります。これにより、コンポーネントがマウントされ、ref
が利用できるようになった後で、getElement()
が実行されることが保証されます。
wp-key
wp-key
ディレクティブは要素に一意のキーを割り当て、Interactivity API が要素の配列を反復処理する際に、その要素を識別しやすくします。これは、配列の要素がソートなどによって移動、挿入、削除される場合に重要になります。キー値を適切に選択することで、Interactivity API は配列内で何が変更されたかを推測し、正しく DOM を更新できるようになります。
キーは、兄弟要素の中でその要素を一意に識別する文字列でなければなりません。一般的には、リスト項目のような繰り返し要素で使用されます。例えば
<ul>
<li data-wp-key="unique-id-1">Item 1</li>
<li data-wp-key="unique-id-2">Item 2</li>
</ul>
しかし、他の要素でも使用できます。
<div>
<a data-wp-key="previous-page" ...>Previous page</a>
<a data-wp-key="next-page" ...>Next page</a>
</div>
リストが再度、レンダーされるとき、Interactivity API はキーによって要素を照合し、項目が追加、削除、並べ替えられたかどうかを判断します。要素にキーがなければ、不必要に再作成される可能性があります。
wp-each
wp-each
ディレクティブは要素のリストの表示を目的とします。このディレクティブは <template>
タグの中で使用でき、値はグローバルステートまたはコンテキストに保存された配列へのパスとなります。<template>
タグ内のコンテンツは、各項目のレンダーに使用されるテンプレートです。
各項目は、コンテキスト内にデフォルト名 item
で含まれるため、テンプレート内のディレクティブはこの名前を使用して現在の項目にアクセスできます。
例えば、次の HTML を考えます。
<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
<template data-wp-each="context.list" >
<li data-wp-text="context.item"></li>
</template>
</ul>
これは以下の出力を生成します。
<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
<li data-wp-text="context.item">hello</li>
<li data-wp-text="context.item">hola</li>
<li data-wp-text="context.item">olá</li>
</ul>
コンテキストの項目を保持する prop は、ディレクティブ名に接尾辞を付けることで変更できます。以下の例では、デフォルトの prop は item
から greeting
に変更されます。
<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
<template data-wp-each--greeting="context.list" >
<li data-wp-text="context.greeting"></li>
</template>
</ul>
デフォルトでは、各要素をレンダーするノードのキーとして使用しますが、リストにオブジェクトが含まれている場合など、必要に応じてキーを取得するパスを指定できます。
そのためには、<template>
タグの中、テンプレートのコンテンツの中で、data-wp-key
ではなく、data-wp-each-key
を使う必要があります。なぜなら、data-wp-each
はレンダーされる各アイテムの周りにコンテキストプロバイダのラッパーを作成し、そのラッパーが key
プロパティを必要とするためです。
<ul data-wp-context='{
"list": [
{ "id": "en", "value": "hello" },
{ "id": "es", "value": "hola" },
{ "id": "pt", "value": "olá" }
]
}'>
<template
data-wp-each--greeting="context.list"
data-wp-each-key="context.greeting.id"
>
<li data-wp-text="context.greeting.value"></li>
</template>
</ul>
wp-each-child
サーバー側でレンダーされるリストでは、別のディレクティブ data-wp-each-child
が、期待通りにハイドレーションが動作することを保証します。このディレクティブはディレクティブがサーバで処理されるときに自動的に追加されます。
<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
<template data-wp-each--greeting="context.list" >
<li data-wp-text="context.greeting"></li>
</template>
<li data-wp-each-child>hello</li>
<li data-wp-each-child>hola</li>
<li data-wp-each-child>olá</li>
</ul>
ディレクティブの値は、ストアのプロパティへの参照
ディレクティブに割り当てられる値は、特定のステート、アクション、副作用を指す文字列です。
以下の例では、state.isPlaying
の派生値を定義するために getter を使用しています。
// view.js
const { state } = store( "myPlugin", {
state: {
currentVideo: '',
get isPlaying() {
return state.currentVideo !== '';
}
},
} );
そして、文字列値 "state.isPlaying"
を使用して、このセレクタの結果を data-wp-bind--hidden
に割り当てます。
<div data-wp-bind--hidden="!state.isPlaying" ... >
<iframe ...></iframe>
</div>
ディレクティブに割り当てられたこれらの値は、ストアの特定のプロパティへの 参照 です。これらはディレクティブに自動的に接続され、追加の設定をしなくても各ディレクティブは、ストアの要素が何を参照しているかを「知る」 ようになります。
注意点として参照は、デフォルトで現在の名前空間のプロパティを指します。名前空間は、data-wp-interactive
属性を持った最も近い祖先によって指定されます。別の名前空間からプロパティにアクセスするには、アクセスするプロパティが定義されている名前空間を明示的に設定します。構文は namespace::reference
で、namespace
を適切な値で置き換えます。
以下の例では、state.isPlaying
を myPlugin
ではなく、otherPlugin
から取得しています。
<div data-wp-interactive="myPlugin">
<div data-wp-bind--hidden="otherPlugin::!state.isPlaying" ... >
<iframe ...></iframe>
</div>
</div>
ストア
ストアは、ディレクティブにリンクされたロジック (アクション、副作用など) と、そのロジックの内部で使用されるデータ (ステート、派生ステートなど) の作成に使用されます。
ストアは通常、各ブロックの view.js
ファイルで作成されます が、ブロックの render.php
からも状態を初期化できます。
ストアの要素
ステート
ページの HTML ノードが利用できるデータを定義します。データを定義する2つの方法を区別することが重要です。
- グローバルステート:
store()
関数でstate
プロパティを指定して定義され、データはページのすべての HTML ノードで利用できます。 - コンテキスト / ローカル状態: HTML ノードで
data-wp-context
ディレクティブを使用して定義され、その HTML ノードと子ノードで利用できます。アクション、派生ステート、副作用の内部でgetContext
関数を使用してアクセスできます。
<div data-wp-context='{ "someText": "Hello World!" }'>
<!-- グローバルステートへアクセス -->
<span data-wp-text="state.someText"></span>
<!-- ローカルステート (コンテキスト) へアクセス -->
<span data-wp-text="context.someText"></span>
</div>
const { state } = store( "myPlugin", {
state: {
someText: "Hello Universe!"
},
actions: {
someAction: () => {
state.someText // アクセス、変更先はグローバルステート - "Hello Universe!"
const context = getContext();
context.someText // アクセス、変更先はローカルステート (コンテキスト) - "Hello World!"
},
},
} )
アクション
アクションは普通の JavaScript 関数に過ぎません。通常、data-wp-on
ディレクティブ (イベントリスナーを使用する) や他のアクションによって起動されます。
// TypeScript
const { state, actions } = store("myPlugin", {
actions: {
selectItem: ( id ) => {
const context = getContext();
// ここで `id` はオプションです。したがってこのアクションはディレクティブでも使用できます。
state.selected = id || context.id;
},
otherAction: () => {
// しかし、他のアクションからも呼び出せます。
actions.selectItem(123); // これは動作し、型も正しい
}
}
});
非同期アクション
非同期アクションは、async/await ではなく、ジェネレーターを使用してください。
非同期関数では制御も関数自体に渡されます。関数の呼び出し側には関数が待ち状態なのかどうか、さらに重要な点として、待ち状態が解消 (resolve) し、関数が実行を再開したかどうかをを知る方法がありません。スコープを復元するには情報が必要です。
ここで2つのボタンを持つブロックを考えます。一つは isOpen: true
、もう一つは isOpen: false
のコンテキストの中にあります。
<div data-wp-context='{ "isOpen": true }'>
<button data-wp-on--click="actions.someAction">Click</button>
</div>
<div data-wp-context='{ "isOpen": false }'>
<button data-wp-on--click="actions.someAction">Click</button>
</div>
アクションが async で、長い遅延を await するとします。このとき
- ユーザーが最初のボタンをクリックする。
- スコープは最初のコンテキストを指し、
isOpen: true
となる。 state.isOpen
への最初のアクセスは正しい。なぜならgetContext
は現在のスコープを返すため。- アクションは長く遅延する await を開始する。
- アクションが再開する前に、ユーザーが2番目のボタンをクリックする。
- スコープは2番目のコンテキストに変更され、
isOpen: false
となる。 state.isOpen
への最初のアクセスは正しい。なぜならgetContext
は現在のスコープを返すため。- 2番目のアクションは長く遅延する await を開始する。
- 最初のアクションが await を終え、実行を再開する。
- 最初のアクションの
state.isOpen
への2回目のアクセスは正しくない。なぜならここでgetContext
は誤ったスコープを返すため。
非同期アクションが、いつ await を開始したのか、いつ操作を再開したのかを知る必要があります。それが分かれば適切なスコープを復元できます。ジェネレーターはこれを実行します。
以下のように書けば、ストアは正しく動作します。
const { state } = store("myPlugin", {
state: {
get isOpen() {
return getContext().isOpen;
},
},
actions: {
someAction: function* () {
state.isOpen; // このコンテキストは正しい。なぜなら同期のため。
yield longDelay(); // ジェネレーターを利用して、呼び出し側はこの関数にいつ戻ったかを制御する。
state.isOpen; // このコンテキストは正しい。なぜなら関数を再開する前に適切なスコープを復元したため。
},
},
});
上の wp-on
、wp-on-window
、wp-on-document
でも触れたように、アクションが event
オブジェクトへの同期アクセスを必要とするためにこれらのディレクティブの async
バージョンを使用できない場合は、常に非同期アクションを使用する必要があります。アクションが event.preventDefault()
、event.stopPropagation()
、event.stopImmediatePropagation()
を呼び出す必要がある場合は、常に同期アクセスが必要です。アクションのコードが時間のかかるタスクに寄与しないよう、同期イベント API の呼び出し後に、メインスレッドに手動で yield できます。例えば
// 注意: WordPress 6.6では、この splitTask 関数は @wordpress/interactivity でエクスポートされます。
function splitTask() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
store("myPlugin", {
actions: {
handleClick: function* (event) {
event.preventDefault();
yield splitTask();
doTheWork();
},
},
});
多数のタスクの実行が必要であれば、アクション内にこのような yield
ポイントを複数追加できます。
副作業
ステートの変化に自動的に反応します。通常は data-wp-watch
または data-wp-init
ディレクティブによって起動されます。
派生ステート
ステートの計算されたバージョンを返します。state
と context
の両方にアクセスできます。
// view.js
const { state } = store( "myPlugin", {
state: {
amount: 34,
defaultCurrency: 'EUR',
currencyExchange: {
USD: 1.1,
GBP: 0.85,
},
get amountInUSD() {
return state.currencyExchange[ 'USD' ] * state.amount;
},
get amountInGBP() {
return state.currencyExchange[ 'GBP' ] * state.amount;
},
},
} );
コールバック内でのデータアクセス
store
には state
、action
、callbacks
などのストアのすべてのプロパティが含まれています。store()
をコールするとこれらが返るため、分割することでアクセスできます。
const { state, actions } = store( "myPlugin", {
// ...
} );
store()
関数は複数回呼び出すことができ、すべての store 部分がマージされます。
store( "myPlugin", {
state: {
someValue: 1,
}
} );
const { state } = store( "myPlugin", {
actions: {
someAction() {
state.someValue // = 1
}
}
} );
同じ名前空間を持つすべての
store()
呼び出しは、同じ参照、つまり、同じstate
、actions
等、渡されたすべてのストア部分のマージした結果を返します。
- アクション、派生ステート、副作用の中でコンテキストにアクセスするには、
getContext
関数を使用します。 - 参照にアクセスするには、
getElement
関数を使用します。
const { state } = store( "myPlugin", {
state: {
get someDerivedValue() {
const context = getContext();
const { ref } = getElement();
// ...
}
},
actions: {
someAction() {
const context = getContext();
const { ref } = getElement();
// ...
}
},
callbacks: {
someEffect() {
const context = getContext();
const { ref } = getElement();
// ...
}
}
} );
このアプローチは、ディレクティブを柔軟で強力なものにする、以下のような機能を可能にします。
- アクションと副作用は、ステートとコンテキストを読み取り、変更できる。
- ブロック内のアクションとステートは、他のブロックからアクセスできる。
- アクションと副作用は、通常の JavaScript 関数ができることは何でもできる。例えば、DOM へのアクセスや API リクエストなど。
- 副作用は、ステートの変更に自動的に反応する。
ストアの設定
クライアント側にて
各ブロックの view.js
ファイルの中 で、開発者はステートと、ストアの要素の両方を定義できます。ストアの要素はアクション、副作用、派生ステートなどの関数を参照します。
JavaScript でストアを設定する store
メソッドは、@wordpress/interactivity
からインポートできます。
// store
import { store, getContext } from '@wordpress/interactivity';
store( "myPlugin", {
actions: {
toggle: () => {
const context = getContext();
context.isOpen = !context.isOpen;
},
},
callbacks: {
logIsOpen: () => {
const { isOpen } = getContext();
// `isOpen` が変更されるたびに、その値をログする。
console.log( `Is open: ${ isOpen }` );
}
},
});
サーバー側にて
wp_interactivity_state()
関数を使用すると、ステートはサーバー上でも初期化できます。この関数は通常、ブロックの render.php
ファイルで呼び出します (render.php
テンプレートは WordPress 6.1で 導入 されました)。
wp_interactivity_state()
でサーバー上で定義されたステートは、view.js ファイルで定義されたストアにマージされます。
wp_interactivity_state
関数は2つの引数を受け取ります。参照として使用される名前空間の string
と、値を含む連想配列です。
サーバーで state
= { someValue: 123 }
で初期化されたストアの例
// render.php
wp_interactivity_state( 'myPlugin', array (
'someValue' => get_some_value()
));
サーバーで状態を初期化するということは、WordPress の API を利用できることも意味します。たとえば、Core 翻訳 API を使用して、ステートの一部を翻訳できます。
// render.php
wp_interactivity_state( 'favoriteMovies', array(
"1" => array(
"id" => "123-abc",
"movieName" => __("someMovieName", "textdomain")
),
) );
プライベートストア
指定するストアの名前空間をプライベートとマークすると、他の名前空間からそのコンテンツにアクセスできなくなります。これには、以下の例のように store()
呼び出しに lock
オプションを追加します。すると、この名前空間には最初の store()
呼び出しで返された参照からのみアクセスでき、以後、同じ名前空間を指定した store()
の実行ではエラーが発生します。これはプラグイン開発者にとって特に便利な機能で、プラグイン内のストアの一部を、プラグインのエクステンダーからアクセスできないように隠せます。
const { state } = store(
"myPlugin/private",
{ state: { messages: [ "private message" ] } },
{ lock: true }
);
// 以下の呼び出しではエラーが発生する
store( "myPlugin/private", { /* ストア部分 */ } );
プライベートストアのロックの解除には別の方法もあります。それには、真偽値を渡す代わりに、文字列を lock
値として使用します。この文字列を、同じ名前空間に対する後続の store()
呼び出しで使用すれば、コンテンツのロックを解除できます。文字列ロックを知っているコードだけが、保護されたストアの名前空間のロックを解除できます。これは、複数の JavaScript モジュールで定義された複雑なストアで便利です。
const { state } = store(
"myPlugin/private",
{ state: { messages: [ "private message" ] } },
{ lock: PRIVATE_LOCK }
);
// 以下の呼び出しは期待どおりに動く
store( "myPlugin/private", { /* ストア部分 */ }, { lock: PRIVATE_LOCK } );
ストアのクライアントメソッド
store 関数以外に、開発者が store 関数のデータにアクセスするためのメソッドもあります。
- getContext()
- getElement()
getContext()
関数を評価する要素が継承したコンテキストをストアから取得します。戻り値は要素と getContext()
を呼び出した関数が存在する名前空間に依存します。オプションで名前空間を引数に取り、特定の interactive 領域のコンテキストを取得できます。
const context = getContext('namespace');
namespace
(オプション): interactive 領域の名前空間と合致する文字列。指定がなければ、現行の interactive 領域のコンテキストを取得します。
// render.php
<div data-wp-interactive="myPlugin" data-wp-context='{ "isOpen": false }'>
<button data-wp-on--click="actions.log">Log</button>
</div>
// store
import { store, getContext } from '@wordpress/interactivity';
store( "myPlugin", {
actions: {
log: () => {
const context = getContext();
// "false" をログ
console.log('context => ', context.isOpen)
// With namespace argument.
const myPluginContext = getContext("myPlugin");
// Logs "false"
console.log('myPlugin isOpen => ', myPluginContext.isOpen);
},
},
});
getElement()
アクションがバインドされた要素、または呼び出された要素の表現を取得します。この表現は読み込み専用で、DOM 要素への参照、その prop、ローカルのリアクティブなステートを含みます。 2つのキーを持つオブジェクトを返します。
ref
ref
は HTMLElement としての DOM 要素への参照です。Preact や React の useRef
と等価で、ref
がまだ実際の DOM 要素にアタッチされていないとき、たとえばハイドレーションやマウントされているときには null
になります。
attributes
attributes
には Proxy が含まれ、他のストアの名前空間を参照できる getter を追加します。コード内の getter を確認してみてください。リンク
これらの属性は、その要素のディレクティブを含みます。ボタンの例では
// store
import { store, getElement } from '@wordpress/interactivity';
store( "myPlugin", {
actions: {
log: () => {
const element = getElement();
// Logs attributes
console.log('element attributes => ', element.attributes)
},
},
});
コードはログに以下を出力します。
{
"data-wp-on--click": 'actions.log',
"children": ['Log'],
"onclick": event => { evaluate(entry, event); }
}
withScope()
アクションは、呼び出されたときのスコープに依存できます。例えば、getContext()
や getElement()
を呼び出した場合です。
Interactivity API ランタイムがコールバックを実行するとき、スコープは自動的に設定されます。しかし、setInterval()
コールバックのような、ランタイムが実行しないコールバックからアクションを呼び出すときは、スコープが適切に設定されていることを確認する必要があります。この場合、withScope()
関数を使用して、スコープが適切に設定されていることを確認します。
以下の例でラッパーがなければ、actions.nextImage
は未定義エラーを引き起こします。
store('mySliderPlugin', {
callbacks: {
initSlideShow: () => {
setInterval(
withScope( () => {
actions.nextImage();
} ),
3_000
);
},
},
})
サーバー関数
Interactivity API には、サーバー上の構成オプションを初期化、参照するための便利な関数があります。これは Server Directive Processing に初期データを与えるために必要で、この初期データは、Server Directive Processing がブラウザに HTML マークアップを送信する前に、修正するために使用されます。また、nonce、AJAX、翻訳などの WordPress の多くの API を活用する素晴らしい手段が提供されます。
wp_interactivity_config
wp_interactivity_config
はストアの名前空間を参照する、構成の配列を設定、取得できます。 構成はクライアントでも利用できますが、静的な情報です。
これはサイトのインタラクションに関するグローバルな設定と考えてください。ユーザーのインタラクションでは更新されません。
設定の例
wp_interactivity_config( 'myPlugin', array( 'showLikeButton' => is_user_logged_in() ) );
取得の例
wp_interactivity_config( 'myPlugin' );
この構成はクライアントで取得できます。
// view.js
const { showLikeButton } = getConfig();
wp_interactivity_state
wp_interactivity_state
を使用するとサーバー上でグローバルステートを初期化できます。このグローバルステートはサーバー上のディレクティブの処理に使用され、クライアントで定義されたグローバルステートとマージされます。
また、サーバー上でグローバルステートを初期化することで、AJAX や nonces など多くの重要な WordPress API を使用できます。
関数 wp_interactivity_state
は2つの引数を取ります。1つは参照として使用される名前空間の文字列で、もう1つは値を含む連想配列です。
以下は、nonce と共に WP Admin AJAX エンドポイントを渡す例です。
// render.php
wp_interactivity_state(
'myPlugin',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myPlugin_nonce' ),
),
);
// view.js
const { state } = store( 'myPlugin', {
actions: {
*doSomething() {
try {
const formData = new FormData();
formData.append( 'action', 'do_something' );
formData.append( '_ajax_nonce', state.nonce );
const data = yield fetch( state.ajaxUrl, {
method: 'POST',
body: formData,
} ).then( ( response ) => response.json() );
console.log( 'Server data!', data );
} catch ( e ) {
// Something went wrong!
}
},
},
}
);
wp_interactivity_process_directives
wp_interactivity_process_directives
は、ディレクティブが処理された後の、更新された HTML を返します。
これは、Interactivity API のサーバーサイドレンダリング部分のコア機能であり、パブリックなので、ブロックかどうかにかかわらず、任意の HTML で処理できます。
以下のコードは、
wp_interactivity_state( 'myPlugin', array( 'greeting' => 'Hello, World!' ) );
$html_content = '<div data-wp-text="myPlugin::state.greeting"></div>';
$processed_html = wp_interactivity_process_directives( $html_content );
echo $processed_html;
以下の出力になります。
<div data-wp-text="myPlugin::state.greeting">Hello, World!</div>
wp_interactivity_data_wp_context
wp_interactivity_data_wp_context
はコンテキストディレクティブを文字列化した JSON を返します。 この関数は data-wp-context
属性をサーバ側のレンダーマークアップ内に出力する、推奨の方法です。
$my_context = array(
'counter' => 0,
'isOpen' => true,
);
?>
<div
<?php echo wp_interactivity_data_wp_context( $my_context ); ?>
>
</div>
以下を出力します。
<div data-wp-context='{"counter":0,"isOpen":true}'>
最終更新日: