Gutenberg区块:如何利用`useSelect`和`useDispatch`与数据存储交互?

Gutenberg 区块:利用 useSelectuseDispatch 与数据存储交互

各位同学,今天我们来深入探讨 Gutenberg 区块开发中,如何利用 useSelectuseDispatch 这两个 React Hook 与 WordPress 数据存储进行交互。这两个 Hook 是 @wordpress/data 包提供的核心工具,允许我们在区块组件中轻松地读取和修改数据。

WordPress 数据存储体系概览

在深入 useSelectuseDispatch 之前,我们需要对 WordPress 数据存储体系有一个基本的了解。WordPress 使用基于 Redux 的数据存储来管理各种数据,例如:

  • Posts: 文章、页面、自定义文章类型
  • Taxonomies: 分类目录、标签、自定义分类法
  • Settings: 站点设置、主题选项
  • Users: 用户信息
  • Editor: 编辑器状态、区块属性

每个数据类型都对应一个或多个 store,例如 core/editor store 管理编辑器状态,core/block-editor store 管理区块编辑器相关的数据。每个 store 都包含:

  • State: 存储数据的 JavaScript 对象。
  • Selectors: 函数,用于从 state 中提取数据。
  • Actions: 函数,用于触发 state 的更新。
  • Reducers: 函数,接收 action 并更新 state。
  • Controls: 允许 action 发出副作用的中间件,例如 API 请求。
  • Resolvers: 用于异步获取数据并将其添加到 state 的函数。

我们可以通过 @wordpress/data 包提供的 selectdispatch 函数访问这些 store 的 selectors 和 actions。useSelectuseDispatch Hook 则是对这些函数的 React 友好的封装,允许我们在函数组件中更方便地使用它们。

useSelect: 从数据存储中读取数据

useSelect Hook 允许我们从数据存储中选择(select)特定的数据,并在数据发生变化时自动更新组件。它的基本语法如下:

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

function MyBlockComponent() {
  const myData = useSelect( ( select ) => {
    // 从数据存储中选择数据的逻辑
    return select( 'my/store' ).getMyData();
  }, [ /* 依赖项数组 */ ] );

  // 使用 myData

  return (
    // JSX
  );
}

参数:

  • selector: 一个函数,接收一个 select 对象作为参数。select 对象包含访问所有注册 store 的 selector 函数的方法。 Selector 函数必须是纯函数,不能有副作用。
  • dependencies: 一个可选的依赖项数组。只有当依赖项发生变化时,useSelect 才会重新执行 selector 函数并更新组件。如果省略依赖项数组,selector 函数将在每次渲染时执行。

返回值:

  • selector 函数的返回值。当 selector 函数返回的数据发生变化时,组件将重新渲染。

示例:

假设我们有一个名为 my/store 的自定义 store,它包含一个名为 getMyData 的 selector,用于获取存储在 state 中的数据。以下是如何使用 useSelect 从该 store 中读取数据:

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

function MyBlockComponent() {
  const myData = useSelect( ( select ) => {
    return select( 'my/store' ).getMyData();
  }, [] ); // 空数组表示只在组件挂载时执行一次

  return (
    <div>
      <p>My Data: {myData}</p>
    </div>
  );
}

在这个例子中,useSelect Hook 会调用 select( 'my/store' ).getMyData() 函数,并将返回的值赋给 myData 变量。如果 my/store 的 state 发生变化,并且 getMyData selector 返回的值与之前的不同,MyBlockComponent 组件将重新渲染,显示新的数据。

优化性能:

为了优化性能,我们应该尽量使用依赖项数组来控制 useSelect Hook 的执行频率。只有当依赖项发生变化时,才重新执行 selector 函数。例如,如果我们只需要在 my/storemyData 属性发生变化时更新组件,我们可以将 myData 作为依赖项:

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

function MyBlockComponent() {
  const myData = useSelect( ( select ) => {
    return select( 'my/store' ).getMyData();
  }, [ select( 'my/store' ).getMyData() ] );

  return (
    <div>
      <p>My Data: {myData}</p>
    </div>
  );
}

然而,直接使用 select( 'my/store' ).getMyData() 作为依赖项通常不是最佳实践,因为它会导致每次渲染都执行 selector 函数,即使数据没有变化。更好的做法是将 selector 函数的结果赋值给一个变量,然后在依赖项数组中使用该变量:

import { useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';

function MyBlockComponent() {
  const myData = useSelect( ( select ) => {
    return select( 'my/store' ).getMyData();
  }, [] );

  const [previousData, setPreviousData] = useState(myData);

  if (myData !== previousData) {
    setPreviousData(myData);
  }

  return (
    <div>
      <p>My Data: {previousData}</p>
    </div>
  );
}

或者,也可以使用 useMemo 来缓存 selector 函数的结果:

import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';

function MyBlockComponent() {
  const myData = useSelect( ( select ) => {
    return select( 'my/store' ).getMyData();
  }, [] );

  const memoizedData = useMemo(() => myData, [myData]);

  return (
    <div>
      <p>My Data: {memoizedData}</p>
    </div>
  );
}

从核心 store 中读取数据:

useSelect 也可以用来从 WordPress 核心 store 中读取数据。例如,我们可以使用 useSelectcore/editor store 中获取当前的文章标题:

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

function MyBlockComponent() {
  const title = useSelect( ( select ) => {
    return select( 'core/editor' ).getEditedPostAttribute( 'title' );
  }, [] );

  return (
    <div>
      <p>Current Title: {title}</p>
    </div>
  );
}

在这个例子中,我们使用了 core/editor store 的 getEditedPostAttribute selector 来获取当前文章的 title 属性。

useDispatch: 触发数据存储的更新

useDispatch Hook 允许我们获取一个 dispatch 函数,用于触发数据存储的 action,从而更新 state。它的基本语法如下:

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

function MyBlockComponent() {
  const { myAction } = useDispatch( 'my/store' );

  const handleClick = () => {
    myAction( 'some data' );
  };

  return (
    <button onClick={handleClick}>
      Update Data
    </button>
  );
}

参数:

  • storeName: 要获取 dispatch 函数的 store 的名称。

返回值:

  • 一个对象,包含指定 store 的所有 action creator 函数。

示例:

假设我们有一个名为 my/store 的自定义 store,它包含一个名为 setMyData 的 action,用于更新存储在 state 中的数据。以下是如何使用 useDispatch 触发该 action:

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

function MyBlockComponent() {
  const { setMyData } = useDispatch( 'my/store' );

  const handleClick = () => {
    setMyData( 'new data' );
  };

  return (
    <button onClick={handleClick}>
      Update Data
    </button>
  );
}

在这个例子中,useDispatch( 'my/store' ) 返回一个包含 setMyData action creator 函数的对象。当我们点击按钮时,handleClick 函数会被调用,它会调用 setMyData( 'new data' ) 来触发 setMyData action,从而更新 my/store 的 state。

从核心 store 中触发 action:

useDispatch 也可以用来从 WordPress 核心 store 中触发 action。例如,我们可以使用 useDispatchcore/editor store 中更新当前的文章标题:

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

function MyBlockComponent() {
  const { editPost } = useDispatch( 'core/editor' );

  const handleClick = () => {
    editPost( { title: 'New Title' } );
  };

  return (
    <button onClick={handleClick}>
      Update Title
    </button>
  );
}

在这个例子中,我们使用了 core/editor store 的 editPost action 来更新当前文章的 title 属性。

使用 useSelectuseDispatch 的组合:

useSelectuseDispatch 通常一起使用,以实现组件的双向数据绑定。例如,我们可以使用 useSelect 从 store 中读取数据,并使用 useDispatch 更新 store 中的数据。以下是一个完整的例子:

import { useSelect, useDispatch } from '@wordpress/data';

function MyBlockComponent() {
  const myData = useSelect( ( select ) => {
    return select( 'my/store' ).getMyData();
  }, [] );

  const { setMyData } = useDispatch( 'my/store' );

  const handleChange = ( event ) => {
    setMyData( event.target.value );
  };

  return (
    <div>
      <label htmlFor="my-data">My Data:</label>
      <input
        type="text"
        id="my-data"
        value={myData}
        onChange={handleChange}
      />
    </div>
  );
}

在这个例子中,我们使用 useSelectmy/store 中读取 myData,并将其显示在 input 框中。当 input 框的值发生变化时,handleChange 函数会被调用,它会调用 setMyData action 来更新 my/store 中的 myData

最佳实践和注意事项

  • 理解数据存储结构: 在使用 useSelectuseDispatch 之前,请务必了解要交互的 store 的数据结构、selectors 和 actions。
  • 避免过度渲染: 使用依赖项数组来控制 useSelect Hook 的执行频率,避免不必要的渲染。可以使用 useMemo 来缓存 selector 函数的结果。
  • 使用纯函数: Selector 函数必须是纯函数,不能有副作用。
  • 正确处理异步操作: 如果 action 触发了异步操作,例如 API 请求,请确保正确处理 loading 状态和错误处理。
  • 测试: 对使用 useSelectuseDispatch 的组件进行单元测试,以确保数据的正确读取和更新。
  • 避免直接修改 state: 始终通过 actions 来更新 state。直接修改 state 可能会导致不可预测的行为。
  • 利用 resolvers 异步加载数据: 对于初始化或者需要异步加载的数据,使用resolvers可以有效的管理数据加载过程,并且可以利用hasFinishedResolution判断数据是否加载完成,避免不必要的渲染。

示例代码:创建一个简单的计数器区块

以下是一个完整的示例代码,演示如何创建一个简单的计数器区块,使用 useSelectuseDispatch 来读取和更新计数器的值:

// index.js (主插件文件)
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, Button } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { createReduxStore, register } from '@wordpress/data';

