社区新闻

使用 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 数据存储中返回 replaceBlockselectBlock 方法。它们将作为 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 将使用它们来记忆化(缓存)第一个参数。

延伸阅读