我有一个嵌套的选项对象,该对象被某些代码修改并将某些属性上移一个级别。
在这种情况下,我正在努力获得适当的打字稿支持。
function createStore(options) {
// do stuff
}
// given
const options = {
state: {},
modules: {
buildings: {
state: {},
modules: {
school: {
state: {}
},
cafe: {
state: {}
}
}
}
}
};
// type definitions that aren't working
type ObjectInfer<T> = T extends { modules: infer A } ? A : T;
type Store<T> = { [P in keyof T]: ObjectInfer<T[P]> };
// process the options object recursively and move the module definitions up one level
const store = (createStore(options) as unknown) as Store<typeof options>;
console.log(store);
// output:
/*
{
state: {},
buildings: {
state: {},
school: {
state: {}
},
cafe: {
state: {}
}
}
}
*/
我希望商店能够如上所示正确键入,但是类型转换不能按预期工作,并且仍然具有与options对象相同的结构。
答案 0 :(得分:1)
好的,原来的Store<T>
的一个问题是,像{[K in keyof T]: XXX}
这样的映射类型将具有与T
相同的键。因此,如果T
具有modules
属性,那么Store<T>
也是如此。另一个问题是它不是递归的... ObjectInfer<T>
提取modules
的{{1}}属性(如果有的话),但不会归结到这些属性中进行相同的操作。
实际的实现将必须是递归的,并且实际上从其输出中省略了T
键。这是一种可行的方法:
modules
在解释这一点之前,让我解释一个等效的类型,但是产生的输出类型更难看:
type Store<T> = T extends { modules: infer A }
? (StoreMap<Omit<T, "modules"> & A> extends infer S
? { [K in keyof S]: S[K] }
: never)
: T;
type StoreMap<T> = { [K in keyof T]: Store<T[K]> };
在这里,您看到type StoreUgly<T> = T extends { modules: infer A }
? StoreUglyMap<Omit<T, "modules"> & A>
: T;
type StoreUglyMap<T> = { [K in keyof T]: StoreUgly<T[K]> };
所做的是检查StoreUgly
是否具有T
属性。如果不是,则返回modules
。否则,它将评估T
的价值,其中Omit<T, "modules"> & A
是A
属性。 modules
在标准库中是defined的映射类型,可从Omit<T, K>
的已知键中删除K
。因此T
是Omit<{modules: 1, nodules: 2}, "modules">
。 {nodules: 2}
的交集是您尝试做的基本“将Omit<T, "modules"> & A
属性向上拉一层”。
但是我们当然需要递归地执行它,所以我们在它上执行modules
,它只是将StoreUglyMap<>
映射到每个属性。
皱纹是输出是绝对可怕的……如果您执行StoreUgly
,您会感到一团糟。因此是StoreUgly<typeof options>
版本。如果对象类型很丑Store
,有时可以通过编写O
来清理它。这只是将O extends infer X ? {[K in keyof X]: X[K]} : never
的所有属性映射到它们自己,但效果是更急切地评估属性,而不是将它们留为相交束。
让我们确保输出类型是您想要的:
O
对我很好!希望能有所帮助。祝你好运!