You can also destructure the object returned by store:
```js
const { state } = store( 'myPlugin' );
```
And you can do the same even if you are defining the store at that moment, which is the most common scenario:
```js
const { state } = store( 'myPlugin', {
state: {
// ...
},
actions: {
toggle() {
state.show = ! state.show;
},
},
} );
```
The global state initialized on the server using the `wp_interactivity_state` function is also included in that object because it is automatically serialized from the server to the client:
```php
wp_interactivity_state( 'myPlugin', array(
'someValue' => 1,
));
```
```js
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.
},
},
} );
```
Lastly, all calls to the `store` function with the same namespace are merged together:
```js
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.
},
},
} );
```
Updating the global state
To update the global state, all you need to do is mutate the state object once you have obtained it from the store function:
The local context is initialized directly within the HTML structure using the data-wp-context directive. This directive accepts a JSON string that defines the initial values for that piece of context.
html
<div data-wp-context='{ "counter": 0 }'>
<!-- Child elements will have access to `context.counter` -->
</div>
You can also initialize the local context on the server using the wp_interactivity_data_wp_context PHP helper, which ensures proper escaping and formatting of the stringified values:
Typically, the derived state should be initialized on the server using the wp_interactivity_state function in the exact same way as the global state.
When the initial value is known and static, it can be defined directly:
php
wp_interactivity_state( 'myCounterPlugin', array(
'counter' => 1, // This is global state.
'double' => 2, // This is derived state.
));
Or it can be defined by doing the necessary computations:
```php
$counter = 1;
$double = $counter * 2;
wp_interactivity_state( 'myCounterPlugin', array(
'counter' => $counter, // This is global state.
'double' => $double, // This is derived state.
));
```
Regardless of the approach, the initial derived state values will be used during the rendering of the page in PHP, and the HTML can be populated with the correct values.
Derived state can depend on local context, or local context and global state at the same time.
js
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;
},
},
} );
In some cases, when the derived state depends on the local context and the local context can change dynamically in the server, instead of the initial derived state, you can use a function (Closure) that calculates it dynamically.
<?phpwp_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> ``` This `data-wp-each` template will render this HTML (directives omitted): ```html <span>3</span> <span>6</span> <span>9</span> ```- **Accessing the derived state** In the HTML markup, the syntax for the derived state is the same as the one for the global state, just by referencing `state` in the directive attribute values. ```html <span data-wp-text="state.double"></span> ``` The same happens in JavaScript. Both global state and derived state can be consumed through the `state` property of the store: ```js const { state } = store( 'myCounterPlugin', { // ... actions: { readValues() { state.counter; // Regular state, returns 1. state.double; // Derived state, returns 2. }, }, } ); ``` This lack of distinction is intentional, allowing developers to consume both derived and global state uniformly, and making them interchangeable in practice. You can also access the derived state from another derived state and, thus, create multiple levels of computed values. ```js const { state } = store( 'myPlugin', { state: { get double() { return state.counter * 2; }, get doublePlusOne() { return state.double + 1; }, }, } ); ```- **Updating the derived state** The derived state cannot be updated directly. To update its values, you need to update the global state or local context on which that derived state depends. ```js 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. }, }, } ); ```### 示例:不使用派生状态 vs 使用派生状态让我们考虑一个场景:有一个计数器需要显示其双倍值,并比较两种方法:一种不使用派生状态,另一种使用派生状态。- **不使用派生状态** ```js 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` 同步,可能会导致错误。 - 需要更多的认知负担来记住更新相关值。- **使用派生状态** ```js 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.counter` 如何或在何处更新,`state.double` 都始终保证具有正确的值。### Example: Using derived state with local contextLet's now consider a scenario where there is a local context that initializes a counter.```jsstore( 'myCounterPlugin', { state: { get double() { const { counter } = getContext(); return counter * 2; }, }, actions: { increment() { const context = getContext(); context.counter += 1; }, },} );
<divdata-wp-interactive="myCounterPlugin"><!-- This will render "Double: 2" --><divdata-wp-context='{ "counter": 1 }'>
Double: <spandata-wp-text="state.double"></span><!-- This button will increment the local counter. --><buttondata-wp-on--click="actions.increment">Increment</button></div><!-- This will render "Double: 4" --><divdata-wp-context='{ "counter": 2 }'>
Double: <spandata-wp-text="state.double"></span><!-- This button will increment the local counter. --><buttondata-wp-on--click="actions.increment">Increment</button></div></div>
In this example, the derived state state.double reads from the local context present in each element and returns the correct value for each instance where it is used.
Example: Using derived state with both local context and global state
Let's now consider a scenario where there is a global tax rate and local product prices and calculate the final price, including tax.
const{state}=store('myProductPlugin',{state:{taxRate:0.21,gettaxRatePercentage(){return`${state.taxRate*100}%`;},getpriceWithTax(){const{priceWithoutTax}=getContext();returnpriceWithoutTax*(1+state.taxRate);},},actions:{updateTaxRate(event){// Updates the global tax rate.state.taxRate=event.target.value;},updatePrice(event){// Updates the local product price.constcontext=getContext();context.priceWithoutTax=event.target.value;},},});
In this example, priceWithTax is derived from both the global taxRate and the local priceWithoutTax. Every time you update the global state or local context through the updateTaxRate or updatePrice actions, the Interactivity API recomputes the derived state and updates the necessary parts of the DOM.
By using derived state, you create a more maintainable and less error-prone codebase. It ensures that related state values are always in sync, reduces the complexity of your actions, and makes your code more declarative and easier to reason about.
使用基于区域导航时,必须确保交互式区块与服务器提供的全局状态和本地上下文保持同步。默认情况下,交互性 API 不会用服务器提供的值覆盖全局状态或本地上下文。该 API 提供了两个函数来帮助管理同步:getServerState() 和 getServerContext()。
getServerState()
getServerState() allows you to subscribe to changes in the global state that occur during client-side navigation. This function is analogous to getServerContext(), but it works with the global state instead of the local context.
The getServerState() function returns a read-only reactive object. This means that any callbacks you have defined that watch the returned object will only trigger when the value returned by the function changes. If the value remains the same, the callback will not re-trigger.
Let's consider a quiz that has multiple questions. Each question is a separate page. When the user navigates to a new question, the server provides the new question and the time left to answer all the questions.
<?phpwp_interactivity_state('myPlugin',array('question'=>get_question_for_page(get_the_ID()),'timeLeft'=>5*60,// Time to answer all the questions.));?><div data-wp-interactive="myPlugin">
import{store,getServerState,withSyncEvent}from'@wordpress/interactivity';const{state}=store('myPlugin',{actions:{// This action would be triggered by a directive, like:// <button data-wp-on--click="actions.nextQuestion">Next Question</button>nextQuestion:withSyncEvent(function*(event){event.preventDefault();const{actions}=yieldimport('@wordpress/interactivity-router');actions.navigate('/question-2');}),},callbacks:{// This callback would be triggered by a directive, like:// <div data-wp-watch="callbacks.updateQuestion"></div>updateQuestion(){constserverState=getServerState();// Update with the new value coming from the server.// We DON'T want to update `timeLeft` because it represents the time left to answer ALL the questions.state.question=serverState.question;},},});
Note: Actions that need to call synchronous event methods like event.preventDefault() must wrap the handler with withSyncEvent(). See the withSyncEvent() documentation for details.
getServerContext()
getServerContext() allows you to subscribe to changes in the local context that occur during client-side navigation. This function is analogous to getServerState(), but it works with the local context instead of the global state.
The getServerContext() function returns a read-only reactive object. This means that any callbacks you have defined that watch the returned object will only trigger when the value returned by the function changes. If the value remains the same, the callback will not re-trigger.
Consider a quiz that has multiple questions. Each question is a separate page. When the user navigates to a new question, the server provides the new question and the time left to answer all the questions.
import{store,getContext,getServerContext,withSyncEvent,}from'@wordpress/interactivity';store('myPlugin',{actions:{// This action would be triggered by a directive, like:// <button data-wp-on--click="actions.nextQuestion">Next Question</button>nextQuestion:withSyncEvent(function*(event){event.preventDefault();const{actions}=yieldimport('@wordpress/interactivity-router');actions.navigate('/question-2');}),},callbacks:{// This callback would be triggered by a directive, like:// <div data-wp-watch="callbacks.updateQuestion"></div>updateQuestion(){constserverContext=getServerContext();constcontext=getContext();// Update with the new value coming from the server.context.currentQuestion=serverContext.currentQuestion;},},});