TypeScript unknown 类型
本章介绍 TypeScript unknown类型,它们可以作为学习 TypeScript 类型系统的起点。
unknown 类型 #
为了解决any
类型“污染”其他变量的问题,TypeScript 3.0 引入了unknown
类型。它与any
含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像any
那样自由,可以视为严格版的any
。
unknown
跟any
的相似之处,在于所有类型的值都可以分配给unknown
类型。
let x:unknown;
x = true; // 正确
x = 42; // 正确
x = 'Hello World'; // 正确
上面示例中,变量x
的类型是unknown
,可以赋值为各种类型的值。这与any
的行为一致。
unknown
类型跟any
类型的不同之处在于,它不能直接使用。主要有以下几个限制。
首先,unknown
类型的变量,不能直接赋值给其他类型的变量(除了any
类型和unknown
类型)。
let v:unknown = 123;
let v1:boolean = v; // 报错
let v2:number = v; // 报错
上面示例中,变量v
是unknown
类型,赋值给any
和unknown
以外类型的变量都会报错,这就避免了污染问题,从而克服了any
类型的一大缺点。
其次,不能直接调用unknown
类型变量的方法和属性。
let v1:unknown = { foo: 123 };
v1.foo // 报错
let v2:unknown = 'hello';
v2.trim() // 报错
let v3:unknown = (n = 0) => n + 1;
v3() // 报错
上面示例中,直接调用unknown
类型变量的属性和方法,或者直接当作函数执行,都会报错。
再次,unknown
类型变量能够进行的运算是有限的,只能进行比较运算(运算符==
、===
、!=
、!==
、||
、&&
、?
)、取反运算(运算符!
)、typeof
运算符和instanceof
运算符这几种,其他运算都会报错。
let a:unknown = 1;
a + 1 // 报错
a === 1 // 正确
上面示例中,unknown
类型的变量a
进行加法运算会报错,因为这是不允许的运算。但是,进行比较运算就是可以的。
那么,怎么才能使用unknown
类型变量呢?
答案是只有经过“类型缩小”,unknown
类型变量才可以使用。所谓“类型缩小”,就是缩小unknown
变量的类型范围,确保不会出错。
let a:unknown = 1;
if (typeof a === 'number') {
let r = a + 10; // 正确
}
上面示例中,unknown
类型的变量a
经过typeof
运算以后,能够确定实际类型是number
,就能用于加法运算了。这就是“类型缩小”,即将一个不确定的类型缩小为更明确的类型。
下面是另一个例子。
let s:unknown = 'hello';
if (typeof s === 'string') {
s.length; // 正确
}
上面示例中,确定变量s
的类型为字符串以后,才能调用它的length
属性。
这样设计的目的是,只有明确unknown
变量的实际类型,才允许使用它,防止像any
那样可以随意乱用,“污染”其他变量。类型缩小以后再使用,就不会报错。
总之,unknown
可以看作是更安全的any
。一般来说,凡是需要设为any
类型的地方,通常都应该优先考虑设为unknown
类型。
在集合论上,unknown
也可以视为所有其他类型(除了any
)的全集,所以它和any
一样,也属于 TypeScript 的顶层类型。