グローバルステート、ローカルコンテキスト、派生ステートの理解

Interactivity API は、インタラクティブブロックを作成する強力なフレームワークを提供します。この機能を最大限、活用するにはグローバルステート、ローカルコンテキスト、派生ステートを使用するタイミングの理解が重要です。このガイドではこれらの概念を明らかにし、それぞれを使用するタイミングを決める際に役立つ実践的な例を提供します。

まず、グローバルステート、ローカルコンテキスト、派生ステートの簡単な定義から始めましょう。

  • グローバルステート: ページ上の任意のインタラクティブブロックからアクセス、変更可能なグローバルデータ。インラクティブブロックの異なるパーツの同期を維持できます。
  • ローカルコンテキスト: HTML 構造内の特定の要素内で定義されたローカルデータ。その要素と子要素のみアクセス可能で、それぞれのブロックが独立したステートを持ちます。
  • 派生ステート: グローバルステートやローカルコンテキストに基づいて計算される値。動的に必要に応じて計算され、冗長なデータを保存することなく一貫したデータ表現が保証されます。

それではそれぞれのコンセプトをより詳しく学習し、いくつかの例を見てみましょう。

グローバルステート

Interactivity API における グローバルステート とは、ページ上の任意のインタラクティブブロックからアクセス、変更可能なグローバルデータを指します。共有された情報ハブとして機能し、ブロックの異なるパーツが通信し、同期を維持できます。グローバルステートは、DOM ツリー内の位置に関係なく、インタラクティブブロック間で情報を交換する理想的なメカニズムです。

以下のような場面ではグローバルステートを使用してください。

  • DOM 階層内で直接関連していない複数のインタラクティブブロック間でデータを共有する必要がある。
  • すべてのインタラクティブブロックを越えて、特定のデータにおける、単一の真のソース (single source of truth) を維持したい。
  • UI の複数の部分に同時に影響するデータを扱っている。
  • ページに対してグローバルな機能を実装したい。

グローバルステートの操作

グローバルステートの初期化

通常、グローバルステートの初期値は wp_interactivity_state 関数を使用してサーバー上で定義します。

// グローバルステートの初期値を設定
wp_interactivity_state( 'myPlugin', array(
  'isDarkTheme' => true,
  'show'        => false,
  'helloText'   => __( 'world' ),
));

このグローバルステートの初期値は、PHP 内でページのレンダリングに使用され、HTML マークアップとなり、ブラウザに送信されます。

  • 開発者が PHP ファイル内に記述した HTML マークアップ:```html <div data-wp-interactive="myPlugin" data-wp-class--is-dark-theme="state.isDarkTheme" class="my-plugin" > <div data-wp-bind--hidden="!state.show"> Hello <span data-wp-text="state.helloText"></span> </div> <button data-wp-on--click="actions.toggle">Toggle</button> </div> ```
    • HTML markup after the directives have been processed and it is ready to be sent to the browser: –>
  • ディレクティブが処理され、ブラウザに送る準備ができた後の HTML マークアップ:```html <div data-wp-interactive="myPlugin" data-wp-class--is-dark-theme="state.isDarkTheme" class="my-plugin is-dark-theme" > <div hidden data-wp-bind--hidden="!state.show"> Hello <span data-wp-text="state.helloText">world</span> </div> <button data-wp-on--click="actions.toggle">Toggle</button> </div> ```

ディレクティブがサーバ上でどのように処理されるかについては、サーバサイドレンダリングガイドを参照してください。

PHP 内でのページのレンダリング中にグローバルステートを使用しない場合は、直接クライアントでも定義できます。

const { state } = store( 'myPlugin', {
  state: {
    isLoading: false,
  },
  actions: {
    *loadSomething() {
      state.isLoading = true;
      // ...
    },
  },
} );

注意: これは正しく動作しますが、一般的なベストプラクティスとしてはすべてのグローバルステートをサーバー上で定義してください。

グローバルステートへのアクセス

HTML マークアップ内のディレクティブ属性の値では state を参照することでグローバルステートの値に直接アクセスできます。

<div data-wp-bind--hidden="!state.show">
  <span data-wp-text="state.helloText"></span>
</div>

