TypeScript 详解
一、TS 基础概念
1. 什么是TS
a. 对比原理
- 是JS的一个超集,在原有基础上,添加了
可选静态类型
基于类的面向对象编程
TS的使用场景
面向项目:
TS - 面向解决大型复杂项目,繁杂架构以及代码维护的场景
JS - 脚本化语言,用于面向简单页面场景
自主检测:
TS - 编译期间,主动发现并指出错误
JS - 无编译阶段
类型检测:
TS - 强类型
JS - 弱类型
运行流程:
TS - 依赖编译,依靠编译打包后,翻译成JS
JS - 可直接运行于浏览器中
复杂特性:
TS - 模块化、泛型、接口
b. 安装运行
npm install -g typescript
tsc -v
tsc test.ts
// 面试点:ts相较于js优势,功能 => 上面的四个点
// ts是如何实现这些功能的 => 多了编译时 => ts官方编译器 / babel
2. TS基础类型与写法
- boolean string number array null undefined
// es
let isEnabled = true;
let class = 'ts';
let classNum = 2;
let classArr = ['basic', 'execute'];
// ts
let isEnabled: boolean = true;
let class: string = 'ts';
let classNum: number = 2;
let classArr: string[] = ['basic', 'execute'];
let classArr: Array<string> = ['basic', 'execute'];
- tuple - 元组
let tupleType: [string, boolean]
tupleType = ['ts', true];
- enum - 枚举
// 数字类枚举 - 默认从零开始,依次递增
enum Score {
BAD,
NG,
GOOD,
PERFECT
}
let score: Score = Score.BAD;
// 字符串类型
enum Score {
BAD = 'BAD',
NG = 'NG',
GOOD = 'GOOD',
PERFECT = 'PERFECT'
}
// 反响映射
enum Score {
BAD,
NG,
GOOD,
PERFECT
}
let scoreName = Score[0]; // 'BAD'
let scoreValue = Score['BAD']; // 0
// 异构 - 字符串 + 数字
enum Score {
A,
B,
C = 'C',
D = 'D',
E = 6,
F,
}
// 面试题:手写实现一个异构枚举
let Enum;
(function(Enum) {
Enum['A'] = 0;
Enum['B'] = 1;
Enum['C'] = 'C';
Enum['D'] = 'D';
Enum['E'] = 6;
Enum['F'] = 7;
Enum[0] = 'A';
Enum[1] = 'B';
Enum[6] = 'E';
Enum[7] = 'F';
})(Enum || (Enum = {}))
- any unknown void
- any类型是TypeScript类型系统中的顶级类型,它表示任意类型的值。
当一个变量的类型被指定为any时,TypeScript编译器将不会对它进行类型检查,你可以对它进行任何操作,包括获取不存在的属性、调用任何方法等。
使用any类型会使得TypeScript退化成JavaScript,因为关闭了类型检查,所以应该尽量避免使用any类型。
- any类型是TypeScript类型系统中的顶级类型,它表示任意类型的值。
// any - 绕过所有检查 => 类型检测和编译筛查全部失效
let anyValue: any = 123;
anyValue = 'anyValue';
- unknown类型是TypeScript 3.0中引入的,它是TypeScript类型系统中的另一种顶级类型。
unknown类型表示任何值,但是与any类型不同的是,unknown类型的值在执行大多数操作之前需要进行类型检查或类型断言。
这意味着unknown类型提供了比any类型更安全的类型检查。
// unknown - 绕过赋值检查 => 禁止更改传递
// 传递
let unknownValue: unknown;
unknownValue = 'unknownValue';
let value1: unknown = unknownValue; // OK
let value2: any = unknownValue; // OK
let value3: boolean = unknownValue; // NOK
- void类型表示没有任何类型,通常用于函数没有返回值的情况。
当一个函数的返回类型是void时,它实际上表示该函数不期望返回任何值(或者说返回undefined)。
// void - 声明函数返回值
function voidFunction(): void {
console.log('no return');
}
- never类型表示不应该出现的类型,它通常用于函数永远不会返回值的情况,例如函数抛出异常或进入无限循环。
never类型是所有类型的子类型,可以赋值给任何类型,但是没有任何类型可以赋值给never类型(除了never本身)。
使用never类型可以帮助TypeScript编译器进行更严格的类型检查,以确保代码的完整性。
// never - 永不返回
function error(msg: string): never {
throw new Error(msg);
}
function longlongloop(): never {
while(true) {}
}
- object | Object | {} - 对象
// object - 非原始类型
// 声明文件
interface ObjectConstrutor {
create(o: object | null): any;
}
// 逻辑文件
const proto = {
a: 1
};
Object.create(proto); // OK
// Object - 原型属性
// Object.prototype上属性
interface Object {
constructor: Function;
toString(): string;
valueOf(): Object;
}
// {} 空对象 - 没有成员的对象
const a = {} as A;
a.class = 'es';
a.age = 30;
二、接口 - interface
- 对行为的抽象,具体行为由类实现
interface Class {
name: string;
time: number;
}
let course: Class = {
name: 'ts',
time: 2
}
// 只读
interface Class {
readonly name: string;
time: number;
}
// 任意
interface Class {
readonly name: string;
time: number;
[propName: string]: any;
}
// 面试题 - 和JS的引用不同 < = > const
let arr: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = arr;
ro[0] = 12; // Error - 赋值
ro.push(5); // Error - 增加
ro.length = 100; // Error - 长度改写
arr = ro; // Error - 覆盖
三、交叉类型
// 合并
interface A { x: D }
interface B { x: E }
interface C { x: F }
interface D { d: boolean }
interface E { e: string }
interface F { f: number }
type ABC = A & B & C;
let abc: ABC = {
x: {
d: false,
e: 'class',
f: 5
}
}
// 合并冲突
interface A {
c: string;
d: string;
}
interface B {
c: number;
d: string;
}
type AB = A & B;
// 合并的关系是且 => c: never
四、断言 - 类型声明、转换
// 尖括号
let anyValue: any = 'hi ts';
let anyLength: number = (<string>anyValue).length; // 阶段性声明
// as声明
let anyLength: number = (anyValue as string).length;
// 非空判断
type ClassTime = () => number;
const start = (classTime: ClassTime | undefined) => {
let num = classTime!(); // 确认一定不会为空
}
// 面试题
const tsClass: number | undfined = undefined;
const course: number = tsClass!;
// 使用的意义 => 告知编译器,运行时下,会被赋值
五、类型守卫
interface Teacher {
name: string;
courses: string[];
score: number;
}
interface Student {
name: string;
startTime: Date;
score: string;
}
type Class = Teacher | Student;
function startCourse(cls: Class) {
if ('courses' in cls) {
// teacher的逻辑
}
if ('startTime' in cls) {
// student的逻辑
}
}
function startCourse(cls: Class) {
if (cls intanceof Teacher) {
// teacher的逻辑
}
if (cls intanceof Student) {
// student的逻辑
}
}
function startCourse(name: string, score: string | number) {
if (typeof score === 'number') {
// teacher的逻辑
}
if (typeof score === 'string') {
// student的逻辑
}
}
六、TS进阶
1. 泛型 - 重用
function startClass<T, U>(name: T, score: U): T {
return name + score;
}
console.log(startClass<string, number>('yy', 5))
function startClass<T, U>(name: T, score: U): string {
return `${name}${score}`;
}
function startClass<T, U>(name: T, score: U): T {
return (name + String(score)) as any as T;
}
// T U K 键值 | V 值 | E 节点
2. 装饰器 - decorator
function Yunyin(target: Function): void {
target.prototype.startClass = function(): void {
// start逻辑
}
}
// 类装饰器
@Yunyin
class Course {
constructor() {
// 业务逻辑
}
}
// 属性装饰器
function nameWrapper(target: any, key: string) {
// 逻辑处理
Object.defineProperty(target, key, {
// 劫持
});
}
class Course {
constructor() {
// 业务逻辑
}
@nameWrapper
public name: string;
}
// 方法装饰器
3. 原理解析
// 1. 源码输入
let a: number = 2;
// 2. scanner扫描器扫描 => 识别内容范围生成数据流
[
"let": "keyword",
"a": "identifier",
"=": "assignment",
"2": "integer",
";": "eos" (end of statement)
]
// number
// 3. 解析器 parser 生成语法树 - AST
{
operation: "=",
left: {
keyword: 'var',
// ...
}
}
// 4. 绑定器 binder 主要职责 创建symbols
// node.symbol
// 5. 校验器 checker 检查TS语法错误 => 检查器中进行的
// 6. 发射器 emitter根据每个节点的检查结果产出node翻译成js
TS常用的工具类型
- Partial:构造一个类型,将T的所有属性设置为可选。
// 内部实现原理
type Partial<T> = {
[K in keyof T]?: T[K]
}
// 通常使用
interface defaultOpt {
uuid: string | void 0 ;
requestUrl: string | void 0 ;
}
type pluginOps = Partial<defaultOpt >;
//继承使用
export interface initOptions extends Partial<DefaultOptons> {
requestUrl: string;
}
- Require:将类型T所有属性设为require。
这个就和上面的是相反的,都变为必填
type Require<T> = {
[P in keyof T]-?: T[P];
}
- Readonly:构造一个类型,将T的所有属性设置为只读。
type Readonly<T> = {
readonly [K in keyof T]: T[K];
}
- Record<K, T>:构造一个对象类型,其属性键为K,属性值为T。
//内部实现原理
type Record<K extends keyof any, T> = {
[P in K]: T;
}
// 解释一下
type a = {
title: string;
}
type b = 'uuid' | 'requestUrl' | 'name' ;
type c = Record<b,a>;
// 达成的效果就是
type c = {
uuid:{
title: string ;
};
requestUrl:{
title: string ;
};
name:{
title: string ;
}
}
- Pick<T, K>:从T中选择一组属性K来构造一个新的类型。
// 内部实现原理
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
// 解释一下
type a = {
uuid: string ;
requestUrl?:string ;
name:string;
}
type b = Record<a,'uuid' | 'name'>;
// 达成的效果就是
type b = {
uuid: string ;
name:string;
}
- Omit<T, K>:从T中剔除一组属性K来构造一个新的类型。
// 实现原理
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
// 解释一下
interface Todo {
uuid: string ;
requestUrl?:string ;
name?:string;
}
type T1 = Omit<Todo, 'uuid'>
// 达成的效果
type T1 = {
requestUrl?:string ;
name?:string;
}
- Exclude<T, U>:从T中剔除所有可以赋值给U的元素类型。
// 内部实现原理
type Exclude<T, U> = T extends U ? never : T;
// 简单演示一下
type T = 'a' | 'b' | 'c';
type U = 'a' | 'b';
// 使用 Exclude 工具类型
type Excluded = Exclude<T, U>;
// 结果是 'c',因为 'a' 和 'b' 都可以赋值给 U,所以被排除掉了
const value: Excluded = 'c';
// 稍微复杂的演示
type P = number | string | (()=>void)
type L = Function
type PL = Exclude<P,L>
// 达成的效果
type PL = number | string ;
// 就是说P是完整的一个类型,L是指要从P中删除掉的类型
- Extract<T, U>:提取T中可以赋值给U的所有元素类型。
//实现原理
type Extract<T, U> = T extends U ? T : never;
// 简单演示一下
type T = 'a' | 'b' | 'c' | 'd';
type U = 'a' | 'c' | 'f';
// 使用 Extract 工具类型
type Extracted = Extract<T, U>;
// 结果是 'a' | 'c',因为只有 'a' 和 'c' 可以赋值给 U
const value1: Extracted = 'a';
const value2: Extracted = 'c';
// const value3: Extracted = 'b'; // 错误,'b' 不能赋值给 U
// 说白了,就是找两个类型的交集
- NonNullable:从T中剔除null和undefined。
type NonNullable<T> = T extends undefined | null ? never : T;
type T = string | number | null | undefined;
// 使用 NonNullable 工具类型
type NonNullableType = NonNullable<T>;
// 结果是 string | number,因为 null 和 undefined 被剔除了
const value1: NonNullableType = 'hello';
const value2: NonNullableType = 42;
// const value3: NonNullableType = null; // 错误,null 被剔除了
// const value4: NonNullableType = undefined; // 错误,undefined 被剔除了
- ReturnType:获取函数类型T的返回类型。
function add(a: number, b: number): number {
return a + b;
}
// 使用 ReturnType 工具类型
type AddReturnType = ReturnType<typeof add>;
// 结果是 number,因为 add 函数的返回类型是 number
const result: AddReturnType = add(1, 2);
- InstanceType:获取构造函数类型T的实例类型。
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
// 举个栗子
class MyClass {
constructor(public name: string) {}
}
// 使用 InstanceType 工具类型
type MyInstanceType = InstanceType<typeof MyClass>;
// 结果是 MyClass,因为 MyClass 的实例类型是 MyClass
const instance: MyInstanceType = new MyClass('example');
还看到了关于instanceType比较深层次的解释,类的值类二象性,不过多赘述了,等我学明白了再讲讲