TypeScript中能否为映射类型每个属性实现独立泛型推断
normalizeData函数独立reducer类型推断实现方案
问题原因
原有类型定义为所有reducer配置项共用同一个InitialValue泛型,TypeScript会以首个配置项的initialValue类型为统一基准,要求其余配置项的累加器类型完全匹配,导致不同reducer需要独立累加器类型时触发类型报错。
实现思路
不需要为每个属性单独声明泛型参数,利用TypeScript对泛型对象的逐属性推断能力即可实现需求:
- 将reducers配置整体作为一个泛型参数约束,让TS自动保留每个配置项的独立类型
- 通过映射类型遍历配置对象,将每个配置项的
initialValue类型绑定到同项reduce回调的acc参数类型 - 同样通过映射类型提取最终返回值的结构类型,保证返回值类型和运行结果一致
修正后完整代码
// 数组reduce回调基础类型 type ReduceCallback<Value, Output> = ( previousValue: Output, currentValue: Value, currentIndex: number, array: Value[], ) => Output // 示例数据类型 type Person = { id: string name: string parentId?: string age: number } // 修正类型后的normalizeData函数 export function normalizeData< Model, Reducers extends Record<string, { initialValue: any; reduce?: Function }> >( data: Model[], reducers: { [K in keyof Reducers]: { initialValue: Reducers[K]['initialValue'] // 每个reducer的acc类型自动绑定同项initialValue类型 reduce?: ReduceCallback<Model, Reducers[K]['initialValue']> } } ): { [K in keyof Reducers]: Reducers[K]['initialValue'] } { const reducerKeys = Object.keys(reducers) as Array<keyof Reducers> // 初始化返回值 const initialValues = reducerKeys.reduce( (obj, key) => ({ ...obj, [key]: reducers[key].initialValue, }), {} as { [K in keyof Reducers]: Reducers[K]['initialValue'] }, ) const reduceCallbacks = reducerKeys.map((key) => ({ key, callback: reducers[key].reduce })) // 遍历数据执行所有reducer const normalizedData = data.reduce((acc, datum, index, array) => { return reduceCallbacks.reduce((acc, { key, callback }) => { const callbackWithDefault = callback || ((id: any) => id) return { ...acc, [key]: callbackWithDefault(acc[key], datum, index, array), } }, acc) }, initialValues) return normalizedData } // 测试用例 类型完全匹配 const parent: Person = { id: "001", name: "Dad", parentId: undefined, age: 53, } const son: Person = { id: "002", name: "Son", parentId: "001", age: 12, } const result = normalizeData([parent, son], { byId: { initialValue: {} as { [key: string]: Person }, // acc自动推断为{[key: string]: Person}类型 reduce: (acc, person) => { acc[person.id] = person return acc }, }, list: { initialValue: [] as Person[], // acc自动推断为Person[]类型 reduce: (acc, person) => { acc.push(person) return acc }, }, }) // result自动推断类型为: // { // byId: { [key: string]: Person }, // list: Person[] // }
类型效果
- 每个reducer配置项的
reduce回调acc参数会自动匹配同项initialValue的类型,无需手动标注 - 函数返回值自动推断为和reducer配置对应的结构化类型,无需额外声明
- 支持任意数量、任意累加器类型的reducer配置项,不会出现类型互相干扰的问题
内容的提问来源于stack exchange,提问作者valtism
相关产品推荐
相关产品推荐

