225 lines
7.4 KiB
Markdown
225 lines
7.4 KiB
Markdown
## 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 的支持。 |