WordPress 的数据模块作为管理插件和 WordPress 自身应用程序状态的枢纽,提供了在独立模块内部及模块之间管理数据的工具。它被设计为一种用于组织和共享数据的模块化模式:足够简单以满足小型插件的需求,同时具备可扩展性以服务于复杂单页应用的要求。

数据模块建立在 Redux 之上并共享其许多相同的核心原则,但不应将其误认为是单纯的 WordPress 版的 Redux,因为它包含了一些自身的 显著特征。在阅读本指南时,参考 Redux 文档——特别是 其术语表——可能会对您理解核心概念有所帮助。

安装

安装该模块

npm install @wordpress/data --save

此软件包假设您的代码将在 ES2015+ 环境中运行。如果您使用的环境对这些语言特性和 API 的支持有限或完全不支持,您应该在代码中包含 @wordpress/babel-preset-default 随附的 polyfill。

注册 Store

使用 register 函数将您自己的 store 添加到集中式数据注册表中。此函数接受一个参数——一个可以使用 createReduxStore 工厂函数创建的 store 描述符。createReduxStore 接受两个参数:一个用于标识模块的名称,以及一个描述状态如何表示、修改和访问的配置对象。至少,您必须提供一个 reducer 函数来描述状态的形状以及它在响应分发到 store 的动作时的变化方式。

import apiFetch from '@wordpress/api-fetch';
import { createReduxStore, register } from '@wordpress/data';

const DEFAULT_STATE = {
    prices: {},
    discountPercent: 0,
};

const actions = {
    setPrice( item, price ) {
        return {
            type: 'SET_PRICE',
            item,
            price,
        };
    },

    startSale( discountPercent ) {
        return {
            type: 'START_SALE',
            discountPercent,
        };
    },
};

