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 タグプロセッサ を使用して動的にも注入できます。

ディレクティブを使用すると、副作用、ステート、イベントハンドラ、属性、コンテンツなどのインタラクションを直接管理できます。

Top ↑

ディレクティブ一覧

Top ↑

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 ディレクティブは自動的に注入される予定です。

Top ↑

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>

Top ↑

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">

Top ↑

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 {
  /* ... */
}

Top ↑

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;">

Top ↑

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>

Top ↑

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) を受け取ります。コールバックが返す値は無視されます。

Top ↑

wp-on-async

このディレクティブは wp-on よりもパフォーマンスに優れたアプローチです。直ちにメインスレッドに yield して、時間のかかるタスクの待ちを回避し、メインスレッドで待機している他の処理がより早く実行できるようにします。event オブジェクトに同期的にアクセスする必要がない場合、特に event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() メソッドでは、この非同期バージョンを使用してください。

Top ↑

wp-on-window

もしもディレクティブのコードがイベントオブジェクトへの同期アクセスを必要としなければ、パフォーマンスに優れた wp-on-async-window の使用を検討してください。また同期アクセスが必要な場合も、同期 API を呼び出した後にメインスレッドへ yield する「async アクション」 (後述) の実装を検討してください。

このディレクティブを使用すると、resizecopyfocus などのグローバルウィンドウイベントにアタッチし、それらが起きたときに、定義されたコールバックを実行できます。

リンク: サポートするウィンドウイベントのリスト

このディレクティブの構文は 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 から削除されると、イベントリスナーも削除されます。

Top ↑

wp-on-async-window

wp-on-async と同じように、このディレクティブも wp-on-window の最適化バージョンで、直ちにメインスレッドに yield して、時間のかかるタスクの待ちを回避します。event オブジェクトに同期的にアクセスする必要がない場合、特に event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() メソッドでは、この非同期バージョンを使用してください。またこのイベントリスナーは passive として追加されます。

Top ↑

wp-on-document

もしもディレクティブのコードがイベントオブジェクトへの同期アクセスを必要としなければ、パフォーマンスに優れた wp-on-async-document の使用を検討してください。また同期アクセスが必要な場合も、同期 API を呼び出した後にメインスレッドへ yield する「async アクション」 (後述) の実装を検討してください。

このディレクティブを使用すると、scrollmousemovekeydown などのグローバルドキュメントイベントにアタッチし、それらが起きたときに、定義されたコールバックを実行できます。

リンク: サポートするドキュメントイベントのリスト

このディレクティブの構文は data-wp-on-document--[document-event] です (例: data-wp-on-document--keydowndata-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 から削除されると、イベントリスナーも削除されます。

Top ↑

wp-on-async-document

wp-on-async と同じように、このディレクティブも wp-on-document の最適化バージョンで、直ちにメインスレッドに yield して、時間のかかるタスクの待ちを回避します。event オブジェクトに同期的にアクセスする必要がない場合、特に event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() メソッドでは、この非同期バージョンを使用してください。またこのイベントリスナーは passive として追加されます。

Top ↑

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() でフォーカスを設定
  • 特定の条件が満たされたときにステートやコンテキストを変更

Top ↑

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 から削除されたときに実行されます。

Top ↑

wp-run

このディレクティブは ノードのレンダー実行中に 渡されたコールバックを実行します。

渡されるコールバックの中では useStateuseWatchuseEffect などのフックを使用し、組み合わせて、独自のロジックを作成できます。このため、これまでのディレクティブよりも柔軟性に優れます。

構文 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 要素の参照に適切にアクセスするには、通常 useEffectuseInituseWatch などのエフェクト系のフックを使用する必要があります。これにより、コンポーネントがマウントされ、ref が利用できるようになった後で、getElement() が実行されることが保証されます。

Top ↑

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 はキーによって要素を照合し、項目が追加、削除、並べ替えられたかどうかを判断します。要素にキーがなければ、不必要に再作成される可能性があります。

Top ↑

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>

Top ↑

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>

Top ↑

ディレクティブの値は、ストアのプロパティへの参照

ディレクティブに割り当てられる値は、特定のステート、アクション、副作用を指す文字列です。

以下の例では、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>

Top ↑

ストア

ストアは、ディレクティブにリンクされたロジック (アクション、副作用など) と、そのロジックの内部で使用されるデータ (ステート、派生ステートなど) の作成に使用されます。

ストアは通常、各ブロックの view.js ファイルで作成されます が、ブロックの render.php からも状態を初期化できます。

Top ↑

ストアの要素

Top ↑

ステート

ページの 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!"
    },
  },
} )

