如何模块化自定义React状态容器Hook并保留严格TypeScript类型约束
解决方案
核心实现思路
我们采用工厂函数替代你构思的类式API,更适配TypeScript的类型推导逻辑,不需要用户手动声明任何额外类型,所有约束和原有实现完全一致,同时彻底消除样板代码重复问题。
通用包实现代码(@company/react-state-container)
import React, { useReducer, useContext, ReactNode, createContext } from "react"; import _cloneDeep from "lodash/cloneDeep"; // 内部工具类型,推导动作参数类型 type ActionParams<H> = H extends (state: any, ...args: infer P) => any ? P : never; export function createStateContainer<S extends Record<string, any>, H extends Record<string, (state: S, ...args: any[]) => Partial<S> | void>>( hookName: string, config: { initialState: S; actionHandlers: H; } ) { const { initialState, actionHandlers } = config; // 推导动作类型 type Actions = { [K in keyof H]: ActionParams<H[K]> extends [infer D] ? D : null; }; type Dispatch = <T extends keyof H>( type: T, ...data: ActionParams<H[T]> ) => void; type ActionsMap = { [K in keyof Actions]: Actions[K] extends null ? { type: K, data?: undefined } : { type: K, data: Actions[K] } }; type Action = ActionsMap[keyof Actions]; const Context = createContext<{ state: S; dispatch: Dispatch; } | undefined>(undefined); function reducer(state: S, action: Action): S { const stateClone = _cloneDeep(state); // @ts-ignore 内部逻辑已保证类型安全 const newState = actionHandlers[action.type](stateClone, action.data as any); if (!newState) return state; return { ...stateClone, ...newState }; } function Provider({ children, defaultState }: { children: ReactNode, defaultState?: S }) { const [state, reducerDispatch] = useReducer(reducer, defaultState ?? initialState); const dispatch: Dispatch = (type, ...data) => { // @ts-ignore reducerDispatch({ type, data: data[0] } as Action); }; return ( <Context.Provider value={{ state, dispatch }}> {children} </Context.Provider> ); } function useStateContainer() { const context = useContext(Context); if (context === undefined) { throw new Error(`use${hookName} must be used within a ${hookName}Provider`); } return context; } return { Provider, useStateContainer }; }
业务侧使用方式
import { createStateContainer } from "@company/react-state-container"; const { Provider: MyStateProvider, useStateContainer: useMyState } = createStateContainer("MyState", { initialState: { nums: [] as number[] }, actionHandlers: { RESET_NUMS: () => ({ nums: [] }), ADD_NUM: ({ nums }, num: number) => { nums.push(num); return { nums }; }, SET_NUMS: ({}, nums: number[]) => ({ nums }) } }); export { MyStateProvider, useMyState };
特性匹配说明
所有原有类型约束完全保留:
initialState类型自动推导,不需要手动声明State类型dispatch调用严格校验:仅支持传入已定义的动作名,参数数量、类型完全匹配动作处理函数的定义,无参数的动作不允许传参- 动作处理函数的入参类型自动推导,状态参数类型和
initialState完全一致,动作参数类型由用户在处理函数中定义一次即可全局生效 - 无重复样板代码:后续需要调整底层实现(比如替换深拷贝实现、调整错误提示逻辑)仅需修改通用包,所有业务侧的状态容器自动同步更新
如果你坚持要用之前构思的链式调用API,也可以通过TS的方法泛型链式推导实现,不过工厂函数的方式代码更简洁,类型推导逻辑更稳定,没有边界case问题。
内容的提问来源于stack exchange,提问作者ChevCast
相关产品推荐
相关产品推荐