JavaScript では、@wordpress/interactivity パッケージの store 関数が設定、取得の両方で機能し、選択されたネームスペースのストアを返します。

アクションやコールバックでグローバルステートにアクセスするには、 store 関数が返すオブジェクトの state プロパティを使用します。

const myPluginStore = store( 'myPlugin' );

myPluginStore.state; // これは名前空間 myPlugin のストア

store が返すオブジェクトは分割も可能です。

const { state } = store( 'myPlugin' );

そして同じことは同時にストアを定義しながら行うこともでき、これが最も一般的な使用方法です。

const { state } = store( 'myPlugin', {
  state: {
    // ...
  },
  actions: {
    toggle() {
      state.show = ! state.show;
    },
  },
} );

wp_interactivity_state 関数を使用してサーバー上で初期化されたグローバルステートも、サーバーからクライアントに自動的にシリアライズされるため、オブジェクトに含まれます。

wp_interactivity_state( 'myPlugin', array(
  'someValue' => 1,
));

const { state } = store( 'myPlugin', {
  state: {
    otherValue: 2,
  },
  actions: {
    readGlobalState() {
      state.someValue; // これは存在して、初期値は1
      state.otherValue; // これも存在して、初期値は2
    },
  },
} );

最後に、同じ名前空間を持つすべての store 関数呼び出しはマージされます。

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

store( 'myPlugin', { state: { otherValue: 2 } } );

/* すべての `store` 呼び出しは同じオブジェクトへの安定した参照 (stable reference) を返す。 このため、
  * どの呼び出しからでも `state` への参照を取得できる */
const { state } = store( 'myPlugin' );

store( 'myPlugin', {
  actions: {
    readValues() {
      state.someValue; // 存在して、初期値は1
      state.otherValue; // 存在して、初期値は2
    },
  },
} );

グローバルステートの更新

グローバルステートを更新するために必要な作業は、一度 store 関数から取得した state オブジェクトを変更 (mutate) するだけです。

const { state } = store( 'myPlugin', {
  actions: {
    updateValues() {
      state.someValue = 3;
      state.otherValue = 4;
    },
  },
} );

グローバルステートを変更すると、変更した値に依存する任意のディレクティブ内の変更が自動的にトリガーされます。

Interactivity API 内でリアクティブがどのように動作するかの詳細については「リアクティブと宣言型の考え方」を参照してください。

例: グローバルステートを使用して通信する2つのインタラクティブブロック

この例には2つの独立したインタラクティブブロックがあります。1つはカウンターを表示し、もう1つにはそのカウンターを増分するボタンがあります。この2つのブロックは、HTML の構造に関係なくページのどこにでも配置できます。言い換えれば、一方が他方の内部ブロックである必要はありません。

カウンターブロック

<?php
wp_interactivity_state( 'myCounterPlugin', array(
  'counter' => 0
));
?>

<div
  data-wp-interactive="myCounterPlugin"
  <?php echo get_block_wrapper_attributes(); ?>
>
  Counter: <span data-wp-text="state.counter"></span>
</div>

インクリメントブロック

<div
  data-wp-interactive="myCounterPlugin"
  <?php echo get_block_wrapper_attributes(); ?>
>
  <button data-wp-on--click="actions.increment">
    Increment
  </button>
</div>

const { state } = store( 'myCounterPlugin', {
  actions: {
    increment() {
      state.counter += 1;
    },
  },
} );

この例では

  1. グローバルステートは wp_interactivity_state を使用してサーバー上で初期化され、初期値として counter を 0 に設定します。
  2. カウンターブロックは、data-wp-text="state.counter" を使用して現在のカウンターを、グローバルステートから読み取り、表示します。
  3. インクリメントブロックにはクリックすると increment アクションをトリガーするボタンがあり、これには data-wp-on--click="actions.increment" を使用します。
  4. JavaScript 内で increment アクションは state.counter を増分することで、グローバルステートを直接変更します。

2つのブロックは独立していてページのどこにでも配置できます。互いを入れ子にしたり、DOM 構造で直接関連づける必要はありません。これらのインタラクティブブロックの複数のインスタンスをページに追加でき、すべて同じグローバルなカウンターの値を共有し、更新します。

