TypeScript 之 any vs unknown

972 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

前言

TypeScript 是对于 JavaScript 的超集,我们能通过 TypeScript 来发现编译时错误,以保证我们的代码是绝对安全的,而不是在运行时才来产生报错。

但是 TypeScript 提供了一个 any 类型,这个类型可以跳过 类型检查器 的检查,轻松的实现 AnyScript

在之后的版本当中,TypeScript 也发现了 any 的权利过于巨大,会产生很多的问题,“TS编译过不了怎么办,干any就完事了”,这种问题,所以在之后的版本,TypeScript 推出了 unknown 这个类型,那么 unknownany 有什么不同的地方呢? 本文就稍微的聊一聊两者的区别所在。

本文就不聊两个类型都是什么了,这些网上介绍很多,主要来说说两个类型的共同的和不同点,以及,知道这些有什么用呢?

相同点

anyunknown 都是 Top Type

可以接收任何值作为他们的参数

比如:

let a: any = "asdasddsa" || 112 || {asds:2} || Symbol()
//...其他任何类型
  
let b: unknown = "asdasddsa" || 112 || {asds:2} || Symbol()
//...其他任何类型

就说明两个类型都是包容万物,就像是我们平时写 JavaScript 一样,一个变量你想要它在什么时候等于什么值都是可以的

let a: any = "asdasddsa" || 112 || {asds:2} || Symbol()
  
let b: unknown = "asdasddsa" || 112 || {asds:2} || Symbol()

a = Symbol()
b = Symbol()

区别

两者不同的地方在于, 被赋予 any 类型的变量将会跳过类型检测,但是 unknown 不会。

any 类型 我们可以简单的给一个定义:“I don't care”,简单的说就是我不在意,因为any 类型并不会经过 类型检查器,所以它毫不在意你给它的是什么类型,或者说它要用在什么地方。

unknown 类型 我们也可以简单的给一个定义:“I don't know”,简单的说就是我不知道,作为和 any 类型一样的 Top Type ,我们可以给unknown 类型赋值任何类型,但是你要是想要拿它来做一些事的时候,对不起,他都是不允许的,因为,它不知道啊,它不知道它能不能完成你交给它的任务,所以,它就会给出报错。

any 类型 可以赋值给任何类型,任何类型也可以赋值给 any 类型 因为它完全不在意。

但是 任何值都可以赋给unknown 类型,unknown 类型却不能赋值给 unknownany 以外的任何类型,因为它不知道,它不知道它和你将要赋给的那个变量是不是安全的,打个比方:

let any1:any
let str:string = '2'
  
any1=str
str=any1

any 类型的随便赋值都是可以实现的,但是看unknown 类型

let str:string = '2'

let unknown1:unknown

unknown1=str

str=unknown1 //err:Type 'unknown' is not assignable to type 'string'.

编译就会给出报错,字符串不能够赋值一个unknown 类型的值,因为编译器它不知道你能不能赋值给字符串,所以给出了报错。

那么上面那段代码,要怎么才能够通过编译呢,很简单,它不知道它是什么类型,就要由程序员来告诉它:

let str:string = '2'
let unknown1:unknown

unknown1=str 

if(typeof unknown1 == 'string'){
  str=unknown1 //ok
}

我们可以使用类型守卫,或者是其他的任何方式,只要是你能够满足你要执行的代码的约束,比如说你要赋值给字符串,那你就一定也要是一个字符串,你要调用,那你就得是一个方法,以此类推。

这样做之后,虽然我们用了 Top Type 但是还是能够保证写出来的代码足够安全,起码不是产生那种,传一个字符串却拿来当方法调用这种情况。

体操一下

前面介绍了两种类型,那么来做一些简单的体操,第一个是,我们要怎么判断一个类型它是否是 any 类型。第二个,我们要怎么判断一个类型是否是 unknown 类型

IsAny

这里我们要用到一个 any 类型 的特性,那就是any 类型和任何类型的交集,都是any:

type Z1 = any & 1            // any
type Z2 = any & '1'          // any
type Z3 = any & {a:1}        // any
type Z4 = any & unknown      // any
type Z5 = any & null         // any
type Z6 = any & undefined    // any

当然这是只有 any 才满足的一个特性,那么我们就可以利用这个特性来判断,一个数它是不是 any

type IsAny <T> = 0 extends 1&T ? true: false

type W1 = IsAny<undefined> // false
type W2 = IsAny<any>       // true

只有当传入的 泛型 T 是 any 的时候,他才能够满足后面为 any ,这个条件类型的 0 或者 1 并不重要,重要的是,只有当 T 是 any 的时候,前后才能够满足,这样我们就能够判断一个类型是否是 any

IsUnknown

我最开始自己思考这个问题的时候,发现了 unknown 的一个特性,就是unknown和任何类型的并集都是unknown,除了 any:

type Z1 = unknown | 1            // unknown
type Z2 = unknown | '1'          // unknown
type Z3 = unknown | {a:1}        // unknown
type Z4 = unknown | unknown      // unknown
type Z5 = unknown | null         // unknown
type Z6 = unknown | undefined    // unknown
type Z7 = unknown | any          // any

那么我们是不是可以按照上上面的思路,最后只要排除掉 any 就好了。

这里还有一个知识点,就是 keyof anystring | number | symbolkeyof unknownnever 我们可以以此来判断。

type IsUnknown <T> = 0 extends 1|T ? 
  keyof T extends never? true: false
  : false

type e = IsUnknown<1>

后面发现我想的太麻烦了,我们之前说过 unknown 类型却不能赋值给 unknownany 以外的任何类型,所以我们可以用这点来优化一下

type IsNotAny<T> = 0 extends 1 & T ? false : true ;
  
type IsUnknown<T> = unknown extends T ? IsNotAny<T> : false;
  
type Test1 = IsUnknown<any>     // false
type Test2 = IsUnknown<unknown> // true

通过判断传入的类型是不是作为unknown 的父集,这样就能够把范围缩小到 unknownany,再通过上一个判断是否是 any 的工具类型,就能够更简单的实现了。

总结

今天简单的介绍了 anyunknown类型,知道了它们之间的区别,所以在我们日常的开发当中,对一个变量的类型不确定的话,可以先尝试着用 unknown 类型,只有在实在是没办法的时候才使用 any 类型