Gutenberg 11.6 增加了对 thunks 的支持。你可以将 thunk 理解为可以分发的函数:
// actions.js
export const myThunkAction = () => ( { select, dispatch } ) => {
return "I'm a thunk! I can be dispatched, use selectors, and even dispatch other actions.";
};
为什么 thunk 很有用?
Thunks 扩展了 Redux action 的含义。在引入 thunk 之前,actions 是纯函数,只能返回和产出数据。像与 store 交互或从 action 请求 API 数据这样的常见用例需要使用单独的 control。你经常会看到如下代码:
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: // ...,
};
像 store 操作和 fetch 函数这样的副作用会在 action 之外实现。Thunks 提供了一种替代方法,允许你在内部使用副作用,如下所示:
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 可以访问 store helper
让我们看看 Gutenberg core 中的一个示例。在引入 thunk 之前,@wordpress/interface 包中的 toggleFeature action 是这样实现的:
export function* toggleFeature( scope, featureName ) {
const currentValue = yield controls.select(
interfaceStoreName,
'isFeatureActive',
scope,
featureName
);
yield controls.dispatch(
interfaceStoreName,
'setFeatureValue',
scope,
featureName,
! currentValue
);
}
Controls 是 dispatch actions 和从 store select 数据的唯一方式。
有了 thunk,有一种更清晰的方法。这就是现在 toggleFeature 的实现方式:
export function toggleFeature( scope, featureName ) {
return function ( { select, dispatch } ) {
const currentValue = select.isFeatureActive( scope, featureName );
dispatch.setFeatureValue( scope, featureName, ! currentValue );
};
}
多亏了 select 和 dispatch 参数,thunks 可以直接使用 store,而无需生成器和 controls。
Thunk 可以是异步的
想象一个简单的 React 应用程序,允许你设置恒温器的温度。它只有一个输入和一个按钮。点击该按钮会分发一个 saveTemperatureToAPI action,其中包含来自输入的数值。
如果我们使用 controls 来保存温度,store 定义将如下所示:
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 action 不直接与 API 通信,而是必须通过 FETCH_JSON control。
让我们看看如何使用 thunk 消除这种间接性:
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:
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 );
}
},
// ...
} );
对 thunk 的支持默认包含在每个 data store 中,就像(现在是遗留的)对
生成器和 controls 的支持一样。
Thunk API
Thunk 接收一个单个对象参数,具有以下键:
select
一个包含 store 的选择器对象的对象,这些选择器已预先绑定到 state,这意味着你不需要提供 state,只需提供额外的参数。select 会触发相关的 resolvers(如果有),但不会等待它们完成。即使值为 null,它也会返回当前值。
如果选择器是公共 API 的一部分,则作为 select 对象上的方法可用:
const thunk = () => ( { select } ) => {
// select 是 store 的选择器对象,已预先绑定到当前 state:
const temperature = select.getTemperature();
}
由于并非所有选择器都暴露在 store 上,select 也充当支持将选择器作为参数传递的函数:
const thunk = () => ( { select } ) => {
// select 支持私有选择器:
const doubleTemperature = select( ( temperature ) => temperature * 2 );
}
resolveSelect
resolveSelect 与 select 相同,但它返回一个 promise,该 promise 通过相关的 resolver 提供的值解析。
const thunk = () => ( { resolveSelect } ) => {
const temperature = await resolveSelect.getTemperature();
}
dispatch
一个包含 store actions 的对象
如果 action 是公共 API 的一部分,则作为 dispatch 对象上的方法可用:
const thunk = () => ( { dispatch } ) => {
// dispatch 是 store actions 的对象:
const temperature = await dispatch.retrieveTemperature();
}
由于并非所有 action 都暴露在 store 上,dispatch 也充当支持将 Redux action 作为参数传递的函数:
const thunk = () => async ( { dispatch } ) => {
// dispatch 也是一个接受内联 action 的函数:
dispatch({ type: 'SET_TEMPERATURE', temperature: result.value });
// thunks 可以与 actions 互换使用
dispatch( updateTemperature( 100 ) );
// Thunks 也可以是异步的。如果是这样,dispatch 返回一个 promise
await dispatch( ( ) => window.fetch( /* ... */ ) );
}
registry
Registry 通过其 dispatch、select 和 resolveSelect 方法提供对其他 store 的访问。
这些与上面描述的那些非常相似,只是略有不同。调用 registry.select( storeName ) 返回一个函数,该函数返回来自 storeName 的选择器对象。当你需要与其他 store 交互时这很有用。例如:
const thunk = () => ( { registry } ) => {
const error = registry.select( 'core' ).getLastEntitySaveError( 'root', 'menu', menuId );
/* ... */
}