gutenbergdocs/reference-guides/interactivity-api/api-reference.md
2025-10-22 01:33:45 +08:00

44 KiB
Raw Blame History

服务器端函数

Interactivity API 提供了一系列便捷函数,使您能够在服务器端初始化和引用配置选项。这对于提供初始数据至关重要,服务器端指令处理将利用这些数据在 HTML 标记发送到浏览器前对其进行修改。这也是充分利用 WordPress 诸多 API如随机数验证、AJAX 和翻译功能)的绝佳方式。

wp_interactivity_config

wp_interactivity_config 用于设置或获取与存储命名空间关联的配置数组。 该配置在客户端也可用,但属于静态信息。

可将其视为网站交互的全局设置,这些设置不会随用户交互而更新。

设置示例:

	wp_interactivity_config( 'myPlugin', array( 'showLikeButton' => is_user_logged_in() ) );

获取示例:

  wp_interactivity_config( 'myPlugin' );

此配置可在客户端获取:

// view.js

const { showLikeButton } = getConfig();

wp_interactivity_state

wp_interactivity_state 用于在服务器端初始化全局状态,该状态将用于处理服务器端指令,随后会与客户端定义的任何全局状态合并。

在服务器端初始化全局状态还允许您使用许多关键的 WordPress API包括 AJAX随机数验证

wp_interactivity_state 函数接收两个参数:一个用作引用标识的命名空间字符串,以及一个包含值的关联数组。

以下是传递带有随机数的 WP 管理后台 AJAX 端点的示例:

// render.php

wp_interactivity_state(
	'myPlugin',
	array(
		'ajaxUrl' => admin_url( 'admin-ajax.php' ),
		'nonce'   => wp_create_nonce( 'myPlugin_nonce' ),
	),
);
// view.js

const { state } = store( 'myPlugin', {
	actions: {
		*doSomething() {
			try {
				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( '服务器数据!', data );
			} catch ( e ) {
				// 出现错误!
			}
		},
	},
} );

wp_interactivity_process_directives

wp_interactivity_process_directives 在处理完指令后返回更新后的 HTML。

这是 Interactivity API 服务器端渲染部分的核心函数,且为公开函数,因此任何 HTML无论是否为区块都可以被处理。

以下代码:

wp_interactivity_state( 'myPlugin', array( 'greeting' => 'Hello, World!' ) );
$html_content = '<div data-wp-text="myPlugin::state.greeting"></div>';
$processed_html = wp_interactivity_process_directives( $html_content );
echo $processed_html;

将输出:

<div data-wp-text="myPlugin::state.greeting">Hello, World!</div>

wp_interactivity_data_wp_context

wp_interactivity_data_wp_context 返回上下文指令的字符串化 JSON。 此函数是在服务器端渲染标记中输出 data-wp-context 属性的推荐方式。


$my_context = array(
	'counter' => 0,
	'isOpen'  => true,
);
?>
<div
 <?php echo wp_interactivity_data_wp_context( $my_context ); ?>
>
</div>

将输出:

<div data-wp-context='{"counter":0,"isOpen":true}'></div>

私有存储

特定的存储命名空间可被标记为私有,从而阻止其他命名空间访问其内容。实现方式是在 store() 调用中添加 lock 选项,如下例所示。这样,后续对同一锁定命名空间执行 store() 时会抛出错误,意味着该命名空间仅能在首次 store() 调用返回引用的位置被访问。这对于希望隐藏部分插件存储、避免被扩展者访问的开发者特别有用。

const { state } = store(
	'myPlugin/private',
	{ state: { messages: [ '私有信息' ] } },
	{ lock: true }
);

// 以下调用会抛出错误!
store( 'myPlugin/private', {
	/* 存储部分 */
} );

同时存在解锁私有存储的方法:可通过传递字符串(而非布尔值)作为 lock 参数。后续对同一命名空间的 store() 调用可使用该字符串解锁内容。只有知晓字符串锁的代码才能解锁受保护的存储命名空间。这对于在多个 JS 模块中定义的复杂存储非常实用。

const { state } = store(
	'myPlugin/private',
	{ state: { messages: [ '私有信息' ] } },
	{ lock: PRIVATE_LOCK }
);

// 以下调用可正常执行
store(
	'myPlugin/private',
	{
		/* 存储部分 */
	},
	{ lock: PRIVATE_LOCK }
);

存储客户端方法

除存储函数外,还提供若干方法供开发者访问存储函数中的数据:

  • getContext()
    • getServerContext()
    • getServerState()
  • getElement()

getContext()

获取由评估存储函数的元素继承的上下文。返回值取决于元素及调用 getContext() 函数所在的命名空间。该方法可接受可选命名空间参数,以获取特定交互区域的上下文。

const context = getContext( '命名空间' );
  • 命名空间(可选):与交互区域命名空间匹配的字符串。若未提供,则获取当前交互区域的上下文。