const store = createReduxStore( 'my-shop', {
    reducer( state = DEFAULT_STATE, action ) {
        switch ( action.type ) {
            case 'SET_PRICE':
                return {
                    ...state,
                    prices: {
                        ...state.prices,
                        [ action.item ]: action.price,
                    },
                };

            case 'START_SALE':
                return {
                    ...state,
                    discountPercent: action.discountPercent,
                };
        }

        return state;
    },

    actions,

    selectors: {
        getPrice( state, item ) {
            const { prices, discountPercent } = state;
            const price = prices[ item ];

            return price * ( 1 - 0.01 * discountPercent );
        },
    },

    resolvers: {
        getPrice: ( item ) => async ({ dispatch }) => { {
            const path = '/wp/v2/prices/' + item;
            const price = await apiFetch( { path } );
            dispatch.setPrice( item, price );
        },
    },
} );

register( store );

createReduxStore 的返回值是 StoreDescriptor 对象,它包含两个属性:

  • name (string) – store 的名称
  • instantiate (Function) – 它返回一个具有以下方法的 类 Redux store 对象:
    • getState():返回已注册 reducer 的状态值
    • subscribe( listener: Function ):注册一个函数,每当状态值发生变化时调用该函数。
    • dispatch( action: Object ):给定一个动作对象,调用已注册的 reducer 并更新状态值。

Redux Store 选项

reducer

一个 reducer 是一个接受前一个 state 和 action 作为参数并返回更新后的 state 值的函数。

actions

actions 对象应描述您的 store 可用的所有 action creators。action creator 是一个可选地接受参数并返回要分发给已注册 reducer 的动作对象的函数。分发动作是更改状态的主要机制。

selectors

selectors 对象包括一组用于访问和派生状态值的函数。selector 是一个接受状态和可选参数并从状态中返回某些值的函数。调用 selector 是从状态中检索数据的主要机制,并为通常更容易发生变化且不太适合作为 规范化对象 使用的原始数据提供了一种有用的抽象。

resolvers

resolver 是 selector 的副作用。如果您的 selector 结果可能需要从外部源获取,您可以定义一个 resolver,以便在第一次调用 selector 时执行获取行为。

resolvers 选项应作为对象传递,其中每个键是要作用用的 selector 的名称,值是一个接收与传递给 selector 的参数相同的参数的函数(不包括 state 参数)。然后,它可以按需进行分发以满足 selector 的要求,利用大多数数据消费者会订阅后续状态变化(通过 subscribe 或 withSelect)这一事实。

Resolver 与 thunks 结合使用,可用于为您的 store 实现异步数据流。

对象形式的 resolvers

resolver 可以定义为具有 fulfill 方法和可选的 isFulfilled 和 shouldInvalidate 方法的对象:

resolvers: {
    getPage: {
        fulfill: ( id ) => async ( { dispatch } ) => {
            const data = await fetchPage( id );
            dispatch( { type: 'RECEIVE_PAGE', id, data } );
        },
        isFulfilled: ( state, id ) => !! state.pages[ id ],
        shouldInvalidate: ( action, id ) => action.type === 'INVALIDATE_PAGE' && action.id === id,
    },
},

resolver 也可以是一个普通函数,将 isFulfilled 或 shouldInvalidate 分配为属性。它将在内部规范化为 { fulfill, isFulfilled, shouldInvalidate } 对象形式。

isFulfilled( state, ...args ) 允许您为一组特定的参数覆盖 hasFinishedResolution 元 selector。通常,仅当分辨率元数据记录表明它已经针对这些参数运行时才会跳过 resolver。使用 isFulfilled,您可以查看实际的 store 状态并确定数据已经存在:例如因为它是被不同的 resolver 加载的或在服务器端预加载的。当 isFulfilled 返回 true 时,不会调用 fulfill 方法,也不会写入任何分辨率元数据。

shouldInvalidate( action, ...args ) 会在每次分发的动作上针对每一组已解析的参数调用。如果它返回 true,则这些参数的分辨率将被无效化,导致在下一次 selector 调用时再次运行 resolver。这对于在相关动作发出信号表明缓存结果可能过时时自动重新获取数据非常有用。

controls (已弃用)

为了处理异步数据流,建议使用 thunks 而不是 controls。

View controls explanation

control 定义了与特定动作类型相关的执行流程行为。在 thunks 之前,controls 用于为您的 store 实现异步数据流。通过将您的 action creator 或 resolvers 定义为产生特定受控动作类型的生成器,执行将按照 control handler 的定义进行。

controls 选项应作为一个对象传递,其中每个键是所采取行动类型的名称,值是一个接收原始动作对象的函数。它应该返回一个承诺(promise),在评估动作应继续时解析,或者返回一个值。该值或解析后的承诺值将被分配给 yield 赋值的返回值。如果控制处理程序返回 undefined,则执行不会继续。

有关更多信息,请参阅 @wordpress/redux-routine 的文档。

initialState

商店的可选预加载初始状态。您可以使用它来恢复某些序列化状态值或服务器端生成的状态。

通用商店

@wordpress/data 模块提供了一个更高级且通用的接口,旨在集成其他数据系统以及在需要更直接控制数据系统的情况下使用。在这种情况下,需要在 @wordpress/data 之外实现一个数据存储,然后通过三个函数将其接入:

  • getSelectors():返回选择器函数对象,已预映射到商店。
  • getActions():返回动作函数对象,已预映射到商店。
  • subscribe( listener: Function ):注册一个在状态值发生任何变化时调用的函数。
    • 行为类似于 Redux subscribe
      具有以下区别:
      • 不需要实现取消订阅,因为注册表从不使用它。
        - 只需支持一个监听器(即注册表)。

通过为您的自定义商店实现上述接口,您可以在应用程序代码中获得使用注册表和 withSelect 以及 withDispatch 高阶组件的好处。这提供了与现有和替代数据系统的无缝集成。

可以通过以下方式将现有的 Redux 商店及其自己的 reducers、store enhancers 和 middleware 进行集成:

示例:

import { register } from '@wordpress/data';
import existingSelectors from './existing-app/selectors';
import existingActions from './existing-app/actions';
import createStore from './existing-app/store';

const reduxStore = createStore();

const mapValues = ( obj, callback ) =>
    Object.entries( obj ).reduce(
        ( acc, [ key, value ] ) => ( {
            ...acc,
            [ key ]: callback( value ),
        } ),
        {}
    );

const boundSelectors = mapValues(
    existingSelectors,
    ( selector ) =>
        ( ...args ) =>
            selector( reduxStore.getState(), ...args )
);

const boundActions = mapValues(
    existingActions,
    ( action ) =>
        ( ...args ) =>
            reduxStore.dispatch( action( ...args ) )
);

const genericStore = {
    name: 'existing-app',
    instantiate: () => ( {
        getSelectors: () => boundSelectors,
        getActions: () => boundActions,
        subscribe: reduxStore.subscribe,
    } ),
};

register( genericStore );

也可以从头开始实现一个完全自定义的商店:

示例:

import { register } from '@wordpress/data';

function customStore() {
    return {
        name: 'custom-data',
        instantiate: () => {
            const listeners = new Set();
            const prices = { hammer: 7.5 };

            function storeChanged() {
                for ( const listener of listeners ) {
                    listener();
                }
            }

            function subscribe( listener ) {
                listeners.add( listener );
                return () => listeners.delete( listener );
            }

            const selectors = {
                getPrice( itemName ) {
                    return prices[ itemName ];
                },
            };

            const actions = {
                setPrice( itemName, price ) {
                    prices[ itemName ] = price;
                    storeChanged();
                },
            };

            return {
                getSelectors: () => selectors,
                getActions: () => actions,
                subscribe,
            };
        },
    };
}

register( customStore );

与 Redux 的比较

数据模块共享 核心原则 和 API 方法命名 的许多相同点,这些也适用于 Redux。事实上,它是基于 Redux 实现的。其不同之处在于建立了用于创建独立但相互依赖的商店的模块化模式,并将约定编码为例如将选择器函数作为数据访问的主要入口点。

高阶组件 的创建是为了补充这一区别。拆分 withSelect 和 withDispatch 的意图在于 — 而在 React Redux 中它们组合在 connect 下作为 mapStateToProps 和 mapDispatchToProps 参数 — 以更准确地反映 dispatch 不依赖于对状态变化的订阅,并允许在 withDispatch 中使用派生自状态的值为(通过 高阶组件组合)。

数据模块还具有内置解决方案来处理异步副作用,通过 resolvers 和 thunks。这些与 标准 redux 异步解决方案(如 redux-thunk 或 redux-saga)略有不同。

与 Redux 和 React Redux 的具体实现差异:

  • 在 Redux 中,subscribe 监听器在每次 dispatch 时都会调用,无论状态值是否已更改。
    • 在 @wordpress/data 中,仅在状态发生更改时才调用订阅者。
  • 在 React Redux 中,mapStateToProps 函数必须返回一个对象。
    • 在 @wordpress/data 中,如果 withSelect 映射函数没有要注入的 props,它可以返回 undefined。
  • 在 React Redux 中,mapDispatchToProps 参数可以定义为对象或函数。
    • 在 @wordpress/data 中,withDispatch 高阶组件创建器必须传递一个函数。

API

AsyncModeProvider

上下文提供程序组件,用于在同步模式和异步模式之间切换数据模块组件的重渲染。

用法

import { useSelect, AsyncModeProvider } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

function BlockCount() {
    const count = useSelect( ( select ) => {
        return select( blockEditorStore ).getBlockCount();
    }, [] );

    return count;
}

function App() {
    return (
        <AsyncModeProvider value={ true }>
            <BlockCount />
        </AsyncModeProvider>
    );
}

在此示例中,BlockCount 组件以异步方式重渲染。
这意味着如果正在执行更关键的任务(如在输入框中输入内容),
重渲染将延迟直到浏览器进入 IDLE 状态。
可以嵌套多个级别的 AsyncModeProvider 以微调渲染行为。

参数

  • props.value boolean:启用异步模式。

combineReducers

combineReducers 辅助函数将一个值是不同的缩减函数的对象转换为单个缩减函数,您可以将其传递给 registerReducer。

用法

import { combineReducers, createReduxStore, register } from '@wordpress/data';

const prices = ( state = {}, action ) => {
    return action.type === 'SET_PRICE'
        ? {
                ...state,
                [ action.item ]: action.price,
            }
        : state;
};

const discountPercent = ( state = 0, action ) => {
    return action.type === 'START_SALE' ? action.discountPercent : state;
};

const store = createReduxStore( 'my-shop', {
    reducer: combineReducers( {
        prices,
        discountPercent,
    } ),
} );
register( store );

类型

  • import('./types').combineReducers

参数

  • reducers Object:一个对象,其值对应于需要合并为一个的不同缩减函数。

返回

  • Function:一个缩减器,它会调用 reducers 对象中的每个缩减器,并构建一个具有相同形状的状态对象。

controls

未记录的声明。

createReduxStore

为提供的 Redux 商店配置创建一个数据存储描述符,包含描述 reducer、actions、selectors、controls 和 resolvers 的属性。

用法

import { createReduxStore } from '@wordpress/data';

const store = createReduxStore( 'demo', {
    reducer: ( state = 'OK' ) => state,
    selectors: {
        getValue: ( state ) => state,
    },
} );

参数

  • key string: 唯一的命名空间标识符。
  • options ReduxStoreConfig< State, Actions, Selectors >: 已注册的存储选项,包含描述 reducer、actions、selectors 和 resolvers 的属性。

返回

  • StoreDescriptor< ReduxStoreConfig< State, Actions, Selectors > >: 存储对象。

createRegistry

创建一个新的存储注册表,给定一个可选的初始存储配置对象。

参数

  • storeConfigs Record< string, ReduxStoreConfig< any, any, any > >: 初始存储配置。
  • parent DataRegistry | null: 父注册表。

返回

  • DataRegistry: 数据注册表。

createRegistryControl

创建一个控制函数,该函数接受带有 registry 对象的额外柯里化参数。虽然常规控制的签名是

( action ) => iteratorOrPromise;

其中控制函数处理与其绑定的 action,但注册表控制的签名为:

( registry ) => ( action ) => iteratorOrPromise;

注册表控制通常用于选择数据或向已注册的存储分发操作。

当使用 createRegistryControl 创建的控制在与存储一起注册时,存储知道在执行控制时应使用哪种调用约定。

参数

  • registryControl T & { isRegistryControl?: boolean; }: 接收注册表对象并返回控制的函数。

返回

  • 可以与存储一起注册的注册表控制。

createRegistrySelector

创建一个选择器函数,该函数接受带有注册表 select 函数的额外柯里化参数。虽然常规选择器的签名是

( state, ...selectorArgs ) => result;

允许从存储的 state 中选择数据,但注册表选择器的签名为:

( select ) =>
    ( state, ...selectorArgs ) =>
        result;

还支持从其他已注册的存储中选择数据。

用法

import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';

const getCurrentPostId = createRegistrySelector( ( select ) => ( state ) => {
    return select( editorStore ).getCurrentPostId();
} );

const getPostEdits = createRegistrySelector( ( select ) => ( state ) => {
    // 像调用任何其他函数一样调用另一个注册表选择器
    const postType = getCurrentPostType( state );
    const postId = getCurrentPostId( state );
    return select( coreStore ).getEntityRecordEdits(
        'postType',
        postType,
        postId
    );
} );

注意 getCurrentPostId 选择器可以像任何其他函数一样被调用,
(即使在常规的 non-registry 选择器内部也能正常工作),并且我们不需要传递
注册表作为参数。注册表绑定在将选择器与存储一起注册时自动发生。

参数

  • registrySelector ( select: ) => Selector: 接收注册表 select 函数并返回状态选择器的函数。

返回

  • RegistrySelector< Selector >: 可以与存储一起注册的注册表选择器。

createSelector

创建一个记忆化选择器,它根据“依赖项”数组和选择器参数缓存计算出的值,并且仅在其中的任何一个发生变化时才重新计算值。

请参阅 rememo 包的文档,createSelector 函数即是从该包重新导出的。

类型

  • ( selector: S, getDependants: GetDependants ) => S & EnhancedSelector

dispatch

给定一个存储描述符,返回该存储的操作创建者对象。调用操作创建者将导致其被分发,从而相应地更新状态值。

注意:由 dispatch 返回的操作创建者在被调用时将返回一个 promise。

警告:此全局 dispatch 函数仅适用于默认注册表。它不适用于自定义的 RegistryProvider 或 BlockEditorProvider 上下文。在 React 组件中,请优先使用 useDispatch 钩子,它是感知注册表的。

用法

import { dispatch } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

dispatch( myCustomStore ).setPrice( 'hammer', 9.75 );

参数

  • storeNameOrDescriptor StoreNameOrDescriptor: 存储描述符。也支持传递存储名称的传统调用约定。

返回

  • DispatchReturn< StoreNameOrDescriptor >: 包含操作创建者的对象。

EnhancedSelector

未记录的声明。

GetDependants

未记录的声明。

keyedReducer

高阶 reducer 创建器,它创建一个组合的 reducer 对象,以 action 对象上的属性为键。

用法

import { keyedReducer } from '@wordpress/data';

const itemsByContext = keyedReducer( 'context' )( ( state = [], action ) => {
    switch ( action.type ) {
        case 'ADD_ITEM':
            return [ ...state, action.item ];
    }
    return state;
} );

参数

  • actionProperty string: 用于对对象进行键控的 action 属性。

返回

  • 高阶 reducer。

plugins

可用于注册表的可用插件对象。

相关

register

注册标准的 @wordpress/data 存储描述符。

用法

import { createReduxStore, register } from '@wordpress/data';

const store = createReduxStore( 'demo', {
    reducer: ( state = 'OK' ) => state,
    selectors: {
        getValue: ( state ) => state,
    },
} );
register( store );

参数

  • store StoreDescriptor: 存储描述符。

registerGenericStore

已弃用 请使用 register( storeDescriptor ) 代替。

注册通用存储实例。

参数

  • name string: 存储注册表名称。
  • store Object: 存储实例 ({ getSelectors, getActions, subscribe })。

registerStore

已弃用 请使用 register 代替。

注册标准的 @wordpress/data 存储。

参数

  • storeName string: 存储的唯一命名空间标识符。
  • options Object: 存储描述(reducer、actions、selectors、resolvers)。

返回

  • Object: 已注册的存储对象。

RegistryConsumer

一个自定义的 React context consumer,它将提供的 registry 暴露给子组件。与 RegistryProvider 一起使用。

您可以在此处阅读有关 React context API 的更多信息:https://react.dev/learn/passing-data-deeply-with-context#step-3-provide-the-context

用法

import {
  RegistryProvider,
  RegistryConsumer,
  createRegistry
} from '@wordpress/data';

const registry = createRegistry( {} );

const App = ( { props } ) => {
  return <RegistryProvider value={ registry }>
    <div>Hello There</div>
    <RegistryConsumer>
      { ( registry ) => (
        <ComponentUsingRegistry
                { ...props }
              registry={ registry }
      ) }
    </RegistryConsumer>
  </RegistryProvider>
}

RegistryProvider

一个自定义 Context provider,通过 consumer 将提供的 registry 暴露给子组件。

参见 RegistryConsumer 文档中的示例。

resolveSelect

给定一个存储描述符,返回一个包含预绑定到状态的存储选择器的对象,这样您只需要提供额外的参数,并且经过修改后,它们在运行任何 resolvers 后会返回解析为其最终值的 promises。

用法

import { resolveSelect } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

resolveSelect( myCustomStore ).getPrice( 'hammer' ).then( console.log );

参数

  • storeNameOrDescriptor StoreDescriptor|string: 存储描述符。也支持传递存储名称的传统调用约定。

返回

  • ResolveSelectReturn< StoreNameOrDescriptor >: 包含预绑定选择器的对象。
  • CurriedSelectorsResolveOf< T >: 包含商店 promise 包装选择器的对象。

select

给定一个商店描述符,返回该商店的选择器对象。选择器函数已预先绑定以自动传递当前状态。作为使用者,你只需传递选择器的参数(如果有的话)。

警告:此全局 select 函数仅适用于默认注册表。它不适用于自定义 RegistryProvider 或 BlockEditorProvider 上下文。在 React 组件中,请优先使用 useSelect 钩子,因为它感知注册表。

用法

import { select } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

select( myCustomStore ).getPrice( 'hammer' );

参数

  • storeNameOrDescriptor string | T: 商店描述符。也支持传递商店名称的传统调用约定。

返回

  • CurriedSelectorsOf< T >: 包含商店选择器的对象。

subscribe

给定一个监听函数,每当已注册商店之一的状态值发生变化时,该函数将被调用。如果你指定了可选的 storeNameOrDescriptor 参数,则监听函数仅在该特定已注册商店的更新时被调用。

此函数返回一个 unsubscribe 函数,用于停止订阅。

用法

import { subscribe } from '@wordpress/data';

const unsubscribe = subscribe( () => {
    // 你可以利用这个机会测试由状态更新导致的
    // 选择器的派生结果是否随后发生了变化。
} );

// 稍后,如有必要...
unsubscribe();

参数

  • listener Function: 回调函数。
  • storeNameOrDescriptor string|StoreDescriptor?: 可选的商店名称。

suspendSelect

给定一个商店描述符,返回一个包含商店选择器的对象,这些选择器已预先绑定到状态,以便你只需提供额外的参数,并被修改为在选择器尚未解析时抛出承诺。

参数

  • storeNameOrDescriptor StoreDescriptor|string: 商店描述符。也支持传递商店名称的传统调用约定。

返回

  • Object: 包含商店 suspense 包装选择器的对象。

use

扩展注册表以继承给定插件提供的功能。插件是一个对象,其属性与注册表的属性对齐,合并以扩展默认注册表行为。

参数

  • plugin Object: 插件对象。

useDispatch

一个自定义 React 钩子,返回当前注册表的调度动作创建器。

注意:使用此钩子的组件必须位于 RegistryProvider 的上下文中。

用法

这展示了一种模式,你可能需要通过
useSelect 钩子从服务器检索动态数据,以便与调度
操作结合使用。

import { useCallback } from 'react';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

function Button( { onClick, children } ) {
    return (
        <button type="button" onClick={ onClick }>
            { children }
        </button>
    );
}

const SaleButton = ( { children } ) => {
    const { stockNumber } = useSelect(
        ( select ) => select( myCustomStore ).getStockNumber(),
        []
    );
    const { startSale } = useDispatch( myCustomStore );
    const onClick = useCallback( () => {
        const discountPercent = stockNumber > 50 ? 10 : 20;
        startSale( discountPercent );
    }, [ stockNumber ] );
    return <Button onClick={ onClick }>{ children }</Button>;
};

// 在应用程序中的某处渲染:
//
// <SaleButton>开始促销!</SaleButton>

参数

  • storeNameOrDescriptor StoreNameOrDescriptor: 可选择提供要从中检索动作创建器的商店名称或其描述符。如果未提供,则返回 registry.dispatch 函数。

返回

  • UseDispatchReturn< StoreNameOrDescriptor >: 商店的调度函数或动作创建器。

useRegistry

一个自定义 React 钩子,公开注册表上下文以供使用。

这将通过 Registry Provider 提供的 registry 值暴露给实现此钩子的组件。

它的行为类似于 useContext React 钩子。

注意:一般来说,useRegistry 是一个低级钩子,在大多数情况下不需要用于实现。@wordpress/data API 的大多数交互可以通过 useSelect 钩子,或者 withSelect 和 withDispatch 高阶组件来执行。

用法

import { RegistryProvider, createRegistry, useRegistry } from '@wordpress/data';

const registry = createRegistry( {} );

const SomeChildUsingRegistry = ( props ) => {
    const registry = useRegistry();
    // ...在其他 react 钩子中实现注册表的逻辑。
};

const ParentProvidingRegistry = ( props ) => {
    return (
        <RegistryProvider value={ registry }>
            <SomeChildUsingRegistry { ...props } />
        </RegistryProvider>
    );
};

返回

  • DataRegistry: 一个自定义 React 钩子,公开注册表上下文值。

useSelect

用于从已注册选择器中检索属性的自定义 React 钩子。

通常,这个自定义 React 钩子遵循钩子规则。

用法

import { useSelect } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

function HammerPriceDisplay( { currency } ) {
    const price = useSelect(
        ( select ) => {
            return select( myCustomStore ).getPrice( 'hammer', currency );
        },
        [ currency ]
    );
    return new Intl.NumberFormat( 'en-US', {
        style: 'currency',
        currency,
    } ).format( price );
}

// 在应用程序中渲染:
// <HammerPriceDisplay currency="USD" />

在上面的示例中,当 HammerPriceDisplay 被渲染到应用程序中时,价格将使用 useSelect 上的 mapSelect 回调从商店状态中检索。如果 currency prop 更改,则会检索该货币的任何价格状态。如果 currency prop 没有更改,并且传入了其他发生变化的 props,则价格不会改变,因为依赖项仅仅是 currency。

当数据仅在事件回调中使用且不希望在渲染时检索数据时,获取选择器函数可能更有用。

不要在渲染函数中调用选择器时使用 useSelect,否则你的组件在数据更改时将不会重新渲染。

import { useSelect } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

function Paste( { children } ) {
    const { getSettings } = useSelect( myCustomStore );
    function onPaste() {
        // 对设置做一些处理。
        const settings = getSettings();
    }
    return <div onPaste={ onPaste }>{ children }</div>;
}

参数

  • mapSelect T: 每次状态变化时调用的函数。返回值暴露给实现此钩子的组件。该函数接收第一个参数中的 registry.select 方法和第二个参数中的 registry。当传递商店键时,将返回该商店的所有选择器。这仅用于在事件回调中使用这些选择器,而不是用于创建元素树所需的数据。
  • deps unknown[]: 如果提供,这将使 mapSelect 记忆化,除非依赖项更改,否则将在每次状态变化时调用相同的 mapSelect。

返回

  • UseSelectReturn< T >: 选定的数据或商店选择器。

useSuspenseSelect

useSelect 钩子的变体,具有相同的 API,但是是兼容 Suspense 的数据源。

参数

  • mapSelect T: 每次状态变化时调用的函数。返回值暴露给使用此钩子的组件。该函数接收第一个参数中的 registry.suspendSelect 方法和第二个参数中的 registry。
  • deps unknown[]: 依赖数组,用于使 mapSelect 记忆化,除非依赖项更改,否则将在每次状态变化时调用相同的 mapSelect。

返回

  • ReturnType< T >: 由 mapSelect 函数返回的数据对象。

withDispatch

用于添加使用已注册动作创建器的调度 props 的高阶组件。

用法

function Button( { onClick, children } ) {
    return (
        <button type="button" onClick={ onClick }>
            { children }
        </button>
    );
}

import { withDispatch } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

const SaleButton = withDispatch( ( dispatch, ownProps ) => {
    const { startSale } = dispatch( myCustomStore );
    const { discountPercent } = ownProps;

    return {
        onClick() {
            startSale( discountPercent );
        },
    };
} )( Button );

// 在应用程序中渲染:
//
// <SaleButton discountPercent="20">开始促销!</SaleButton>

在大多数情况下,仅使用传递给 mapDispatchToProps 的前两个参数就足够了,如前面的示例所示。
但是,在某些非常高级的使用场景中,可以使用 registry 对象作为优化组件性能的工具。当你需要在事件触发时从存储中获取一些动态数据,但同时又不使用这些数据来渲染组件时,使用 registry 中的 select 函数可能会很有用。在这种情况下,你可以避免使用 withSelect 高阶组件来计算此类 prop,从而避免因频繁的值变化导致组件不必要的重新渲染。
请记住,mapDispatchToProps 必须返回一个仅包含函数的对象。

function Button( { onClick, children } ) {
    return (
        <button type="button" onClick={ onClick }>
            { children }
        </button>
    );
}

import { withDispatch } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => {
    // 库存数量频繁变化。
    const { getStockNumber } = select( myCustomStore );
    const { startSale } = dispatch( myCustomStore );
    return {
        onClick() {
            const discountPercent = getStockNumber() > 50 ? 10 : 20;
            startSale( discountPercent );
        },
    };
} )( Button );