// 创建一个简单的 Redux Store
const COUNTER_STORE_NAME = 'my-plugin/counter';

const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

const counterStore = createReduxStore(COUNTER_STORE_NAME, {
  reducer: counterReducer,
});

register(counterStore);

registerBlockType('my-plugin/counter-block', {
  title: 'Counter Block',
  icon: 'smiley',
  category: 'common',
  attributes: {
    // 可以添加一些区块级别的配置,例如是否显示计数器
    showCounter: {
      type: 'boolean',
      default: true,
    },
  },
  edit: ({ attributes, setAttributes }) => {
    const { showCounter } = attributes;

    const count = useSelect((select) => select(COUNTER_STORE_NAME).getState().count, []);
    const { dispatch } = useDispatch(COUNTER_STORE_NAME);

    const increment = () => {
      dispatch({ type: 'INCREMENT' });
    };

    const decrement = () => {
      dispatch({ type: 'DECREMENT' });
    };

    const blockProps = useBlockProps();

    return (
      <div {...blockProps}>
        <InspectorControls>
          <PanelBody title="Counter Settings">
            <Button isPrimary onClick={() => setAttributes({ showCounter: !showCounter })}>
              {showCounter ? 'Hide Counter' : 'Show Counter'}
            </Button>
          </PanelBody>
        </InspectorControls>
        {showCounter && (
          <>
            <p>Count: {count}</p>
            <Button onClick={increment}>Increment</Button>
            <Button onClick={decrement}>Decrement</Button>
          </>
        )}
      </div>
    );
  },
  save: ({ attributes }) => {
    const { showCounter } = attributes;
    const blockProps = useBlockProps.save();

    return (
      <div {...blockProps}>
        {showCounter && <p>Counter Block Content (Dynamic)</p>}
      </div>
    );
  },
});

步骤解释:

  1. 创建 Redux Store: 创建了一个名为 my-plugin/counter 的 Redux store,包含一个 reducer 用于处理 INCREMENTDECREMENT action。
  2. 注册 Store: 使用 register 函数将 store 注册到 WordPress 数据存储系统中。
  3. 注册 Block Type: 使用 registerBlockType 函数注册一个名为 my-plugin/counter-block 的 Gutenberg 区块。
  4. 使用 useSelect:edit 函数中使用 useSelect Hook 从 store 中读取 count 的值。
  5. 使用 useDispatch:edit 函数中使用 useDispatch Hook 获取 dispatch 函数,用于触发 INCREMENTDECREMENT action。
  6. Inspector Controls: 添加了 Inspector Controls,允许用户控制是否显示计数器。
  7. 显示计数器和按钮: 如果 showCounter 属性为 true,则显示计数器的值和递增/递减按钮。
  8. Save 函数: save 函数返回区块的静态 HTML,该 HTML 将在前端显示。

完整表格总结:

Hook/Function 描述 参数 返回值 用途
useSelect 从 WordPress 数据存储中选择数据,并在数据发生变化时自动更新组件。 selector:一个函数,接收一个 select 对象作为参数,用于从数据存储中选择数据。 dependencies:一个可选的依赖项数组,只有当依赖项发生变化时,useSelect 才会重新执行 selector 函数并更新组件。 selector 函数的返回值。 用于在区块组件中读取 WordPress 数据存储中的数据,例如文章标题、分类目录、标签、站点设置等。可以订阅数据变化,当数据发生变化时自动更新组件。可以用于实现区块的动态渲染,例如根据文章标题动态显示区块内容。
useDispatch 获取一个 dispatch 函数,用于触发数据存储的 action,从而更新 state。 storeName:要获取 dispatch 函数的 store 的名称。 一个对象,包含指定 store 的所有 action creator 函数。 用于在区块组件中触发 WordPress 数据存储的 action,例如更新文章标题、分类目录、标签、站点设置等。可以用于实现区块的交互功能,例如点击按钮更新数据。可以用于实现区块的异步操作,例如发送 API 请求更新数据。
select 用于从数据存储中选择数据的函数。 storeName:要选择数据的 store 的名称。 一个对象,包含指定 store 的所有 selector 函数。 useSelect Hook 的 selector 函数中使用,用于从数据存储中选择数据。
dispatch 用于触发数据存储的 action 的函数。 action:要触发的 action 对象。 无返回值。 useDispatch Hook 返回的 action creator 函数中使用,用于触发数据存储的 action。

结论

useSelectuseDispatch 是 Gutenberg 区块开发中不可或缺的工具。它们允许我们轻松地与 WordPress 数据存储进行交互,实现区块的动态渲染和交互功能。通过理解这两个 Hook 的基本原理和最佳实践,我们可以开发出更强大、更灵活的 Gutenberg 区块。 熟练掌握这两个Hook, 可以让你以更高效和可维护的方式来开发 Gutenberg 区块,与WordPress的数据存储进行交互,实现更加复杂和动态的功能。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注