TypeScript 类型系统——从原始类型到函数签名
本文为「TypeScript 深入理解」系列第 1 篇,后续文章将分别覆盖面向对象与泛型、装饰器与编译原理、实战技巧与模式。
JavaScript 的动态类型在项目初期是优势——写起来快,改起来随意。但代码量上来之后,同一个灵活性就变成了技术债务:改一个函数签名,调用方不会收到任何提示,你只能跑一遍测试或者等线上报错来发现遗漏。
TypeScript 在 JavaScript 之上加了一层编译时的类型检查。类型不匹配、属性拼写错误、参数遗漏——这些问题在构建阶段就能被拦截,而不是等到浏览器控制台报错。
类型注解带来的不止是安全网。它本身就是活的文档:看一个函数签名就知道入参和返回值,不需要翻源码或读注释。
类型注解:给变量加上约束
JavaScript 原始类型也同样适应于 TypeScript 的类型系统,string、number、boolean 可以直接当作类型注解使用:
原始类型
1 | let num: number; |
TypeScript 为数组提供了专用的类型语法。后缀 [] 配合类型注解(如 :boolean[]),既能让你安全地操作数组,也能防止赋值错误类型的成员:
数组
1 | let boolArray: boolean[]; |
JavaScript 没有原生的元组,通常只能用数组模拟。TypeScript 补上了这个缺口,使用 :[typeofmember1, typeofmember2] 语法即可定义定长定类型的元组:
元组类型
1 | let nameNumber: [string, number]; |
特殊类型:any、unknown、void、never
any
any 是类型系统的「后门」——一旦你用 any 注解一个变量,TypeScript 对它的类型检查就完全关闭了。所有类型都能赋值给 any,any 也能赋值给任何类型。换句话说,用了 any 就等于回到了纯 JavaScript。
null 和 undefined
在类型系统中,JavaScript 中的 null 和 undefined 字面量和其他被标注了 any 类型的变量一样,都能被赋值给任意类型的变量,如下例子所示:
1 | // strictNullChecks: false 关闭严格的 null 检查 |
void
使用 :void 来表示一个函数没有一个返回值
never
never 类型是 TypeScript 中的底层类型,表示永远不会发生的类型。它自然出现的场景:
- 一个永远不会有返回值的函数(如:
while(true) {}无限循环); - 一个总是会抛出错误的函数(如:
function foo() { throw new Error('Not Implemented') },foo的返回类型是never);
你也可以将它用做类型注解:
1 | let foo: never; // ok |
但是,never 类型仅能被赋值给另外一个 never:
1 | let foo: never = 123; // Error: number 类型不能赋值给 never 类型 |
unknown
unknown 是 TypeScript 3.0 引入的类型,它是 any 的「安全版」。与 any 不同,unknown 类型的值不能直接赋值给其他类型,也不能直接调用方法——你必须先进行类型收窄。
1 | let value: unknown; |
能用 unknown 就不要用 any。unknown 保留了类型安全,迫使你在使用前做检查。
接口:给对象形状下定义
1 | // interface object |
函数:给签名加上类型保障
函数的类型注解可以约束参数和返回值。在 TypeScript 中,函数类型有多种写法:
1 | // 1. 直接注解参数和返回值 |
可选参数和默认值:
1 | // 可选参数必须放在必选参数后面 |
剩余参数使用 ... 展开:
1 | function sum(...numbers: number[]): number { |
函数重载允许同一个函数名根据参数类型的不同执行不同逻辑。TypeScript 要求先声明重载签名,再提供统一实现:
1 | // 重载签名 |
掌握了类型注解、接口和函数类型,你已经能应对大部分日常开发场景。下一篇将进入 TypeScript 的面向对象特性——类、泛型、枚举、迭代器,以及继承、多态和抽象类。