// 在应用程序中渲染:
//
//  <SaleButton>开始促销!</SaleButton>

注意:重要的是,mapDispatchToProps 函数始终返回具有相同键的对象。例如,它不应包含根据不同条件返回不同值的逻辑。

参数

  • mapDispatchToProps ( dispatch: DataRegistry[ 'dispatch' ], ownProps: Record< string, unknown >, registry: DataRegistry ) => Record< string, ( , ...args: unknown[] ) => unknown >: 一个返回 prop 名称对象的函数,其值为绑定到 dispatch 的操作创建器,或者是一个接收组件 props 并返回操作创建器的函数。

返回

  • 合并了分发器 prop 的增强组件。

withRegistry

高阶组件,它将当前 registry 上下文作为 registry prop 传递给原始组件进行渲染。

withSelect

用于使用注册的选择器注入基于状态的 prop 的高阶组件。

用法

import { withSelect } from '@wordpress/data';
import { store as myCustomStore } from 'my-custom-store';

function PriceDisplay( { price, currency } ) {
    return new Intl.NumberFormat( 'en-US', {
        style: 'currency',
        currency,
    } ).format( price );
}

const HammerPriceDisplay = withSelect( ( select, ownProps ) => {
    const { getPrice } = select( myCustomStore );
    const { currency } = ownProps;

    return {
        price: getPrice( 'hammer', currency ),
    };
} )( PriceDisplay );

