title: "服务器端渲染:在服务器上处理指令" post_status: publish comment_status: open taxonomy: category: - gutenberg-docs post_tag: - Core Concepts - Interactivity Api - Reference Guides
服务器端渲染:在服务器上处理指令
WordPress 始终建立在服务器端渲染的基础之上。传统上,当用户请求一个 WordPress 页面时,服务器会处理 PHP 代码、查询数据库,并生成发送给浏览器的 HTML 标记。
近年来,像 Vue、React 或 Svelte 这样的现代 JavaScript 框架彻底改变了我们构建 Web 应用程序的方式。这些框架提供了响应式和声明式的编程模型,使开发者能够轻松创建动态、交互式的用户界面。
然而,在服务器端渲染方面,这些框架需要一个基于 JavaScript 的服务器(如 NodeJS)来执行其代码并生成初始 HTML。这意味着像 WordPress 这样基于 PHP 的服务器,在不牺牲其原生 PHP 渲染能力的情况下,无法直接使用这些框架。这一限制给希望利用响应式和声明式编程能力,同时仍受益于 WordPress 传统服务器端渲染优势的开发者带来了挑战。交互性 API 通过将响应式和声明式编程原则引入 WordPress,弥合了这一差距,且不损害其服务器端渲染基础。
在本指南中,我们将探讨交互性 API 如何在服务器上处理指令,使 WordPress 能够从初始页面加载就交付交互式、状态感知的 HTML,并为无缝的客户端交互奠定基础。
在服务器上处理指令
Interactivity API 的服务器指令处理功能使 WordPress 能够生成具有正确交互状态的初始 HTML,提供更快的初始渲染。在初始服务器端渲染之后,Interactivity API 的客户端 JavaScript 接管,实现动态更新和交互,无需完全重新加载页面。这种方法结合了两者的优点:传统 WordPress 服务器端渲染的 SEO 和性能优势,以及现代 JavaScript 框架提供的动态、响应式用户界面。
要理解服务器指令处理的工作原理,让我们从一个使用 data-wp-each 指令渲染水果列表的示例开始。
以下是确保指令在 WordPress 服务器端渲染期间被 Interactivity API 的服务器指令处理正确处理的必要步骤。
-
1. 将区块标记为交互式
首先,要启用交互式区块指令的服务器处理,必须在
block.json中添加supports.interactivity:json { "supports": { "interactivity": true } } -
2. 初始化全局状态或本地上下文
然后,必须初始化将在页面服务器端渲染期间使用的全局状态或本地上下文。
如果使用全局状态,必须使用
wp_interactivity_state函数:php wp_interactivity_state( 'myFruitPlugin', array( 'fruits' => array( 'Apple', 'Banana', 'Cherry' ) ));如果使用本地上下文,初始值通过
data-wp-context指令本身定义,可以通过以下方式之一:-
直接添加到 HTML。
html <ul data-wp-context='{ "fruits": ["Apple", "Banana", "Cherry"] }'> ... </ul> -
使用
wp_interactivity_data_wp_context辅助函数。```php <?php $context = array( 'fruits' => array( 'Apple', 'Banana', 'Cherry' ) ); ?>
<ul <?php echo wp_interactivity_data_wp_context( $context ); ?>> ...
-
3. 使用指令定义交互式元素
接下来,需要在 HTML 标记中添加必要的指令。
html
<ul data-wp-interactive="myFruitPlugin">
<template data-wp-each="state.fruits">
<li data-wp-text="context.item"></li>
</template>
</ul>
在这个示例中:
data-wp-interactive 指令用于激活 DOM 元素及其子元素的交互性。
data-wp-each指令用于渲染元素列表。该指令可在<template>标签中使用,其值为指向全局状态或本地上下文中存储的数组的引用路径。data-wp-text指令设置 HTML 元素的内部文本。此处它指向context.item,这是data-wp-each指令存储数组每个元素的位置。
当使用本地上下文而非全局状态时,也可以使用完全相同的指令。唯一的区别是 data-wp-each 指向 context.fruits 而不是 state.fruits:
```html <ul data-wp-interactive="myFruitPlugin" data-wp-context='{ "fruits": [ "Apple", "Banana", "Cherry" ] }'
<template data-wp-each="context.fruits"> <li data-wp-text="context.item"></li> </template>```
就是这样!一旦你通过 supports.interactivity 设置了交互块,初始化了全局状态或本地上下文,并将指令添加到 HTML 标记中,Interactivity API 就会处理其余部分。开发者无需在服务器端编写额外代码来处理这些指令。
在幕后,WordPress 使用 wp_interactivity_process_directives 函数来查找并处理块 HTML 标记中的指令。该函数利用 HTML API 根据找到的指令以及初始的全局状态和/或本地上下文,对标记进行必要的修改。
因此,发送到浏览器的 HTML 标记已经是最终形式,所有指令都已正确处理。这意味着当页面首次在浏览器中加载时,它已经包含了所有交互元素的正确初始状态,无需任何 JavaScript 来修改它。
以下是水果列表示例的最终 HTML 标记(指令已省略):
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
如你所见,data-wp-each 指令为数组中的每种水果生成了一个 <li> 元素,并且 data-wp-text 指令已被处理,用正确的水果名称填充了每个 <li>。
Manipulating the global state and local context in the client
One of the key strengths of the Interactivity API is how it bridges the gap between server-side rendering and client-side interactivity. To do so, the global state and local context initialized on the server are also serialized and made available to the Interactivity API stores in the client, allowing the application to continue functioning and manipulating the DOM dynamically.
Let's extend this example to include a button that the user can click to add a new fruit to the list:
<button data-wp-on--click="actions.addMango">Add Mango</button>
This new button has a data-wp-on--click directive that references actions.addMango, which is defined in our JavaScript store:
const { state } = store( 'myFruitPlugin', {
actions: {
addMango() {
state.fruits.push( 'Mango' );
},
},
} );
The same example would also work if you were using local context:
store( 'myFruitPlugin', {
actions: {
addMango() {
const context = getContext();
context.fruits.push( 'Mango' );
},
},
} );
Now, when the user clicks the "Add Mango" button:
- The
addMangoaction is triggered. - The
'Mango'item is added to thestate.fruits(orcontext.fruits) array. - The Interactivity API automatically updates the DOM, adding a new
<li>element for the new fruit.
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
<li>Mango</li>
</ul>
Remember: initializing the state on the client is not necessary when it has already been done on the server.
store( 'myFruitPlugin', {
state: {
fruits: [ 'Apple', 'Banana', 'Cherry' ], // This is not necessary!
},
} );
在服务器中初始化派生状态
派生状态,无论其源自全局状态、本地上下文还是两者,都可以通过服务器指令处理在服务器端进行处理。
请访问理解全局状态、本地上下文、派生状态和配置指南,以了解更多关于派生状态在交互性 API 中如何工作的信息。
可以静态定义的派生状态
假设我们添加一个可以删除所有水果的按钮:
<button data-wp-on--click="actions.deleteFruits">
删除所有水果
</button>
const { state } = store( 'myFruitPlugin', {
actions: {
// ...
deleteFruits() {
state.fruits = [];
},
},
} );
现在,让我们在没有水果时显示一条特殊消息。为此,我们将使用一个引用名为 state.hasFruits 的派生状态的 data-wp-bind--hidden 指令来显示/隐藏消息。
<div data-wp-interactive="myFruitPlugin">
<ul data-wp-bind--hidden="!state.hasFruits">
<template data-wp-each="state.fruits">
<li data-wp-text="context.item"></li>
</template>
</ul>
<div data-wp-bind--hidden="state.hasFruits">抱歉,没有水果!</div>
</div>
派生状态 state.hasFruits 在客户端使用 getter 定义:
const { state } = store( 'myFruitPlugin', {
state: {
get hasFruits() {
return state.fruits.length > 0;
},
},
// ...
} );
到目前为止,客户端一切正常,当我们按下"删除所有水果"按钮时,"抱歉,没有水果!"的消息将会显示。问题在于,由于 state.hasFruits 未在服务器上定义,hidden 属性将不会成为初始 HTML 的一部分,这意味着在 JavaScript 加载之前,消息也会一直显示,这不仅会给访问者带来困惑,还会在 JavaScript 最终加载并隐藏消息时导致布局偏移。
要解决此问题,您必须使用 wp_interactivity_state 在服务器上定义派生状态的初始值。
-
当初始值已知且为静态时,可以直接定义:
php wp_interactivity_state( 'myFruitPlugin', array( 'fruits' => array( 'Apple', 'Banana', 'Cherry' ), 'hasFruits' => true )); -
或者可以通过进行必要的计算来定义:
```php $fruits = array( 'Apple', 'Banana', 'Cherry' ); $hasFruits = count( $fruits ) > 0;
wp_interactivity_state( 'myFruitPlugin', array( 'fruits' => $fruits, 'hasFruits' => $hasFruits, )); ```
无论采用哪种方法,关键点在于 state.hasFruits 的初始值现在已在服务器上定义。这使得服务器指令处理能够处理 data-wp-bind--hidden 指令并修改 HTML 标记,在需要时添加 hidden 属性。
需要动态定义的派生状态
在大多数情况下,初始派生状态可以像前一个示例那样静态定义。但有时,其值取决于服务器中也会变化的动态值,此时需要在 PHP 中复制派生逻辑。
为了查看此示例,让我们继续为每种水果添加购物车表情符号(🛒),具体取决于它是否在购物清单中。
首先,添加一个表示购物清单的数组。请记住,尽管为了简单起见这些数组是静态的,但通常您将处理动态信息,例如来自数据库的信息。
wp_interactivity_state( 'myFruitPlugin', array(
'fruits' => array( 'Apple', 'Banana', 'Cherry' ),
'shoppingList' => array( 'Apple', 'Cherry' ),
));
现在,在客户端添加一个派生状态,用于检查每种水果是否在购物清单中并返回表情符号。
store( 'myFruitPlugin', {
state: {
get onShoppingList() {
const context = getContext();
return state.shoppingList.includes( context.item ) ? '🛒' : '';
},
},
// ...
} );
让我们使用该派生状态为每种水果显示相应的表情符号。
<ul data-wp-interactive="myFruitPlugin">
<template data-wp-each="state.fruits">
<li>
<span data-wp-text="context.item"></span>
<span data-wp-text="state.onShoppingList"></span>
</li>
</template>
</ul>
同样,到目前为止,客户端一切正常,访问者将看到购物清单中水果的正确表情符号。然而,由于 state.onShoppingList 未在服务器上定义,表情符号将不是初始 HTML 的一部分,并且在 JavaScript 加载之前不会显示。
让我们通过使用 wp_interactivity_state 添加初始派生状态来解决此问题。请记住,这次该值取决于来自 data-wp-each 指令的 context.item,这使得派生值成为动态的,因此让我们在 PHP 中复制 JavaScript 逻辑:
wp_interactivity_state( 'myFruitPlugin', array(
// ...
'onShoppingList' => function() {
$state = wp_interactivity_state();
$context = wp_interactivity_get_context();
return in_array( $context['item'], $state['shoppingList'] ) ? '🛒' : '';
}
));
就是这样!现在,我们的服务器可以计算派生状态,并知道哪些水果在购物清单上,哪些不在。这使得服务器指令处理能够用正确的值填充初始 HTML,确保用户即使在 JavaScript 运行时加载之前也能立即看到正确的信息。
Serializing other processed values to be consumed on the client
The wp_interactivity_state function is also valuable for sending processed values from the server to the client so they can be consumed later on. This feature is useful in many situations, such as managing translations.
Let's add translations to our example to see how this would work.
<?php
wp_interactivity_state( 'myFruitPlugin', array(
'fruits' => array( __( 'Apple' ), __( 'Banana' ), __( 'Cherry' ) ),
'shoppingList' => array( __( 'Apple' ), __( 'Cherry' ) ),
// ...
?>
<div data-wp-interactive="myFruitPlugin">
<button data-wp-on--click="actions.deleteFruits">
<?php echo __( 'Delete all fruits' ); ?>
</button>
<button data-wp-on--click="actions.addMango">
<?php echo __( 'Add Mango' ); ?>
</button>
<ul data-wp-bind--hidden="!state.hasFruits">
<template data-wp-each="state.fruits">
<li>
<span data-wp-text="context.item"></span>
<span data-wp-text="state.onShoppingList"></span>
</li>
</template>
</ul>
<div data-wp-bind--hidden="state.hasFruits">
<?php echo __( 'No fruits, sorry!' ); ?>
</div>
</div>
That's it! Since the Interactivity API works in PHP, you can add translations directly to the global state, the local context and the HTML markup.
But wait, what happens with our addMango action? Remember, this action is defined only on JavaScript:
const { state } = store( 'myFruitPlugin', {
actions: {
addMango() {
state.fruits.push( 'Mango' ); // Not translated!
},
},
} );
To fix this issue, you can use the wp_interactivity_state function to serialize the translated mango string and then access that value in your action.
wp_interactivity_state( 'myFruitPlugin', array(
'fruits' => array( __( 'Apple' ), __( 'Banana' ), __( 'Cherry' ) ),
'mango' => __( 'Mango' ),
));
const { state } = store( 'myFruitPlugin', {
actions: {
addMango() {
// `state.mango` contains the 'Mango' string already translated.
state.fruits.push( state.mango );
},
},
} );
Take into account that if your application is more dynamic, you could serialize an array with all the fruit translations and just work with fruit keywords in your actions. For example:
wp_interactivity_state( 'myFruitPlugin', array(
'fruits' => array( 'apple', 'banana', 'cherry' ),
'translatedFruits' => array(
'apple' => __( 'Apple' ),
'banana' => __( 'Banana' ),
'cherry' => __( 'Cherry' ),
'mango' => __( 'Mango' ),
),
'translatedFruit' => function() {
$state = wp_interactivity_state();
$context = wp_interactivity_get_context();
return $state['translatedFruits'][ $context['item'] ];
}
));
const { state } = store( 'myFruitPlugin', {
state: {
get translatedFruit() {
const context = getContext();
return state.translatedFruits[ context.item ];
}
}
actions: {
addMango() {
state.fruits.push( 'mango' );
},
},
} );
<template data-wp-each="state.fruits">
<li data-wp-text="state.translatedFruit"></li>
</template>
从服务器序列化信息在其他场景中也很有用,例如传递 Ajax/REST-API URL 和随机数。
wp_interactivity_state( 'myPlugin', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myPlugin_nonce' ),
));
const { state } = store( 'myPlugin', {
actions: {
*doSomething() {
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 );
},
},
} );
经典主题中的指令处理
在交互式区块中,只要在 block.json 文件中添加 supports.interactivity,服务器指令处理就会自动进行。但经典主题呢?
经典主题同样可以使用交互式 API,如果它们希望利用服务器指令处理(这通常是推荐的),可以通过 wp_interactivity_process_directives 函数来实现。该函数接收包含未处理指令的 HTML 标记,并根据全局状态、局部上下文和派生状态的初始值返回修改后的 HTML 标记。
// 初始化全局状态和派生状态…
wp_interactivity_state( '...', /* ... */ );
// 包含指令的交互式 HTML 标记。
$html = '<div data-wp-...>...</div>';
// 处理指令,使其准备好发送到客户端。
$processed_html = wp_interactivity_process_directives( $html );
就是这样!你不需要做其他任何事情。
如果要在模板文件中使用 wp_interactivity_process_directives,可以使用 ob_start 和 ob_get_clean 来捕获 HTML 输出并在渲染前进行处理。
<?php
wp_interactivity_state( 'myClassicTheme', /* ... */ );
ob_start();
?>
<div data-wp-interactive="myClassicTheme">
...
</div>
<?php
$html = ob_get_clean();
echo wp_interactivity_process_directives( $html );
重要提示: 你只需要处理指令一次。如果在另一个模板中包含内部模板文件,请确保仅在最外层的模板文件中调用 wp_interactivity_process_directives,以避免重复处理。
结论
Interactivity API 确保了从服务器渲染内容到客户端交互的无缝透明过渡。您在服务器上定义的指令、初始全局状态或本地上下文以及客户端行为,共同构成了同一生态系统的一部分。这种统一方法简化了开发,提高了可维护性,并在创建交互式 WordPress 区块时提供了更好的开发者体验。