メインコンテンツにスキップ

マッチングユーティリティ

Redux Toolkitは、特定の種類のアクションをチェックする際に活用できる、型安全なアクションマッチングユーティリティをいくつかエクスポートしています。これらは、主に`createSlice`と`createReducer`の`builder.addMatcher()`ケース、およびカスタムミドルウェアを記述する際に役立ちます。

汎用

  • `isAllOf` - **すべて**の条件が満たされた場合にtrueを返します
  • `isAnyOf` - 条件の**少なくとも1つ**が満たされた場合にtrueを返します

`createAsyncThunk`固田のmatcher

これらのmatcherはすべて、1つ以上のthunkを引数として呼び出すことができます。その場合、それらの条件とthunkに対するmatcher関数を返します。または、1つのアクションで呼び出すことができ、その場合、それらは条件付きのthunkアクションに一致します。

  • `isAsyncThunkAction` - 1つ以上のアクションクリエーターを受け入れ、すべて一致する場合にtrueを返します
  • `isPending` - 1つ以上のアクションクリエーターを受け入れ、すべて一致する場合にtrueを返します
  • `isFulfilled` - 1つ以上のアクションクリエーターを受け入れ、すべて一致する場合にtrueを返します
  • `isRejected` - 1つ以上のアクションクリエーターを受け入れ、すべて一致する場合にtrueを返します
  • `isRejectedWithValue` - 1つ以上のアクションクリエーターを受け入れ、すべて一致する場合にtrueを返します

`isAllOf`

以下の1つ以上を受け入れる高階関数です。

  • `redux-toolkit` アクションクリエーター関数 ( `createAction` によって生成される関数など)
  • タイプガード関数
  • タイプガードである `.match` プロパティを持つカスタムアクションクリエーター関数

提供された関数の*すべて*が一致する場合に `true` を返すタイプガード関数を返します。

`isAnyOf`

`isAllOf` と同じ入力を受け入れ、提供された関数の少なくとも1つが一致する場合に `true` を返すタイプガード関数を返します。

`isAsyncThunkAction`

アクションが `createAsyncThunk` によって作成されたかどうかをチェックするために使用できるタイプガード関数を返す高階関数です。

isAsyncThunkAction の使用
import { isAsyncThunkAction } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'

const isARequestAction = isAsyncThunkAction(requestThunk1, requestThunk2)

function handleRequestAction(action: UnknownAction) {
if (isARequestAction(action)) {
// action is an action dispatched by either `requestThunk1` or `requestThunk2`
}
}

`isPending`

`createAsyncThunk` プロミスライフサイクルからのアクションが「pending」アクションクリエーターであるかどうかをチェックするために使用できるタイプガード関数を返す高階関数です。

isPending の使用
import { isPending } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'

const isAPendingAction = isPending(requestThunk1, requestThunk2)

function handlePendingAction(action: UnknownAction) {
if (isAPendingAction(action)) {
// action is a pending action dispatched by either `requestThunk1` or `requestThunk2`
}
}

`isFulfilled`

`createAsyncThunk` プロミスライフサイクルからのアクションが「fulfilled」アクションクリエーターであるかどうかをチェックするために使用できるタイプガード関数を返す高階関数です。

isFulfilled の使用
import { isFulfilled } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'

const isAFulfilledAction = isFulfilled(requestThunk1, requestThunk2)

function handleFulfilledAction(action: UnknownAction) {
if (isAFulfilledAction(action)) {
// action is a fulfilled action dispatched by either `requestThunk1` or `requestThunk2`
}
}

`isRejected`

`createAsyncThunk` プロミスライフサイクルからのアクションが「rejected」アクションクリエーターであるかどうかをチェックするために使用できるタイプガード関数を返す高階関数です。

isRejected の使用
import { isRejected } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'

const isARejectedAction = isRejected(requestThunk1, requestThunk2)

function handleRejectedAction(action: UnknownAction) {
if (isARejectedAction(action)) {
// action is a rejected action dispatched by either `requestThunk1` or `requestThunk2`
}
}

`isRejectedWithValue`

`rejectWithValue` によって作成された `createAsyncThunk` プロミスライフサイクルからのアクションが「rejected」アクションクリエーターであるかどうかをチェックするために使用できるタイプガード関数を返す高階関数です。