ローカルコンテキスト

Interactivity API における ローカルコンテキスト とは、HTML 構造内の特定の要素内で定義されたローカルデータを指します。グローバルステートとは異なりローカルコンテキストは、そのローカルコンテキストを定義した要素とその子要素からのみアクセスできます。

ローカルコンテキストは、個々のインタラクティブブロックで、独立したステートが必要な場合に特に有用です。ブロックの各インスタンスは、他のインスタンスと干渉しない自身のデータを維持できます。

以下のような場面ではローカルコンテキストを使用してください。

  • あるインタラクティブブロックの複数のインスタンスに対して、それぞれ個別のステートを保持する必要がある。
  • あるインタラクティブブロックとその子にのみ関連するデータをカプセル化したい。
  • UI の特定の部分に分離された機能を実装する必要がある。

ローカルコンテキストの操作

ローカルコンテキストの初期化

ローカルコンテキストは data-wp-context ディレクティブを使用して HTML 構造の中で直接、初期化します。このディレクティブはコンテキストの初期値を定義する JSON 文字列を受け付けます。

<div data-wp-context='{ "counter": 0 }'>
  <!-- 子要素は `context.counter` にアクセスできる -->
</div>

またローカルコンテキストは wp_interactivity_data_wp_context PHP ヘルパーを使用して、サーバー上でも初期化できます。このヘルパーは文字列化した値の適切なエスケープとフォーマットを保証します。

<?php
$context = array( 'counter' => 0 );
?>

<div <?php echo wp_interactivity_data_wp_context( $context ); ?>>
  <!-- 子要素は `context.counter` にアクセスできる -->
</div>

ローカルコンテキストへのアクセス

HTML のマークアップ内では、ディレクティブの値で context を参照することで、ローカルコンテキストの値に直接アクセスできます。

<div data-wp-bind--hidden="!context.isOpen">
  <span data-wp-text="context.counter"></span>
</div>

JavaScript では、getContext関数を使用してローカルコンテキストの値にアクセスできます。

store( 'myPlugin', {
  actions: {
    sendAnalyticsEvent() {
      const { counter } = getContext();
      myAnalyticsLibrary.sendEvent( 'updated counter', counter );
    },
  },
  callbacks: {
    logCounter() {
      const { counter } = getContext();
      console.log( `Current counter: ${ counter }` );
    },
  },
} );

getContext 関数はアクションやコールバックの実行をトリガーした要素のローカルコンテキストを返します。

ローカルコンテキストの更新

JavaScript 内でローカルコンテキストの値を更新するには、getContext が返すオブジェクトを変更します。

store( 'myPlugin', {
  actions: {
    increment() {
      const context = getContext();
      context.counter += 1;
    },
    updateName( event ) {
      const context = getContext();
      context.name = event.target.value;
    },
  },
} );

ローカルコンテキストを変更すると、変更した値に依存する任意のディレクティブ内での変更が自動的にトリガーされます。

Interactivity API 内でリアクティブがどのように動作するかの詳細については「リアクティブと宣言型の考え方」を参照してください。

ローカルコンテキストの入れ子

ローカルコンテキストは入れ子にでき、子コンテキストは親コンテキストの値を継承し、必要であれば上書きできます。

<div data-wp-context='{ "theme": "light", "counter": 0 }'>
  <p>Theme: <span data-wp-text="context.theme"></span></p>
  <p>Counter: <span data-wp-text="context.counter"></span></p>

  <div data-wp-context='{ "theme": "dark" }'>
    <p>Theme: <span data-wp-text="context.theme"></span></p>
    <p>Counter: <span data-wp-text="context.counter"></span></p>
  </div>
</div>

この例では、内側の div は theme 値として "dark" を持ちますが、counter 値は親コンテキストから 0 を継承します。

例: ローカル・コンテキストを使用して独立したステートを持つ1つのインタラクティブブロック

この例には1つのインタラクティブブロックがあり、カウンターを表示し、これを増分できます。ローカルコンテキストを使うことでこのブロックの各インスタンスは、複数のブロックがページに追加されても、それぞれ独立したカウンターを持ちます。