// render.php
<div data-wp-interactive="myPlugin" data-wp-context='{ "isOpen": false }'>
	<button data-wp-on--click="actions.log">记录</button>
</div>
// 存储模块
import { store, getContext } from '@wordpress/interactivity';

store( 'myPlugin', {
	actions: {
		log: () => {
			const context = getContext();
			// 输出 "false"
			console.log( '上下文 => ', context.isOpen );

			// 使用命名空间参数
			const myPluginContext = getContext( 'myPlugin' );
			// 输出 "false"
			console.log( 'myPlugin isOpen => ', myPluginContext.isOpen );
		},
	},
} );

getServerContext()

此函数与 getContext() 类似,但存在两个关键差异:

  1. 当调用 @wordpress/interactivity-router 中的 actions.navigate() 时,getServerContext() 返回的对象会同步更新。适用于需要根据通过 actions.navigate() 加载页面传来的上下文更新区块上下文的情况。该新上下文会嵌入通过 actions.navigate() 加载页面的 HTML 中。
  2. getServerContext() 返回的对象为只读。

服务器上下文不能直接在指令中使用,但可通过回调函数订阅其变更。

const serverContext = getServerContext( '命名空间' );
  • 命名空间(可选):与交互区域命名空间匹配的字符串。若未提供,则获取当前交互区域的服务器上下文。

使用示例:

store( 'myPlugin', {
	callbacks: {
		updateServerContext() {
			const context = getContext();
			const serverContext = getServerContext();
			// 用服务器传来的新值覆盖特定属性
			context.overridableProp = serverContext.overridableProp;
		},
	},
} );

副作用

自动响应状态变化。通常由 data-wp-watchdata-wp-init 指令触发。

派生状态

返回经过计算的状态版本。可同时访问 statecontext

// view.js
const { state } = store( 'myPlugin', {
	state: {
		amount: 34,
		defaultCurrency: 'EUR',
		currencyExchange: {
			USD: 1.1,
			GBP: 0.85,
		},
		get amountInUSD() {
			return state.currencyExchange[ 'USD' ] * state.amount;
		},
		get amountInGBP() {
			return state.currencyExchange[ 'GBP' ] * state.amount;
		},
	},
} );

在回调中访问数据

store 包含所有存储属性,如 stateactionscallbacks。这些属性通过 store() 调用返回,因此可以通过解构方式访问:

const { state, actions } = store( 'myPlugin', {
	// ...
} );

store() 函数可被多次调用,所有存储部分将被合并:

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

const { state } = store( 'myPlugin', {
	actions: {
		someAction() {
			state.someValue; // = 1
		},
	},
} );
所有具有相同命名空间的 store() 调用都会返回相同的引用,即相同的 stateactions 等,包含所有传入存储部分的合并结果。
  • 要在操作、派生状态或副作用中访问上下文,可使用 getContext 函数
  • 要访问引用,可使用 getElement 函数
const { state } = store( 'myPlugin', {
	state: {
		get someDerivedValue() {
			const context = getContext();
			const { ref } = getElement();
			// ...
		},
	},
	actions: {
		someAction() {
			const context = getContext();
			const { ref } = getElement();
			// ...
		},
	},
	callbacks: {
		someEffect() {
			const context = getContext();
			const { ref } = getElement();
			// ...
		},
	},
} );

这种实现方式赋予指令灵活而强大的功能:

  • 操作和副作用可读取并修改状态及上下文
  • 区块中的操作和状态可被其他区块访问
  • 操作和副作用能执行常规 JavaScript 函数的所有操作,如访问 DOM 或发起 API 请求
  • 副作用会自动响应状态变化

设置存储

客户端设置

开发者可在_每个区块的 view.js 文件_中定义状态和存储元素引用操作、副作用或派生状态等功能。

用于在 JavaScript 中设置存储的 store 方法可从 @wordpress/interactivity 导入:

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

store( 'myPlugin', {
	actions: {
		toggle: () => {
			const context = getContext();
			context.isOpen = ! context.isOpen;
		},
	},
	callbacks: {
		logIsOpen: () => {
			const { isOpen } = getContext();
			// 每次 isOpen 变化时记录其值
			console.log( `Is open: ${ isOpen }` );
		},
	},
} );

服务端设置

状态也可在服务端使用 wp_interactivity_state() 函数进行初始化。通常可在区块的 render.php 文件中实现(render.php 模板于 WordPress 6.1 引入)。

通过 wp_interactivity_state() 在服务端定义的状态会与 view.js 文件中定义的存储合并。

wp_interactivity_state 函数接收两个参数:作为引用标识的命名空间字符串,以及包含值的关联数组

