Skip to main content

[TS] TS with Redux 預備知識與實作

預備知識

我們會需要透過下面三個 TypeScript 用法,來幫助我們確定我們傳入的action type是我們需要的,再進行該action type的操作`,所以我們要先認識

  1. Type Predicate Functions
  2. Intersection Type
  3. ReturnType

Type Predicate Functions (規定回傳值的類別的函式)

在撰寫 TypeScripts 時,我們可以透過type predicate functions來確認說一個函式的回傳值是不是我們想要的,通常可以用在reduxaction上面

以下舉個簡單的例子

type Alien = {
fly: () => {};
};

type human = {
speak: () => {};
};

// isHuman 傳入 entity,entity可以是Human | Alien類別
function isHuman(entity: Human | Alien): entity is Human {
// 預設 entity 是 Human 類別,當 speak 不為 undefined 時,會回傳 true;反之,回傳false
return (entity as Human).speak !== undefined;
}

isHuman的回傳值(:entity is Human),指定為 Human 類別,因此我們可以確定這個函式包裹住的entity類別為 Human,又稱作narrowing down to Human type

Intersection Type (&)

下方我們定義HybridHumanAlien的交集,必須有namefly的屬性

type Human = {
name: string;
};

type Alien = {
fly: () => void;
};

type Hybrid = Human & Alien;

const Josh: Hybrid = {
// can have name & fly
name: "josh",
fly: () => {},
};

ReturnType (回傳值類別為前面定義的類別)

type Human = {
name: string;
}

type MyFunc = () => number

type MyReturn = ReturnType<MyFunc> // type MyReturn = {name: string;}
}

實作

透過這次commit 紀錄,可以更清楚的知道那些地方做了修改。

Matchable

reducer.utils.ts,我們建立一個Matchable來確認傳入的action type是不是符合我們要的action type

src/utils/reducer/reducer.utils.ts
import { AnyAction } from "redux";

/* AnyAction:
export interface AnyAction extends Action {
Allows any extra properties to be defined in an action.
[extraProps: string]: any
}
*/

type Matchable<AC extends () => AnyAction> = AC & {
type: ReturnType<AC>["type"];
match(action: AnyAction): action is ReturnType<AC>; // match function to check the AC match param AC
};

withMatcher

接著,建立withMatcher來幫助我們判斷action的類型

src/utils/reducer/reducer.utils.ts
// with payload
export function withMatcher<AC extends () => AnyAction & { type: string }>(
actionCreator: AC
): Matchable<AC>;

// overload with no payload
export function withMatcher<
AC extends (...args: any[]) => AnyAction & { type: string }
>(actionCreator: AC): Matchable<AC>;

export function withMatcher(actionCreator: Function) {
const type = actionCreator().type;
return Object.assign(actionCreator, {
type,
match(action: AnyAction) {
return action.type === type;
},
});
}

fetchCategories 函式們

透過前面定義的withMatcher包裹住三種 fetch 函式的參數,形成一個物件有match的方法可以判斷該action是不是符合我們指定的action

src/store/categories/category.action.ts
export const fetchCategoriesStart = withMatcher(() =>
createAction(CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_START)
);
export const fetchCategoriesSuccess = withMatcher(
(categoriesArray: Category[]): FetchCategoriesSuccess =>
createAction(
CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_SUCCESS,
categoriesArray
)
);
export const fetchCategoriesFailure = withMatcher(
(error: Error): FetchCategoriesFailure =>
createAction(CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_FAILED, error)
);

reducer 修改

src/store/categories/category.reducer.ts
export const categoriesReducer = (
state = CATEGORIES_INITIAL_STATE,
action = {} as AnyAction // 任意string object
): CategoriesState => {
if (fetchCategoriesStart.match(action)) {
return { ...state, isLoading: true };
if (fetchCategoriesSuccess.match(action)) {
return { ...state, categories: action.payload, isLoading: false };
}

if (fetchCategoriesFailure.match(action)) {
return { ...state, error: action.payload, isLoading: false };
}
return state;
};

結論

透過 TypeScript 更為深入的觀念,在 redux 裡面有許多 action,使用 type predicate function 可以確定是哪一種 action,我們再針對該 action 去執行特定的操作。

參考資料