gutenbergdocs/docs/how-to-guides/thunks.md
2025-10-22 01:40:18 +08:00

225 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Thunks API
thunk 函数接收一个包含以下键的单一对象参数:
### select
一个包含与状态预绑定的存储选择器的对象,这意味着您无需提供状态,只需提供额外参数。`select` 会触发相关的解析器(如果存在),但不会等待其完成。即使当前值为空,它也会直接返回当前值。
如果某个选择器是公共 API 的一部分,它将以方法形式存在于 select 对象上:
```js
const thunk = () => ( { select } ) => {
// select 是该存储选择器的对象,已预绑定到当前状态:
const temperature = select.getTemperature();
}
```
由于并非所有选择器都在存储中公开,`select` 同时支持以函数形式传递选择器作为参数:
```js
const thunk = () => ( { select } ) => {
// select 支持私有选择器:
const doubleTemperature = select( ( temperature ) => temperature * 2 );
}
```
### resolveSelect
`resolveSelect``select` 相同,但它返回一个 Promise该 Promise 会通过相关解析器提供的值进行解析。
```js
const thunk = () => ( { resolveSelect } ) => {
const temperature = await resolveSelect.getTemperature();
}
```
### dispatch
一个包含存储操作的对象
如果某个操作是公共 API 的一部分,它将以方法形式存在于 `dispatch` 对象上:
```js
const thunk = () => ( { dispatch } ) => {
// dispatch 是该存储操作的对象:
const temperature = await dispatch.retrieveTemperature();
}
```
由于并非所有操作都在存储中公开,`dispatch` 同时支持以函数形式传递 Redux 操作作为参数:
```js
const thunk = () => async ( { dispatch } ) => {
// dispatch 也是一个接受内联操作的函数:
dispatch({ type: 'SET_TEMPERATURE', temperature: result.value });
// thunk 可与操作互换使用
dispatch( updateTemperature( 100 ) );
// thunk 也可以是异步的。当它们是异步时dispatch 会返回一个 Promise
await dispatch( ( ) => window.fetch( /* ... */ ) );
}
```
### registry
注册表通过其 `dispatch`、`select` 和 `resolveSelect` 方法提供对其他存储的访问。
这些方法与上述方法非常相似,但略有不同。调用 `registry.select( storeName )` 会返回一个函数,该函数返回来自 `storeName` 的选择器对象。当您需要与另一个存储交互时,这会非常方便。例如:
```js
const thunk = () => ( { registry } ) => {
const error = registry.select( 'core' ).getLastEntitySaveError( 'root', 'menu', menuId );
/* ... */
}
```
# Core-Data 中的 Thunk 函数
[Gutenberg 11.6](https://github.com/WordPress/gutenberg/pull/27276) 新增了对 _thunk_ 的支持。您可以将 thunk 理解为可被调度的函数:
```js
// actions.js
export const myThunkAction = () => ( { select, dispatch } ) => {
return "我是一个 thunk我可以被调度使用选择器甚至调度其他动作。";
};
```
## Thunk 函数的优势何在?
Thunk [拓展了 Redux 动作的定义边界](https://jsnajdr.wordpress.com/2021/10/04/motivation-for-thunks/)。在 thunk 出现之前,动作是纯函数化的,只能返回和生成数据。常见的应用场景(例如在动作中与存储库交互或请求 API 数据)需要使用独立的 [control](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/#controls)。您经常会看到这样的代码:
```js
export function* saveRecordAction( id ) {
const record = yield controls.select( 'current-store', 'getRecord', id );
yield { type: 'BEFORE_SAVE', id, record };
const results = yield controls.fetch({ url: 'https://...', method: 'POST', data: record });
yield { type: 'AFTER_SAVE', id, results };
return results;
}
const controls = {
select: // ...,
fetch: // ...,
};
```
像存储库操作和 fetch 函数这样的副作用需要在动作之外实现。Thunk 为这种方法提供了替代方案,允许您直接内联处理副作用:
```js
export const saveRecordAction = ( id ) => async ({ select, dispatch }) => {
const record = select( 'current-store', 'getRecord', id );
dispatch({ type: 'BEFORE_SAVE', id, record });
const response = await fetch({ url: 'https://...', method: 'POST', data: record });
const results = await response.json();
dispatch({ type: 'AFTER_SAVE', id, results });
return results;
}
```
这样就无需再单独实现 controls。
### Thunk 可访问存储库辅助工具
让我们看一个 Gutenberg 核心代码中的例子。在 thunk 出现之前,`@wordpress/interface` 包中的 `toggleFeature` 动作是这样实现的:
```js
export function* toggleFeature( scope, featureName ) {
const currentValue = yield controls.select(
interfaceStoreName,
'isFeatureActive',
scope,
featureName
);
yield controls.dispatch(
interfaceStoreName,
'setFeatureValue',
scope,
featureName,
! currentValue
);
}
```
Controls 曾是唯一能调度动作和从存储库选择数据的方式。
通过 thunk现在有了更简洁的实现方式。这是 `toggleFeature` 当前的实现:
```js
export function toggleFeature( scope, featureName ) {
return function ( { select, dispatch } ) {
const currentValue = select.isFeatureActive( scope, featureName );
dispatch.setFeatureValue( scope, featureName, ! currentValue );
};
}
```
借助 `select``dispatch` 参数thunk 可以直接使用存储库,无需依赖生成器和 controls。
### Thunk 支持异步操作
假设有个简单的 React 应用,允许您设置恒温器温度。它只有一个输入框和一个按钮。点击按钮会调用携带输入值的 `saveTemperatureToAPI` 动作。
若使用 controls 保存温度,存储库定义如下所示:
```js
const store = wp.data.createReduxStore( 'my-store', {
actions: {
saveTemperatureToAPI: function*( temperature ) {
const result = yield { type: 'FETCH_JSON', url: 'https://...', method: 'POST', data: { temperature } };
return result;
}
},
controls: {
async FETCH_JSON( action ) {
const response = await window.fetch( action.url, {
method: action.method,
body: JSON.stringify( action.data ),
} );
return response.json();
}
},
// reducers, selectors, ...
} );
```
虽然代码逻辑清晰,但存在一层间接调用。`saveTemperatureToAPI` 动作并不直接与 API 通信,而是需要通过 `FETCH_JSON` control 中转。
让我们看看如何用 thunk 消除这层间接调用:
```js
const store = wp.data.createReduxStore( 'my-store', {
actions: {
saveTemperatureToAPI: ( temperature ) => async () => {
const response = await window.fetch( 'https://...', {
method: 'POST',
body: JSON.stringify( { temperature } ),
} );
return await response.json();
}
},
// reducers, selectors, ...
} );
```
这非常简洁更棒的是resolvers 也同样支持这种写法:
```js
const store = wp.data.createReduxStore( 'my-store', {
// ...
selectors: {
getTemperature: ( state ) => state.temperature
},
resolvers: {
getTemperature: () => async ( { dispatch } ) => {
const response = await window.fetch( 'https://...' );
const result = await response.json();
dispatch.receiveCurrentTemperature( result.temperature );
}
},
// ...
} );
```
与(现已过时的)生成器和 controls 支持一样,所有数据存储库默认都包含对 thunk 的支持。