从服务端初始化存储的示例(state = { someValue: 123 }

// render.php
wp_interactivity_state( 'myPlugin', array (
	'someValue' => get_some_value()
));

在服务端初始化状态还允许使用任何 WordPress API。例如可使用核心翻译 API 翻译部分状态内容:

// render.php
wp_interactivity_state( 'favoriteMovies', array(
      "1" => array(
        "id" => "123-abc",
        "movieName" => __("someMovieName", "textdomain")
      ),
) );

wp-text 指令

该指令用于设置 HTML 元素的内部文本内容。

<div data-wp-context='{ "text": "文本1" }'>
	<span data-wp-text="context.text"></span>
	<button data-wp-on--click="actions.toggleContextText">
		切换上下文文本
	</button>
</div>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	actions: {
		toggleContextText: () => {
			const context = getContext();
			context.text = context.text === '文本1' ? '文本2' : '文本1';
		},
	},
} );

wp-text 指令会在以下时机执行:

  • 元素创建时
  • 每当涉及获取指令最终值的 statecontext 属性发生变化时(在回调函数或作为引用传递的表达式中)

返回值将用于改变元素的内部内容:<div>数值</div>

wp-on 指令

若您的指令代码无需同步访问事件对象,建议改用性能更优的 wp-on-async。如需同步访问,可考虑实现 异步操作,在调用同步 API 后释放主线程控制权。

该指令用于在触发 DOM 事件(如 clickkeyup)时执行代码。语法格式为 data-wp-on--[事件名](例如 data-wp-on--clickdata-wp-on--keyup)。

<button data-wp-on--click="actions.logTime" >
  点击我!
</button>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	actions: {
		logTime: ( event ) => {
			console.log( new Date() );
		},
	},
} );

每当关联事件触发时,wp-on 指令便会执行。

作为引用传递的回调函数会接收事件对象event),该回调函数的返回值将被忽略。

wp-on-async 指令

这是 wp-on 指令的高性能版本。它会立即释放主线程控制权,避免造成长任务阻塞,让其他等待主线程的交互操作能更快执行。当不需要同步访问 event 对象(特别是 event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() 方法)时,请使用此异步版本。

wp-on-window 指令

若您的指令代码无需同步访问事件对象,建议改用性能更优的 wp-on-async-window。如需同步访问,可考虑实现 异步操作,在调用同步 API 后释放主线程控制权。

该指令允许您绑定全局窗口事件(如 resizecopyfocus),并在事件触发时执行预定义的回调函数。

支持的窗口事件列表

指令语法为 data-wp-on-window--[窗口事件名](例如 data-wp-on-window--resizedata-wp-on-window--languagechange)。

<div data-wp-on-window--resize="callbacks.logWidth"></div>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	callbacks: {
		logWidth() {
			console.log( '窗口宽度: ', window.innerWidth );
		},
	},
} );

作为引用传递的回调函数会接收事件对象event),该回调函数的返回值将被忽略。当元素从 DOM 中移除时,对应的事件监听器也会同步移除。

wp-on-async-window 指令

wp-on-async 类似,这是 wp-on-window 的优化版本,会立即释放主线程控制权以避免长任务阻塞。当不需要同步访问 event 对象(特别是 event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() 方法)时,请使用此异步版本。此事件监听器还会以被动模式进行注册。

getServerState()

获取交互区域的服务器状态。

此函数与 getServerContext() 用途相同,但返回的是状态而非上下文

返回的对象为只读,包含通过 PHP 中 wp_interactivity_state() 定义的状态。当使用 @wordpress/interactivity-router 中的 actions.navigate() 时,getServerState() 返回的对象会更新以反映其属性的变化,而不会影响 store() 返回的状态。指令可以订阅这些变化以在需要时更新状态。

const serverState = getServerState( 'namespace' );
  • namespace(可选):与交互区域命名空间匹配的字符串。如果未提供,则获取当前交互区域的服务器状态。

使用示例:

const { state } = store( 'myStore', {
	callbacks: {
		updateServerState() {
			const serverState = getServerState();
			// 用服务器传来的新值覆盖某些属性。
			state.overridableProp = serverState.overridableProp;
		},
	},
} );

getElement()

获取绑定或调用操作的元素表示。该表示为只读,包含对 DOM 元素的引用、其属性及本地响应式状态。 返回一个包含两个键的对象:

ref

ref 是对 DOM 元素的引用,类型为 HTMLElement。相当于 Preact 或 React 中的 useRef,因此在 ref 尚未附加到实际 DOM 元素时(例如在水合或挂载期间)可能为 null

attributes

attributes 包含一个 Proxy,它添加了一个允许引用其他存储命名空间的 getter。欢迎在代码中查看该 getter。链接

这些属性将包含该元素的指令。在按钮示例中:

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

store( 'myPlugin', {
	actions: {
		log: () => {
			const element = getElement();
			// 输出属性
			console.log( 'element attributes => ', element.attributes );
		},
	},
} );

代码将输出:

{
	"data-wp-on--click": 'actions.log',
	"children": ['Log'],
	"onclick": event => { evaluate(entry, event); }
}

withScope()

操作在被调用时可能依赖于作用域,例如调用 getContext()getElement() 时。

