如何在typesafe-actions的createAsyncAction中收窄TypeScript Action类型
我来帮你搞定这个TypeScript类型收窄的问题,同时帮你砍掉那些繁琐的样板代码,让代码更简洁易维护。
第一步:简化AuthActions类型定义,告别重复代码
你之前手动联合AuthRequestActions/AuthSuccessActions/AuthErrorActions的方式太繁琐了,完全可以利用typesafe-actions的ActionType工具自动生成完整的Action联合类型。
修改你的auth.actions.ts:
import { ActionType, createAsyncAction } from 'typesafe-actions'; import { getAsyncFetchActionConstants, getAsyncCreateActionConstants, getAsyncDeleteActionConstants, } from '../utils/asyncActions.utils'; const BASE_ACTION = 'AUTH'; // 定义所有异步Action export const fetchAuth = createAsyncAction( ...getAsyncFetchActionConstants(BASE_ACTION))<void, AuthState, Error>(); export const createAuth = createAsyncAction( ...getAsyncCreateActionConstants(BASE_ACTION))<void, AuthState, Error>(); export const deleteAuth = createAsyncAction( ...getAsyncDeleteActionConstants(BASE_ACTION))<void, AuthState, Error>(); // 把所有Action打包成一个对象 const authActions = { fetchAuth, createAuth, deleteAuth, }; // 自动生成包含所有request/success/failure状态的Action联合类型 export type AuthActions = ActionType<typeof authActions>;
这样一来,你再也不用手动编写那一堆重复的类型联合了,ActionType会自动识别每个异步Action的三个状态,并把它们全部合并到AuthActions类型里。
第二步:修复Reducer的类型收窄问题
现在回到你的Reducer里,之前的类型错误是因为TS无法正确识别当前case对应的Action类型。现在用简化后的AuthActions类型,配合getType就能完美解决类型收窄的问题:
修改你的auth.reducer.ts:
import { getType } from 'typesafe-actions'; import { AUTH_INITIAL_STATE } from './auth.constants'; import { fetchAuth, createAuth, deleteAuth, AuthActions } from './auth.actions'; export function authReducer(state: AuthState = AUTH_INITIAL_STATE, action: AuthActions) { switch (action.type) { case getType(fetchAuth.success): // 现在TS会自动把action收窄为SuccessAction类型,payload可以直接访问 return { ...state, ...action.payload }; case getType(createAuth.success): return { ...state, ...action.payload }; case getType(deleteAuth.success): return AUTH_INITIAL_STATE; // 可以轻松添加request和failure的处理逻辑,类型同样会自动收窄 case getType(fetchAuth.request): return { ...state, loading: true, error: null }; case getType(fetchAuth.failure): return { ...state, loading: false, error: action.payload }; default: return state; } }
为什么这样能解决问题?
ActionType<typeof authActions>生成的联合类型包含了每个异步Action三个状态的完整类型信息,当你用getType(fetchAuth.success)匹配action.type时,TS会精准识别当前action的类型,自动排除掉没有payload的request状态Action,所以再也不会出现Property 'payload' does not exist的错误。
额外优化:统一异步常量生成逻辑(可选)
如果你的getAsyncFetchActionConstants这类函数逻辑很相似,还可以进一步简化常量生成的代码,比如写一个通用的CRUD常量生成函数:
// asyncActions.utils.ts const ASYNC_REQUEST = 'REQUEST'; const ASYNC_SUCCESS = 'SUCCESS'; const ASYNC_FAILURE = 'FAILURE'; export const getCrudAsyncConstants = (baseAction: string) => ({ fetch: [`${baseAction}_FETCH_${ASYNC_REQUEST}`, `${baseAction}_FETCH_${ASYNC_SUCCESS}`, `${baseAction}_FETCH_${ASYNC_FAILURE}`], create: [`${baseAction}_CREATE_${ASYNC_REQUEST}`, `${baseAction}_CREATE_${ASYNC_SUCCESS}`, `${baseAction}_CREATE_${ASYNC_FAILURE}`], delete: [`${baseAction}_DELETE_${ASYNC_REQUEST}`, `${baseAction}_DELETE_${ASYNC_SUCCESS}`, `${baseAction}_DELETE_${ASYNC_FAILURE}`], }); // 在auth.actions.ts中使用 const authConstants = getCrudAsyncConstants(BASE_ACTION); export const fetchAuth = createAsyncAction(...authConstants.fetch)<void, AuthState, Error>();
这样可以进一步减少重复的常量拼接代码,让项目结构更整洁。
内容的提问来源于stack exchange,提问作者Danziger

