サーバー上のディレクティブの処理

インタラクティビティAPIのサーバーディレクティブ処理機能により、WordPressは正しいインタラクティブ状態で初期HTMLを生成し、初期レンダリングを迅速に提供します。初期のサーバーサイドレンダリングの後、インタラクティビティAPIのクライアントサイドJavaScriptが引き継ぎ、ページ全体のリロードを必要とせずに動的な更新とインタラクションを可能にします。このアプローチは、従来のWordPressサーバーサイドレンダリングのSEOとパフォーマンスの利点と、現代のJavaScriptフレームワークが提供する動的で反応的なユーザーインターフェースの両方の利点を組み合わせています。

サーバーディレクティブ処理がどのように機能するかを理解するために、data-wp-eachディレクティブを使用して果物のリストをレンダリングする例から始めましょう。

以下は、WordPressのサーバーサイドレンダリング中にインタラクティビティAPIのサーバーディレクティブ処理によってディレクティブが正しく処理されることを保証するために必要な手順です。

  • 1. ブロックをインタラクティブとしてマークする
    まず、インタラクティブブロックのディレクティブのサーバー処理を有効にするために、supports.interactivityblock.jsonに追加する必要があります:
    1. {
    2. "supports": {
    3. "interactivity": true
    4. }
    5. }
  • 2. グローバル状態またはローカルコンテキストを初期化する
    次に、ページのサーバーサイドレンダリング中に使用されるグローバル状態またはローカルコンテキストのいずれかを初期化する必要があります。
    グローバル状態を使用している場合は、wp_interactivity_state関数を使用する必要があります:
    1. wp_interactivity_state( 'myFruitPlugin', array(
    2. 'fruits' => array( 'Apple', 'Banana', 'Cherry' )
    3. ));
    ローカルコンテキストを使用している場合、初期値はdata-wp-contextディレクティブ自体で定義されます。方法は次のとおりです:
    • HTMLに直接追加します。
      1. <ul data-wp-context='{ "fruits": ["Apple", "Banana", "Cherry"] }'>
      2. ...
      3. </ul>
    1. ``````bash
    2. <?php
    3. $context = array( 'fruits' => array( 'Apple', 'Banana', 'Cherry' ) );
    4. ?>
    5. <ul <?php echo wp_interactivity_data_wp_context( $context ); ?>>
    6. ...
    7. </ul>
    8. `
  • 3. ディレクティブを使用してインタラクティブ要素を定義する
    次に、HTMLマークアップに必要なディレクティブを追加する必要があります。
    1. <ul data-wp-interactive="myFruitPlugin">
    2. <template data-wp-each="state.fruits">
    3. <li data-wp-text="context.item"></li>
    4. </template>
    5. </ul>
    この例では:
    • data-wp-interactiveディレクティブは、DOM要素とその子要素のインタラクティブ性を有効にします。
    • data-wp-eachディレクティブは、要素のリストをレンダリングするために使用されます。このディレクティブは<template>タグで使用でき、値はグローバル状態またはローカルコンテキストに保存された配列への参照パスです。
      1. 同じディレクティブは、グローバル状態の代わりにローカルコンテキストを使用する場合にも使用できます。唯一の違いは、`````data-wp-each``````````context.fruits`````を指し、`````state.fruits`````ではないことです:
      2. ``````bash
      3. <ul
      4. data-wp-interactive="myFruitPlugin"
      5. data-wp-context='{ "fruits": [ "Apple", "Banana", "Cherry" ] }'
      6. >
      7. <template data-wp-each="context.fruits">
      8. <li data-wp-text="context.item"></li>
      9. </template>
      10. </ul>
      11. `

それだけです!supports.interactivityでインタラクティブブロックを設定し、グローバル状態またはローカルコンテキストを初期化し、HTMLマークアップにディレクティブを追加すると、インタラクティビティAPIが残りの処理を行います。これらのディレクティブをサーバー側で処理するために、開発者からの追加のコードは必要ありません。