当交互 API 运行时执行回调时,作用域会自动设置。但是,如果从不由运行时执行的回调(如 setInterval() 回调)中调用操作,则需要确保作用域正确设置。在这些情况下,使用 withScope() 函数确保作用域正确设置。

例如,在没有包装器的情况下,actions.nextImage 会触发未定义错误:

store( 'mySliderPlugin', {
	callbacks: {
		initSlideShow: () => {
			setInterval(
				withScope( () => {
					actions.nextImage();
				} ),
				3_000
			);
		},
	},
} );

withSyncEvent()

需要同步访问 event 对象的操作必须使用 withSyncEvent() 函数来注解其处理程序回调。这是由于正在进行的努力,默认情况下将存储操作视为异步处理,除非它们需要同步事件访问。因此,从 Gutenberg 20.4 / WordPress 6.8 开始,所有需要同步事件访问的操作都必须使用 withSyncEvent() 函数。否则将触发弃用警告,并在未来版本中相应更改行为。

只有非常特定的事件方法和属性需要同步访问,因此建议仅在必要时使用 withSyncEvent()。以下事件方法和属性需要同步访问:

  • event.currentTarget
  • event.preventDefault()
  • event.stopImmediatePropagation()
  • event.stopPropagation()

以下示例展示了一个操作需要同步事件访问,而其他操作不需要的情况:

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

store( 'myPlugin', {
	actions: {
		// `event.preventDefault()` 需要同步事件访问。
		preventNavigation: withSyncEvent( ( event ) => {
			event.preventDefault();
		} ),

		// `event.target` 不需要同步事件访问。
		logTarget: ( event ) => {
			console.log( 'event target => ', event.target );
		},

		// 完全不使用 `event` 不需要同步事件访问。
		logSomething: () => {
			console.log( 'something' );
		},
	},
} );

API 参考

交互性 API 仅适用于 WordPress 6.5 及以上版本。

要通过交互性 API 为区块添加交互功能,开发者可以使用:

  • 指令: 添加到标记中,为区块的 DOM 元素添加特定行为
  • 存储: 包含行为所需的逻辑和数据(状态、操作、副作用等)

DOM 元素通过指令与存储在状态和上下文中的数据建立连接。如果状态或上下文中的数据发生变化,指令将响应这些变化,并相应地更新 DOM参见示意图)。

状态与指令

什么是指令?

指令是自定义属性,可添加到区块标记中,为其 DOM 元素添加行为。这可以在 render.php 文件(针对动态区块)或 save.js 文件(针对静态区块)中完成。

交互性 API 指令使用 data- 前缀。以下是一个在 HTML 标记中使用指令的示例:

<div
	data-wp-interactive="myPlugin"
	data-wp-context='{ "isOpen": false }'
	data-wp-watch="callbacks.logIsOpen"
>
	<button
		data-wp-on--click="actions.toggle"
		data-wp-bind--aria-expanded="context.isOpen"
		aria-controls="p-1"
	>
		切换
	</button>

	<p id="p-1" data-wp-bind--hidden="!context.isOpen">
		此元素现在可见!
	</p>
</div>

指令还可以使用 HTML 标签处理器动态注入。

通过指令,可以直接管理交互,例如副作用、状态、事件处理程序、属性或内容。

指令列表

wp-interactive

wp-interactive 指令通过交互性 API指令和存储为 DOM 元素及其子元素“激活”交互功能。该指令包含一个命名空间,用于引用特定的存储,可以设置为 stringobject

<!-- 让此元素及其子元素具有交互性,并设置命名空间 -->
<div
	data-wp-interactive="myPlugin"
	data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
	<p>
		我现在具有交互性,
		<span data-wp-style--background-color="context.myBgColor"
			>并且可以使用指令!</span
		>
	</p>
	<div>
		<p>
		我也具有交互性,
			<span data-wp-style--color="context.myColor"
				>并且我也可以使用指令!</span
			>
		</p>
	</div>
</div>

<!-- 这也是有效的 -->
<div
	data-wp-interactive='{ "namespace": "myPlugin" }'
	data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
	<p>
		我现在具有交互性,
		<span data-wp-style--background-color="context.myBgColor"
			>并且可以使用指令!</span
		>
	</p>
	<div>
		<p>
		我也具有交互性,
			<span data-wp-style--color="context.myColor"
				>并且我也可以使用指令!</span
			>
		</p>
	</div>
</div>
使用 data-wp-interactive 是交互性 API“引擎”工作的必要条件。在以下示例中为了简化未添加 data-wp-interactive。此外,data-wp-interactive 指令将在未来自动注入。

wp-context

它提供了一个局部状态,可供特定的 HTML 节点及其子节点使用。

wp-context 指令接受一个字符串化的 JSON 作为值。

// render.php
<div data-wp-context='{ "post": { "id": <?php echo $post->ID; ?> } }' >
  <button data-wp-on--click="actions.logId" >
    点击我!
  </button>
