TypeScript:如何在循环中正确使用生成的联合类型?
TypeScript: How to use generated Union Type correctly in a loop?
我有以下类型:
type Updater<T> = {
[K in keyof T]: {
key: K;
update: (value: string) => T[K];
};
}[keyof T]
这里我的意图是为接受输入的任何类型提供一些通用更新程序,并使用更新函数更新每个个体 属性,该更新函数具有强制类型,因此输出与 [= 的类型相匹配40=]。达到此目的:
type TestType = {
fieldA: string;
fieldB: number;
}
type TestTypeUpdater = Updater<TestType>[];
const testTypeUpdater: TestTypeUpdater = [
{
key: "fieldA", // enforced to be one of "fieldA" or "fieldB"
update: s => s + "(updated)" // enforced to output the type of "fieldA"
},{
key: "fieldB",
update: s => s.length // enforced to output the type of "fieldB"
}
];
但是,当我尝试使用它时,它并不像我预期的那样。我想做的是:
const testType: TestType = {
fieldA: "",
fieldB: 0
}
function test(input: string) {
for (const u of testTypeUpdater) {
testType[u.key] = u.update(input);
}
}
test("input");
我原以为它和上面一样简单,但 TypeScript 在 testType[u.key]
:
下抱怨
Type 'string | number' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.ts(2322)
似乎 update
的 return 类型是 T[K]
的事实丢失了,但变成了 string | number
的并集。我必须做的是:
function testOK(input: string) {
for (const u of testTypeUpdater) {
if (u.key === "fieldA") {
testType[u.key] = u.update(input);
} else if (u.key === "fieldB") {
testType[u.key] = u.update(input);
}
}
}
testOK("input");
事实上,我需要在循环中的类型中再次重复每个 属性 名称,这破坏了使用 keyof
生成联合类型的整个目的。
如何在无需检查 属性 名称的情况下使循环版本有效?
添加:
如果 Updater
是一个字典类型,例如:
type Updater<T> = {
[K in keyof T]: {
update: (value: string) => T[K];
};
}
type TestType = {
fieldA: string;
fieldB: number;
}
type TestTypeUpdater = Updater<TestType>;
const testTypeUpdater: TestTypeUpdater = {
fieldA: { // enforced to be one of "fieldA" or "fieldB"
update: s => s + "(updated)" // enforced to output the type of "fieldA"
},
fieldB: {
update: s => s.length // enforced to output the type of "fieldB"
}
};
const testType: TestType = {
fieldA: "",
fieldB: 0
}
function test(input: string) {
Object.keys(testTypeUpdater).forEach(k => {
testType[k] = testTypeUpdater[k].update(input);
});
}
test("input");
你 运行 遇到了一个我称之为“相关联合类型”的问题,正如 microsoft/TypeScript#30581. Many cases of this have been addressed by indexed access inference improvements as implemented in microsoft/TypeScript#47109 中所讨论的,尽管它往往需要一些重构。
的问题
function test(input: string) {
for (const u of testTypeUpdater) {
testType[u.key] = u.update(input);
}
}
是编译器无法跟踪类型u.key
和u.update(input)
类型之间的相关性。这是它看到的:
const uKey = u.key; // "fieldA" | "fieldB"
const uUpdated = u.update(input) // string | number
testType[uKey] = uUpdated; // error!
请注意 uKey
和 uUpdated
分别属于 union types。这些类型没有错误; uKey
确实可以是这两个字符串文字中的任何一个,而 uUpdated
可以是 string
或 number
。但它将这些视为 independent 或 uncorrelated 类型。就编译器可以从这些类型来看,uKey
可能是 "fieldA"
而 uUpdated
可能是 number
。因此分配失败。
无法跟踪联合相关性是 microsoft/TypeScript#30581 的主题。您可以通过编写冗余代码来解决它,如您所示...或使用 type assertions 来抑制错误:
testType[uKey] = uUpdated as never; // fixed I guess?
但是...您也可以使用 generics and indexed access types 进行重构。基本思想是使用 分布式对象类型 ,如 microsoft/TypeScript#47109 中创造的那样。以下是我们可以对 Updater<T>
:
执行的操作
type Updater<T, K extends keyof T = keyof T> = { [P in K]: {
key: P;
update: (value: string) => T[P];
}; }[K]
所以类型 Updater<T>
与以前相同,但是您可以通过将其指定为 Updater<T, K>
来将其缩小到 T
的一个键 K
。例如:
type TestTypeUpdaterEither = Updater<TestType>;
/* type TestTypeUpdaterEither = {
key: "fieldA";
update: (value: string) => string;
} | {
key: "fieldB";
update: (value: string) => number;
} */
type TestTypeUpdaterFieldA = Updater<TestType, "fieldA">
/* type TestTypeUpdaterFieldA = {
key: "fieldA";
update: (value: string) => string;
} */
现在,我们将您的 for..of
循环更改为 forEach()
调用,因此我们可以传入 K
中通用的回调,[=37] 的一些特定键=].编译器允许将 TestType[K]
分配给 TestType
值的通用 K
属性。 (这实际上也是不安全的,如果 K
是用联合指定的,但编译器允许这样做,我们需要使用它才能继续。我们实际上不会用它做任何不安全的事情。)
看起来像这样:
testTypeUpdater.forEach(<K extends keyof TestType>(u: Updater<TestType, K>) => {
const uKey = u.key; // K
const uUpdated = u.update(input) // TestType[K]
testType[uKey] = uUpdated; // okay
}
有效!哦,我想我们不需要将它们分解成单独的变量:
testTypeUpdater.forEach(<K extends keyof TestType>(u: Updater<TestType, K>) => {
testType[u.key] = u.update(input); // okay
})
仍然有效。
我有以下类型:
type Updater<T> = {
[K in keyof T]: {
key: K;
update: (value: string) => T[K];
};
}[keyof T]
这里我的意图是为接受输入的任何类型提供一些通用更新程序,并使用更新函数更新每个个体 属性,该更新函数具有强制类型,因此输出与 [= 的类型相匹配40=]。达到此目的:
type TestType = {
fieldA: string;
fieldB: number;
}
type TestTypeUpdater = Updater<TestType>[];
const testTypeUpdater: TestTypeUpdater = [
{
key: "fieldA", // enforced to be one of "fieldA" or "fieldB"
update: s => s + "(updated)" // enforced to output the type of "fieldA"
},{
key: "fieldB",
update: s => s.length // enforced to output the type of "fieldB"
}
];
但是,当我尝试使用它时,它并不像我预期的那样。我想做的是:
const testType: TestType = {
fieldA: "",
fieldB: 0
}
function test(input: string) {
for (const u of testTypeUpdater) {
testType[u.key] = u.update(input);
}
}
test("input");
我原以为它和上面一样简单,但 TypeScript 在 testType[u.key]
:
Type 'string | number' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.ts(2322)
似乎 update
的 return 类型是 T[K]
的事实丢失了,但变成了 string | number
的并集。我必须做的是:
function testOK(input: string) {
for (const u of testTypeUpdater) {
if (u.key === "fieldA") {
testType[u.key] = u.update(input);
} else if (u.key === "fieldB") {
testType[u.key] = u.update(input);
}
}
}
testOK("input");
事实上,我需要在循环中的类型中再次重复每个 属性 名称,这破坏了使用 keyof
生成联合类型的整个目的。
如何在无需检查 属性 名称的情况下使循环版本有效?
添加:
如果 Updater
是一个字典类型,例如:
type Updater<T> = {
[K in keyof T]: {
update: (value: string) => T[K];
};
}
type TestType = {
fieldA: string;
fieldB: number;
}
type TestTypeUpdater = Updater<TestType>;
const testTypeUpdater: TestTypeUpdater = {
fieldA: { // enforced to be one of "fieldA" or "fieldB"
update: s => s + "(updated)" // enforced to output the type of "fieldA"
},
fieldB: {
update: s => s.length // enforced to output the type of "fieldB"
}
};
const testType: TestType = {
fieldA: "",
fieldB: 0
}
function test(input: string) {
Object.keys(testTypeUpdater).forEach(k => {
testType[k] = testTypeUpdater[k].update(input);
});
}
test("input");
你 运行 遇到了一个我称之为“相关联合类型”的问题,正如 microsoft/TypeScript#30581. Many cases of this have been addressed by indexed access inference improvements as implemented in microsoft/TypeScript#47109 中所讨论的,尽管它往往需要一些重构。
的问题
function test(input: string) {
for (const u of testTypeUpdater) {
testType[u.key] = u.update(input);
}
}
是编译器无法跟踪类型u.key
和u.update(input)
类型之间的相关性。这是它看到的:
const uKey = u.key; // "fieldA" | "fieldB"
const uUpdated = u.update(input) // string | number
testType[uKey] = uUpdated; // error!
请注意 uKey
和 uUpdated
分别属于 union types。这些类型没有错误; uKey
确实可以是这两个字符串文字中的任何一个,而 uUpdated
可以是 string
或 number
。但它将这些视为 independent 或 uncorrelated 类型。就编译器可以从这些类型来看,uKey
可能是 "fieldA"
而 uUpdated
可能是 number
。因此分配失败。
无法跟踪联合相关性是 microsoft/TypeScript#30581 的主题。您可以通过编写冗余代码来解决它,如您所示...或使用 type assertions 来抑制错误:
testType[uKey] = uUpdated as never; // fixed I guess?
但是...您也可以使用 generics and indexed access types 进行重构。基本思想是使用 分布式对象类型 ,如 microsoft/TypeScript#47109 中创造的那样。以下是我们可以对 Updater<T>
:
type Updater<T, K extends keyof T = keyof T> = { [P in K]: {
key: P;
update: (value: string) => T[P];
}; }[K]
所以类型 Updater<T>
与以前相同,但是您可以通过将其指定为 Updater<T, K>
来将其缩小到 T
的一个键 K
。例如:
type TestTypeUpdaterEither = Updater<TestType>;
/* type TestTypeUpdaterEither = {
key: "fieldA";
update: (value: string) => string;
} | {
key: "fieldB";
update: (value: string) => number;
} */
type TestTypeUpdaterFieldA = Updater<TestType, "fieldA">
/* type TestTypeUpdaterFieldA = {
key: "fieldA";
update: (value: string) => string;
} */
现在,我们将您的 for..of
循环更改为 forEach()
调用,因此我们可以传入 K
中通用的回调,[=37] 的一些特定键=].编译器允许将 TestType[K]
分配给 TestType
值的通用 K
属性。 (这实际上也是不安全的,如果 K
是用联合指定的,但编译器允许这样做,我们需要使用它才能继续。我们实际上不会用它做任何不安全的事情。)
看起来像这样:
testTypeUpdater.forEach(<K extends keyof TestType>(u: Updater<TestType, K>) => {
const uKey = u.key; // K
const uUpdated = u.update(input) // TestType[K]
testType[uKey] = uUpdated; // okay
}
有效!哦,我想我们不需要将它们分解成单独的变量:
testTypeUpdater.forEach(<K extends keyof TestType>(u: Updater<TestType, K>) => {
testType[u.key] = u.update(input); // okay
})
仍然有效。