<div
  data-wp-interactive="myCounterPlugin"
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-context='{ "counter": 0 }'
>
  <p>Counter: <span data-wp-text="context.counter"></span></p>
  <button data-wp-on--click="actions.increment">Increment</button>
</div>

store( 'myCounterPlugin', {
	actions: {
		increment() {
			const context = getContext();
			context.counter += 1;
		},
	},
} );

この例では、

  1. 初期値 0 の ローカルコンテキスト counter を data-wp-context ディレクティブを使用して定義する。
  2. カウンタは data-wp-text="context.counter" を使用して表示され、ローカルコンテキストから値を読み込む。
  3. インクリメントボタンは data-wp-on--click="actions.increment" を使用して increment アクションをトリガーする。
  4. JavaScript では、getContext 関数を使用して、各ブロックインスタンスのローカルコンテキストにアクセスして変更する。

ユーザーはこのブロックの複数のインスタンスをページに追加でき、各インスタンスはそれぞれの独立したカウンターを保持します。あるブロックの Increment ボタンをクリックすると、その特定のブロックのカウンターのみ増分し、他のブロックには影響しません。

派生ステート

Interactivity API における 派生ステート とは、グローバルステートまたはローカルコンテキストの他の部分から計算される値を指します。値は必要に応じて計算され、保存されません。これにより一貫性が保証され、冗長性が減り、コードの宣言的な性質が強化されます。

派生ステートはモダンなステート管理における基本的な概念で、Interactivity API に固有ではありません。他の一般的なステート管理システムでも使用されていて、Redux では selectors、Preact Signals では computed 値と呼ばれます。

派生ステートにはアプリケーションステートを適切に設計するために不可欠な、いくつかの重要な利点があります。

  1. 単一の真のソース: 派生ステートは本質的な、生のデータのみをステート内に保存することを推進します。核となるデータから計算できる値は、派生ステートにできます。このアプローチにより、インタラクティブブロック内での不整合のリスクを低減できます。
  1. 自動更新: 派生ステートを使用するとき、値は、元となるデータが変更されるたびに自動的に再計算されます。これにより、インタラクティブブロックのすべての部分が、手動で操作することなく、常に最新の情報にアクセスできます。
  1. ステート管理の簡素化: 手動で値を保存し、更新するのではなく、必要に応じて値を計算することで、ステート管理ロジックの複雑さを軽減できます。これは、よりクリーンで保守性の高いコードにつながります。
  1. パフォーマンスの向上: 多くの場合、派生ステートは必要な場合にのみ再計算するよう最適化でき、インタラクティブブロックのパフォーマンスを向上する可能性があります。
  1. デバッグの容易性: 派生ステートでは、データがどこから来て、どのように変換されたが明確です。このため、インタラクティブブロック内の問題を追跡しやすくなります。

つまり派生ステートでは、インタラクティブブロック内の異なるデータ間の関係を宣言的に表現できます。何かが変更されるたびに、関連する値を強制的に更新する必要はありません。

Interactivity API の宣言的コーディングの活用について学習するには「リアクティブと宣言型の考え方」を参照してください。

以下のような場面では派生ステートを使用してください。

  • グローバルステートやローカルコンテキストの一部が、他のステート値から計算できる。
  • 手動で同期を保つ必要のある冗長なデータを避けたい。
  • 派生値を自動的に更新することで、インタラクティブブロック全体での一貫性を確保したい。
  • 関連する複数のステートプロパティを更新する必要性を失くすことで、アクションを単純化したい。

派生ステートの操作

派生ステートの初期化

通常、派生ステートはグローバルステートとまったく同じように wp_interactivity_state 関数を使用して、サーバー上で初期化する必要があります。

  • 初期値が分かっていて、静的な場合は、直接定義できます。
wp_interactivity_state( 'myCounterPlugin', array(
  'counter' => 1, // これはグローバルステート
  'double'  => 2, // これは派生ステート
));

  • あるいは、必要な計算をして定義できます。
$counter = 1;
$double  = $counter * 2;

wp_interactivity_state( 'myCounterPlugin', array(
  'counter' => $counter, // これはグローバルステート
  'double'  => $double,  // これは派生ステート
));

