使用 withDispatch、withSelect 和 compose 管理应用状态 101
与编辑器相关的 JavaScript 代码中充满了对 withDispatch、withSelect 和 compose 的引用。本文简要解释了这些方法为何重要、它们的用途以及如何使用它们。
withDispatch、withSelect 和 compose 是 Gutenberg 中经常一起使用的三个方法,用于管理应用状态。它们是 Gutenberg 组件和区块从组件外部读取和修改编辑器数据的方式。
你应该知道,useSelect 和 useDispatch 钩子是管理应用状态的更现代方式,但 with* 方法在 Gutenberg 和自定义编辑器代码中广泛存在,并且可能还会持续一段时间,因此能够读懂它们是有价值的。use* 钩子的主要优势是它们使代码更具可读性。从 with* 方法切换到现代的 use* 钩子相对简单。
withDispatch
你向 withDispatch 传递一个接收 dispatch 方法的函数。该函数的目的是返回一个供组件使用的 dispatch(数据修改)方法列表。
withDispatch( dispatch => {
return { replaceBlock, selectBlock } = dispatch( 'core/block-editor' );
} )
在上面的示例中,它从 core/block-editor 数据存储中返回 replaceBlock 和 selectBlock 方法。它们将作为 prop 传递给你的组件,因此你可以在需要更改状态时使用它们。
返回的方法只是方法,它们不需要是现有的方法,也可以是现有方法的包装器,如下面这个来自 Jetpack 循环付款区块 的示例所示。
withDispatch( ( dispatch, props ) => ( {
/**
* Updates the plan on the Recurring Payments block acting as a subscribe button.
* @param {number} planId - Plan ID.
*/
setSubscribeButtonPlan( planId ) {
dispatch( 'core/block-editor' ).updateBlockAttributes(
props.subscribeButton.clientId,
{ planId }
);
},
} ) ),
在浏览器开发者控制台中,可以直接使用 wp.data.dispatch( store_name ) 进行调试。
withSelect
你向 withSelect 传递一个接收 select 方法的函数。该函数的目的是返回组件所需的选择(数据查询)值列表。
withSelect( ( select ) => ( {
postId: select( 'core/editor' ).getCurrentPostId(),
} ) );
在上面的示例中,它返回当前正在编辑的文章的 postId,这是从 core/editor 存储中获取的。
postId 将作为 prop 传递给组件。然后,当选择器返回的值发生变化时,组件内 prop 的值会自动更改。
在浏览器开发者控制台中,可以直接使用 wp.data.select( store_name ) 进行调试。
compose
compose 接收一个方法数组,它从右到左依次运行这些方法。每个方法的结果都传递给下一个方法。下面两个 JavaScript 代码片段是相同的。
compose( [ methodOne, methodTwo, methodThree ] ) ()
methodOne( methodTwo( methodThree() ) )
withSelect 和 withDispatch 通常在 compose 方法中这样调用:
// Where your_component_class_or_function is a standard class/function receiving props
export default compose ( [
withSelect( the_with_select_method ),
withDispatch( the_withDispatch_method )
] ) ( your_component_class_or_fn )
然后,你传递的组件将拥有传递给 withSelect 和 withDispatch 的方法返回的值,这些值作为 props 可用。
比较 use 和 with 方法
use* 钩子是实现相同功能的更现代方式,通常比 with* 方法更受青睐,因为它们更简洁且更易于阅读。之所以更易于阅读,是因为 use* 将值的定义放在使用值的地方附近,而 with* 将值的使用放在组件内部,而值的定义放在文件底部、组件外部。
此外,完全不再需要 compose 方法。值和方法通过钩子的使用在内部可用,而不是通过 compose 作为 props 传递。
下面是一个简短的虚构示例,用于说明两种方法之间的差异。
with*
function MyAwesomeComponent( {
// The values from the with* methods are passed as props
clientId,
defaultVariation,
hasInnerBlocks,
replaceBlock,
selectBlock
} ) {
// A made-up example using the props.
const replaceEmptyBlock = () => {
if ( ! hasInnerBlocks && ‘flibble/baz’ === defaultVariation ) {
replaceBlock( clientId, ‘flibble/block’ );
selectBlock( clientId );
}
}
}
export default compose ( [
withSelect( (select, props ) => {
const { getDefaultBlockVariation } = select( blocksStore );
const { getBlocks } = select( blockEditorStore );
return {
defaultVariation: getDefaultBlockVariation( props.name );
hasInnerBlocks: getBlocks( props.clientId )?.length > 0,
};
} ),
withDispatch( ( select ) => {
const { replaceBlock, selectBlock } = dispatch( blockEditorStore );
return { replaceBlock, selectBlock }
} )
] )( MyAwesomeComponent );
use*
export function MyAwesomeComponent( { name, clientId } ) {
// The values from useSelect are fetched and are immediately available rather than being passed as props
const { defaultVariation, hasInnerBlocks } = useSelect( select => {
const { getDefaultBlockVariation } = select( blocksStore );
const { getBlocks } = select( blockEditorStore );
return {
defaultVariation: getDefaultBlockVariation( name ),
hasInnerBlocks: getBlocks( clientId )?.length > 0,
};
}, [ name, clientId ] );
const { replaceBlock, selectBlock } = useDispatch( blockEditorStore );
// A made-up example using the props.
const replaceEmptyBlock = () => {
if ( ! hasInnerBlocks && ‘flibble/baz’ === defaultVariation ) {
replaceBlock( clientId, ‘flibble/quux’ );
selectBlock( clientId );
}
}
}
你可能注意到的另一个区别是,[ name, clientId ] 作为第二个参数传递给了 useSelect。这些变量来自 useSelect 外部,但在第一个参数内部使用,将它们作为第二个参数传递意味着 useSelect 将使用它们来记忆化(缓存)第一个参数。
延伸阅读
- 核心数据存储列表
- 使用 withSelect 方法的真实世界代码示例
- useDispatch 源代码及示例
- useSelect 源代码及示例
- 使用 use* 方法的真实世界代码示例