WordPress 6.5 提出了新的交互 API——Interactivity API

交互API为开发人员提供了一种向其块前端添加交互的标准方法。

该标准旨在使开发人员更轻松地创建丰富的交互式用户体验,从计数器或弹出窗口等简单情况到即时页面导航、即时搜索、购物车或结帐等更复杂的功能。

块可以在它们之间共享数据、操作和回调。这使得块之间的通信更加简单并且不易出错。例如,单击“添加到购物车”块可以无缝更新单独的“购物车”块。

为了更好地理解其背后的原因,您可以查看原始提案,其中有更详细的解释。

有关它的更多信息可以在合并公告、状态更新帖子和交互 API 的Trac 票证中找到。

本开发说明涵盖了 6.5 中包含的 API 以及如何使用交互 API。

如何使用交互 API 创建交互

需要强调的是,区块创建*工作流程***不会改变**。

到目前为止,WordPress 一直有意对区块前端使用的不同解决方案不发表意见。交互 API 改变了这一点。它添加了一种新的标准方法,可以轻松地将前端交互性添加到块,同时处理块编辑器的 API 保持不变。

您首先需要通过在文件中的supports内添加交互属性来声明其与API的兼容性block.json

"supports": {
    "interactivity": true
},

请参阅块编辑器手册以获取支持属性的更详细说明interactivity

交互 API 脚本需要使用WordPress 6.5 中的新脚本模块,因此块应使用 viewScriptModule将JavaScript排入队列:

// block.json
{
   ...
   "viewScriptModule": "file:./view.js"
}

您可以按照本快速入门指南轻松构建和测试交互式块,该指南解释了如何使用CLI**命令来加速此过程。

考虑到这一点,为了向由交互 API 提供支持的块添加交互性,开发人员需要:

  1. 向标记添加指令以向块添加特定交互。
  2. 使用交互逻辑(状态、操作或回调)创建存储。

我们用一个简单的例子来解释一下:一个显示和隐藏一些文本的按钮。当按钮隐藏或显示时,我们还可以在控制台中发送一条消息。

1. 添加指令

指令是添加到块标记中的自定义属性,以向其 DOM 元素添加交互。它们被放置在 render.php 文件中(对于动态块)。

第一步是添加指令data-wp-interactive。这用于“激活” DOM 元素及其子元素中的交互 API,其值必须是插件或块的唯一命名空间:

<div data-wp-interactive="myPlugin">
    <!-- Interactivity API zone -->
</div>

其余指令可以添加所需的交互。

// render.php
 
$context = array('isOpen' => false);
 
<div
  <?php echo get_block_wrapper_attributes(); ?>
  <?php echo wp_interactivity_data_wp_context($context) ?>
  data-wp-interactive='myPlugin'
  data-wp-watch="callbacks.logIsOpen"
>
  <button
      data-wp-on--click="actions.toggle"
      data-wp-bind--aria-expanded="context.isOpen"
  >
    Toggle
  </button>
  <p id="p-1" data-wp-bind--hidden="!context.isOpen">
    This element is now visible!
  </p>
</div>

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

如果您还不明白它是如何工作的,请不要担心。到目前为止,重要的部分是上面的示例使用wp-on和等指令向HTMLwp-bind添加交互性。这是 WordPress 6.5 中可用的指令列表:

