我正在创建一个函数,该函数接受config(结构非常可变的文字对象),然后根据配置类型输出同样可变的结构。我想尽可能地使用TypeScript推断来检测配置的类型,但是我还需要该函数的用户能够提示的某些部分。
这可以满足我的需求
type Type<T> = (_: T) => T;
const type =<T>(): Type<T> => T => T;
type ListConfig<T> = {
itemType: Type<T>;
}
type Config = {
[name: string]: ListConfig<any>;
}
type ItemType<L> = L extends ListConfig<infer T> ? T : never;
const createLists = <C extends Config>(
cfg: C
): { [K in keyof C]: Array<ItemType<C[K]>> } => {
return {} as any;
}
const config = {
numbers: {
itemType: type<number>(),
},
strings: {
itemType: type<string>(),
}
};
// This is correctly inferred as { numbers: number[], strings: string[] }
const lists = createLists(config);
请注意itemType
,它们的存在只是为了暗示createLists
的返回类型。上面的方法有效,但也让人觉得很棘手-我正在创建函数只是为了让TypeScript了解所需的类型。
最干净的解决方案是什么?
我一直希望按照以下方式寻求解决方案
type ListConfig<T> = {
}
type Config = {
[name: string]: ListConfig<any>;
}
type ItemType<L> = L extends ListConfig<infer T> ? T : never;
const createLists = <C extends Config>(
cfg: C
): { [K in keyof C]: Array<ItemType<C[K]>> } => {
return {} as any;
}
const listOf = <T, C extends ListConfig<T>>(c: C): C => c;
const config = {
numbers: listOf<number>({ // <-- error here
somOtherPartOfTheListConfig: "foo",
}),
strings: listOf<string>({}), // <-- error here
};
// This is correctly inferred as { numbers: number[], strings: string[] }
const lists = createLists(config);
但是,我仍然希望通过打字稿(项目类型除外)推断出config[K]
的类型,因此,为了实现这一点,我需要partial type argument inferrence。
编辑:我正在做的是一个简化Redux存储/动作创建的库。所以基本上我想提供State<T>
和createRedux
允许我这样做:
const myConfig = {
$combine: {
listOfNumbers: {
$preset: 'list' as 'list',
$item: {
$type: {
$itemType: type<string>(),
}
},
}
}
};
type MyState = State<typeof myConfig>;
// MyState == { listOfNumbers: string[] }
const { Actions } = createRedux(myConfig);
// typeof Actions == {
// listOfNumbers: {
// push: (item: string) => ReduxAction
// }
// }
答案 0 :(得分:1)
我不知道您真正想如何在这里进行,因为从根本上讲,在运行时没有任何东西在乎您使用的类型,对吗?如果您有初始数组内容,或者是type guard function或其他一些运行时工件,那么我会提出一些具体建议。
一种可能的前进方式是表示类型名称到类型的映射,并要求配置使用以下名称:
interface TypeNames {
string: string,
number: number,
boolean: boolean,
arrayOfStrings: string[],
// etc
}
type ListConfig<K extends keyof TypeNames> = {
itemType: K
}
type Config = {
[name: string]: ListConfig<keyof TypeNames>;
}
const createLists = <C extends Config>(
cfg: C
): { [K in keyof C]: Array<TypeNames[C[K]['itemType']]> } => {
return {} as any;
}
// This is correctly inferred as { numbers: number[], strings: string[] }
const lists = createLists({
numbers: { itemType: "number", otherStuff: "foo" },
strings: { itemType: "string", otherStuff: 1234 }
});
这可以尽其所能,但是需要一个固定的(可能是可扩展的)类型列表。
另一种方法是要求调用者createLists()
为type参数指定对象类型,其键与配置键相匹配,并且值是相关的列表类型:
const createLists = <T>() => <C extends Record<keyof T, any>>(
cfg: C
): { [K in keyof C]: K extends keyof T ? Array<T[K]> : any } => {
return {} as any;
}
// This is correctly inferred as { numbers: number[], strings: string[] }
const lists = createLists<{ numbers: number, strings: string }>()({
numbers: { otherStuff: "foo" },
strings: { otherStuff: 1234 }
});
(请注意,curried函数在没有部分类型推断的情况下。)这有点 ,因为您需要指定numbers
和{{1} }键两次,但对我来说却干净很多。
无论如何,希望那些能给您一些想法。祝你好运!