どのようなアプローチにせよ、PHP でページをレンダリングする際には、派生ステートの初期値が使用され、HTML に正しい値を挿入できます。

ディレクティブがサーバ上でどのように処理されるかについては、サーバサイドレンダリングガイドを参照してください。

派生ステートプロパティがローカルコンテキストに依存する場合でも、同じメカニズムが適用されます。

<?php
$counter = 1;

// これはローカルコンテキスト
$context = array( 'counter' => $counter );

wp_interactivity_state( 'myCounterPlugin', array(
  'double' => $counter * 2, // これは派生ステート
));
?>

<div
  data-wp-interactive="myCounterPlugin"
  <?php echo wp_interactivity_data_wp_context( $context ); ?>
>
  <div>
    Counter: <span data-wp-text="context.counter"></span>
  </div>
  <div>
    Double: <span data-wp-text="state.double"></span>
  </div>
</div>

JavaScript で派生ステートは getter を使用して定義します。

const { state } = store( 'myCounterPlugin', {
  state: {
    get double() {
      return state.counter * 2;
    },
  },
} );

派生ステートはローカルコンテキストに依存でき、また、ローカルコンテキストとグローバルステートに同時に依存できます。

const { state } = store( 'myCounterPlugin', {
  state: {
    get double() {
      const { counter } = getContext();
      // ローカルコンテキストに依存
      return counter * 2;
    },
    get product() {
      const { counter } = getContext();
      // ローカルコンテキストとグローバルステートに依存
      return counter * state.factor;
    },
  },
} );

派生ステートがローカルコンテキストに依存し、ローカルコンテキストがサーバー内で動的に変化する場合、派生ステートの初期値の代わりとして、動的に派生ステートを計算する関数 (クロージャ) を使用できます。

<?php
wp_interactivity_state( 'myProductPlugin', array(
  'list'    => array( 1, 2, 3 ),
  'factor'  => 3,
  'product' => function() {
    $state   = wp_interactivity_state();
    $context = wp_interactivity_get_context();
    return $context['item'] * $state['factor'];
  }
));
?>

<template
  data-wp-interactive="myProductPlugin"
  data-wp-each="state.list"
>
  <span data-wp-text="state.product"></span>
</template>

この data-wp-each テンプレートは、以下の HTML をレンダーします (ディレクティブは省略)。

<span>3</span>
<span>6</span>
<span>9</span>

派生ステートへのアクセス

HTML のマークアップ内で、派生ステートの構文はグローバルステートの構文と同じです。ディレクティブ属性の値内で state を参照するだけです。

<span data-wp-text="state.double"></span>

JavaScript 内でも同じです。グローバルステートも派生ステートもストアの state プロパティを通して使用できます。

const { state } = store( 'myCounterPlugin', {
  // ...
  actions: {
    readValues() {
      state.counter; // 通常のステート。1 を返す
      state.double; // 派生ステート。2 を返す
    },
  },
} );

両者に違いがないのは意図的です。開発者は派生ステートとグローバルステートの両方を同じように利用できるため、実用的な意味で互換性があります。

また、派生ステートは別の派生ステートからもアクセスできるため、計算値の複数のレベルを作成できます。

const { state } = store( 'myPlugin', {
  state: {
    get double() {
      return state.counter * 2;
    },
    get doublePlusOne() {
      return state.double + 1;
    },
  },
} );

派生ステートの更新

派生ステートは直接、更新できません。その値を更新するには、派生ステートが依存するグローバルステートまたはローカルコンテキストを更新する必要があります。

const { state } = store( 'myCounterPlugin', {
  // ...
  actions: {
    updateValues() {
      state.counter; // 通常のステート。1 を返す
      state.double; // 派生ステート。2 を返す

      state.counter = 2;

      state.counter; // 通常のステート。2 を返す
      state.double; // 派生ステート。4 を返す
    },
  },
} );

例: 派生ステートを使用しない例と使用する例

ここでカウンターを持ち、その2倍の値を表示する例を考え、派生ステートを使用しない場合と、使用する場合の2つのアプローチを比較します。

派生ステートを使用しない例

