Javascript 如何告诉typescript{type:enum,[type:string]:value}的所有可能组合都在我的类型中?
我能想到的最简单的例子是:Javascript 如何告诉typescript{type:enum,[type:string]:value}的所有可能组合都在我的类型中?,javascript,typescript,Javascript,Typescript,我能想到的最简单的例子是: interface text { type: "text"; text: string; } interface integer { type: "integer"; integer: number; } type Config = text | integer; function appendToBody(config: Config) { if (!config[config.type]) return; document.body.ap
interface text {
type: "text";
text: string;
}
interface integer {
type: "integer";
integer: number;
}
type Config = text | integer;
function appendToBody(config: Config) {
if (!config[config.type]) return;
document.body.append(config[config.type]);
}
function createConfig(type: "text" | "integer", value: string | number) {
// how to let TS know that type and [type] will be matching?
// TS naturally assumes { type: 'text', text: 5 } is possible, even though it isn't
const config: Config = {
type,
[type]: value
};
appendToBody(config);
}
createConfig("text", "hello world");
基本上,我使用的模式是通过请求obj[obj.type]来提取值。
这对于我的实际情况很有用,因为我可以创建一个通用解析器,根据类型提取所需的值。它还有一个优点,即在类型更改时不必清空,因为它将保存在另一个[type]上,如果您重新更改,则不会丢失旧值
我只是不知道如何让typescript理解所有可能的类型和[type]的组合都包含在“Config”类型中。让您的
类型配置变得灵活,强制它接受任何格式为{type:string,[anything]:anything}
的类型
type Config = text | integer | {type: string, [key: string]: any};
首先,让我们明确一下,integer
应该有一个名为integer
的键,而不是number
,对吗?像这样:
interface text {
type: "text";
text: string;
}
interface integer {
type: "integer";
integer: number; // right?
}
type Config = text | integer;
function appendToBody(config: Config) {
if (!config[config.type]) return;
document.body.append(config[config.type]);
}
好的
createConfig()
存在两个类型安全问题。。。一个是为调用方强制执行类型安全,另一个是在实现中强制执行类型安全。现在,编译器在实现内部警告您,它无法验证{type:type,[type]:value}
是否为有效的配置。现在警告您这一点是正确的,因为呼叫者可以无误地执行以下操作:
createConfig("text", 5); // no error, oops!
对于调用方和实现方来说,没有简单的方法可以解决这个问题。每一方都有自己的问题
要为调用者修复它,您可以使用以下命令:
function createConfig(type: "text", value: string): void;
function createConfig(type: "integer", value: number): void;
function createConfig(type: string, value: any): void {
// impl
}
这很容易理解,但需要为Config
联合的每个组成部分添加重载。您也可以使用类型和函数,如下所示:
type Lookup<T, K> = K extends keyof T ? T[K] : never;
type ConfigFor<T extends Config['type']> = Extract<Config, { type: T }>;
function createConfig<T extends Config['type']>(
type: T,
value: Lookup<ConfigFor<T>, T>
) {
// impl
}
要为实现修复它(这是您的实际问题),编译器仍然无法确定config
是有效的config
,即使使用了固定的调用签名。对于重载,这是因为实现签名过于松散,无法表达约束,并且重载了实现。对于泛型条件类型,这是因为条件类型也依赖于它们中未解析的泛型类型参数。因此,在这两种情况下,编译器基本上放弃了在实现中强制执行相关数据类型的类型安全性。我经常想知道一些机制,它允许您通过控制流分析提示编译器遍历联合类型,但现在这只是一个幻想
那么,你能做什么?就我所知,在这方面真正只有两条路可走。您可以使用:保持代码不变,但只需告诉编译器您将负责确保类型安全,如:
const configAsserted = {
type,
[type]: value
} as any as Config;
appendToBody(configAsserted);
或者,您在运行时执行额外的手动检查,使编译器确信您所做的是安全的,如:
let configManual: Config;
if (type === "integer" && typeof value === "number") {
configManual = { type: "integer", integer: value };
} else if (type === "text" && typeof value === "string") {
configManual = { type: "text", text: value };
} else {
throw new Error("YOU MESSED UP");
}
appendToBody(configManual);
两种方法都有效。在Config
中添加成分时,断言不太安全,但可扩展性更好。手动检查是安全的,但它是多余的,每次向Config
添加组件时都必须添加代码
在我看来,这是你的选择。就个人而言,我会选择更好的缩放解决方案,如:
type Lookup<T, K> = K extends keyof T ? T[K] : never;
type ConfigFor<T extends Config['type']> = Extract<Config, { type: T }>;
function createConfig<T extends Config['type']>(
type: T,
value: Lookup<ConfigFor<T>, T>
) {
const config = {
type,
[type]: value
} as any as Config;
appendToBody(config);
}
type Lookup=K扩展了keyt?T[K]:从不;
类型ConfigFor=Extract;
函数createConfig(
类型:T,
值:查找
) {
常量配置={
类型,
[类型]:值
}与配置一样;
附体(配置);
}
好的,希望能有帮助。祝你好运 我已经试过了,虽然它确实让typescript关闭了,但它也破坏了任何类型检查:\n例如,如果为每种类型编写一个开关大小写,它将不再识别该大小写:“integer”将保证obj.integer作为属性。因此vs代码不提供代码完成等一些答案!你当然是对的,我的意思是整数是关键。我试着在操场上玩,看看什么东西在哪里断了,然后注意到绳子。类型ConfigFor=Extract似乎有点多余?首先将T分配给Extendes all Config['type'],然后将type分配给T。type ConfigFor不也是这样做的吗?我不明白您认为什么是多余的type ConfigFor=…
是一个类型别名定义,因此我不能省略右侧T
扩展Config['type']
;它不一定等于Config['type']
。例如,您可以为T
传入“text”
。ConfigFor
的思想是获取union类型Config
,并只提取具有{type:T}
的union的组成部分。它是在类型层次上区分一个受歧视的联合体。
type Lookup<T, K> = K extends keyof T ? T[K] : never;
type ConfigFor<T extends Config['type']> = Extract<Config, { type: T }>;
function createConfig<T extends Config['type']>(
type: T,
value: Lookup<ConfigFor<T>, T>
) {
const config = {
type,
[type]: value
} as any as Config;
appendToBody(config);
}