裏では、WordPressはwp_interactivity_process_directives関数を使用して、ブロックのHTMLマークアップ内のディレクティブを見つけて処理します。この関数は、見つかったディレクティブと初期のグローバル状態および/またはローカルコンテキストに基づいて、マークアップに必要な変更を加えるためにHTML APIを使用します。

その結果、ブラウザに送信されるHTMLマークアップはすでに最終形であり、すべてのディレクティブが正しく処理されています。これは、ページがブラウザで最初に読み込まれるとき、すでにすべてのインタラクティブ要素の正しい初期状態が含まれていることを意味し、JavaScriptを使用してそれを変更する必要はありません。

果物リストの最終HTMLマークアップは次のようになります(ディレクティブは省略されています):

  1. <ul>
  2. <li>Apple</li>
  3. <li>Banana</li>
  4. <li>Cherry</li>
  5. </ul>

ご覧のとおり、data-wp-eachディレクティブは配列内の各果物に対して<li>要素を生成し、data-wp-textディレクティブが処理され、各<li>に正しい果物名が入力されています。

クライアントにおけるグローバル状態とローカルコンテキストの操作

インタラクティビティAPIの主要な強みの1つは、サーバーサイドレンダリングとクライアントサイドインタラクティビティのギャップを埋める方法です。そのために、サーバーで初期化されたグローバル状態とローカルコンテキストは、クライアントのインタラクティビティAPIストアにシリアライズされ、利用可能になります。これにより、アプリケーションは動的にDOMを操作し続けることができます。

この例を拡張して、ユーザーがクリックしてリストに新しい果物を追加できるボタンを含めてみましょう:

  1. <button data-wp-on-async--click="actions.addMango">Add Mango</button>

この新しいボタンには、data-wp-on-async--clickディレクティブがあり、actions.addMangoを参照しています。これは私たちのJavaScriptストアで定義されています:

  1. const { state } = store( 'myFruitPlugin', {
  2. actions: {
  3. addMango() {
  4. state.fruits.push( 'Mango' );
  5. },
  6. },
  7. } );

同じ例は、ローカルコンテキストを使用している場合にも機能します:

  1. store( 'myFruitPlugin', {
  2. actions: {
  3. addMango() {
  4. const context = getContext();
  5. context.fruits.push( 'Mango' );
  6. },
  7. },
  8. } );

さて、ユーザーが「マンゴーを追加」ボタンをクリックすると:

  • 1. addMangoアクションがトリガーされます。
  • 2. 'Mango'アイテムがstate.fruits(またはcontext.fruits)配列に追加されます。
  • 3. インタラクティビティAPIが自動的にDOMを更新し、新しい果物のために新しい<li>要素を追加します。
  1. <ul>
  2. <li>Apple</li>
  3. <li>Banana</li>
  4. <li>Cherry</li>
  5. <li>Mango</li>
  6. </ul>

覚えておいてください:クライアントで状態を初期化する必要はありません。すでにサーバーで行われている場合は必要ありません。

  1. store( 'myFruitPlugin', {
  2. state: {
  3. fruits: [ 'Apple', 'Banana', 'Cherry' ], // This is not necessary!
  4. },
  5. } );

サーバーでの派生状態の初期化

派生状態は、グローバル状態、ローカルコンテキスト、またはその両方から派生するかどうかにかかわらず、サーバーでサーバーディレクティブ処理によって処理することもできます。

詳細については、グローバル状態、ローカルコンテキスト、派生状態の理解ガイドをご覧ください。

静的に定義できる派生状態

すべての果物を削除できるボタンを追加すると想像してみましょう:

  1. <button data-wp-on-async--click="actions.deleteFruits">
  2. Delete all fruits
  3. </button>
  1. const { state } = store( 'myFruitPlugin', {
  2. actions: {
  3. // ...
  4. deleteFruits() {
  5. state.fruits = [];
  6. },
  7. },
  8. } );

