[TS] TS with Redux 預備知識與實作
預備知識
我們會需要透過下面三個 TypeScript 用法,來幫助我們確定我們傳入的action type是我們需要的,再進行該action type的操作`,所以我們要先認識
- Type Predicate Functions
- Intersection Type
- ReturnType
Type Predicate Functions (規定回傳值的類別的函式)
在撰寫 TypeScripts 時,我們可以透過type predicate functions來確認說一個函式的回傳值是不是我們想要的,通常可以用在redux的action上面
以下舉個簡單的例子
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 (&)
下方我們定義Hybrid為Human和Alien的交集,必須有name、fly的屬性
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 去執行特定的操作。