Type[keyof Type] 函数参数的 Typescript 类型保护
Posted
技术标签:
【中文标题】Type[keyof Type] 函数参数的 Typescript 类型保护【英文标题】:Typescript type guard for Type[keyof Type] function parameter 【发布时间】:2021-09-12 19:32:06 【问题描述】:抱歉标题混乱。
我正在尝试使用类似于https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types 中的setProperty
示例的查找类型
调用函数时已正确检查查找类型,但不能在函数内部使用。我尝试使用类型保护解决此问题,但这似乎不起作用。
例子:
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
TS Playground
有没有办法让打字稿解决这个问题,而无需硬编码类型断言:(v as number)
?在代码中,编译器应该能够推断出v
是一个数字。
【问题讨论】:
你想达到什么目的?变异函数参数,被认为是一种不好的做法 @captain-yossarian 我同意。我将编辑问题以反映我的意图。我想使用我知道的类型来使用参数。 【参考方案1】:第一个问题是编译器不能通过在handleProperty()
的实现中检查k
的值来缩小类型参数K
。 (见microsoft/TypeScript#24085。)它甚至没有尝试。从技术上讲,编译器不这样做是正确的,因为K extends "name" | "age"
并不意味着K
是"name"
或"age"
。它可能是完整的联合"name" | "age"
,在这种情况下,您不能假设检查k
对K
有暗示,因此对T[K]
有暗示:
handleProperty(x, Math.random() < 0.5 ? "name" : "age", "bar"); // accepted!
在这里您可以看到k
参数的类型为"name" | "age"
,这就是K
的推断类型。因此v
参数被允许为string | number
类型。所以暗示中的错误是正确的:k
可能是"age"
和v
可能仍然是string
。这完全违背了您的功能的目的,绝对不是您的预期用例,但编译器担心这是一种可能性。
您真正想说的是要么 K extends "name"
或 K extends "age"
,或类似K extends_one_of ("name", "age")
,(见microsoft/TypeScript#27808,)但目前没有办法表示这一点。因此,泛型并不能真正为您提供您想要转向的手柄。
当然,您不必担心有人使用完整联合调用handleProperty()
,但您需要在实现中使用type assertion,例如v as number
。
如果您想将调用者实际约束到预期的用例,您可以使用 rest tuples 的联合而不是泛型:
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
您可以看到KV
类型是元组的联合(由mapping Entity
创建到其属性是此类元组的类型,然后立即looking up 这些属性的联合)并且@987654359 @ 接受它作为最后两个参数。
很好,对吧?不幸的是,这并不能解决实现内部的问题:
function handleProperty(e: Entity, ...[k, v]: KV): void
if (k === 'age')
console.log(v + 2); // still error!
console.log(v);
这是由于缺乏对我一直称为相关联合类型的支持(请参阅microsoft/TypeScript#30581)。编译器将解构的k
的类型视为"name" | "age"
,将解构的v
的类型视为string | number
。这些类型是正确的,但不是全部。通过解构 rest 参数,编译器忘记了第一个元素的类型与第二个元素的类型相关。
所以,要绕过那个,你不能解构 rest 参数,或者至少在你检查它的第一个元素之前不能。例如:
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]);
在这里,我们将其余元组保留为单个数组值kv
。编译器将此视为discriminated union,当您检查kv[0]
(前k
)时,编译器将最终为您缩小kv
的类型,以便kv[1]
也会变窄。使用kv[0]
和kv[1]
很难看,虽然您可以在检查kv[0]
后通过解构来部分缓解这种情况,但它仍然不是很好。
这样,handleProperty()
的完全类型安全(或至少更接近类型安全)实现就完成了。这值得么?可能不是。在实践中,我发现编写惯用的 javascript 和类型断言来消除编译器警告通常会更好,就像你一开始所做的那样。
Playground link to code
【讨论】:
感谢您提供极其详细和翔实的解释!添加了对相关联合类型问题的赞许。以上是关于Type[keyof Type] 函数参数的 Typescript 类型保护的主要内容,如果未能解决你的问题,请参考以下文章
在 Immutable.js 中,类型“字符串”不可分配给 keyof
python isinstance()函数和type()函数
R语言使用plot函数和lines函数可视化线图(line plot)时图之间的主要区别是由选项type产生的type参数常用参数说明不同type生成的可视化图像对比