</div>
查看与上述指令一起使用的存储
store( 'myPlugin', {
	actions: {
		logId: () => {
			const { post } = getContext();
			console.log( post.id );
		},
	},
} );

可以在不同层级定义不同的上下文,更深层级的上下文会将其自身的上下文与任何父级上下文合并:

<div data-wp-context='{ "foo": "bar" }'>
	<span data-wp-text="context.foo"><!-- 将输出:"bar" --></span>

	<div data-wp-context='{ "bar": "baz" }'>
		<span data-wp-text="context.foo"><!-- 将输出:"bar" --></span>

		<div data-wp-context='{ "foo": "bob" }'>
			<span data-wp-text="context.foo"><!-- 将输出:"bob" --></span>
		</div>
	</div>
</div>

wp-on-document

若您的指令代码无需同步访问事件对象,建议改用性能更佳的 wp-on-async-document。如需同步访问,可考虑实现一个异步操作,在调用同步 API 后主动让出主线程。

该指令允许您绑定全局文档事件(如 scrollmousemovekeydown),并在事件触发时执行预定义的回调函数。

支持的文档事件列表

指令语法为 data-wp-on-document--[文档事件名](例如 data-wp-on-document--keydowndata-wp-on-document--selectionchange)。

<div data-wp-on-document--keydown="callbacks.logKeydown"></div>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	callbacks: {
		logKeydown( event ) {
			console.log( '按键按下: ', event.key );
		},
	},
} );

传入的回调函数会接收事件对象event),其返回值将被忽略。当元素从 DOM 中移除时,对应的事件监听器也会同步移除。

wp-on-async-document

wp-on-async 类似,这是 wp-on-document 的优化版本,会立即让出主线程以避免造成长任务。当不需要同步访问 event 对象(特别是 event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() 方法)时,请使用此异步版本。此事件监听器还会被添加为被动模式

wp-watch

该指令会在节点创建时执行回调,并在状态或上下文变更时重新执行

您可以通过 data-wp-watch--[唯一标识] 语法为同一 DOM 元素附加多个副作用回调。

唯一标识 无需全局唯一,只需在该 DOM 元素的 wp-watch 指令中保持唯一性即可。

<div data-wp-context='{ "counter": 0 }' data-wp-watch="callbacks.logCounter">
	<p>计数器: <span data-wp-text="context.counter"></span></p>
	<button data-wp-on--click="actions.increaseCounter">+</button>
	<button data-wp-on--click="actions.decreaseCounter">-</button>
</div>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	actions: {
		increaseCounter: () => {
			const context = getContext();
			context.counter++;
		},
		decreaseCounter: () => {
			const context = getContext();
			context.counter--;
		},
	},
	callbacks: {
		logCounter: () => {
			const { counter } = getContext();
			console.log( '计数器值:' + counter + ' 记录时间:' + new Date() );
		},
	},
} );

wp-watch 指令会在以下时机执行:

  • 元素创建时
  • 回调函数内部使用的 statecontext 属性发生变更时

wp-watch 指令可返回一个函数。若返回函数,该函数将作为清理逻辑使用:在回调再次执行前运行,并在元素从 DOM 移除时再次执行。

典型使用场景包括:

  • 日志记录
  • 修改页面标题
  • 通过 .focus() 设置元素焦点
  • 满足特定条件时更新状态或上下文

wp-init

该指令仅在节点创建时执行一次回调

您可以通过 data-wp-init--[唯一标识] 语法为同一 DOM 元素附加多个 wp-init 指令。

唯一标识 无需全局唯一,只需在该 DOM 元素的 wp-init 指令中保持唯一性即可。

<div data-wp-init="callbacks.logTimeInit">
	<p>您好!</p>
</div>

以下示例展示了同一 DOM 元素上多个 wp-init 指令的用法:

<form
	data-wp-init--log="callbacks.logTimeInit"
	data-wp-init--focus="callbacks.focusFirstElement"
>
	<input type="text" />
</form>
查看与上述指令配合使用的存储模块
import { store, getElement } from '@wordpress/interactivity';

store( "myPlugin", {
  callbacks: {
    logTimeInit: () => console.log( `初始化时间:` + new Date() ),
    focusFirstElement: () => {
      const { ref } = getElement();
      ref.querySelector( 'input:first-child' ).focus(),
    },
  },
} );

wp-init 指令返回函数,该函数会在元素从 DOM 移除时执行。

wp-bind 指令

该指令允许根据布尔值或字符串值为元素设置 HTML 属性。其语法格式为 data-wp-bind--attribute

<li data-wp-context='{ "isMenuOpen": false }'>
	<button
		data-wp-on--click="actions.toggleMenu"
		data-wp-bind--aria-expanded="context.isMenuOpen"
	>
		切换
	</button>
	<div data-wp-bind--hidden="!context.isMenuOpen">
		<span>标题</span>
		<ul>
			子菜单项
		</ul>
	</div>