さて、果物がないときに特別なメッセージを表示しましょう。これを行うために、data-wp-bind--hiddenディレクティブを使用して、state.hasFruitsという派生状態を参照してメッセージを表示/非表示にします。

  1. <div data-wp-interactive="myFruitPlugin">
  2. <ul data-wp-bind--hidden="!state.hasFruits">
  3. <template data-wp-each="state.fruits">
  4. <li data-wp-text="context.item"></li>
  5. </template>
  6. </ul>
  7. <div data-wp-bind--hidden="state.hasFruits">No fruits, sorry!</div>
  8. </div>

派生状態state.hasFruitsは、クライアントでゲッターを使用して定義されます:

  1. const { state } = store( 'myFruitPlugin', {
  2. state: {
  3. get hasFruits() {
  4. return state.fruits.length > 0;
  5. },
  6. },
  7. // ...
  8. } );

ここまで、クライアント側ではすべてが正常で、「すべての果物を削除」ボタンを押すと「果物がありません、申し訳ありません!」というメッセージが表示されます。問題は、state.hasFruitsがサーバーで定義されていないため、hidden属性が初期HTMLの一部にならないことです。これは、JavaScriptが読み込まれるまでメッセージが表示され続けることを意味し、訪問者に混乱を引き起こすだけでなく、JavaScriptが最終的に読み込まれたときにレイアウトシフトを引き起こします。

これを修正するには、wp_interactivity_stateを使用してサーバーで派生状態の初期値を定義する必要があります。

  • 初期値が既知で静的な場合は、直接定義できます:
    1. wp_interactivity_state( 'myFruitPlugin', array(
    2. 'fruits' => array( 'Apple', 'Banana', 'Cherry' ),
    3. 'hasFruits' => true
    4. ));
  • または、必要な計算を行うことで定義できます:

    1. $fruits = array( 'Apple', 'Banana', 'Cherry' );
    2. $hasFruits = count( $fruits ) > 0;
    3. wp_interactivity_state( 'myFruitPlugin', array(
    4. 'fruits' => $fruits,
    5. 'hasFruits' => $hasFruits,
    6. ));

アプローチに関係なく、重要な点は、state.hasFruitsの初期値が現在サーバーで定義されていることです。これにより、サーバーディレクティブ処理がdata-wp-bind--hiddenディレクティブを処理し、必要に応じてHTMLマークアップを変更し、hidden属性を追加できるようになります。

動的に定義する必要がある派生状態

ほとんどの場合、初期の派生状態は前の例のように静的に定義できます。しかし、時には値がサーバーで変更される動的な値に依存し、派生ロジックをPHPで再現する必要があります。

これを示す例として、ショッピングリストにあるかどうかに応じて、各果物のためにショッピングカートの絵文字 (🛒)を追加してみましょう。

まず、ショッピングリストを表す配列を追加しましょう。これらの配列は簡単のために静的ですが、通常はデータベースからの情報など、動的な情報を扱います。

  1. wp_interactivity_state( 'myFruitPlugin', array(
  2. 'fruits' => array( 'Apple', 'Banana', 'Cherry' ),
  3. 'shoppingList' => array( 'Apple', 'Cherry' ),
  4. ));

次に、クライアントで各果物がショッピングリストにあるかどうかを確認し、絵文字を返す派生状態を追加します。

  1. store( 'myFruitPlugin', {
  2. state: {
  3. get onShoppingList() {
  4. const context = getContext();
  5. return state.shoppingList.includes( context.item ) ? '' : '';
  6. },
  7. },
  8. // ...
  9. } );

そして、その派生状態を使用して、各果物に適切な絵文字を表示します。

  1. <ul data-wp-interactive="myFruitPlugin">
  2. <template data-wp-each="state.fruits">
  3. <li>
  4. <span data-wp-text="context.item"></span>
  5. <span data-wp-text="state.onShoppingList"></span>
  6. </li>
  7. </template>
  8. </ul>