// 在应用程序中渲染:
//
//  <HammerPriceDisplay currency="USD" />

在上面的示例中,当 HammerPriceDisplay 被渲染到应用程序中时,它会将价格传递给底层的 PriceDisplay 组件,如果商店中锤子的价格发生变化,它会自动更新。

参数

  • mapSelectToProps ( select: SelectFunction, ownProps: Record< string, unknown >, registry: DataRegistry ) => Record< string, unknown >: 每次状态更改时调用的函数,预期返回要与组件自身 prop 合并的 prop 对象。

返回

  • 合并了状态数据 prop 的增强组件。

batch

batch 方法允许多个存储更新同时发生,从而减少在顺序状态更改期间选择器和组件重新渲染的不必要执行。

在 WordPress 数据应用中,分发连续操作通常会触发存储监听器并运行选择器,这可能导致重新渲染。batch 方法会暂停这些监听器,仅在最后激活一次,确保选择器仅以最终状态运行一次。

此方法对于优化昂贵选择器的性能、确保跨多个存储的原子操作以及为几个同步更新创建单个撤销/重做条目特别有效。

与 React 内置的批处理或 React Redux 的 batch 函数不同,registry.batch 在存储监听器级别运行,完全避免了不必要的选择器计算。

用法

import { useRegistry } from '@wordpress/data';

