Interactivity API 提供了一个强大的框架,用于创建交互式区块。为了充分利用其功能,理解何时使用全局状态、本地上下文、派生状态或配置至关重要。本指南将阐明这些概念,并提供实用示例,帮助你决定在每种情况下使用哪一个。

Interactivity API 区分响应式数据(更改时会触发 UI 更新)和非响应式数据(在整个客户端生命周期中保持静态)。让我们先简要定义每个概念。

响应式(状态和上下文):

  • 全局状态:可以在页面上被任何交互式区块访问和修改的全局数据,允许你的交互式区块的不同部分保持同步。
  • 本地上下文:在 HTML 结构中的特定元素内定义的本地数据,仅对该元素及其子级可访问,为各个区块提供独立的状态。
  • 派生状态:基于全局状态或本地上下文计算得出的值,按需动态计算,以确保一致的数据表示,而无需存储冗余数据。

非响应式:

  • Config:从服务器序列化到客户端的静态配置数据,例如 API 端点、nonces 或功能标志。Config 值不会触发 UI 更新,并且在用户交互期间保持不变。

现在让我们深入探讨这些概念,进行更详细的研究并提供一些示例。

全局状态

Interactivity API 中的全局状态指的是页面上任何交互式区块都可以访问和修改的全局数据。它作为一个共享的信息中心,允许你区块的不同部分进行通信并保持同步。无论它们在 DOM 树中的位置如何,全局状态都是在交互式区块之间交换信息的理想机制。

你应该在以下情况使用全局状态:

  • 你需要在不直接属于同一 DOM 层级的多个交互式区块之间共享数据。
  • 你想在所有交互式区块中为某些数据维护单一的事实来源。
  • 你正在处理同时影响 UI 多个部分的数据。
  • 你想实现针对整个页面的全局功能。

处理全局状态

  • 初始化全局状态

    通常,初始全局状态值应在服务器上使用 wp_interactivity_state 函数定义:

    // Populates the initial global state values.
    wp_interactivity_state( 'myPlugin', array(
      'isDarkTheme' => true,
      'show'        => false,
      'helloText'   => __( 'world' ),
    ));
    

    这些初始全局状态值将在 PHP 中渲染页面时使用,以填充发送给浏览器的 HTML 标记。

    • 开发人员编写的 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--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--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 中,来自 @wordpress/interactivity 包的 store 函数既作为设置器也作为获取器工作,返回所选命名空间的存储。

    要在你的操作和回调中访问全局状态,你可以使用 store 函数返回对象的 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;
            },
        },
    } );
    

    使用 wp_interactivity_state 函数在服务器上初始化的全局状态也包含在该对象中,因为它会自动从服务器序列化到客户端:

    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;
            },
        },
    } );
    

    对全局状态的更改将自动触发任何依赖于修改值的指令的更新。

    请访问响应式和声明式思维模式指南,了解更多关于 Interactivity API 中反应性工作原理的信息。

示例:两个使用全局状态进行通信的交互式区块

在此示例中,有两个独立的交互式区块。一个显示计数器,另一个是增加该计数器的按钮。这些区块可以放置在页面上的任何位置,无论 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--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--click="actions.increment" 触发 increment 操作。
  4. 在 JavaScript 中,increment 操作通过递增 state.counter 直接修改全局状态。

这两个块是独立的,可以放置在页面的任何位置。它们不需要在 DOM 结构中嵌套或直接关联。可以向页面添加这些交互块的多个实例,它们将共享并更新同一个全局计数器值。

局部上下文

局部上下文(Local context) 在 Interactivity API 中指的是在 HTML 结构中的特定元素内定义的本地数据。与全局状态不同,局部上下文仅对定义它的元素及其子元素可访问。

当你需要为单个交互块提供独立状态时,局部上下文特别有用,它确保每个块实例都能维护自己的唯一数据,而不会相互干扰。

你应该在以下情况使用局部上下文:

  • 你需要为同一交互块的多个实例维护独立的状态。
  • 你想要封装仅与特定交互块及其子元素相关的数据。
  • 你需要实现仅限于 UI 特定部分的功能。

