映射类型与函数参数难题:Redux Reducer处理函数的类型问题
解决Redux Reducer Map的类型约束问题
我懂你碰到的这个痛点——当前的代码虽然能输出符合预期的类型,但拦不住非预期的输入(比如把'hello'这种字符串当成某个state字段对应的reducer传进去)。这本质是TypeScript泛型推断顺序的问题,而柯里化确实是搞定这类问题的绝佳方案,咱们一步步来解决:
先明确问题根源
你原来的代码大概是这样的(补全了截断的部分):
type Reducer<State> = (s: State, p: any) => State; const reducerMap = <State>(s: State) => <ScopedReducers extends { [k in keyof State]: Reducer<State[k]> }>(input: ScopedReducers) => { // 你的reducer合并逻辑 };
这里的问题在于:TypeScript会优先推断State类型,但它可能会为了满足ScopedReducers的约束,扭曲State的推断结果,或者干脆放松检查,导致不符合Reducer类型的输入混进来。
柯里化调整方案
我们把泛型参数拆分成两层柯里化,先锁定State类型,再约束传入的reducers,这样TypeScript的类型检查就会严格起来:
type Reducer<State> = (state: State, action: any) => State; // 外层函数先指定State类型,内层函数接收符合约束的reducers const createReducerMap = <State>() => { return <ScopedReducers extends Record<keyof State, Reducer<State[keyof State]>>>( reducers: ScopedReducers ) => { // 示例:实现一个合并scoped reducers的根reducer return (currentState: State, action: any): State => { const updatedState = { ...currentState }; Object.entries(reducers).forEach(([key, reducer]) => { updatedState[key as keyof State] = reducer(updatedState[key as keyof State], action); }); return updatedState; }; }; };
使用示例(严格约束生效)
// 先定义你的应用State类型 type AppState = { count: number; username: string; }; // 第一步:锁定State类型,此时后续的reducers会被严格校验 const buildAppReducers = createReducerMap<AppState>(); // ✅ 合法输入:每个字段都传入符合Reducer类型的函数 const validRootReducer = buildAppReducers({ count: (state, action) => state + (action.payload ?? 1), username: (state, action) => action.payload || state }); // ❌ 非法输入:TypeScript会直接报错,拦截非预期的输入 const invalidRootReducer = buildAppReducers({ count: (state, action) => state + 1, username: 'hello' // 报错:类型'string'不能赋值给类型'Reducer<string>' });
为什么柯里化能解决问题?
通过把State泛型放在外层函数,我们强制TypeScript先确定State的类型,之后内层函数的ScopedReducers泛型就会基于这个固定的State做严格约束,不会再为了匹配输入而调整State的推断。这样就能彻底阻止不符合要求的非预期输入,同时保证输出的根reducer类型完全符合预期。
内容的提问来源于stack exchange,提问作者wkrueger
相关产品推荐
相关产品推荐