ここまで、クライアント側ではすべてが正常で、訪問者はショッピングリストにある果物のために正しい絵文字が表示されます。しかし、state.onShoppingListがサーバーで定義されていないため、絵文字は初期HTMLの一部にならず、JavaScriptが読み込まれるまで表示されません。

  1. ``````bash
  2. wp_interactivity_state( 'myFruitPlugin', array(
  3. // ...
  4. 'onShoppingList' => function() {
  5. $state = wp_interactivity_state();
  6. $context = wp_interactivity_get_context();
  7. return in_array( $context['item'], $state['shoppingList'] ) ? '' : '';
  8. }
  9. ));
  10. `

それだけです!これで、サーバーは派生状態を計算し、どの果物がショッピングリストにあり、どれがないかを知ることができます。これにより、サーバーディレクティブ処理が初期HTMLを正しい値で埋めることができ、ユーザーはJavaScriptランタイムが読み込まれる前に正しい情報を即座に見ることができます。

クライアントで消費される他の処理された値のシリアライズ

  1. 例に翻訳を追加して、これがどのように機能するかを見てみましょう。
  2. ``````bash
  3. <?php
  4. wp_interactivity_state( 'myFruitPlugin', array(
  5. 'fruits' => array( __( 'Apple' ), __( 'Banana' ), __( 'Cherry' ) ),
  6. 'shoppingList' => array( __( 'Apple' ), __( 'Cherry' ) ),
  7. // ...
  8. ?>
  9. <div data-wp-interactive="myFruitPlugin">
  10. <button data-wp-on-async--click="actions.deleteFruits">
  11. <?php echo __( 'Delete all fruits' ); ?>
  12. </button>
  13. <button data-wp-on-async--click="actions.addMango">
  14. <?php echo __( 'Add Mango' ); ?>
  15. </button>
  16. <ul data-wp-bind--hidden="!state.hasFruits">
  17. <template data-wp-each="state.fruits">
  18. <li>
  19. <span data-wp-text="context.item"></span>
  20. <span data-wp-text="state.onShoppingList"></span>
  21. </li>
  22. </template>
  23. </ul>
  24. <div data-wp-bind--hidden="state.hasFruits">
  25. <?php echo __( 'No fruits, sorry!' ); ?>
  26. </div>
  27. </div>
  28. `

それだけです!インタラクティビティAPIはPHPで動作するため、翻訳をグローバル状態、ローカルコンテキスト、HTMLマークアップに直接追加できます。

しかし、待ってください、私たちのaddMangoアクションはどうなりますか?このアクションはJavaScriptでのみ定義されています:

  1. const { state } = store( 'myFruitPlugin', {
  2. actions: {
  3. addMango() {
  4. state.fruits.push( 'Mango' ); // Not translated!
  5. },
  6. },
  7. } );

この問題を修正するには、wp_interactivity_state関数を使用して翻訳されたマンゴー文字列をシリアライズし、その値にアクションでアクセスします。

  1. wp_interactivity_state( 'myFruitPlugin', array(
  2. 'fruits' => array( __( 'Apple' ), __( 'Banana' ), __( 'Cherry' ) ),
  3. 'mango' => __( 'Mango' ),
  4. ));
  1. const { state } = store( 'myFruitPlugin', {
  2. actions: {
  3. addMango() {
  4. // `state.mango` contains the 'Mango' string already translated.
  5. state.fruits.push( state.mango );
  6. },
  7. },
  8. } );