</li>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	actions: {
		toggleMenu: () => {
			const context = getContext();
			context.isMenuOpen = ! context.isMenuOpen;
		},
	},
} );

wp-bind 指令在以下情况执行:

  • 元素创建时
  • 每当影响指令最终值的 statecontext 属性发生变化时(在回调函数或传递的引用表达式中)

wp-bind 指令引用回调函数获取最终值时:

  • 只要该回调函数内使用的 statecontext 属性发生变化,wp-bind 指令就会执行
  • 回调函数的返回值将用于修改对应属性的值

wp-bind 根据值的不同会对 DOM 元素进行不同操作:

  • 值为 true 时添加属性:<div attribute>
  • 值为 false 时移除属性:<div>
  • 值为字符串时添加属性并赋值:<div attribute="value">
  • 属性名以 aria-data- 开头且值为布尔值时,会将布尔值转为字符串添加到 DOM<div aria-attribute="true">

wp-class 指令

该指令根据布尔值为 HTML 元素添加或移除类名。其语法格式为 data-wp-class--classname

<div>
	<li
		data-wp-context='{ "isSelected": false }'
		data-wp-on--click="actions.toggleSelection"
		data-wp-class--selected="context.isSelected"
	>
		选项 1
	</li>
	<li
		data-wp-context='{ "isSelected": false }'
		data-wp-on--click="actions.toggleSelection"
		data-wp-class--selected="context.isSelected"
	>
		选项 2
	</li>
</div>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	actions: {
		toggleSelection: () => {
			const context = getContext();
			context.isSelected = ! context.isSelected;
		},
	},
} );

wp-class 指令在以下情况执行:

  • 元素创建时
  • 每当影响指令最终值的 statecontext 属性发生变化时(在回调函数或传递的引用表达式中)

指令接收的布尔值用于切换 class 属性中的关联类名(值为 true 时添加,值为 false 时移除)。

需要注意:使用 wp-class 指令时,推荐使用短横线命名法而非驼峰命名法。因为 HTML 属性不区分大小写,data-wp-class--isDarkdata-wp-class--isdarkDATA-WP-CLASS--ISDARK 在 HTML 中会被视为相同属性。

因此,建议使用 is-dark 替代 isDark,使用 data-wp-class--is-dark 替代 data-wp-class--isDark

<!-- 推荐用法 -->
<div data-wp-class--is-dark="context.isDarkMode">
	<!-- ... -->
</div>

<!-- 不推荐用法 -->
<div data-wp-class--isDark="context.isDarkMode">
	<!-- ... -->
</div>
/* 推荐用法 */
.is-dark {
	/* ... */
}

/* 不推荐用法 */
.isDark {
	/* ... */
}

wp-style 指令

该指令根据值为 HTML 元素添加或移除内联样式。其语法格式为 data-wp-style--css-property

<div data-wp-context='{ "color": "red" }'>
	<button data-wp-on--click="actions.toggleContextColor">
		切换文字颜色
	</button>
	<p data-wp-style--color="context.color">Hello World!</p>
</div>
查看与上述指令配合使用的存储模块
store( 'myPlugin', {
	actions: {
		toggleContextColor: () => {
			const context = getContext();
			context.color = context.color === 'red' ? 'blue' : 'red';
		},
	},
} );

wp-style 指令在以下情况执行:

  • 元素创建时
  • 每当影响指令最终值的 statecontext 属性发生变化时(在回调函数或传递的引用表达式中)

指令接收的值用于添加或移除包含对应 CSS 属性的样式属性:

  • 值为 false 时移除样式属性:<div>
  • 值为字符串时添加样式属性并赋值:<div style="css-property: value;">

wp-run 指令

该指令会在节点渲染执行期间运行传入的回调函数。

您可以在传入的回调中使用并组合诸如 useStateuseWatchuseEffect 之类的钩子,创建自己的逻辑,相比之前的指令提供了更大的灵活性。

您可以通过 data-wp-run--[唯一标识符] 语法在同一个 DOM 元素上附加多个 wp-run 指令。

唯一标识符 不需要全局唯一,只需与该 DOM 元素上其他 wp-run 指令的唯一标识符不同即可。

<div data-wp-run="callbacks.logInView">
	<p>你好!</p>
</div>
查看与上述指令配合使用的存储
import {
	getElement,
	store,
	useState,
	useEffect,
} from '@wordpress/interactivity';

// 与 `data-wp-init` 和 `data-wp-watch` 不同,您可以在 `data-wp-run` 回调中使用任何钩子。
const useInView = () => {
	const [ inView, setInView ] = useState( false );
	useEffect( () => {
		const { ref } = getElement();
		const observer = new IntersectionObserver( ( [ entry ] ) => {
			setInView( entry.isIntersecting );
		} );
		observer.observe( ref );
		return () => ref && observer.unobserve( ref );
	}, [] );
	return inView;
};