处理局部上下文

  • 初始化局部上下文

    局部上下文直接使用 data-wp-context 指令在 HTML 结构中初始化。该指令接受一个 JSON 字符串,用于定义该部分上下文的初始值。

    <div data-wp-context='{ "counter": 0 }'>
        <!-- 子元素将可以访问 `context.counter` -->
    </div>
    

    你也可以在服务端使用 wp_interactivity_data_wp_context PHP 辅助函数来初始化局部上下文,这能确保字符串化值的正确转义和格式化:

    <?php
    $context = array( 'counter' => 0 );
    ?>
    
    <div <?php echo wp_interactivity_data_wp_context( $context ); ?>>
      <!-- 子元素将可以访问 `context.counter` -->
    </div>
    
  • 访问局部上下文

    在 HTML 标记中,你可以直接在指令值中引用 context 来访问局部上下文值:

    <div data-wp-bind--hidden="!context.isOpen">
        <span data-wp-text="context.counter"></span>
    </div>
    

    在 JavaScript 中,你可以使用 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;
            },
        },
    } );
    

    对局部上下文的更改将自动触发任何依赖于修改后值的指令的更新。

    请访问 响应式和声明式思维模式 指南,以了解更多关于 Interactivity API 中响应式工作原理的信息。

  • 嵌套局部上下文

    局部上下文可以嵌套,子上下文继承父上下文的值,并可能覆盖这些值:

    <div data-wp-context='{ "theme": "light", "counter": 0 }'>
        <p>主题:<span data-wp-text="context.theme"></span></p>
        <p>计数器:<span data-wp-text="context.counter"></span></p>
    
        <div data-wp-context='{ "theme": "dark" }'>
            <p>主题:<span data-wp-text="context.theme"></span></p>
            <p>计数器:<span data-wp-text="context.counter"></span></p>
        </div>
    </div>
    

    在此示例中,内部的 div 将具有 "dark" 的 theme 值,但将从其父上下文继承 counter 值 0。

示例:使用局部上下文拥有独立状态的单个交互块

在此示例中,有一个显示计数器并可递增计数器的单一交互块。通过使用局部上下文,即使向页面添加了多个块,每个块实例也将拥有自己独立的计数器。

<?php
$context = array( 'counter' => 0 );
?>

<div
  data-wp-interactive="myCounterPlugin"
  <?php echo get_block_wrapper_attributes(); ?>
  <?php echo wp_interactivity_data_wp_context( $context ); ?>
>
  <p>计数器:<span data-wp-text="context.counter"></span></p>
  <button data-wp-on--click="actions.increment">递增</button>
</div>
store( 'myCounterPlugin', {
    actions: {
        increment() {
            const context = getContext();
            context.counter += 1;
        },
    },
} );

在此示例中:

  1. 使用 data-wp-context 指令定义了一个初始 counter 值为 0 的局部上下文。
  2. 使用 data-wp-text="context.counter" 显示计数器,该指令从局部上下文读取值。
  3. 递增按钮使用 data-wp-on--click="actions.increment" 来触发递增操作。
  4. 在 JavaScript 中,使用 getContext 函数来访问和修改每个块实例的局部上下文。

用户能够将此块的多个实例添加到页面上,每个实例都将维护其自己独立的计数器。单击某个块上的“递增”按钮只会影响该特定块的计数器,而不会影响其他块。

派生状态

派生状态(Derived state) 在 Interactivity API 中指的是从全局状态或其他部分的局部上下文计算得出的值。它是按需计算的,而不是存储的。它确保了数据的一致性,减少了冗余,并增强了代码的声明式特性。

派生状态是现代状态管理中的一个基本概念,并非 Interactivity API 独有。它也用于其他流行的状态管理系统中,例如 Redux 中称为 selectors,或在 Preact Signals 中称为 computed 值。

派生状态提供了几个关键优势,使其成为设计良好的应用程序状态的重要组成部分,包括:

  1. 单一事实来源: 派生状态鼓励你仅在状态中存储必要的原始数据。可以从这些核心数据计算出的任何值都成为派生状态。这种方法降低了交互块中数据不一致的风险。
  2. 自动更新: 当你使用派生状态时,当底层数据发生变化时,值会自动重新计算。这确保了你的交互块的所有部分始终能够访问最新的信息,而无需手动干预。

  3. 简化的状态管理: 通过按需计算值而不是手动存储和更新它们,你可以减少状态管理逻辑的复杂性。这导致更清晰、更易维护的代码。

  4. 性能提升: 在许多情况下,派生状态可以优化为仅在必要时重新计算,从而有可能提高你的交互块的性能。

  5. 更易于调试: 使用派生状态时,数据的来源及其转换方式更加清晰。这使得更容易追踪交互块中的问题。

本质上,派生状态允许你以声明式的方式表达交互块中不同数据片段之间的关系,而不是在每次更改时命令式地更新相关值。