const { state } = store( 'myCounterPlugin', {
  state: {
    counter: 1,
    double: 2,
  },
  actions: {
    increment() {
      state.counter += 1;
      state.double = state.counter * 2;
    },
  },
} );

このアプローチでは、state.counter と state.double の両方の値を increment アクション内で手動で更新します。機能は動作しますが、いくつかの欠点があります。

  • 宣言的でない。
  • 複数の場所で state.counter が更新され、開発者が state.double の同期を忘れるとバグにつながる。
  • 関連する値の更新を覚えておく必要があるため、認知的な負荷が高い。

派生ステートを使用する例

const { state } = store( 'myCounterPlugin', {
  state: {
    counter: 1,
    get double() {
      return state.counter * 2;
    },
  },
  actions: {
    increment() {
      state.counter += 1;
    },
  },
} );

この改良版では、

  • state.double は getter として定義され、その値は自動的に state.counter から取得される。
  • increment アクションは state.counter を更新するだけでよい。
  • state.double は、state.counter がどこで、どのように更新されても、常に正しい値を持つことが保証される。

例: 派生ステートをローカルコンテキストと使用する

次に、カウンターを初期化するローカルコンテキストの例を考えます。

store( 'myCounterPlugin', {
	state: {
		get double() {
			const { counter } = getContext();
			return counter * 2;
		},
	},
	actions: {
		increment() {
			const context = getContext();
			context.counter += 1;
		},
	},
} );

<div data-wp-interactive="myCounterPlugin">
	<!-- "Double: 2" をレンダーする -->
	<div data-wp-context='{ "counter": 1 }'>
		Double: <span data-wp-text="state.double"></span>

		<!-- このボタンはローカルカウンターを増分する -->
		<button data-wp-on--click="actions.increment">Increment</button>
	</div>

	<!-- "Double: 4" をレンダーする -->
	<div data-wp-context='{ "counter": 2 }'>
		Double: <span data-wp-text="state.double"></span>

		<!-- このボタンはローカルカウンターを増分する -->
		<button data-wp-on--click="actions.increment">Increment</button>
	</div>
</div>

この例では、派生ステート state.double は、各要素に存在するローカルコンテキストを読み込み、使用されるインスタンスに応じた正しい値を返します。

例: 派生ステートをローカルコンテキストとグローバルステートの両方と使用する

次に、グローバルの税率とローカルの商品価格があるときに、税込みの最終価格を計算する例を考えます。

<div
	data-wp-interactive="myProductPlugin"
	data-wp-context='{ "priceWithoutTax": 100 }'
>
	<p>Product Price: $<span data-wp-text="context.priceWithoutTax"></span></p>
	<p>Tax Rate: <span data-wp-text="state.taxRatePercentage"></span></p>
	<p>Price (inc. tax): $<span data-wp-text="state.priceWithTax"></span></p>
</div>

const { state } = store( 'myProductPlugin', {
	state: {
		taxRate: 0.21,
		get taxRatePercentage() {
			return `${ state.taxRate * 100 }%`;
		},
		get priceWithTax() {
			const { priceWithoutTax } = getContext();
			return priceWithoutTax * ( 1 + state.taxRate );
		},
	},
	actions: {
		updateTaxRate( event ) {
			// グローバルの税率を更新する
			state.taxRate = event.target.value;
		},
		updatePrice( event ) {
			// ローカルの商品価格を更新する
			const context = getContext();
			context.priceWithoutTax = event.target.value;
		},
	},
} );

この例では、priceWithTax はグローバルな taxRate とローカルの priceWithoutTax の両方から派生しています。updateTaxRate アクションや updatePrice アクションでグローバルステートやローカルコンテキストを更新するたびに、Interactivity API は派生ステートを再計算し、DOM の必要な部分を更新します。

派生ステートを使用することでコードベースは、より保守性が高く、エラーが起こりにくくなります。関連するステートの値は常に同期していることが保証され、アクションの複雑さは減り、コードはより宣言的で推測しやすくなります。

サーバーのステートやコンテキストのサブスクライブ