store( 'myPlugin', {
	callbacks: {
		logInView: () => {
			const isInView = useInView();
			useEffect( () => {
				if ( isInView ) {
					console.log( '在视口内' );
				} else {
					console.log( '在视口外' );
				}
			} );
		},
	},
} );

需要注意的是,与 (P)React 组件类似,在首次渲染期间,getElement() 返回的 refnull。要正确访问 DOM 元素引用,通常需要使用类似效果的钩子,如 useEffectuseInituseWatch。这确保了 getElement() 在组件挂载后运行,并且 ref 可用。

wp-key 指令

wp-key 指令为元素分配一个唯一键,以帮助交互式 API 在遍历元素数组时识别它。如果您的数组元素可能移动(例如,由于排序)、插入或删除,这一点就变得很重要。选择恰当的键值有助于交互式 API 推断数组中具体发生了哪些变化,从而使其能够正确更新 DOM。

键应是一个字符串,用于在兄弟元素中唯一标识该元素。通常,它用于重复元素,如列表项。例如:

<ul>
	<li data-wp-key="唯一标识符-1">项目 1</li>
	<li data-wp-key="唯一标识符-2">项目 2</li>
</ul>

但它也可以用于其他元素:

<div>
	<a data-wp-key="上一页" ...>上一页</a>
	<a data-wp-key="下一页" ...>下一页</a>
</div>

当列表重新渲染时,交互式 API 将通过元素的键进行匹配,以确定是否添加/删除/重新排序了项目。没有键的元素可能会不必要地重新创建。

wp-each 指令

wp-each 指令用于渲染元素列表。该指令可以在 <template> 标签中使用,其值是存储在全局状态或上下文中的数组的路径。<template> 标签内的内容是用于渲染每个项目的模板。

默认情况下,每个项目都包含在上下文中的 item 名称下,因此模板内的指令可以访问当前项目。

例如,考虑以下 HTML。

<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
	<template data-wp-each="context.list">
		<li data-wp-text="context.item"></li>
	</template>
</ul>

它将生成以下输出:

<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
	<li data-wp-text="context.item">hello</li>
	<li data-wp-text="context.item">hola</li>
	<li data-wp-text="context.item">olá</li>
</ul>

可以通过向指令名称添加后缀来更改上下文中保存项目的属性。在以下示例中,默认属性从 item 更改为 greeting

<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
	<template data-wp-each--greeting="context.list">
		<li data-wp-text="context.greeting"></li>
	</template>
</ul>

默认情况下,它使用每个元素作为渲染节点的键,但如果需要,您也可以指定一个路径来检索键,例如当列表包含对象时。

为此,您必须在 <template> 标签中使用 data-wp-each-key,而不是在模板内容中使用 data-wp-key。这是因为 data-wp-each 会为每个渲染的项目创建一个上下文提供者包装器,而这些包装器需要 key 属性。

<ul
	data-wp-context='{
  "list": [
    { "id": "en", "value": "hello" },
    { "id": "es", "value": "hola" },
    { "id": "pt", "value": "olá" }
  ]
}'
>
	<template
		data-wp-each--greeting="context.list"
		data-wp-each-key="context.greeting.id"
	>
		<li data-wp-text="context.greeting.value"></li>
	</template>
</ul>

wp-each-child

针对服务端渲染的列表,另一项名为 data-wp-each-child 的指令可确保水合过程按预期工作。该指令在服务器端处理指令时会自动添加。

<ul data-wp-context='{ "list": [ "hello", "hola", "olá" ] }'>
	<template data-wp-each--greeting="context.list">
		<li data-wp-text="context.greeting"></li>
	</template>
	<li data-wp-each-child>hello</li>
	<li data-wp-each-child>hola</li>
	<li data-wp-each-child>olá</li>
</ul>

指令的值是对存储属性的引用

分配给指令的值是一个指向特定状态、操作或副作用的字符串。

在以下示例中,使用 getter 定义了派生值 state.isPlaying

const { state } = store( 'myPlugin', {
	state: {
		currentVideo: '',
		get isPlaying() {
			return state.currentVideo !== '';
		},
	},
} );

然后,字符串值 "state.isPlaying" 用于将此选择器的结果分配给 data-wp-bind--hidden

<div data-wp-bind--hidden="!state.isPlaying" ...>
	<iframe ...></iframe>
</div>

分配给指令的这些值是存储中特定属性的引用。它们会自动与指令关联,使每个指令“知道”其引用的存储元素,无需任何额外配置。

请注意,默认情况下,引用指向当前命名空间中的属性,该命名空间由具有 data-wp-interactive 属性的最近祖先指定。如果需要访问不同命名空间中的属性,可以显式设置属性所在的命名空间。语法为 namespace::reference,将 namespace 替换为适当的值。

以下示例从 otherPlugin 获取 state.isPlaying,而不是 myPlugin

<div data-wp-interactive="myPlugin">
	<div data-wp-bind--hidden="otherPlugin::!state.isPlaying" ...>
		<iframe ...></iframe>
	</div>
</div>