您可以在相关链接中找到每个指令的更深入解释以及如何使用它的示例。

  • wp-interactive:此属性必须设置为您的插件或块的唯一标识符,以便它使用交互 API。
  • wp-context:它提供了特定 HTML 节点及其子节点可用的本地状态。它接受字符串化的JSON作为值。推荐使用PHPwp_interactivity_data_wp_context()来设置。
  • wp-bind:它允许基于布尔值或字符串值在元素上设置 HTML 属性。它遵循语法data-wp-bind--[attribute]。(喜欢data-wp-bind--value
  • wp-class:它根据布尔值向 HTML 元素添加或删除类。它遵循语法data-wp-class--[classname]
  • wp-style:它根据其值向 HTML 元素添加或删除内联样式。它遵循语法data-wp-style--[css-property].
  • wp-text:它设置 HTML 元素的内部文本。它只接受字符串作为参数。
  • wp-on:它在调度的 DOM 事件(例如单击或 keyup)上运行代码。它的语法是data-wp-on--[event](如data-wp-on--clickdata-wp-on--keyup)。
  • wp-watch :它在创建节点时运行回调,并在状态或上下文更改时再次运行它。
  • wp-init :仅在创建节点时运行回调。
  • wp-run :它在节点渲染执行期间运行传递的回调。
  • wp-key:它为元素分配一个唯一的键,以帮助交互 API 在迭代元素数组时识别它。
  • wp-each:旨在呈现元素列表。
  • wp-each-child:确保水合按预期工作,在 wp-each 指令的服务器处理中自动添加。

2. 创建商店

存储用于创建将指令与该逻辑内使用的数据链接起来的逻辑。

所有存储都由唯一的命名空间引用,分离逻辑并避免不同存储属性和函数之间的名称冲突。

如果使用相同的命名空间定义了多个存储,它们将被合并到一个存储中。

存储通常在每个块的 view.js 文件中创建,尽管状态可以在后端初始化,例如在块的渲染文件中。

状态是一个全局对象,可用于页面的所有 HTML 节点。它是由store()函数定义的。如果您只需要节点及其子节点的本地状态,请检查上下文定义。

该对象可以接受任何属性,为了保持项目之间的一致性,建议使用此约定。

  • State

    :定义页面 HTML 节点可用的数据。该州内的房产将在全球范围内开放。如果您需要编辑它们,推荐的方法是使用 getter。

    • 派生状态。如果您需要任何状态属性的修改版本,建议使用getter方法(下面将详细介绍如何导出状态)。
  • 操作:通常由指令触发data-wp-on(使用事件侦听器)。

  • 回调:自动对状态变化做出反应。通常由data-wp-on-window,data-wp-on-documentdata-wp-init指令触发。

回到我们的示例,这可能是一个块中的简单存储,已添加全局状态以获得存储外观的完整示例。

// view.js
import { store, getContext } from "@wordpress/interactivity";
 
const { state } = store( 'myPlugin', {
 state: {
  likes: 0,
  getDoubleLikes() {
    return 2 * state.likes;
  }
 },
  actions: {
    toggle: () => {
      const context = getContext();
      context.isOpen = !context.isOpen;
    },
  },
  callbacks: {
    logIsOpen: () => {
      const context = getContext();
      // Log the value of `isOpen` each time it changes.
      console.log(`Is open: ${context.isOpen}`);
    },
  },
});

在某些情况下,商店中可能只定义了操作和回调。

DOM 元素通过指令连接到存储在状态和上下文中的数据。如果状态或上下文更改指令中的数据将对这些更改做出反应,则会相应地更新 DOM(参见图表)。

U xU0VBEtxy79D9FnLDb6W8MDFLSt31gGjfoeXg qMxk2CuCKf5VP5oBsoEvCXULu8jkYBIolLJyMNaLwZYrpmr XK Ihb7TdO5IAG8P5dDHts uyRgizfPEzNZStlDP831QZH B MmgzOydPW06sA

创建商店时,有一些重要事项需要注意:

使用派生状态

派生状态使用getter返回状态的计算版本。它可以访问状态和上下文。

// 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()调用返回,因此您可以通过解构它们来访问它们:

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

请注意,上下文不是存储的一部分,而是通过getContext函数访问的。

如果您想更深入地了解该函数的工作原理,请随时查看此处的store()函数文档。

异步操作

异步操作应该使用生成器函数而不是 async/await 或 Promise。交互 API 需要能够跟踪异步行为,以便恢复正确的范围。否则,getContext如果与异步操作同时更新,则可能会返回过时的值。不用等待承诺,而是从生成器函数中生成它,并且交互 API 将处理等待其完成的情况。

所以,而不是:

store("myPlugin", {
  state: {
    get isOpen() {
      return getContext().isOpen;
    },
  },
  actions: {
    someAction: async () => {
      state.isOpen; // This is the expected context.
      await longDelay();
      state.isOpen; // This may not get the proper context unless it's properly restored.
    },
  },
});
 
function longDelay() {
  return new Promise( ( resolve ) => {
    setTimeout( () => resolve(), 3_000 );
  } );
}

商店应该是:

store("myPlugin", {
  state: {
    get isOpen() {
      return getContext().isOpen;
    },
  },
  actions: {
    someAction: function* () {
      state.isOpen; // This is the expected context.
      yield longDelay(); // With generators, the caller controls when to resume this function.
      state.isOpen; // This context is correct because the scope was restored before resuming after the yield.
    },
  },
});

如果您想更深入地了解该示例,请查看api 参考。


使用其他命名空间

交互块可以在它们之间共享数据,除非它们是私有存储。

指令

为了访问指令中不同命名空间的存储,请在指令值之前添加命名空间。例如:

<!-- This accesses the current store -->
<div data-wp-class--is-hidden="state.isHidden"></div>
 
<!-- This accesses the "otherPlugin" store -->
<button data-wp-on--click="otherPlugin::actions.addToCart>Button</button>

语境

可以通过提供所需的命名空间作为参数来访问来自不同命名空间的上下文getContext( namespace )

import { getContext } from "@wordpress/interactivity";
const otherPluginContext = getContext( "otherPlugin" );

店铺

与上下文一样,可以通过将所需的命名空间作为参数传递来访问不同的存储:

const { state: otherState, actions: otherActions } = store( "otherPlugin" );

私人商店

可以“锁定”存储以防止从其他名称空间访问其内容。为此,请在 store() 调用中将 lock 选项设置为 true,如下例所示。设置锁后,具有相同锁定命名空间的 store() 后续执行将引发错误,这意味着该命名空间只能在第一次 store() 调用返回其引用的位置进行访问。这对于想要隐藏部分插件存储以使扩展程序无法访问的开发人员特别有用。

const { state } = store("myPlugin/private", {
  state: {
      messages: [ "private message" ]
    } 
  },
  { lock: true }
);
 
// The following call throws an Error!
store( "myPlugin/private", { /* store part */ } );

还有一种方法可以解锁私有存储:您可以使用字符串作为锁定值,而不是传递布尔值。然后可以在对同一名称空间的后续 store() 调用中使用这样的字符串来解锁其内容。只有带有锁定字符串的代码才能访问受保护的存储区。这对于跨多个文件定义的复杂存储非常有用。

const { state } = store("myPlugin/private", {
  state: {
      messages: [ "private message" ]
    }
  },
  { lock: PRIVATE_LOCK }
);
 
// The following call works as expected.
store( "myPlugin/private", { /* store part */ }, { lock: PRIVATE_LOCK } );

交互 API 客户端方法

以下方法适用于 JavaScript,由 WordPress 6.5 中提供的 wordpress/interactivity 脚本模块提供。

获取上下文()

data-wp-context可以使用 getContext 函数检索使用该属性定义的上下文:

const { state } = store( "myPlugin", {
  actions: {
    someAction() {
      const context = getContext();
      const otherPluginContext = getContext( 'otherPlugin' );
      // ...
    }
  }
} );

手册说明。

获取元素()

检索正在评估存储中的函数的元素的表示。该表示形式是只读的,并且包含对 DOM 元素及其属性的引用。

手册说明。

获取配置()

通过函数检索先前在服务器中定义的配置对象wp_interactivity_config()

配置在客户端上是不可变的,无法修改。您可以在本文档后面获取示例。

店铺()

创建用于将数据和操作与其各自的指令链接起来的存储。查看主要部分以获取更多信息。

withScope()

操作可以取决于调用它们时的范围,例如,当您调用getContext()或时getElement()

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

一个示例,如果actions.nextImage没有包装器,将触发未定义的错误:

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

交互API服务器功能

这些是交互 API 包含的 PHP 函数:

wp_interactivity_state( $store_namespace, $state )

它用于初始化服务器上的状态并确保其发送的 HTML 与客户端 Hydration 后的 HTML 相同。它还允许您使用任何 WordPress API,例如核心翻译。

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

它接收两个参数,一个带有将用作引用的名称空间的字符串和一个包含值的关联数组。

此函数中定义的状态将与 view.js 文件中定义的存储合并。

wp_interactivity_data_wp_context( $context, $ *store_namespace* )

生成一个data-wp-context准备好在服务器端呈现的属性。除了手动写入 JSON 字符串时可能出现的任何错误之外,此函数会对数组进行转义以防止外部攻击。

$context是一个包含上下文的键和值的数组。

$*store_namespace*允许引用不同的存储,默认为空。

<?php
 $context = array(
  'id' => $post_id,
  'show' => true,
 );
?>
<div <?php echo wp_interactivity_data_wp_context($context) ?> >
  My interactive div
</div>

将返回

<div data-wp-context="{ "id": 1, "show": "true" } ">
  My interactive div
</div>

wp_interactivity_config( $store_namespace, $config )

设置或获取交互存储的配置。客户端可以读取配置的不可变副本。

将配置视为可以影响整个站点并且不会在客户端交互时更新的全局设置。例如,确定站点是否可以处理客户端导航。

<?php
// Sets configuration for the  'myPlugin' namespace.
wp_interactivity_config( 'myPlugin', array( 'setting' => true ) );
 
// Gets the current configuration for the 'myPlugin' namespace.
$config = wp_interactivity_config( 'myPlugin' );

可以在客户端中检索此配置:

// view.js
 
const { setting } = getConfig();
console.log( setting ); // Will log true.

wp_interactivity_process_directives( $html )

处理 HTML 内容中的指令,并在必要时更新标记。

这是交互 API 的核心功能。它是公开的,因此可以处理任何 HTML,而不仅仅是块。

For 带有supports.interactivity,指令的块会被自动处理。在这种情况下,开发人员不需要调用wp_interactivity_process_directives

<?php
$html_content = '<div data-wp-text="myPlugin::state.message"></div>';
wp_interactivity_state( 'myPlugin', array( 'message' => 'hello world!' ) );
 
// Process directives in HTML content.
$processed_html = wp_interactivity_process_directives( $html_content );
// output: <div data-wp-text="myPlugin::state.message">hello world!</div>