Interactivity API は、ページ全体をリロードすることなくページの一部を動的に置き換えられる、領域ベースのナビゲーション機能を提供します。クエリーブロックは、Force page reload (ページの強制リロード) トグルが無効のとき、この機能をネイティブにサポートします。開発者がカスタムブロックで同じ機能を使用するには、@wordpress/interactivity-router スクリプトモジュールの actions.navigate()を呼び出します。

領域ベースのナビゲーションを使用する場合、重要なポイントとして、インタラクティブブロックはサーバー側が提供するグローバルステートやローカルコンテキストと確実に同期してください。デフォルトでは、Interactivity API は、グローバルステートとローカルコンテキストをサーバーが提供する値で上書きしません。Interactivity API にはこの同期を管理する2つの関数があります。getServerState() と getServerContext() です。

getServerState()

getServerState() を使用するとクライアントサイドナビゲーション中に発生する グローバルステート の変更をサブスクライブできます。この関数は getServerContext() と似ていますが、ローカルコンテキストではなく、グローバルステートを扱います。

getServerState() 関数は読み取り専用のリアクティブオブジェクトを返します。すなわち、返されたオブジェクトの監視用に定義したコールバックは、関数によって返された値が変更されたときにのみトリガーされます。値が同じままであれば、コールバックは再トリガーされません。

ここで複数の問題を含むクイズを考えます。それぞれの問題は別々のページにあります。ユーザーが新しい問題に移動すると、サーバーは新しい問題と、クイズの残り時間を返します。

<div <?php echo wp_interactivity_state( 'myPlugin', array(
	'question' => get_question_for_page( get_the_ID() ),
	'timeLeft' => 5 * 60, // すべての問題に答えるための時間。
) ); ?>>

import { store, getServerState, withSyncEvent } from '@wordpress/interactivity';

const { state } = store( 'myPlugin', {
	actions: {
		// このアクションはディレクティブでトリガーされる。例:
		// <button data-wp-on--click="actions.nextQuestion">Next Question</button>
		nextQuestion: withSyncEvent( function* ( event ) {
			event.preventDefault();
			const { actions } = yield import(
				'@wordpress/interactivity-router'
			);
			actions.navigate( '/question-2' );
		} ),
	},
	callbacks: {
		// このコールバックはディレクティブでトリガーされる。例:
		// <div data-wp-watch="callbacks.updateQuestion"></div>
		updateQuestion() {
			const serverState = getServerState();

      // サーバーから来る新しい値で更新する。
      // `timeLeft` は *更新しない*。なぜならこれは、クイズに含まれる *すべて* の問題を回答するための残り時間だから。
			state.question = serverState.question;
		},
	},
} );

注意: event.preventDefault() のような同期的なイベントメソッドを呼び出す必要があるアクションでは、ハンドラを withSyncEvent() でラップする必要があります。詳細については、withSyncEvent() のドキュメント を参照してください。

getServerContext()

getServerContext() を使用すると、クライアントサイドナビゲーション中に発生した ローカルコンテキスト の変更をサブスクライブできます。この関数は getServerState() と似ていますが、グローバルステートではなく、ローカルコンテキストを扱います。

getServerContext() 関数は読み取り専用のリアクティブオブジェクトを返します。すなわち、返されたオブジェクトの監視用に定義したコールバックは、関数によって返された値が変更されたときにのみトリガーされます。値が同じままであれば、コールバックは再トリガーされません。

ここで複数の問題を含むクイズを考えます。それぞれの問題は別々のページにあります。ユーザーが新しい問題に移動すると、サーバーは新しい問題と、クイズの残り時間を返します。

<div <?php echo wp_interactivity_data_wp_context( array(
	'currentQuestion' => get_question_for_page( get_the_ID() ),
), ); ?>>

import { store, getContext, getServerContext, withSyncEvent } from '@wordpress/interactivity';

store( 'myPlugin', {
	actions: {
		// このアクションはディレクティブでトリガーされる。例:
		// <button data-wp-on--click="actions.nextQuestion">Next Question</button>
		nextQuestion: withSyncEvent( function* ( event ) {
			event.preventDefault();
			const { actions } = yield import(
				'@wordpress/interactivity-router'
			);
			actions.navigate( '/question-2' );
		} ),
	},
	callbacks: {
		// このコールバックはディレクティブでトリガーされる。例:
		// <div data-wp-watch="callbacks.updateQuestion"></div>
		updateQuestion() {
			const serverContext = getServerContext();
			const context = getContext();

			// サーバーから来る新しい値で更新する。
			context.currentQuestion = serverContext.currentQuestion;
		},
	},
} );

