wen*_*een 2 typescript typescript-generics
抱歉标题令人困惑。
我正在尝试使用类似于https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-typessetProperty中的示例的查找类型
调用函数时会正确检查查找类型,但不能在函数内部使用。我尝试使用类型保护来解决这个问题,但这似乎不起作用。
例子:
interface Entity {
name: string;
age: number;
}
function handleProperty<K extends keyof Entity>(e: Entity, k: K, v: Entity[K]): void {
if (k === 'age') {
//console.log(v + 2); // Error, v is not asserted as number
console.log((v as number) + 2); // Good
}
console.log(v);
}
let x: Entity = {name: 'foo', age: 10 }
//handleProperty(x, 'name', 10); // Error
handleProperty(x, 'name', 'bar'); // Good
// handleProperty(x, 'age', 'bar'); // Error
handleProperty(x, 'age', 20); // Good
Run Code Online (Sandbox Code Playgroud)
有没有什么方法可以让打字稿解决这个问题,而无需硬编码类型断言:(v as number)?在代码中的这一点上,编译器应该能够推断出这v是一个数字。
第一个问题是编译器无法通过检查 的实现内部的K值来缩小类型参数的范围。(请参阅microsoft/TypeScript#24085。)它甚至没有尝试。从技术上讲,编译器不这样做是正确的,因为并不意味着要么要么。它可能是完整的 union ,在这种情况下,您不能假设检查对and 因此有影响:khandleProperty()K extends "name" | "age"K"name""age""name" | "age"kKT[K]
handleProperty(x, Math.random() < 0.5 ? "name" : "age", "bar"); // accepted!
Run Code Online (Sandbox Code Playgroud)
在这里您可以看到该k参数的类型为"name" | "age",因此这就是K推断的内容。因此,该v参数允许为 类型string | number。因此,蕴涵中的错误是正确的:k可能是"age"并且v可能仍然是 a string。这完全违背了函数的目的,并且绝对不是您预期的用例,但这是编译器担心的可能性。
实际上,您想说的是或 K extends "name" , K extends "age"或类似K extends_one_of ("name", "age"), (请参阅microsoft/TypeScript#27808,),但目前无法表示这一点。因此,仿制药并不能真正为您提供您想要控制的控制权。
当然,您可以不必担心有人使用handleProperty()完整的联合进行调用,但是您需要在实现中进行类型断言,v as number例如.
如果您想实际将调用者限制到预期的用例,您可以使用剩余元组的联合而不是泛型:
type KV = { [K in keyof Entity]: [k: K, v: Entity[K]] }[keyof Entity]
// type KV = [k: "name", v: string] | [k: "age", v: number];
function handleProperty(e: Entity, ...[k, v]: KV): void {
// impl
}
handleProperty(x, 'name', 10); // Error
handleProperty(x, 'name', 'bar'); // Good
handleProperty(x, 'age', 'bar'); // Error
handleProperty(x, 'age', 20); // Good
handleProperty(x, Math.random() < 0.5 ? "name" : "age", "bar"); // Error
Run Code Online (Sandbox Code Playgroud)
您可以看到该类型KV是元组的并集(通过映射 Entity到其属性是此类元组的类型然后立即查找这些属性的并集来创建)并且handleProperty()接受它作为其最后两个参数。
太棒了,对吧?不幸的是,这并不能解决实现中的问题:
function handleProperty(e: Entity, ...[k, v]: KV): void {
if (k === 'age') {
console.log(v + 2); // still error!
}
console.log(v);
}
Run Code Online (Sandbox Code Playgroud)
这是由于缺乏对我所说的相关联合类型的支持(请参阅microsoft/TypeScript#30581)。编译器将解构后的类型视为k,"name" | "age"将解构后的类型v视为string | number。这些类型是正确的,但并不是故事的全部。通过解构剩余参数,编译器忘记了第一个元素的类型与第二个元素的类型相关。
因此,为了解决这个问题,您可以不解构其余参数,或者至少在检查其第一个元素之前不解构。例如:
function handleProperty(e: Entity, ...kv: KV): void {
if (kv[0] === 'age') {
console.log(kv[1] + 2) // no error, finally!
// if you want k and v separate
const [k, v] = kv;
console.log(v + 2) // also no error
}
console.log(kv[1]);
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们将其余元组保留为单个数组值kv。编译器将其视为可区分的联合,当您检查kv[0](前者k)时,编译器最终kv将为您缩小 的类型,以便kv[1]也缩小范围。kv[0]使用and是很丑陋的kv[1],虽然你可以通过在检查之后解构来部分缓解这个问题kv[0],但它仍然不是很好。
这样,您就得到了一个完全类型安全(或至少接近类型安全)的handleProperty(). 这值得么?可能不会。在实践中,我发现通常最好编写惯用的 JavaScript 和类型断言来消除编译器警告,就像您一开始所做的那样。
| 归档时间: |
|
| 查看次数: |
1115 次 |
| 最近记录: |