请访问 响应式和声明式思维模式 指南,以了解更多关于如何在 Interactivity 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 中,派生状态是使用 getter 定义的:

    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 即可。

    <span data-wp-text="state.double"></span>
    

    JavaScript 中也是如此。全局状态和派生状态都可以通过 store 的 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.
            },
        },
    } );
    

示例:不使用派生状态 vs 使用派生状态

让我们考虑一个存在计数器并需要显示双倍值的场景,并比较两种方法:一种不使用派生状态,另一种使用派生状态。

  • 不使用派生状态
    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;
            },
        },
    } );
    

    在这个改进的版本中:

    • state.double 被定义为 getter,自动从 state.counter 派生其值。
    • increment 操作只需要更新 state.counter。
    • 无论 state.counter 如何或在哪里更新,state.double 都保证具有正确的值。

示例:在本地上下文中使用派生状态

现在让我们考虑一个本地上下文初始化计数器的场景。

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--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--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 priceWithoutTax * ( 1 + state.taxRate );
        },
    },
    actions: {
        updateTaxRate( event ) {
            // 更新全局税率。
            state.taxRate = event.target.value;
        },
        updatePrice( event ) {
            // 更新本地产品价格。
            const context = getContext();
            context.priceWithoutTax = event.target.value;
        },
    },
} );

在此示例中,priceWithTax 是由全局 taxRate 和本地 priceWithoutTax 派生而来的。每当您通过 updateTaxRate 或 updatePrice 操作更新全局状态或本地上下文时,Interactivity API 都会重新计算派生状态并更新 DOM 的相应部分。

通过使用派生状态,您可以创建一个更具可维护性且不易出错的代码库。它确保相关的状态值始终保持同步,降低操作的复杂性,并使您的代码更具声明性,更易于推理。

订阅服务器状态和上下文

Interactivity API 提供了一种基于区域的导航功能,可以在不重新加载整个页面的情况下动态替换页面的一部分。查询块(Query block)在禁用 Force page reload 切换开关时原生支持此功能。开发人员可以通过调用 actions.navigate(),在自定义块中使用相同的功能,该函数位于 @wordpress/interactivity-router 脚本模块中。

请访问客户端导航指南,了解有关如何使用 Interactivity Router 并在块中实现客户端导航的更多信息。

在使用基于区域的导航时,确保您的交互式块与服务器提供的全局状态和本地上下文保持同步至关重要。默认情况下,Interactivity API 永远不会使用服务器提供的值覆盖全局状态或本地上下文。Interactivity API 提供了两个函数来帮助管理这种同步:getServerState() 和 getServerContext()。

getServerState()

getServerState() 允许您订阅在客户端导航期间发生的全局状态更改。此函数类似于 getServerContext(),但它处理的是全局状态而不是本地上下文。

getServerState() 函数返回服务器提供的全局状态的快照(深拷贝)。这意味着您定义的任何监视返回对象的回调都会在每次客户端导航事件时触发,无论值是否实际发生变化。修改返回的对象对实际服务器状态没有影响,因为它是克隆对象。

让我们考虑一个包含多个问题的测验。每个问题都是一个单独的页面。当用户导航到新问题时,服务器会提供新问题以及回答所有问题的剩余时间。

<?php
wp_interactivity_state( 'myPlugin', array(
    'question' => get_question_for_page( get_the_ID() ),
    'timeLeft' => 5 * 60, // 回答所有问题的时间。
) );
?>
<div data-wp-interactive="myPlugin">
import { store, getServerState, withSyncEvent } from '@wordpress/interactivity';

const { state } = store( 'myPlugin', {
    actions: {
        // 此操作将由指令触发,例如:
        // <button data-wp-on--click="actions.nextQuestion">下一题</button>
        nextQuestion: withSyncEvent( function* ( event ) {
            event.preventDefault();
            const { actions } = yield import(
                '@wordpress/interactivity-router'
            );
            actions.navigate( '/question-2' );
        } ),
    },
    callbacks: {
        // 此回调将由指令触发,例如:
        // <div data-wp-watch="callbacks.updateQuestion"></div>
        updateQuestion() {
            const serverState = getServerState();

            // 使用来自服务器的新值进行更新。
            // 我们不想更新 `timeLeft`,因为它代表回答所有问题的剩余时间。
            state.question = serverState.question;
        },
    },
} );

注意:需要调用同步事件方法(如 event.preventDefault())的操作必须将处理程序包装在 withSyncEvent() 中。详情请参阅 withSyncEvent() 文档。

getServerContext()

getServerContext() 允许您订阅在客户端导航期间发生的本地上下文更改。此函数类似于 getServerState(),但它处理的是本地上下文而不是全局状态。

