【发布时间】:2019-12-21 08:08:18
【问题描述】:
我想创建一个生成强类型对象“工厂”的函数。这些对象有一个名为“tag”的属性,即string,但我想将此字符串设为string literal。
这是实现某种“AbstractFactory”模式所必需的,换句话说,是一组创建所需对象的对象的函数,如下所示:
//Over Simplified Version
type Factory = {
a: (name: string) => { tag: name };
b: (name: string) => { tag: name };
};
type Names = {
a: "A";
b: "B";
};
type Result = {
a: { tag: "A" };
b: { tag: "B" };
};
function(factory): (names) => result
问题是这个“标签”对象有额外的属性,我需要这个字符串字面量才能正确使用它们。
我对这个概念做了各种实验,得到的是这样的:
// Implementation
type Tag<T extends string> = { tag: T };
type Factory<
TKey extends string,
TName extends string,
TTag extends Tag<TName>
> = {
[key in TKey]: (name: TName) => TTag;
};
type Names<TFactory extends Factory<any, any, any>, TName extends string> = {
[key in keyof TFactory]: TName;
};
type Result<
TFactory extends Factory<any, any, any>,
TNames extends Names<TFactory, any>
> = {
/**
* I am almost sure that the problem lies here, maybe because I am
* not passing the required generic, I just can't figure it out.
*/
[key in keyof TNames]: ReturnType<TFactory[key]>;
};
function implementation<
TKey extends string,
TName extends string,
TTag extends Tag<TName>,
TFactory extends Factory<TKey, TName, TTag>,
TNames extends Names<TFactory, TName>,
TResult extends Result<TFactory, TNames>
>(factory: TFactory): (names: TNames) => TResult {
return (names) => {
const keys = Object.keys(factory) as Extract<keyof TFactory, string>[];
return keys.reduce((result, key) => {
const name: TName = names[key];
(result as Record<string, TTag>)[key] = factory[key](name);
return result;
}, {}) as TResult;
};
}
const factory = implementation({
a: (name: string) => ({ tag: name, index: 2 }),
b: (name: string) => ({ tag: name, name: "Bob" }),
});
const result = factory({ a: "A", b: "B" } as const/* names */);
result.a.index; // the index type got inferred properly
result.b.name; // the name type got inferred properly
result.a.tag; // the tag type got widened to string, I want to be string literal "A"
result.b.tag; // the tag type got widened to string, I want to be string literal "B"
我认为我有点过度使用泛型。我很确定有一种更简单的方法。
TL;DR:我想防止将字符串文字扩大到 string,因此我的 tag 属性成为我提供的文字。
【问题讨论】:
标签: typescript types type-inference