Top ↑

アクション

アクションは普通の 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-onwp-on-windowwp-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 ポイントを複数追加できます。

Top ↑

副作業

ステートの変化に自動的に反応します。通常は data-wp-watch または data-wp-init ディレクティブによって起動されます。

Top ↑

派生ステート

ステートの計算されたバージョンを返します。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;
    },
  },
} );

Top ↑

コールバック内でのデータアクセス

store には stateactioncallbacks などのストアのすべてのプロパティが含まれています。store() をコールするとこれらが返るため、分割することでアクセスできます。

const { state, actions } = store( "myPlugin", {
  // ...
} );

store() 関数は複数回呼び出すことができ、すべての store 部分がマージされます。

store( "myPlugin", {
  state: {
    someValue: 1,
  }
} );

const { state } = store( "myPlugin", {
  actions: {
    someAction() {
      state.someValue // = 1
    }
  }
} );

同じ名前空間を持つすべての store() 呼び出しは、同じ参照、つまり、同じ stateactions 等、渡されたすべてのストア部分のマージした結果を返します。

  • アクション、派生ステート、副作用の中でコンテキストにアクセスするには、 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 リクエストなど。
  • 副作用は、ステートの変更に自動的に反応する。

Top ↑

ストアの設定

Top ↑

クライアント側にて

各ブロックの 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 }` );
    }
  },
});

Top ↑

サーバー側にて

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")
      ),
) );

Top ↑

プライベートストア

指定するストアの名前空間をプライベートとマークすると、他の名前空間からそのコンテンツにアクセスできなくなります。これには、以下の例のように 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 } );

Top ↑

ストアのクライアントメソッド

store 関数以外に、開発者が store 関数のデータにアクセスするためのメソッドもあります。

  • getContext()
  • getElement()

Top ↑

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);
    },
  },
});

Top ↑

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); }
}

Top ↑

withScope()

アクションは、呼び出されたときのスコープに依存できます。例えば、getContext() や getElement()を呼び出した場合です。

Interactivity API ランタイムがコールバックを実行するとき、スコープは自動的に設定されます。しかし、setInterval() コールバックのような、ランタイムが実行しないコールバックからアクションを呼び出すときは、スコープが適切に設定されていることを確認する必要があります。この場合、withScope() 関数を使用して、スコープが適切に設定されていることを確認します。

以下の例でラッパーがなければ、actions.nextImage は未定義エラーを引き起こします。

store('mySliderPlugin', {
	callbacks: {
		initSlideShow: () => {
			setInterval(
				withScope( () => {
					actions.nextImage();
				} ),
				3_000
			);
		},
  },
})

Top ↑

サーバー関数

Interactivity API には、サーバー上の構成オプションを初期化、参照するための便利な関数があります。これは Server Directive Processing に初期データを与えるために必要で、この初期データは、Server Directive Processing がブラウザに HTML マークアップを送信する前に、修正するために使用されます。また、nonce、AJAX、翻訳などの WordPress の多くの API を活用する素晴らしい手段が提供されます。

Top ↑

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();

Top ↑

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!
				}
			},
		},
	}
);

Top ↑

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>

Top ↑

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

原文

最終更新日: