[英]Is it possible to have a Generic for each Mapped Type in Typescript
I have a function that takes multiple reducers and applies them all to a data structure.我有一个函数,它需要多个减速器并将它们全部应用于数据结构。 For example, given two people person1
and person2
it can use this function to normalise their data:例如,给定两个人person1
和person2
,它可以使用这个函数来规范化他们的数据:
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
},
},
})
Which will return something like:这将返回如下内容:
{
byId: {
1: {
id: 1,
name: "John",
},
2: {
id: 2,
name: "Jane",
},
},
list: [
{
id: 1,
name: "John",
},
{
id: 2,
name: "Jane",
},
],
}
I am having trouble implementing these types in Typescript because I want each property that I pass in to use the type of initialValue
as the type for the accumulator acc
in the reduce callback.我在 Typescript 中实现这些类型时遇到问题,因为我希望传入的每个属性都使用initialValue
的类型作为 reduce 回调中累加器acc
的类型。
Is it possible to have this sort of inferred generic type on a mapped type?是否可以在映射类型上使用这种推断的泛型类型?
Here is a link to the full code in a runnable example 这是可运行示例中完整代码的链接
Reproducible example可重现的例子
// 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
},
},
})
Let's start with the function declaration.让我们从函数声明开始。 I think you can remove the ReducerKeys
generic type.我认为您可以删除ReducerKeys
泛型类型。 We can instead rename InitialValue
to InitialValues
to indicate that we will store an object type inside there with each key being a specific InitialValue
.我们可以改为将InitialValue
重命名为InitialValues
,以表明我们将在其中存储一个对象类型,每个键都是一个特定的InitialValue
。
export function normalizeData<Model, InitialValues>(
data: Model[],
reducers: {
[K in keyof InitialValues]: {
reduce?: ReduceCallback<Model, InitialValues[K]>
initialValue: InitialValues[K]
}
},
) { /* ... */ }
Instead of mapping over ReducerKeys
, we map over InitialValues
and store the type of initialValue
inside InitialValues[K]
.我们不是在ReducerKeys
上映射,而是在InitialValues
上映射并将initialValue
的类型存储在InitialValues[K]
中。
This already fixed the typing when calling the function.这已经修复了调用函数时的输入。
We now get an error inside the function implementation because RecucerKeys
does not exist anymore.我们现在在函数实现中得到一个错误,因为RecucerKeys
不再存在。 Let's just replace it with InitialValues
too.让我们也用InitialValues
替换它。
const initialValues = reducerKeys.reduce(
(obj, key) => ({
...obj,
[key]: reducers[key].initialValue,
}),
{} as { [K in keyof InitialValues]: InitialValues[K] },
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.