考虑这段简单的代码,它为Person
定义一个接口,然后创建该类型的对象:
interface Person {
name: string | number
}
const john: Person = {
name: "John"
}
现在,我希望john.name
是string
。但是,令我惊讶的是,即使在分配了string
之后,该属性remains widened as a union的类型也似乎如此:
function greet(name: string) {
console.log(`Hello ${name}`)
}
greet(john.name) // Argument of type 'string | number' is not assignable to parameter of type 'string'.
// Type 'number' is not assignable to type 'string'.(2345)
我什至试图通过强制name
的类型为string
来强制编译器看到光线,但无济于事:
const john: Person = {
name: "John" as string
}
greet(john.name) // Argument of type 'string | number' is not assignable to parameter of type 'string'.
// Type 'number' is not assignable to type 'string'.(2345)
有人可以阐明这种行为吗?
答案 0 :(得分:1)
TypeScript扩展了类型,以使此代码出错:
interface Person {
name: string | number
}
const john: Person = {
name: "John"
}
john.name = 0
function greet(name: string) {
console.log(`Hello ${name}`)
}
greet(john.name)
john
的类型仅被检查为Person
。
答案 1 :(得分:1)
大概您想知道您的示例为何失败:
const john: Person = {
name: "John"
}
greet(john.name) // error!
// -> ~~~~~~~~~ "(string | number) is not assignable to string"
在以下情况下有效:
const name: string | number = "John";
greet(name); // okay
或者这个:
john.name = "Still John";
greet(john.name); // okay
Control flow type analysis是TypeScript的功能,通过类型检查器可以推断出某些较宽类型的变量可以暂时视为较窄类型。因此,如果我有一个变量name
声明为类型string | number
,则可以为其分配任何string
或number
:
name = Math.random() < 0.5 ? 0.007 : "James Millibond";
但是以下情况在没有类型断言的情况下起作用:
console.log(typeof name === "string" ? name.toUpperCase() : name.toExponential(0));
// 7e-3 or JAMES MILLIBOND
在代码的name.toUpperCase()
部分中,编译器将name
的类型缩小为string
,在代码的name.toExponential(0)
部分中,编译器的范围缩小了name
到number
的类型。这是实际的控制流分析。
然后,当您将string
或number
类型的值赋给string | number
类型的变量时,编译器会将该变量的类型缩小为string
或number
,直到为变量分配新值为止。这也是由于控制流分析所致。
对TypeScript的控制流分析有一个重要限制:仅缩小 union 类型。 如果变量不是联合类型,则不会缩小范围。而string | number
是联合类型,而Person
不是。是的,name
的Person
属性为联合类型,但这并不能使Person
为联合类型。
因此,分配john
本身不会进行任何缩小,但是分配john.name
会进行缩小。
为什么编译器不缩小非工会类型?我能找到的最接近规范答案的是控制流分析算法的实现者在a comment中:
缩小分配的非工会类型的问题是我们仍在思考的问题。它变得有些复杂,因为...当涉及到可选属性时,分配的类型实际上可能比声明的类型具有更少的成员。
有open issue (microsoft/TypeScript#16976)建议将控制流分析范围缩小也应用于非工会类型。如果您对此有强烈的信心,则可以考虑给它一个问题,并/或描述您的用例,如果您认为这很令人信服。
好的,希望能有所帮助。祝你好运!
答案 2 :(得分:-1)
我认为您的错误来自此函数:
function greet(name: string) {
console.log(`Hello ${name}`)
}
对于参数,您仅将其定义为string
,但类型为string | number
执行以下操作,它将纠正错误:
function greet(name: string | number) { // add number here =)
console.log(`Hello ${name}`)
}
当您运行其中任何一个时,它将起作用:
const john: Person = {
name: "John"
};
const sam: Person = {
name: 1
};
输出:
greet(john.name); // Hello John
greet(sam.name); // Hello 1