グローバルステート
グローバルステートは、インタラクティビティAPIにおいて、ページ上の任意のインタラクティブブロックによってアクセスおよび変更可能なグローバルデータを指します。これは共有情報のハブとして機能し、ブロックの異なる部分が通信し、同期を保つことを可能にします。グローバルステートは、DOMツリー内の位置に関係なく、インタラクティブブロック間で情報を交換するための理想的なメカニズムです。
グローバルステートを使用すべき場合:
- DOM階層で直接関連していない複数のインタラクティブブロック間でデータを共有する必要がある場合。
- すべてのインタラクティブブロックで特定のデータの単一の真実のソースを維持したい場合。
- UIの複数の部分に同時に影響を与えるデータを扱っている場合。
- ページ全体にグローバルな機能を実装したい場合。
グローバルステートの操作
- グローバルステートの初期化
通常、初期のグローバルステート値は、wp_interactivity_state
関数を使用してサーバー上で定義されるべきです:
これらの初期グローバルステート値は、PHPでページをレンダリングする際に、ブラウザに送信されるHTMLマークアップを埋めるために使用されます。// Populates the initial global state values.
wp_interactivity_state( 'myPlugin', array(
'isDarkTheme' => true,
'show' => false,
'helloText' => __( 'world' ),
));
- 開発者によってPHPファイルに書かれた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-async--click="actions.toggle">Toggle</button>
</div>
- ディレクティブが処理された後、ブラウザに送信される準備が整った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-async--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では、
store
関数は@wordpress/interactivity
パッケージから取得され、セッターおよびゲッターとして機能し、選択された名前空間のストアを返します。
アクションやコールバック内でグローバルステートにアクセスするには、state
関数によって返されるオブジェクトのプロパティを使用できます:const myPluginStore = store( 'myPlugin' );
myPluginStore.state; // This is the state of the 'myPlugin' namespace.
また、
store
によって返されるオブジェクトを分解することもできます:const { state } = store( 'myPlugin' );
そして、ストアをその時点で定義している場合でも同じことができます。これは最も一般的なシナリオです:
const { state } = store( 'myPlugin', {
state: {
// ...
},
actions: {
toggle() {
state.show = ! state.show;
},
},
} );
``````bash
wp_interactivity_state( 'myPlugin', array(
'someValue' => 1,
));
`
const { state } = store( 'myPlugin', {
state: {
otherValue: 2,
},
actions: {
readGlobalState() {
state.someValue; // It exists and its initial value is 1.
state.otherValue; // It exists and its initial value is 2.
},
},
} );
最後に、同じ名前空間を持つstore
関数へのすべての呼び出しは一緒にマージされます:
store( 'myPlugin', { state: { someValue: 1 } } );
store( 'myPlugin', { state: { otherValue: 2 } } );
/* All calls to `store` return a stable reference to the same object, so you
* can get a reference to `state` from any of them. */
const { state } = store( 'myPlugin' );
store( 'myPlugin', {
actions: {
readValues() {
state.someValue; // It exists and its initial value is 1.
state.otherValue; // It exists and its initial value is 2.
},
},
} );
- グローバルステートの更新
グローバルステートを更新するには、store
関数から取得したstate
オブジェクトを一度変更するだけで済みます:
グローバルステートの変更は、変更された値に依存する任意のディレクティブの更新を自動的にトリガーします。const { state } = store( 'myPlugin', {
actions: {
updateValues() {
state.someValue = 3;
state.otherValue = 4;
},
},
} );
反応的かつ宣言的な考え方ガイドを訪れて、インタラクティビティAPIにおけるリアクティビティの仕組みについてさらに学んでください。
例: グローバルステートを使用して通信する2つのインタラクティブブロック
この例では、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-async--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-async--click="actions.increment"
を使用します。 - 4. JavaScriptでは、
increment
アクションがstate.counter
を増加させることによってグローバルステートを直接変更します。
両方のブロックは独立しており、ページ上のどこにでも配置できます。ネストする必要もなく、DOM構造で直接関連する必要もありません。これらのインタラクティブブロックの複数のインスタンスをページに追加することができ、すべてが同じグローバルカウンター値を共有し、更新します。
ローカルコンテキスト
ローカルコンテキストは、インタラクティビティAPIにおいて、HTML構造内の特定の要素内で定義されたローカルデータを指します。グローバルステートとは異なり、ローカルコンテキストは定義された要素とその子要素にのみアクセス可能です。
ローカルコンテキストは、個々のインタラクティブブロックに対して独立したステートを維持する必要がある場合に特に便利であり、各ブロックのインスタンスが他のブロックと干渉することなく独自のユニークなデータを維持できることを保証します。
ローカルコンテキストを使用すべき場合:
- 同じインタラクティブブロックの複数のインスタンスに対して別々のステートを維持する必要がある場合。
- 特定のインタラクティブブロックとその子要素にのみ関連するデータをカプセル化したい場合。
- UIの特定の部分に限定された機能を実装する必要がある場合。
ローカルコンテキストの操作
ローカルコンテキストの初期化
ローカルコンテキストは、data-wp-context
ディレクティブを使用してHTML構造内で直接初期化されます。このディレクティブは、そのコンテキストの初期値を定義するJSON文字列を受け入れます。<div data-wp-context='{ "counter": 0 }'>
<!-- Child elements will have access to `context.counter` -->
</div>
サーバー上で
wp_interactivity_data_wp_context
PHPヘルパーを使用してローカルコンテキストを初期化することもでき、文字列化された値の適切なエスケープとフォーマットが保証されます:<?php
$context = array( 'counter' => 0 );
?>
<div <?php echo wp_interactivity_data_wp_context( $context ); ?>>
<!-- Child elements will have access to `context.counter` -->
</div>
- ローカルコンテキストへのアクセス
HTMLマークアップ内では、ディレクティブ値でcontext
を参照することにより、ローカルコンテキスト値に直接アクセスできます:
JavaScriptでは、<div data-wp-bind--hidden="!context.isOpen">
<span data-wp-text="context.counter"></span>
</div>
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;
},
},
} );
反応的かつ宣言的な考え方ガイドを訪れて、インタラクティビティ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
を継承します。
例: ローカルコンテキストを使用して独立したステートを持つインタラクティブブロック
この例では、カウンターを表示し、それを増加させることができる単一のインタラクティブブロックがあります。ローカルコンテキストを使用することで、このブロックの各インスタンスは独自の独立したカウンターを持ち、ページに複数のブロックが追加されても影響を受けません。
<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-async--click="actions.increment">Increment</button>
</div>
store( 'myCounterPlugin', {
actions: {
increment() {
const context = getContext();
context.counter += 1;
},
},
} );
この例では:
- 1. 初期
counter
値が0
のローカルコンテキストがdata-wp-context
ディレクティブを使用して定義されます。 - 2. カウンターは
data-wp-text="context.counter"
を使用して表示され、これはローカルコンテキストから読み取られます。 - 3. インクリメントボタンは
data-wp-on-async--click="actions.increment"
を使用してインクリメントアクションをトリガーします。 - 4. JavaScriptでは、
getContext
関数を使用して各ブロックインスタンスのローカルコンテキストにアクセスし、変更します。
ユーザーはこのブロックの複数のインスタンスをページに追加でき、各インスタンスは独自の独立したカウンターを維持します。一つのブロックの「インクリメント」ボタンをクリックすると、その特定のブロックのカウンターにのみ影響し、他のブロックには影響しません。
派生ステート
派生ステートは、インタラクティビティAPIにおいて、グローバルステートまたはローカルコンテキストの他の部分から計算された値を指します。これは保存されるのではなく、必要に応じて計算されます。それは一貫性を保証し、冗長性を減らし、コードの宣言的な性質を強化します。
派生ステートは、現代のステート管理における基本的な概念であり、インタラクティビティAPIに特有のものではありません。Reduxのような他の人気のあるステート管理システムでも使用されており、そこでの呼称はselectors
です。また、Preact Signalsではcomputed
値として知られています。
派生ステートは、よく設計されたアプリケーションステートの重要な部分であるいくつかの主要な利点を提供します。これには以下が含まれます:
- 1. 単一の真実のソース: 派生ステートは、ステートに必要な基本的な生データのみを保存することを奨励します。このコアデータから計算できる値はすべて派生ステートになります。このアプローチは、インタラクティブブロック内の不整合のリスクを減らします。
- 2. 自動更新: 派生ステートを使用すると、基礎データが変更されるたびに値が自動的に再計算されます。これにより、インタラクティブブロックのすべての部分が常に最新の情報にアクセスできるようになります。
- 3. 簡素化されたステート管理: 値を手動で保存および更新するのではなく、必要に応じて計算することにより、ステート管理ロジックの複雑さを減らします。これにより、よりクリーンで保守しやすいコードが得られます。
- 4. パフォーマンスの向上: 多くの場合、派生ステートは必要なときにのみ再計算されるように最適化でき、インタラクティブブロックのパフォーマンスを向上させる可能性があります。
- 5. デバッグの容易さ: 派生ステートを使用すると、データの起源と変換方法が明確になります。これにより、インタラクティブブロック内の問題を追跡しやすくなります。
本質的に、派生ステートは、何かが変更されるたびに関連する値を命令的に更新するのではなく、インタラクティブブロック内の異なるデータの間の関係を宣言的に表現することを可能にします。
詳細については、反応的かつ宣言的な考え方ガイドを訪れて、インタラクティビティAPIにおける宣言的コーディングを活用する方法を学んでください。
派生ステートを使用すべき場合:
- グローバルステートまたはローカルコンテキストの一部が他のステート値から計算できる場合。
- 手動で同期を保つ必要がある冗長データを避けるため。
- 派生値を自動的に更新することによって、インタラクティブブロック間の一貫性を確保するため。
- 複数の関連するステートプロパティを更新する必要を取り除くことによって、アクションを簡素化するため。
派生ステートの操作
- 派生ステートの初期化
通常、派生ステートは、グローバルステートと同じ方法でwp_interactivity_state
関数を使用してサーバー上で初期化されるべきです。- 初期値が既知で静的な場合は、直接定義できます:
wp_interactivity_state( 'myCounterPlugin', array(
'counter' => 1, // This is global state.
'double' => 2, // This is derived state.
));
- 初期値が既知で静的な場合は、直接定義できます:
または、必要な計算を行うことによって定義できます:
$counter = 1;
$double = $counter * 2;
wp_interactivity_state( 'myCounterPlugin', array(
'counter' => $counter, // This is global state.
'double' => $double, // This is derived state.
));
アプローチに関係なく、初期派生ステート値はPHPでページをレンダリングする際に使用され、HTMLは正しい値で埋められます。
サーバーサイドレンダリングガイドを訪れて、ディレクティブがサーバーでどのように処理されるかについてさらに学んでください。
派生ステートプロパティがローカルコンテキストに依存する場合でも、同じメカニズムが適用されます。<?php
$counter = 1;
// This is the local context.
$context = array( 'counter' => $counter );
wp_interactivity_state( 'myCounterPlugin', array(
'double' => $counter * 2, // This is derived state.
));
?>
<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では、派生ステートはゲッターを使用して定義されます:
const { state } = store( 'myCounterPlugin', {
state: {
get double() {
return state.counter * 2;
},
},
} );
派生ステートはローカルコンテキスト、またはローカルコンテキストとグローバルステートの両方に依存することができます。
const { state } = store( 'myCounterPlugin', {
state: {
get double() {
const { counter } = getContext();
// Depends on local context.
return counter * 2;
},
get product() {
const { counter } = getContext();
// Depends on local context and global state.
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
を参照するだけです。
JavaScriptでも同様です。グローバルステートと派生ステートの両方は、ストアの<span data-wp-text="state.double"></span>
state
プロパティを通じて消費できます:
この区別の欠如は意図的であり、開発者が派生ステートとグローバルステートの両方を均一に消費できるようにし、実際に相互に置き換え可能にします。const { state } = store( 'myCounterPlugin', {
// ...
actions: {
readValues() {
state.counter; // Regular state, returns 1.
state.double; // Derived state, returns 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; // Regular state, returns 1.
state.double; // Derived state, returns 2.
state.counter = 2;
state.counter; // Regular state, returns 2.
state.double; // Derived state, returns 4.
},
},
} );
例: 派生ステートを使用しない場合と使用する場合
カウンターがあり、その倍の値を表示する必要があるシナリオを考えてみましょう。派生ステートを使用しないアプローチと使用するアプローチの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;
},
},
} );
例: ローカルコンテキストを使用した派生ステート
ローカルコンテキストがカウンターを初期化するシナリオを考えてみましょう。
store( 'myCounterPlugin', {
state: {
get double() {
const { counter } = getContext();
return counter * 2;
},
},
actions: {
increment() {
const context = getContext();
context.counter += 1;
},
},
} );
<div data-wp-interactive="myCounterPlugin">
<!-- This will render "Double: 2" -->
<div data-wp-context='{ "counter": 1 }'>
Double: <span data-wp-text="state.double"></span>
<!-- This button will increment the local counter. -->
<button data-wp-on-async--click="actions.increment">Increment</button>
</div>
<!-- This will render "Double: 4" -->
<div data-wp-context='{ "counter": 2 }'>
Double: <span data-wp-text="state.double"></span>
<!-- This button will increment the local counter. -->
<button data-wp-on-async--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 price * ( 1 + state.taxRate );
},
},
actions: {
updateTaxRate( event ) {
// Updates the global tax rate.
state.taxRate = event.target.value;
},
updatePrice( event ) {
// Updates the local product price.
const context = getContext();
context.priceWithoutTax = event.target.value;
},
},
} );
この例では、priceWithTax
はグローバルtaxRate
とローカルpriceWithoutTax
の両方から派生します。updateTaxRate
またはupdatePrice
アクションを通じてグローバルステートまたはローカルコンテキストを更新するたびに、インタラクティビティAPIは派生ステートを再計算し、DOMの必要な部分を更新します。
派生ステートを使用することで、より保守性が高く、エラーが発生しにくいコードベースを作成できます。関連するステート値が常に同期され、アクションの複雑さが減り、コードがより宣言的で理解しやすくなります。
結論
効果的なステート管理の鍵は、ステートを最小限に保ち、冗長性を避けることです。派生ステートを使用して値を動的に計算し、データのスコープと要件に基づいてグローバルステートとローカルコンテキストの間で選択してください。これにより、クリーンで堅牢なアーキテクチャが実現し、デバッグや保守が容易になります。