getServerContext() 函数返回服务器提供的本地上下文的快照(深拷贝)。这意味着您定义的任何监视返回对象的回调都会在每次客户端导航事件时触发,无论值是否实际发生变化。修改返回的对象对实际服务器上下文没有影响,因为它是克隆对象。

考虑一个包含多个问题的测验。每个问题都是一个单独的页面。当用户导航到新问题时,服务器会提供新问题以及回答所有问题的剩余时间。

<div <?php echo wp_interactivity_data_wp_context( array(
    'currentQuestion' => get_question_for_page( get_the_ID() ),
), ); ?>>
import {
    store,
    getContext,
    getServerContext,
    withSyncEvent,
} from '@wordpress/interactivity';

store( 'myPlugin', {
    actions: {
        // 此操作将由指令触发,例如:
        // <button data-wp-on--click="actions.nextQuestion">下一题</button>
        nextQuestion: withSyncEvent( function* ( event ) {
            event.preventDefault();
            const { actions } = yield import(
                '@wordpress/interactivity-router'
            );
            actions.navigate( '/question-2' );
        } ),
    },
    callbacks: {
        // 此回调将由指令触发,例如:
        // <div data-wp-watch="callbacks.updateQuestion"></div>
        updateQuestion() {
            const serverContext = getServerContext();
            const context = getContext();

            // 使用来自服务器的新值进行更新。
            context.currentQuestion = serverContext.currentQuestion;
        },
    },
} );

何时使用

当您拥有依赖于可能因导航事件而变化的全局状态或本地上下文的交互式块时,确保应用程序不同部分之间的一致性。

使用 getServerState() 和 getServerContext() 的最佳实践

  • 快照引用:getServerState() 和 getServerContext() 均返回快照(深拷贝)对象。修改它们对实际服务器状态或上下文没有影响。您可以读取这些对象以更新全局状态或本地上下文。
  • 回调集成:将这些函数纳入您的存储回调中,以响应状态和上下文的变化。请注意,监视 getServerState() 和 getServerContext() 的回调将在每次客户端导航事件时触发,无论值是否发生变化。

Config

Interactivity API 中的 Config 指的是从服务器序列化到客户端的静态配置数据。与全局状态或本地上下文不同,config 值不是响应式的——它们不会触发 UI 更新,并且在客户端生命周期中保持不变。

Config 非常适合从 PHP 向 JavaScript 发送非响应式数据,例如 API 端点、nonce、功能标志或不会在用户交互过程中发生变化的翻译。

在以下情况下应使用 config:

  • 您需要从服务器向客户端传递静态配置数据
  • 数据不需要是响应式的(在用户交互期间不会改变)
  • 您希望发送 API URL、身份验证令牌或功能开关
  • 您需要提供翻译或其他静态内容

处理配置

  • 在服务器上设置配置

    使用 PHP 中的 wp_interactivity_config() 函数来定义配置值:

    // Setting config values
    wp_interactivity_config( 'myPlugin', array(
        'restApiUrl'     => get_rest_url( null, 'my-plugin/v1/' ),
        'nonce'          => wp_create_nonce( 'my_plugin_action' ),
        'isUserLoggedIn' => is_user_logged_in(),
        'translations'   => array(
            'loading'    => __( 'Loading...', 'my-plugin' ),
            'error'      => __( 'An error occurred', 'my-plugin' ),
        ),
    ) );
    
  • 在客户端访问配置

    使用 JavaScript 中的 getConfig() 函数来检索配置值:

    import { store, getConfig } from '@wordpress/interactivity';
    
    const { state } = store( 'myPlugin', {
        actions: {
            *fetchData() {
                const { restApiUrl, nonce } = getConfig();
    
                try {
                    const response = yield fetch( `${ restApiUrl }data`, {
                        method: 'POST',
                        headers: {
                            'X-WP-Nonce': nonce,
                        },
                    } );
                    const data = yield response.json();
                    state.data = data;
                } catch ( error ) {
                    const { translations } = getConfig();
                    state.errorMessage = translations.error;
                }
            },
        },
    } );
    

结论

请记住,有效的状态管理的关键在于保持状态最小化并避免冗余。使用派生状态动态计算值,根据数据的范围和需求选择全局状态和本地上下文,并使用配置来处理静态的服务器到客户端数据。这将带来更清晰、更稳健的架构,使其更易于调试和维护。最后,如果您需要将状态或上下文与服务器同步,可以使用 getServerState() 和 getServerContext() 来实现这一目标。