这是我参与「第四届青训营 」笔记创作活动的第8天
「前言」
虽然 ts官方文档很详细,但是缺少更好的逻辑组织,本系列文章针对于 js 向 ts 的过渡的小伙伴,文章内容会在讲解 ts 的时候通过与 js 对比,带来更好的学习效果
为什么要学习 typescript
引用 TypeScript 官网上面的话:
TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。
TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
typescript 是对 javascript 的扩展,我们都知道 javascript 作为一门 动态语言 与 弱类型 语言,而 typescript 保留了 弱类型 语言的特点,使其变为 静态语言
静态语言:
- 可读性增强:基于语法解析
ts文件,ide 增强 - 可维护性强:在编译阶段暴露大部分错误
- 方便多人合作:更好的稳定性、开发效率,减少维护成本
一个简单的例子说明 typescript 的好处
const a = 10;
console.log(a.length);
我们都知道在 js 中这一段代码是能够被编译而且正确执行的,但实际上,其实这样的代码是不应该出现在我们的项目中,而在 ts 文件中代码会被 ide 检查,反馈给开发者报错的信息
「系列文章结构」
该系列文章会通过讲解简单的 ts 常用语法,高级语法,ts相关配置 和在 vue 中的使用
「开始」
安装 typescript
npm install -g typescript // 全局安装 ts
tsc -v // 检查 ts 版本,如果成功,则安装成功
tsc --init
在文件夹中会生成 tsconfig.json,在后续的文章中会讲解该文件的内容与配置
运行 typescript
我们可以在文件夹中建立 .ts 文件,ts 文件中兼容 js 的代码,但是 ts 文件不会被任何环境执行,不管是 node 还是 浏览器 都只解析 js 文件
- 将
ts文件转化为js文件
tsc 文件名
运行 tsc 命令,会在改文件夹中建立一同名 .js 文件,这时候就可以运行 js 文件
安装 ts-node
每一次改变代码,都需重新执行 tsc 命令,ts-node 可以解决这个问题
npm i -g ts-node // 全局安装 ts-node
ts-node 文件名
ts 文件可以在 node 环境中被正常执行了
「类型」
类型注解
let a: number = 100;
在 ts 中可以向上述过程一样给一个变量添加一个类型名字,这种方式叫做添加 类型注解
被添加的 类型注解 的变量,改变类型会报错(有特例,在下文中会详细指出)
而像上述类型添加 类型注解 的类型可以为:
基础类型复杂类型ts新增类型
基础类型
这里的基础类型就是原生 js 的基础类型
number:数字型boolean:布尔型string:字符串型symbol:符号型bigint:大整数型null:空undefined:未定义
特别说明:
- 想使用
bigint,必须将tsconfig.json中的"target": "es2020"修改在2020版本之后 - 使用
number等类型的时候不要使用Number
不要使用如下类型
Number,String,Boolean或Object。 这些类型指的是非原始的装盒对象,它们几乎没在JavaScript代码里正确地使用过。 ----- 来源于 ts 文档
使用注意点
- 不要将
包装类赋值给基础值
const num1:number = Number(1);
const num2:number = new Number(1); // error
const num3:number = 1
- 数字支持进制
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
- 字符串型支持多种声明方式
const num = 1;
let str1: string = `${num}`;
let str2: string = 1 + '';
null和undefined是 任何类型 的子类
被添加的 类型注解 的变量,改变类型会报错,但是将类型改为 null 和 undefined 不会产生错误
复杂类型
这里的复杂类型也是原生 js 的复杂类型:
- object:对象
- array:数组
数组
数组有两种声明方式:
- 数组类型声明
let list: number[] = [1, 2, 3];
- 数组泛型
let list: Array<number> = [1, 2, 3];
对象
let o: object = {
name: '腹黑霸道城乡结合部王铁牛'
};
虽然这种方式被允许,但是不是最优的方式(缺少了对对象成员的类型判断),在后续的文章中会用其他方式声明对象
ts新增类型
ts 在对原生 js 处理的同时,也添加了额外的类型,方便变量在类型之间转换和管理
- tuple
- 枚举
- any
- void
- never
- unknown
any
typescript 又被称为 anyscript,首当其冲,就是这个 any
any 类型的变量可以是任何类型的变量,给变量添加 any,变量变回原先的 js变量,失去了 ide 对类型的检查,也就是说使用 any类型不存在的方法不会报错(这显然是一个不好的习惯),但 any 也是十分有用的,常用在定义类型不明确的变量上,等待之后 约束类型 替换 any
any 中的数组,数组成员可以是任意数据类型
const arr: any = [1, '1', null, undefined, {}];
加强版的 any --- unknown
unknown 与 any 最大的区别,更加智能的类型推断
let num1: any = 1;
num1.length;
let num2: unknown = 2;
num2.length; // error
any: ide 不对变量做出任何检查
unknown: 更安全的 any
注意:任何值都可以赋给unknown,但是当没有类型断言或基于控制流的类型细化时unknown不可以赋值给其它类型,除了它自己和any外。同样地,在unknown没有被断言或细化到一个确切类型之前,是不允许在其上进行任何操作的。(这句话有点难理解,在学习到后面的文章,再回头看)
let b: unknown = '1';
let num2: number = b; // error,即使赋值给 string 类型也会报错
let a: number;
let b: unknown = '1';
if (typeof b === 'number') {
a = b;
}
console.log(a); // 不报错,undefined
any 的 “怪异” 表现
let a: any = '1';
let num: number = a; // success,num 为字符串
let b: unknown = '1';
let num2: number = b; // error
let c = '1';
let num3:number = c; // error
使用 any 可以完全骗过编译器
void
void 表示没有任何类型,一般用于约束函数无返回值或者返回为 null 和 undefined
never
never类型表示的是那些永不存在的值的类型。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
} // 执行报错,编译不报错
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
never类型是任何类型的子类型,也可以赋值给任何类型;没有任何类型可以赋值给 never(never 自身除外)包括 null、undefined、any。
let a: number = 1;
let b: never;
a = b;
b = a ; // error
我们利用这个报错的特性可以在进行类型判断的时候穷尽所有的可能性
关于 never 的实际作用可以参考尤雨溪大大的文章: www.zhihu.com/question/35…
tuple 元祖类型
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。本质上是对数组类型的一种扩展
let x: [string, number];
x = ['hello', 10];
x = [10, 'hello']; // Error
x[2] = ''; //Error
访问越界的元素会报错
枚举
通过 enum 关键字来定义,枚举元素默认从 0 开始编号,后面的元素递增加 1
enum Color { Red, Green, Blue }
let c: Color = Color.Green;
console.log(c); // 1
enum 实际上将元素和值在 js 对象上相互映射
js 源码:
var Color;
(function (Color) {
Color[(Color['Red'] = 0)] = 'Red';
Color[(Color['Green'] = 1)] = 'Green';
Color[(Color['Blue'] = 2)] = 'Blue';
})(Color || (Color = {}));
console.log(Color); { '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }
也就意味着 Color 里面的元素可以这样访问
Color.red;
Color[0];
可以修改默认元素编号
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
采用手动赋值
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
枚举中除了最后一项,其他元素赋值字符串,后面的元素失去自动赋值的属性,所以会报错
enum Color { Red, Green = '1', Blue } // error
枚举中每一项元素只能是 number、string、undefined、null,但是可以通过 any 绕开编译并且正确执行
let a: any = { name: '腹黑霸道城乡结合部王铁牛' }
enum Color { Red, Green, Blue = a }
console.log(Color.Blue); // { name: '腹黑霸道城乡结合部王铁牛' }
console.log(Color['[object Object]']); // Blue
枚举中的元素如果赋值相同的值,同赋值的值访问只能访问到最后赋值的元素
enum Color { Red = 1, Green = 1, Blue = 1 }
console.log(Color.Red); // 1
console.log(Color.Green); // 1
console.log(Color.Blue); // 1
console.log(Color[1]); // Blue
这个很好理解,对象中对同一个键赋值,只会保留最后一个元素值
「联合类型」
类型可以为多种类型中的一种
let x: string | number = 1;
x += ''; // error
x = x + '';
在 ts 中 += 和原生 js 有些许区别
「类型断言」
使用类型断言,相当于用户帮编译器自动做了类型推断,用户使用错误的类型断言编译器依然会提示报错
场景:定义一个函数获取数字类型或者字符类型的长度
function getLen(x: number | string) {
return x.length; // error,因为 number 没有 length 属性
}
解决办法: 通过更详细的类型判断来约束类型
function getLen(x: number | string) {
if (typeof x === 'number') {
return x.toString().length;
} else {
return x.length;
}
}
这种解决办法是可以的,但是 ts 官方提供一种写法:<类型>变量名 和 变量名 as 类型
function getLen(x: number | string) {
if ((<string>x).length) {
return (x as string).length; // 这一行和上一行都是类型断言的写法
} else {
return x.toString().length;
}
}
然而,当你在TypeScript里使用 JSX 语法时,只有 as语法断言是被允许的。
「类型推断」
TS会在没有明确的指定类型的时候推测出一个类型
- 定义变量时没有赋值, 推断为
any类型 - 定义变量时赋值了, 推断为对应的类型
let a = 1;
a = '1' // error,在声明变量的时候相当于为 a 添加了类型注解,判断类型为 number