アプリケーションがより動的な場合、すべての果物の翻訳を含む配列をシリアライズし、アクション内で果物のキーワードを使用して作業することもできます。たとえば:

  1. wp_interactivity_state( 'myFruitPlugin', array(
  2. 'fruits' => array( 'apple', 'banana', 'cherry' ),
  3. 'translatedFruits' => array(
  4. 'apple' => __( 'Apple' ),
  5. 'banana' => __( 'Banana' ),
  6. 'cherry' => __( 'Cherry' ),
  7. 'mango' => __( 'Mango' ),
  8. ),
  9. 'translatedFruit' => function() {
  10. $state = wp_interactivity_state();
  11. $context = wp_interactivity_get_context();
  12. return $state['translatedFruits'][ $context['item'] ];
  13. }
  14. ));
  1. const { state } = store( 'myFruitPlugin', {
  2. state: {
  3. get translatedFruit() {
  4. const context = getContext();
  5. return state.translatedFruits[ context.item ];
  6. }
  7. }
  8. actions: {
  9. addMango() {
  10. state.fruits.push( 'mango' );
  11. },
  12. },
  13. } );
  1. <template data-wp-each="state.fruits">
  2. <li data-wp-text="state.translatedFruit"></li>
  3. </template>

サーバーから情報をシリアライズすることは、Ajax/REST-APIのURLやノンスを渡すなど、他のシナリオでも便利です。

  1. wp_interactivity_state( 'myPlugin', array(
  2. 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
  3. 'nonce' => wp_create_nonce( 'myPlugin_nonce' ),
  4. ));
  1. const { state } = store( 'myPlugin', {
  2. actions: {
  3. *doSomething() {
  4. const formData = new FormData();
  5. formData.append( 'action', 'do_something' );
  6. formData.append( '_ajax_nonce', state.nonce );
  7. const data = yield fetch( state.ajaxUrl, {
  8. method: 'POST',
  9. body: formData,
  10. } ).then( ( response ) => response.json() );
  11. console.log( 'Server data', data );
  12. },
  13. },
  14. } );

クラシックテーマにおけるディレクティブの処理

サーバーディレクティブ処理は、supports.interactivityblock.jsonファイルに追加すると、インタラクティブブロック内で自動的に発生します。しかし、クラシックテーマはどうでしょうか?

クラシックテーマもインタラクティビティAPIを使用でき、サーバーディレクティブ処理を利用したい場合(利用すべきです)、wp_interactivity_process_directives関数を通じてそれを行うことができます。この関数は、未処理のディレクティブを含むHTMLマークアップを受け取り、グローバル状態、ローカルコンテキスト、派生状態の初期値に基づいて修正されたHTMLマークアップを返します。

  1. // Initializes the global and derived state
  2. wp_interactivity_state( '...', /* ... */ );
  3. // The interactive HTML markup that contains the directives.
  4. $html = '<div data-wp-...>...</div>';
  5. // Processes the directives so they are ready to be sent to the client.
  6. $processed_html = wp_interactivity_process_directives( $html );

それだけです!他に何もする必要はありません。

テンプレートファイルでwp_interactivity_process_directivesを使用したい場合は、ob_startob_get_cleanを使用してHTML出力をキャプチャし、レンダリング前に処理できます。

  1. <?php
  2. wp_interactivity_state( 'myClassicTheme', /* ... */ );
  3. ob_start();
  4. ?>
  5. <div data-wp-interactive="myClassicTheme">
  6. ...
  7. </div>
  8. <?php
  9. $html = ob_get_clean();
  10. echo wp_interactivity_process_directives( $html );

重要: ディレクティブを処理する必要があるのは一度だけです。別のテンプレート内に内部テンプレートファイルを含める場合は、wp_interactivity_process_directivesが最も外側のテンプレートファイルでのみ呼び出されるようにして、冗長な処理を避けてください。

結論

インタラクティビティAPIは、サーバーでレンダリングされたコンテンツからクライアントサイドのインタラクティビティへのスムーズで透明な移行を保証します。サーバーで定義したディレクティブ、初期のグローバル状態またはローカルコンテキスト、クライアントサイドの動作はすべて同じエコシステムの一部を形成します。この統一されたアプローチは、開発を簡素化し、保守性を向上させ、インタラクティブなWordPressブロックを作成する際の開発者体験を向上させます。