存储

存储用于创建与指令关联的逻辑(操作、副作用等)以及该逻辑中使用的数据(状态、派生状态等)。

存储通常在每个块的 view.js 文件中创建,但状态可以从块的 render.php 文件初始化。

存储的组成部分

状态

它定义了页面 HTML 节点可用的数据。重要的是区分两种定义数据的方式:

  • 全局状态:使用带有 state 属性的 store() 函数定义,数据可供页面的所有 HTML 节点使用。
  • 上下文/局部状态:使用 HTML 节点中的 data-wp-context 指令定义,数据可供该 HTML 节点及其子节点使用。可以在操作、派生状态或副作用中使用 getContext 函数访问它。
<div data-wp-context='{ "someText": "Hello World!" }'>
	<!-- 访问全局状态 -->
	<span data-wp-text="state.someText"></span>

	<!-- 访问局部状态(上下文) -->
	<span data-wp-text="context.someText"></span>
</div>
const { state } = store( 'myPlugin', {
	state: {
		someText: 'Hello Universe!',
	},
	actions: {
		someAction: () => {
			state.someText; // 访问或修改全局状态 - "Hello Universe!"

			const context = getContext();
			context.someText; // 访问或修改局部状态(上下文) - "Hello World!"
		},
	},
} );

操作

操作只是普通的 JavaScript 函数。通常由 data-wp-on 指令(使用事件监听器)或其他操作触发。

const { state, actions } = store( 'myPlugin', {
	actions: {
		selectItem: ( id ) => {
			const context = getContext();
			// `id` 在此处是可选的,因此该操作可以在指令中使用。
			state.selected = id || context.id;
		},
		otherAction: () => {
			// 但它也可以从其他操作中调用。
			actions.selectItem( 123 ); // 可以正常工作且类型正确
		},
	},
} );
异步操作

异步操作应使用生成器而不是 async/await。

在异步函数中,控制权交给函数本身。函数的调用者无法知道函数是否在等待,更重要的是,等待是否已解决以及函数是否已恢复执行。我们需要这些信息才能恢复作用域。

假设一个块有两个按钮。一个位于 isOpen: true 的上下文中,另一个位于 isOpen: false 的上下文中:

<div data-wp-context='{ "isOpen": true }'>
	<button data-wp-on--click="actions.someAction">点击</button>
</div>

<div data-wp-context='{ "isOpen": false }'>
	<button data-wp-on--click="actions.someAction">点击</button>
</div>

如果操作是异步的并且需要等待较长时间:

  • 用户点击第一个按钮。
  • 作用域指向第一个上下文,其中 isOpen: true
  • 第一次访问 state.isOpen 是正确的,因为 getContext 返回当前作用域。
  • 操作开始等待较长时间。
  • 在操作恢复之前,用户点击第二个按钮。
  • 作用域切换到第二个上下文,其中 isOpen: false
  • 第一次访问 state.isOpen 是正确的,因为 getContext 返回当前作用域。
  • 第二个操作开始等待较长时间。
  • 第一个操作完成等待并恢复执行。
  • 第一个操作的第二次访问 state.isOpen 是错误的,因为 getContext 现在返回了错误的作用域。

我们需要能够知道异步操作何时开始等待和恢复操作,以便恢复正确的作用域,这正是生成器的作用。

如果存储按以下方式编写,它将正常工作:

const { state } = store( 'myPlugin', {
	state: {
		get isOpen() {
			return getContext().isOpen;
		},
	},
	actions: {
		someAction: function* () {
			state.isOpen; // 此上下文是正确的,因为它是同步的。
			yield longDelay(); // 使用生成器,调用者控制何时恢复此函数。
			state.isOpen; // 此上下文是正确的,因为我们在恢复函数之前恢复了正确的作用域。
		},
	},
} );

如果操作需要处理大量工作,您可能需要在操作中添加多个这样的 yield 点。

如上文关于 wp-onwp-on-windowwp-on-document 所述,当由于操作需要同步访问 event 对象而无法使用上述指令的 async 版本时,应使用异步操作。当操作需要调用 event.preventDefault()event.stopPropagation()event.stopImmediatePropagation() 时,需要同步访问。

为确保操作代码不会导致长任务,您可以在调用同步事件 API 后手动让出主线程。Interactivity API 为此提供了 splitTask() 函数,它以跨浏览器兼容的方式实现让出。以下是一个示例:

import { splitTask } from '@wordpress/interactivity';

store( 'myPlugin', {
	actions: {
		handleClick: withSyncEvent( function* ( event ) {
			event.preventDefault();
			yield splitTask();
			doTheWork();
		} ),
	},
} );

您可能会注意到此示例中使用了 withSyncEvent() 工具函数。这是必要的,因为正在进行的努力是将存储操作默认异步处理,除非它们需要同步事件访问(此示例由于调用了 event.preventDefault() 而需要)。否则将触发弃用警告,并在未来的版本中相应更改行为。