isRejectedWithValue の使用
import { isRejectedWithValue } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'

const isARejectedWithValueAction = isRejectedWithValue(
requestThunk1,
requestThunk2
)

function handleRejectedWithValueAction(action: UnknownAction) {
if (isARejectedWithValueAction(action)) {
// action is a rejected action dispatched by either `requestThunk1` or `requestThunk2`
// where rejectWithValue was used
}
}

Matcherを使用してコードの複雑さ、重複、定型句を削減する

`builder` パターンを使用してreducerを構築する場合、ケースまたはmatcherを一度に1つずつ追加します。ただし、`isAnyOf` または `isAllOf` を使用することで、型安全な方法で複数のケースに同じmatcherを簡単に使用できます。

まず、不必要に複雑な例を見てみましょう。

Matcherユーティリティを使用しない例
import { createAsyncThunk, createReducer } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

interface Data {
isInteresting: boolean
isSpecial: boolean
}

interface Special extends Data {
isSpecial: true
}

interface Interesting extends Data {
isInteresting: true
}

function isSpecial(
action: PayloadAction<Data>
): action is PayloadAction<Special> {
return action.payload.isSpecial
}

function isInteresting(
action: PayloadAction<Data>
): action is PayloadAction<Interesting> {
return action.payload.isInteresting
}

interface ExampleState {
isSpecial: boolean
isInteresting: boolean
}

const initialState = {
isSpecial: false,
isInteresting: false,
} satisfies ExampleState as ExampleState

export const isSpecialAndInterestingThunk = createAsyncThunk(
'isSpecialAndInterestingThunk',
() => {
return {
isSpecial: true,
isInteresting: true,
}
}
)

// This has unnecessary complexity
const loadingReducer = createReducer(initialState, (builder) => {
builder.addCase(isSpecialAndInterestingThunk.fulfilled, (state, action) => {
if (isSpecial(action)) {
state.isSpecial = true
}
if (isInteresting(action)) {
state.isInteresting = true
}
})
})

このシナリオでは、`isAllOf` を使用してコードを簡略化し、定型句の一部を削減できます。

isAllOf を使用したリファクタリング
import { createReducer, isAllOf } from '@reduxjs/toolkit'
import {
isSpecialAndInterestingThunk,
initialState,
isSpecial,
isInteresting,
} from '@virtual/matchers' // This is a fake pkg that provides the types shown above
import type { Data } from '@virtual/matchers' // This is a fake pkg that provides the types shown above

const loadingReducer = createReducer(initialState, (builder) => {
builder
.addMatcher(
isAllOf(isSpecialAndInterestingThunk.fulfilled, isSpecial),
(state, action) => {
state.isSpecial = true
}
)
.addMatcher(
isAllOf(isSpecialAndInterestingThunk.fulfilled, isInteresting),
(state, action) => {
state.isInteresting = true
}
)
})

MatcherをTypeScriptタイプガードとして使用する

`isAllOf` と `isAnyOf` によって返される関数は、他のコンテキストでTypeScriptタイプガードとしても使用できます。

isAllOf をタイプガードとして使用する
import { isAllOf } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { isSpecial, isInteresting } from '@virtual/matchers' // This is a fake pkg that provides the types shown above
import type { Data } from '@virtual/matchers' // This is a fake pkg that provides the types shown above

const isSpecialAndInteresting = isAllOf(isSpecial, isInteresting)

function someFunction(action: PayloadAction<Data>) {
if (isSpecialAndInteresting(action)) {
// "action" will be correctly typed as:
// `PayloadAction<Special> & PayloadAction<Interesting>`
}
}
isAnyOf をタイプガードとして使用する
import { isAnyOf } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { Data, isSpecial, isInteresting } from '@virtual/matchers' // this is a fake pkg that provides the types shown above

const isSpecialOrInteresting = isAnyOf(isSpecial, isInteresting)

function someFunction(action: PayloadAction<Data>) {
if (isSpecialOrInteresting(action)) {
// "action" will be correctly typed as:
// `PayloadAction<Special> | PayloadAction<Interesting>`
}
}