是否可以为Typescript中的每个映射类型都有一个泛型?



我有一个函数,它接受多个reducer并将它们全部应用于一个数据结构。例如,给定两个人person1person2,它可以使用这个函数来规范化他们的数据:

normalizeData([person1, person2], {
byId: {
initialValue: {} as { [id: string]: Person },
reduce: (acc, model) => {
acc[model["id"]] = model
return acc
},
},
list: {
initialValue: [] as Person[],
reduce: (acc, model) => {
acc.push(model)
return acc
},
},
})

返回如下内容:

{
byId: {
1: {
id: 1,
name: "John",
},
2: {
id: 2,
name: "Jane",
},
},
list: [
{
id: 1,
name: "John",
},
{
id: 2,
name: "Jane",
},
],
}

我在Typescript中实现这些类型时遇到了麻烦,因为我希望我传入的每个属性都使用initialValue的类型作为reduce回调中累加器acc的类型。

是否可能在映射类型上有这种推断的泛型类型?

这是一个链接到完整的代码在一个可运行的例子

<<p>可再生的例子/strong>
// Same type as Array.reduce callback
type ReduceCallback<Value, Output> = (
previousValue: Output,
currentValue: Value,
currentIndex: number,
array: Value[],
) => Output
// Type for sample data
type Person = {
id: string
name: string
parentId?: string
age: number
}
// Function to run multiple reducers over an array of data
// This is the function I want to type properly
export function normalizeData<Model, ReducerKeys extends string, InitialValue>(
data: Model[],
reducers: {
[key in ReducerKeys]: {
reduce?: ReduceCallback<Model, InitialValue>
initialValue: InitialValue
}
},
) {
// Get keys of reducers to split them into two data structures, 
// one for initial values and the other for reduce callbacks
const reducerKeys = Object.keys(reducers) as Array<keyof typeof reducers>
// Get an object of { id: <initialValue> }
// In this case `{ byId: {}, list, [] }`
const initialValues = reducerKeys.reduce(
(obj, key) => ({
...obj,
[key]: reducers[key].initialValue,
}),
{} as { [key in ReducerKeys]: InitialValue },
)
// Get an array of reduce callbacks
const reduceCallbacks = reducerKeys.map((key) => ({ key, callback: reducers[key].reduce }))
// Reduce over the data, applying each reduceCallback to each datum
const normalizedData = data.reduce((acc, datum, index, array) => {
return reduceCallbacks.reduce((acc, { key, callback }) => {
const callbackWithDefault = callback || ((id) => id)
return {
...acc,
[key]: callbackWithDefault(acc[key], datum, index, array),
}
}, acc)
}, initialValues)
return normalizedData
}
// Sample data
const parent: Person = {
id: "001",
name: "Dad",
parentId: undefined,
age: 53,
}
const son: Person = {
id: "002",
name: "Son",
parentId: "001",
age: 12,
}
// This is the test implementation.
// The types do not accept differing generic types of initialValue for each mapped type
// Whatever is listed first sets the InitialValue generic
// I want to be able to have the intialValue type for each mapped type 
// apply that same type to the `acc` value of the reduce callback.
normalizeData([parent, son], {
byId: {
initialValue: {} as {[key: string]: Person},
reduce: (acc, person) => {
acc[person.id] = person
return acc
},
},
list: {
initialValue: [] as Person[],
reduce: (acc, person) => {
acc.push(person)
return acc
},
},
})

让我们从函数声明开始。我认为你可以删除ReducerKeys泛型。我们可以将InitialValue重命名为InitialValues,以表明我们将在其中存储一个对象类型,每个键都是特定的InitialValue

export function normalizeData<Model, InitialValues>(
data: Model[],
reducers: {
[K in keyof InitialValues]: {
reduce?: ReduceCallback<Model, InitialValues[K]>
initialValue: InitialValues[K]
}
},
) { /* ... */ }

我们不是映射到ReducerKeys,而是映射到InitialValues,并将initialValue的类型存储在InitialValues[K]中。

这已经修复了调用函数时的输入问题。

我们现在在函数实现中得到一个错误,因为RecucerKeys不再存在了。让我们把它也换成InitialValues

const initialValues = reducerKeys.reduce(
(obj, key) => ({
...obj,
[key]: reducers[key].initialValue,
}),
{} as { [K in keyof InitialValues]: InitialValues[K] },
)

游乐场

最新更新