TypeScript 基础
# TypeScript 基础
JavaScript 作为一种弱类型语言(类型不安全的语言)、动态类型语言,十分不利于大型项目的维护。而伴随近几年前端业务的崛起,需要一种提供编译时类型检查,也就是静态类型检查的语言。
那么 TypeScript 就应运而生了,满足了静态类型检查这个需求。
TS 本质上是 JS 的超集,基于 ES6 以后的语法提供全面支持,与 JS 语法兼容。
而且还有额外的更好的提示、更易于重构的好处。
官方提供操场:tsPlayGround (opens new window)
开源好书:
- 深入理解 TypeScript (opens new window)
- TypeScript Handbook (opens new window)
- TypeScript 入门教程 (opens new window)
# 基本类型
# JS 的运行时类型
# string
# boolean
# number
0xff、0o77、0b11
# string
原生支持模板字符串
# object
# bigint
# symbol
# undefined
# null
# JS 的包装类型
# Number
# Boolean
# String
# Object
# Symbol
# 复合类型
# class
# array
# ts 新增
# tuple
元组:规定了类型的数组
let x: [number, string];
x = [10, 'hello']; // ok
x = ['hello', 10]; // error
# enum
枚举,默认从 0 开始编码
enum Color {
Red,
Green,
Blue,
}
console.log(Color.Green); // 1
console.log(Coloe[2]); // Blue
// 初始值从 0 开始
enum ProcessState {
'untreated' = 0,
'processing',
'resolved',
'rejected',
'increased',
'repeated',
'timeout',
}
console.log(ProcessState[6]); // timeout
// 可以自定义枚举值
enum Animal {
Cat = 'Garfield',
Dog = 'husky',
}
console.log(Animal.Cat); // Garfield
# Interface
接口,定义了一种抽象,一种声明
可以描述函数、对象、构造器的结构:
interface IPerson {
name: string;
age: number;
}
class Person implements IPerson {
name: string;
age: number;
}
const obj: IPerson = {
name: 'guang',
age: 18,
};
接口和类的区别:
- 接口没有执行的部分,只是约束了一些类型,像一个模板
- 类可以产生一个实例化对象,有实例化的过程
# 特殊的类型
# void
空,可以是 null 或者 undefined,一般是用于函数返回值
# never
永远不会返回,不可达,比如函数抛异常的时候,返回值就是 never
// 返回never的函数必须存在无法到达的终点
function infinteLoop(): never {
while (true) {}
}
# any
任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never)。
相当于放弃类型校验。
# unknown
是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型。
# 高级类型
高级类型的特点是传入类型参数,经过一系列类型运算逻辑后,返回新的类型。
# 推导:infer
如何提取类型的一部分呢?答案是 infer。
比如提取元组类型的第一个元素:
type First<Tuple extends unknown[]> = Tuple extends [infer T, ...infer R]
? T
: never;
type res = First<[1, 2, 3]>; // type res = 1
# 联合:|
联合类型(Union)类似 js 里的或运算符 |,但是作用于类型,代表类型可以是几个类型之一。
type Union = 1 | 2 | 3;
# 交叉:&
交叉类型(Intersection)类似 js 中的与运算符 &,但是作用于类型,代表对类型做合并。
type ObjType = { a: number } & { c: boolean };
注意,同一类型可以合并,不同的类型没法合并,会被舍弃,返回 never
# 映射类型
比如我们把一个索引类型的值变成 3 个元素的数组:
type MapType<T> = {
[Key in keyof T]: [T[Key], T[Key], T[Key]]
}
type res = MapType<{a: 1, b: 2}>;
// type res = {
a: [1, 1, 1];
b: [2, 2, 2];
}
# 类的概念
可以理解为生成对象的模板,通过类可以实例化一个对象,对象就是我们实际可以操作的东西
# 由三部分组成
属性
构造器
方法
# extends 继承
搭配 class 使用,类之间的继承
子类从基类中继承属性和方法
# super 关键字
ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。
代表父类的构造函数,但是返回的是子类的实例,即 super
内部的 this
指的是子类。
class Person {
constructor() {
console.log(new.target.name); // new.target 指向当前正在执行的函数
}
eat() {
console.log('eating');
}
}
class Man extends Person {
constructor {
// 等同 Person.prototype.constructor.call(this, props)
super(); // 代表引入父类的构造函数,但是返回的是子类的实例
}
rudeEat() {
super.eat(); // 此时super被当做对象使用,相当于Person.prototype.eat()
}
}
new A(); // A
new B(); // B
# implements 实现
搭配 Interface 使用,类和接口之间的派生
通过 implements 可以确定派生关系, 也就是说这个类必须实现接口当中的所有方法
# 修饰符
访问权限修饰符 | 访问范围 |
---|---|
public | 默认权限,全部都可以访问 |
protected | 可以在当前类或者派生类中使用 |
private | 只能在当前类中使用 |
readonly | 只读,等于把 set 方法给干掉了,只能 get |
# static
静态属性/方法,无需实例化即可访问
# 类型推断
只需提供必要的类型,ts 能够自动帮助我们推导出全部类型
# 可选参数/默认参数
?:
关键字,实现同 ES6
可选链和默认值:? 和 ?? ,下面这两种写法等价:
const res = data?.name ?? 'zzt';
const res2 = data && data.name || 'zzt';
# 泛型
混淆点:
与 any 不同,any 放弃了类型系统;而泛型表示暂时不确定具体类型,但是需要约束
核心写法:
<T>(arg: T)
并不是 T 字母有什么特殊含义,一种规范而已。
# 例子
比如一个 add 函数既可以做整数加法、又可以做浮点数加法,利用泛型我们只需要声明一个函数
T add<T>(T a, T b) {
return a + b;
}
add(1,2);
add(1.111, 2.2222);
返回对象某个属性值的函数
function getPropValue<T>(obj: T, key): key对应的属性值类型 {
return obj[key];
}
# 支持类型编程的类型系统
在 Java 里面,拿到了对象的类型就能找到它的类,进一步拿到各种信息,所以类型系统支持泛型就足够了。
但是在 JavaScript 里面,对象可以字面量的方式创建,还可以灵活的增删属性,拿到对象并不能确定什么,所以要支持对传入的类型参数做进一步的处理。
对传入的类型参数(泛型)做各种逻辑运算,产生新的类型,这就是类型编程。
比如上面那个 getProps 的函数,类型可以这样写:
function getPropValue<T extends object, Key extends keyof T>(
obj: T,
key: Key
): T[Key] {
return obj[key];
}
// 这里的 keyof T、T[Key] 就是对类型参数 T 的类型运算。
细品下面这段逻辑,能够融会贯通泛型概念
// 泛型基本使用方法
function identity<T>(arg: T): T {
return arg;
}
identity(1); // ok
identity('foo'); // ok
// 泛型类型
let myIdentity1: <T>(arg: T) => T = identity;
// 泛型接口定义
interface GenericIdentityFn {
<T>(arg: T): T;
}
let myIdentity2: GenericIdentityFn = identity;
myIdentity2(1); // ok
// 通过泛型接口限定类型
interface GenericIdentityFn2<T> {
(arg: T): T;
}
let myIdentity3: GenericIdentityFn2<string> = identity;
myIdentity3('foo'); // ok
myIdentity3(1); // error
# 总结:
TypeScript 给 JavaScript 增加了一套类型系统,但并没有改变 JS 的语法,只是做了扩展,是 JavaScript 的超集。
这套类型系统支持泛型,也就是类型参数,有了一些灵活性。而且又进一步支持了对类型参数的各种处理,也就是类型编程,灵活性进一步增强。
现在 TS 的类型系统是图灵完备的,JS 可以写的逻辑,用 TS 类型都可以写。
但是很多类型编程的逻辑写起来比较复杂,因此被戏称为类型体操。