function Component() {
    const registry = useRegistry();

    function handleComplexUpdate() {
        // 不使用 batch:监听器被调用 3 次,可能导致多次组件重新渲染。
        // 使用 batch:仅通知监听器一次,根据需要进行单次组件重新渲染。
        registry.batch( () => {
            registry.dispatch( 'someStore' ).someAction();
            registry.dispatch( 'someStore' ).someOtherAction();
            registry.dispatch( 'someStore' ).thirdAction();
        } );
    }

    return <button onClick={ handleComplexUpdate }>更新</button>;
}

Selectors

以下选择器在 wp.data.select( 'core' ) 返回的对象上可用。

示例

import { store as coreDataStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';

function Component() {
    const result = useSelect( ( select ) => {
        const query = { per_page: 20 };
        const selectorArgs = [ 'postType', 'page', query ];

        return {
            pages: select( coreDataStore ).getEntityRecords( ...selectorArgs ),
            hasStartedResolution: select( coreDataStore ).hasStartedResolution(
                'getEntityRecords', // _selectorName_
                selectorArgs
            ),
            hasFinishedResolution: select(
                coreDataStore
            ).hasFinishedResolution( 'getEntityRecords', selectorArgs ),
            isResolving: select( coreDataStore ).isResolving(
                'getEntityRecords',
                selectorArgs
            ),
        };
    } );

    if ( result.hasStartedResolution ) {
        return <>正在获取数据...</>;
    }

    if ( result.isResolving ) {
        return (
            <>
                {
                    // 显示旋转图标
                 }
            </>
        );
    }

    if ( result.hasFinishedResolution ) {
        return (
            <>
                {
                    // 数据已就绪
                 }
            </>
        );
    }
}

hasFinishedResolution

如果给定选择器名称和参数集的解析已完成,则返回 true。

参数

  • state State: 数据状态。
  • selectorName string: 选择器名称。
  • args unknown[]?: 传递给选择器的参数。

返回

  • boolean: 解析是否已完成。

hasStartedResolution

如果给定选择器名称和参数集的解析已经被触发,则返回 true。

参数

  • state State: 数据状态。
  • selectorName string: 选择器名称。
  • args unknown[]?: 传递给选择器的参数。

返回

  • boolean: 解析是否已被触发。

isResolving

如果给定选择器名称和参数集的解析已被触发但尚未完成,则返回 true。

参数

  • state State: 数据状态。
  • selectorName string: 选择器名称。
  • args unknown[]?: 传递给选择器的参数。

返回

  • boolean: 解析是否正在进行中。

Normalizing Selector Arguments

在特定情况下,可能需要规范化传递给给定选择器/解析器配对的*调用*的参数。

每个解析器都有其解析状态缓存在内部状态中,其中键是在调用时提供给选择器的参数。

例如,对于单个参数的选择器,相关的解析器会生成如下缓存键:[ 123 ]。

此缓存用于确定给定解析器的解析状态,从而避免不必要的解析器额外调用(这些操作通常涉及“昂贵”的操作,如网络请求)。

因此,在调用选择器时,确保参数保持一致性至关重要。例如,默认情况下,即使这两个调用很可能相同,它们也不会使用相同的键进行缓存:

// 参数为数字
getSomeDataById( 123 );

// 参数为字符串
getSomeDataById( '123' );

这是利用 normalizeArgs 属性以确保一致性的机会,通过保护调用者不传递错误的类型来实现。

normalizeArgs 之前名为 __unstableNormalizeArgs,旧名称仍然有效。

示例

以下选择器的第3个参数本应是一个 Number:

const getItemsSelector = ( state, name, type, id ) => {
    return state.items[ name ][ type ][ id ] || null;
};

然而,id 参数有可能作为 String 传递。在这种情况下,可以在选择器上定义 normalizeArgs 方法(属性),以便即使提供的类型“不正确”,也能将参数强制转换为目标类型:

// 定义规范化方法。
getItemsSelector.normalizeArgs = ( args ) => {
    const newArgs = [ ...args ];
    // "id" 参数位于索引 2
    if ( newArgs[ 2 ] && typeof newArgs[ 2 ] === 'string' ) {
        newArgs[ 2 ] = Number( newArgs[ 2 ] );
    }

    return newArgs;
};

有了这个设置,以下代码的行为将保持一致:

const getItemsSelector = ( state, name, type, id ) => {
    // 这里 'id' 现在保证是数字。
    return state.items[ name ][ type ][ id ] || null;
};

const getItemsResolver = ( name, type, id ) => {
    // 在解析器中,'id' 也保证是数字。
    return {};
};

registry.registerStore( 'store', {
    // ...
    selectors: {
        getItems: getItemsSelector,
    },
    resolvers: {
        getItems: getItemsResolver,
    },
} );

// 使用正确的数字类型调用。
registry.select( 'store' ).getItems( 'foo', 'bar', 54 );

// 使用错误的字符串类型调用,**但是**在这里我们避免了
// 不必要的解析器调用,因为 '54' 已通过 `normalizeArgs` 方法
// 保证被强制转换为数字。
registry.select( 'store' ).getItems( 'foo', 'bar', '54' );

确保给定选择器调用的参数一致性是一项重要的优化,有助于提高数据层的性能。但是,通常可以通过确保选择器不为参数使用可变类型来避免此类问题。

在选择器调用之间共享解析结果

normalizeArgs 同时更改选择器和解析器的参数,因此它只能对选择器仍需要的值进行强制转换。有时,选择器需要一个解析器不使用的参数,而每次仅在该参数上不同的调用都应共享一个解析结果。

在解析器上定义 getResolutionArgs 以说明解析结果由哪些参数键控。它在 normalizeArgs 之后运行。其结果是解析缓存键,也是 fulfill、isFulfilled 和 shouldInvalidate 接收的参数。选择器保留自己的参数。

例如,canUser( action, resource ) 一次回答一个操作,但单个请求返回资源上所有操作的权限。将 action 排除在解析参数之外,可以使针对同一资源的所有调用共享一个请求:

const canUserResolver =
    ( resource ) =>
    async ( { dispatch } ) => {
        // 一次性获取所有操作的权限。
        dispatch.receivePermissions( await fetchPermissions( resource ) );
    };

canUserResolver.getResolutionArgs = ( action, resource ) => [ resource ];

registry.registerStore( 'store', {
    // ...
    selectors: {
        canUser: ( state, action, resource ) =>
            state.permissions[ resource ]?.[ action ],
    },
    resolvers: {
        canUser: canUserResolver,
    },
} );

// 一次请求,两个调用都等待它完成。
await registry.resolveSelect( 'store' ).canUser( 'create', 'media' );
await registry.resolveSelect( 'store' ).canUser( 'read', 'media' );

解析元数据也由这些参数键控,因此调用 startResolution、finishResolution(s) 或 invalidateResolution 的代码必须传递它们。在上面这种情况下,意味着 dispatch.finishResolution( 'canUser', [ 'media' ] ),而不是 [ 'create', 'media' ]。

进一步阅读

为此包做出贡献

这是 Gutenberg 项目的一部分,是一个独立的包。该项目组织为一个 monorepo。它由多个自包含的软件包组成,每个包都有特定的用途。此 monorepo 中的包发布到 npm,并被 WordPress 以及其他软件项目使用。

要了解有关为此包或整个 Gutenberg 做出贡献的更多信息,请阅读项目的主要 贡献者指南。