前言
不知道初学 TypeScript 的同学会不会和我一样被 any , unknown , never 和 void 这几个类型搞得晕头转向呢?如果你也有同样的困惑,那么就请跟着本篇文章彻底搞懂这四种类型的区别吧。
any
首先给大家介绍的是 any 类型,我相信一些从 JavaScript 转 TypeScript 的同学一定不会对这个类型感到陌生,因为它是我们将 JavaScript 代码重构为 TypeScript 代码的银弹,甚至有些程序员由于过度依赖 any 类型活生生将 TypeScript 写成了 AnyScript 。
用法
当我们将某个变量定义为 any 类型后, TypeScript 将会 跳过对这个变量的类型检查 :
let something: any = 'Hello World!' something = 18 // ok! ts不会管any的类型检查 something = true // ok! ts不会管any的类型检查
在上面的代码中一般来说如果 something 被初始化为一个字符串类型后,是不可以被赋值为 number 或者 boolean 类型的,不过由于我们声明了它的类型是 any 所以 TypeScript 就完全不管这个对象的类型检查了。除了这个,你还可以随意访问这个 any 对象上面的任意属性,即使它们不存在:
let something: any = 'Hello World!' something.notExistMethod() // ok! something.notExistProperty.name() // ok!
在上面的代码中我们可以访问 any 类型对象的任意属性,并且这个 any 是具有 传递性 的,也就是说 something 后面无论跟了多少个属性访问,它们的类型都是 any 。
使用场景
any 一般的使用场景有下面这些:
代码从JS迁移到TS:这个时候使用 any 我们可以将重构 快速推进 而不用陷入无边无尽的类型错误里面去。这里值得一提的是 any 只能作为临时过渡方案,我们最后的结果一定是确保代码尽可能少 any 的出现 我们不关心对象的类型:例如我们实现了一个 print 函数,这个函数底层调用 console.log ,这个时候其实我们不需要关心传进来的具体数据类型是什么,我们只需要一股脑将它传递给 console.log 函数即可,这个时候我们就可以将函数的参数类型设置为 any 了类型缺失或者补全困难:这种情况一般发生在我们使用了第三方JS编写库的时候,我们没有办法知道某个导出的函数的具体类型,这时我们需要定义一些全局的类型垫片:
// shim.d.ts declare module "unknown-type-lib" { export function someFunction(input: string): any }
注意 :虽然 any 很方便,可是我们还是需要想办法避免代码中出现 any 类型,这是因为 any 跳过了所有的类型检查,而这会给我们带来一些潜在的安全问题。最坏的情况是整个代码除了 any 没有其它有意义的类型,这个时候还不如直接编写 JavaScript 代码来得实在,既然我们选择了 TypeScript ,我们还是希望它可以真真实实为我们带来收益的,因此当我们碰到一些很难编写的类型时,最好的做法不是写 any ,而是询问一些资深的工程师或者网上查找资料来解决这个问题。
unknown
上面我们说到了 any 会跳过所有的类型问题,而这其实会为日后的代码维护和开发埋下巨大的安全隐患。为了解决 any 的问题, TypeScript 在3.0版本引入了 unknown 类型,它可以理解为类型安全的(type-safe) any 。
用法
和 any 一样,任何类型都可以赋值给 unknown 类型的对象:
let vAny: any = 'Hello World!' // ok! any对象接受任何类型 let vUnknown: unknown = 'Hello World!' // ok! unknown对象接受任何类型的对象
和 any 不一样, unknown 类型的对象不可以直接赋值给其它非 unknown 或 any 类型的对象,并且不可以访问上面的任何属性:
let vAny: any = 'Hello World!' let vUnknown: unknown = 'Hello World!' let vNumberForAny: number = vAny // ok! any可以直接赋值给其它任意类型 let vNumberForUnknown: number = vUnknown // error! unknown不可以直接赋值给其它非any和unknown类型的对象 vAny.toLocaleLowerCase() // ok! any可以访问所有的属性 vUnknown.toLocaleLowerCase() // error! unknown对象不可以直接访问上面的属性
那么应该怎样才能使用 unknown 类型的变量呢?答案很简单,那就是你需要先推断出对象的类型,才能使用,推断的方式有很多种,包括 typeof 和 as assertion 等其他 type guard 方法:
let vUnknown: unknown = 'abc' // 使用typeof推断出vUnknown的类型是string if (typeof vUnknown === 'string') { vUnknown.toLocaleUpperCase() // ok! 因为能进入这个if条件体就证明了vUnknown是字符串类型! } let vNumberForUnknown: number = vUnknown as number // unknown类型一定要使用as关键字转换为number才可以赋值给number类型
使用场景
由于 unknown 基本可以替代 any ,所以在任何 any 适用的场景,都应该优先使用 unknown 。使用了 unknown 后,我们既允许某个对象储存任意类型的变量,同时也要求别人在使用这个对象的时候一定要先进行类型推断。
never
never 不像前面那几个类型一样常用,甚至有些同学可能一开始压根就不知道这个类型存在的意义是什么。我们知道 TypeScript 在解析我们的代码时会对代码进行类型推断,并且在代码流不断深入的时候,类型会从较为宽泛的类型(例如any)一直推断到较为具体的类型,而这么推断下去是会有个终点,这个终点就是不存在的,不可能发生的类型,也就是类型系统的底部类型( bottom type ),而 never 就是 TypeScript 的底部类型。
用法
never 类型只接受 never 类型的对象,甚至万金油 any 类型都不可以赋值给 never 类型。
let vAny: any = 1 let vNever: never = vAny // error! never除了自己谁不都接受!
一般当我们想表示某个函数永远 不会返回 时,可以使用 never 类型,例如下面的例子:
// 因为这个是无限循环,我们可以使用never作为返回值表示它永远不会返回 function foreverLoop(): never { while(true) {} } // 因为这个函数会抛出异常,所以也是不会返回的 function crashFunc(): never { throw new Error('this function will crash') }
使用场景
never 类型的一个最大的作用就是帮我们对类型进行 exclusive check ,例如下面这个例子:
interface QA { kind: 'qa' bug: number } interface Developer { kind: 'developer' hair: number } type TechDude = QA | Developer function printTechDude(h: TechDude) { if (h.kind === 'qa') { console.log(h.bug) } else if (h.kind === 'developer') { console.log(h.hair) } else { let exclusiveCheck: never = h // 由于这个代码永远也到达不了,所以h的类型被自动推断为never } }
上面的代码现在是没有问题的,不过假如某一天我们新增了一个新的 PM 类型,而忘记在 printTechDude 函数里面处理这个新类型的话,上面的代码会报错:
interface QA { kind: 'qa' bug: number } interface Developer { kind: 'developer' hair: number } interface PM { kind: 'pm' features: number } // TechDude多了一个PM类型 type TechDude = QA | Developer | PM function printTechDude(h: TechDude) { if (h.kind === 'qa') { console.log(h.bug) } else if (h.kind === 'developer') { console.log(h.hair) } else { let exclusiveCheck: never = h // error! 因为PM类型不可以赋值给never类型 } }
上面代码报错的原因是 TechDude 这个类型在else这个代码体里面已经被 TypeScript 收拢为 PM 类型,所以不再是 never 类型了。要去掉这个错误,我们需要在 printTechDude 函数里面额外加多一个 else if(h.kind === 'pm') 的判断:
function printHuman(h: TechDude) { if (h.kind === 'qa') { console.log(h.bug) } else if (h.kind === 'developer') { console.log(h.hair) } else if (h.kind === 'pm') { console.log(h.features) }else { let exclusiveCheck: never = h } }
也正是因为 exclusive check 的存在我们才可以在类型变化的时候及时发现,避免问题留到了线上环境。
void
void 其实可以理解为null和undefined的联合类型,它表示空值。
用法
我们一般不会声明某个值的类型为 void ,因为它表示这个值只能是 undefined 或者 null ( strictNullChecks 没被指定):
let vVoid: void = undefined
void 一个更加常见的使用场景是表示某个函数没有任何返回值:
function noReturnValue(): void { console.log('hello') // 代码没有任何返回值,所以这个函数的返回值是void }
使用场景
这里只想说明一下 void 和 never 的区别。 void 表示 空值 ,也就是 null 或者 undefined ,而 never 则表示 永远都不会出现的值 。
总结
本篇文章通过例子给大家介绍了TypeScript中几个容易混淆的类型 any , unknown , never 和 void ,希望能帮助有需要的人解答到疑惑,更多关于TypeScript类型使用场景的资料请关注其它相关文章!
原文地址:https://juejin.cn/post/7151926103324983304
查看更多关于TypeScript类型any never void和unknown使用场景区别的详细内容...