なぜ thunk は便利なのか?
Thunks Redux アクションの意味を拡張します。thunks が登場する前は、アクションは純粋に関数的で、データを返したり yield したりすることしかできませんでした。ストアとの相互作用やアクションから 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: // ...,
};
ストア操作やフェッチ関数のような副作用は、アクションの外部で実装されていました。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;
}
thunks はストアヘルパーにアクセスできます
Gutenberg コアの例を見てみましょう。thunks が登場する前は、toggleFeature
アクションは @wordpress/interface
パッケージから次のように実装されていました:
export function* toggleFeature( scope, featureName ) {
const currentValue = yield controls.select(
interfaceStoreName,
'isFeatureActive',
scope,
featureName
);
yield controls.dispatch(
interfaceStoreName,
'setFeatureValue',
scope,
featureName,
! currentValue
);
}
コントロールは、ストアからアクションと select
データを dispatch
する唯一の方法でした。
thunks を使用すると、よりクリーンな方法があります。現在の toggleFeature
の実装は次のようになります:
export function toggleFeature( scope, featureName ) {
return function ( { select, dispatch } ) {
const currentValue = select.isFeatureActive( scope, featureName );
dispatch.setFeatureValue( scope, featureName, ! currentValue );
};
}
select
と dispatch
引数のおかげで、thunks はジェネレーターやコントロールを必要とせずにストアを直接使用できます。
thunks は非同期であることができます
サーモスタットの温度を設定できるシンプルな React アプリを想像してみてください。入力フィールドが 1 つとボタンが 1 つだけあります。ボタンをクリックすると、入力からの値を持つ saveTemperatureToAPI
アクションがディスパッチされます。
コントロールを使用して温度を保存する場合、ストアの定義は次のようになります:
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
コントロールを通過する必要があります。
この間接性を thunks でどのように取り除けるか見てみましょう:
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, ...
} );
それはとてもクールです!さらに良いのは、リゾルバーもサポートされていることです:
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 );
}
},
// ...
} );
thunks のサポートは、(現在はレガシーの)ジェネレーターやコントロールのサポートと同様に、すべてのデータストアにデフォルトで含まれています。
thunks API
thunk は、次のキーを持つ単一のオブジェクト引数を受け取ります:
select
ストアのセレクターが状態に事前バインドされたオブジェクトで、状態を提供する必要はなく、追加の引数のみを提供すればよいことを意味します。select
は、関連するリゾルバーをトリガーしますが、完了を待ちません。現在の値を返すだけで、null であっても構いません。
セレクターが公開 API の一部である場合、select オブジェクトのメソッドとして利用可能です:
const thunk = () => ( { select } ) => {
// select is an object of the store’s selectors, pre-bound to current state:
const temperature = select.getTemperature();
}
すべてのセレクターがストアに公開されているわけではないため、select
は引数としてセレクターを渡すことをサポートする関数としても機能します:
const thunk = () => ( { select } ) => {
// select supports private selectors:
const doubleTemperature = select( ( temperature ) => temperature * 2 );
}
resolveSelect
resolveSelect
は select
と同じですが、関連するリゾルバーによって提供された値で解決されるプロミスを返します。
const thunk = () => ( { resolveSelect } ) => {
const temperature = await resolveSelect.getTemperature();
}
dispatch
ストアのアクションを含むオブジェクト
アクションが公開 API の一部である場合、dispatch
オブジェクトのメソッドとして利用可能です:
const thunk = () => ( { dispatch } ) => {
// dispatch is an object of the store’s actions:
const temperature = await dispatch.retrieveTemperature();
}
すべてのアクションがストアに公開されているわけではないため、dispatch
は引数として Redux アクションを渡すことをサポートする関数としても機能します:
const thunk = () => async ( { dispatch } ) => {
// dispatch is also a function accepting inline actions:
dispatch({ type: 'SET_TEMPERATURE', temperature: result.value });
// thunks are interchangeable with actions
dispatch( updateTemperature( 100 ) );
// Thunks may be async, too. When they are, dispatch returns a promise
await dispatch( ( ) => window.fetch( /* ... */ ) );
}
registry
レジストリは、dispatch
、select
、および resolveSelect
メソッドを通じて他のストアへのアクセスを提供します。
これらは上記のものと非常に似ていますが、少し違いがあります。registry.select( storeName )
を呼び出すと、storeName
からセレクターのオブジェクトを返す関数が返されます。これは、別のストアと対話する必要があるときに便利です。例えば:
const thunk = () => ( { registry } ) => {
const error = registry.select( 'core' ).getLastEntitySaveError( 'root', 'menu', menuId );
/* ... */
}