いつ使うのか

ナビゲーションイベントによって変更され得るグローバルステートやローカルなコンテキストに依存するインタラクティブブロックに対して、常にアプリケーションのさまざまな部分における一貫性を確保できます。

getServerState() や getServerContext() 使用のベストプラクティス

  • 読み出し専用の参照: getServerState() と getServerContext() はどちらも読み取り専用のオブジェクトを返します。これらのオブジェクトを使用して、グローバルステートやローカルコンテキストを更新できます。
  • コールバックとの統合: これらの関数をストアのコールバックに組み込むと、ステートやコンテキストの変更に反応できます。getServerState() と getServerContext() はどちらもリアクティブオブジェクトを返します。すなわち、それらの監視コールバックは、プロパティの値が変更されたときのみ、トリガーされます。値が変わらなければ、コールバックは再トリガーされません。
  • Callback Integration: Incorporate these functions within your store callbacks to react to state and context changes. Both getServerState() and getServerContext() return reactive objects. This means that their watch callbacks will only trigger when the value of a property changes. If the value remains the same, the callback will not re-trigger.

Config

Interactivity API における Config は、サーバーからクライアントへシリアライズされる静的な構成データを指します。グローバルステートやローカルコンテキストとは異なり、Config の値はリアクティブではありません。つまり、UI の更新をトリガーせず、クライアントサイドのライフサイクル全体を通じて一定のままです。

Config は、PHP から JavaScript に非リアクティブなデータを渡す場合に適しています。たとえば、API エンドポイント、nonce、機能フラグ、翻訳などのユーザー操作中に変更されないデータです。

次のような場合は Config を使用してください。

  • サーバーからクライアントへ静的な構成データを渡す必要がある場合
  • データをリアクティブにする必要がない場合(ユーザー操作中に変更されない場合)
  • API の URL、認証トークン、または機能のトグルを渡したい場合
  • 翻訳やその他の静的なコンテンツを提供する必要がある場合

Config の操作

  • サーバー側での Config の設定
PHP で `wp_interactivity_config()` 関数を使用して設定値を定義します。

```php
// Setting config values
wp_interactivity_config( 'myPlugin', array(
    'restApiUrl'     => get_rest_url( null, 'my-plugin/v1/' ),
    'nonce'          => wp_create_nonce( 'my_plugin_action' ),
    'isUserLoggedIn' => is_user_logged_in(),
    'translations'   => array(
        'loading'    => __( 'Loading...', 'my-plugin' ),
        'error'      => __( 'An error occurred', 'my-plugin' ),
    ),
) );
```

  • クライアント側での Config へのアクセスJavaScript で getConfig() 関数を使用して、構成値を取得します。import { store, getConfig } from '@wordpress/interactivity'; const { state } = store( 'myPlugin', { actions: { *fetchData() { const { restApiUrl, nonce } = getConfig(); try { const response = yield fetch( `${ restApiUrl }data`, { method: 'POST', headers: { 'X-WP-Nonce': nonce, }, } ); const data = yield response.json(); state.data = data; } catch ( error ) { const { translations } = getConfig(); state.errorMessage = translations.error; } }, }, } );

まとめ

効率的なステート管理のポイントは、ステートを最小限に保ち、冗長性を避けることです。派生ステートを使用して動的に値を計算し、データのスコープと要件に基づいてグローバルステートとローカルコンテキストを選択し、静的なサーバーからクライアントへのデータには Config を使用してください。この結果、デバッグや保守がしやすく、よりクリーンで堅牢なアーキテクチャが導かれます。また、ステートやコンテキストをサーバー側と動悸する必要がある場合は、getServerState() や getServerContext() を利用できます。

原文

s
検索
c
新規投稿を作成する
r
返信
e
編集
t
ページのトップへ
j
go to the next post or comment
k
go to the previous post or comment
o
コメントの表示を切替
esc
投稿やコメントの編集をキャンセル