typescript 笔记
typescript 学习笔记
基本类型
/**
* Boolean
*/
const bol1: boolean = true;
/**
* String
*/
const str2: string = `bol: ${true}`;
/**
* Number
*/
const num1: number = 123;
/**
* Array
*/
const arr0: (number|string)[] = [1, '2'];
const arr2: Array<string|boolean|number> = ['a', 'b', false, 5];
/**
* Object
*/
const obj: object = { a: 1, b: 'a' };
/**
* Tuple(元组)
*
* 已知元素数量和类型且可重复的 数组
*/
const tpl: [string, number, boolean, boolean] = ['a', 2, true, true];
/**
* Enum(枚举)
*
* 为一组 数值或字符串 指定别名(方便记忆)
*/
enum Color {
Red,
Green,
Blue = 4,
Pink = 'pink',
};
// 枚举属性是只读的,不支持修改
// 访问枚举值 类似访问对象属性
// 变量的类型限定为枚举时,变量的值只能是枚举成员之一
const r: Color = Color.Red; // r === 0
const g: Color = Color.Green; // g === 1
const b: Color = Color.Blue; // b === 4
const p: Color = Color.Pink; // p === "pink"
// 与对象的区别
enum EDirection {
Up,
Down,
Left,
Right,
}
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
// 枚举能直接应用类型
function run1(dir: EDirection): void {};
run1(EDirection.Up);
// 对象(字面量)不具有类型,需要先申明类型再应用
// (keyof typeof ODirection) 表示限制对象属性为 "Up" | "Down" | "Left" | "Right"
// 枚举则默认存在这种机制
type Direction = typeof ODirection[keyof typeof ODirection];
function run2(dir: Direction): void {};
run2(ODirection.Up);
/**
* Any
*
* 任意类型,会跳过类型检查
* 不指定类型默认为 any
*/
let anyVar: any = 123;
const anyArr: any[] = [ true, 123, 'aaa' ];
/**
* Void
*
* 无任何类型,与 any 相反
*/
function voidFn(): void {}
let void1: void = undefined;
/**
* Undefined
*/
let u: undefined = undefined;
/**
* Null
*/
let n: null = null;
/**
* Unknown
*
* 未知类型,相当于 any,但是不允许执行变量的方法(更安全)
*/
let uk: unknown = 'abc';
// uk.toString(); // Error
/**
* Never
*
* 永不存在的值 的类型
*/
// 例如抛出异常的函数,不能具有可访问的终结点
// 因为抛出异常会终止函数执行,也就无法到达终点返回值了
function err(): never {
throw new Error('message');
}
接口
/**
* 属性类型接口
*/
// 限制某个对象的属性类型(这里是入参)
function fn1(obj: { str: string }): string {
return obj.str;
}
fn1({ str: "abc" });
// 使用接口实现
interface IObj {
str: string;
// 不能包含具体实现
// str2: string = 'abc'; // Error
// 属性可以是个复杂对象
aa?: {
ba: {
ca: string;
},
bb: [ number, boolean ];
};
/**
* 可选属性
*/
num?: number;
/**
* 只读属性,类似于 const 变量
*/
readonly bol?: boolean;
/**
* 索引签名 额外的属性
* 'string', 'number', 'symbol'.
*/
[propName: string]: any;
}
// 应用接口类似于申明类型
function fn2(obj: IObj) {
return obj.str;
}
fn2({
str: "abc",
num: 123,
aa: {
ba: { ca: "a" },
bb: [1, true]
},
});
// 属性类型校验,应为 string
// fn2({ str: 123 }); // Error
// 属性存在性校验,必须存在 str,不存在 abc
// fn2({ abc: 'abc' }); // Error
const obj1: IObj = { str: "abc", bol: true };
// 修改只读属性会报错
// obj1.bol = false; // Error
// 由于使用索引签名,传入额外的属性不会报错
const obj2: IObj = { str: "abc", other: "none" };
/**
* 方法类型接口
*/
interface IMethod {
fn1(arg: string): boolean;
}
const fnObj: IMethod = {
// 普通方法
fn1: function (arg: string) {
return arg === "";
},
// 简写格式
// fn1(arg: string) { return arg === ''; }
// 箭头函数格式
// fn1: (arg) => arg === '',
};
/**
* 函数类型接口
*/
interface IFn {
// 函数签名:
// (参数:类型):返回类型
(arg1: string, arg2: number): boolean;
}
// 函数传参可以少于函数签名定义的参数,但不能异于,
// 比如:arg1: number, arg3: boolean
let fn3: IFn = function (arg1: string): boolean {
return arg1 !== "";
};
/**
* 索引类型接口
*/
interface IArr {
// 索引签名:(参数类型只能为 number | string)
// 表示用数字类型的索引访问时,会得到字符类型的值
[index: number]: string;
// 访问字符类型索引时返回任意类型值,比如访问方法名
[prop: string]: any;
// [] 中冒号前的单词(index, prop)可任意指定,
// 只用于阅读,类似于注释,ts 不作处理和判断
}
const arr1: IArr = ["a", "b", "c"];
// 访问数字类型索引
arr1[0]; // 'a'
// 访问字符类型索引
arr1["length"]; // 3
/**
* class 类型接口
*/
interface IPerson {
// 描述类的公共属性,无法包含私有属性
firstName: string;
lastName: string;
age?: number;
}
// 实现接口的类
class Person implements IPerson {
public firstName: string;
public lastName: string;
public age?: number;
constructor(obj: IPerson) {
this.firstName = obj.firstName;
this.lastName = obj.lastName;
this.age = obj.age;
}
}
/**
* 接口继承
*/
interface IOne {
attr1: string;
}
interface ITwo extends IOne {
attr2: number;
}
const obj3: ITwo = {
// 少写任何一个都会报错
attr1: "123",
attr2: 123,
};
// 继承多个接口
interface IThree extends IOne, ITwo {
attr3: boolean;
}
const obj4: IThree = {
attr1: "123",
attr2: 123,
attr3: true,
};
/**
* 混合类型的接口
*/
// 同时声明多种类型,为复杂结构所使用
interface IMulti {
(arg1: number): void;
attr1: string;
fn1(arg: number): number;
}
// 函数类型
const fn5: IMulti = function () {};
// 属性
fn5.attr1 = "def";
// 方法
fn5.fn1 = (arg: number) => arg;
/**
* 接口继承类
*/
class CSPub {
// 公共属性
public pub: string = "aaa";
}
class CSPri {
// 私有属性
private pri: string = "bbb";
getPri?(): string {
return this.pri;
}
}
class CSPro {
// 受保护属性
protected pro: string = "ccc";
}
// 接口会继承类的 所有成员(只有类型声明,不包含具体实现),
// 即继承类的 公共,私有,受保护的成员
interface IClassPub extends CSPub {
attr1: string;
}
interface IClassPri extends CSPri {
attr1: string;
}
interface IClassPro extends CSPro {
attr1: string;
}
// 继承了只有公共成员的类的接口,可以被任意 其他类 实现
// 通常用于复用类的约束
class CSI1 implements IClassPub {
pub = "aaa";
attr1 = "ddd";
}
// 具有私有成员或受保护成员的接口,只能由 继承类 实现
// 通常用于拓展对类的子类的约束
class CSI2 extends CSPri implements IClassPri {
attr1 = "ddd";
}
class CSI3 extends CSPro implements IClassPro {
pro = "ccc";
attr1 = "ddd";
}
/**
* 接口合并
*/
interface IMerge {
str: string;
}
interface IMerge {
num: number;
}
// 接口可以多次重复申明,引用时会取所有申明的 并集
const mergeObj: IMerge = {
str: 'abc',
num: 123,
};
类
/**
* 申明类
*/
class CS1 {
attr1: string; // 不加成员修饰默认是公共(public)
public attr2: string = ""; // 公共属性(可以被派生类、实例对象访)
private attr3: string = ""; // 私有属性(只能自己访问)
protected attr4: string = ""; // 受保护属性(只能被自己和派生类访问)
readonly attr5: string = ""; // 只读属性(只能被当前类的构造函数修改)
static attr7: string = ""; // 静态属性(只能直接通过类访问,实例和当前类 this 都访问不到)
/**
* 构造函数
*
* 具有 protected 构造函数的类,只能被派生类访问,不能实例化(不影响派生类是否能实例化)
* 具有 private 构造函数的类,既不能实例化,也不能被继承
*/
constructor(arg: string, public attr6?: string) {
/**
* 参数属性
*
* public attr6: string 称为 参数属性,用于同时定义并初始化一个指定成员,
* 通过在参数前添加成员修饰符形成,如 public, private, readonly,
* 功能等同于:
*
* attr6: string;
*
* constructor(attr6: string) {
* this.attr6 = attr6;
* }
*/
// 获取所有属性
this.attr1 = arg + this.attr2 + this.attr3 + this.attr4;
// 只读属性可以在该类中修改
this.attr5 = "1";
}
// 方法不加修饰符也默认是 public
getAttr1(): string {
// 只读属性无法在该类的方法中修改
// this.attr5 = '2'; // Error
return this.attr1;
}
// 静态方法
static getStatic(): string {
// 无法访问当前类(this)的属性与方法
// this.attr1; // Error
// 静态属性只能通过该类直接访问(this 获取不到值)
return CS1.attr7;
}
}
/**
* 继承
*
* 被继承类称为 父类,继承类称为 派生类
*/
class CS2 extends CS1 {
constructor(arg: string) {
// 调用父类的 constructor 方法
// 只要申明了构造函数,必须调用 super()
// (不申明构造函数,则不影响该类的属性方法调用父类属性与方法)
super(arg, "");
// 父类的只读属性不能被派生类修改
// this.attr5 = '1';
}
// 甚至可以重写父类的方法(只有该类实例化后能访问到)
getAttr1(): string {
return this.attr1;
}
getAttrs(): string {
// 获取父类的 公共、受保护成员(获取不到私有成员)
return this.attr1 + this.attr2 + this.attr4;
}
getSuperAttr1(): string {
// 获取父类的方法
return this.getAttr1();
}
}
/**
* 类的实例(实例化)
*/
const cs1 = new CS1("aaa", "a");
// 实例只能访问公共属性与方法
cs1.attr1;
cs1.attr2;
cs1.attr5;
cs1.attr6;
cs1.getAttr1();
// 修改只读属性会报错
// cs1.attr5 = '1';
// 访问类的静态属性与方法
CS1.attr7;
CS1.getStatic();
const cs2 = new CS2("bbb");
// 派生类实例只能访问 父类的公共属性与方法、派生类的公共属性与方法
cs2.attr1;
cs2.attr2;
cs2.attr5;
cs2.attr6;
cs2.getAttr1();
cs2.getAttrs();
cs2.getSuperAttr1();
/**
* 实例的类型
*/
// 表示 cs21 应是类 CS2 或其派生类的实例
let cs21: CS1;
cs21 = new CS1('1');
cs21 = new CS2('1');
// 如果成为了其他类的实例会报错
// class CS22 {}
// cs21 = new CS22(); // Error
// 赋予其他值也会报错
// cs21 = '123'; // Error
/**
* 抽象类
*
* 用作其他派生类的基类,不能被直接 实例化
* 与接口的区别是,可以包含成员的 实现细节
*/
abstract class CS3 {
// 可以申明普通成员并初始化值,派生类中可直接访问
attr1: string = "111";
/**
* 抽象属性
*
* 必须在派生类中申明该属性,该基类中不会存在该属性
* 抽象属性也能和成员修饰符结合
*/
abstract attr2: string;
protected abstract attr3: string;
// 抽象类也可以有构造函数
constructor(public name?: string) {}
/**
* 抽象方法
*
* 不能包含具体实现,但必须在派生类中实现
*/
abstract fn1(arg: string): boolean;
// 普通方法,可以包含具体实现
fn2(): string {
return this.attr1;
}
}
class CS4 extends CS3 {
attr2: string = '';
attr3: string = '';
fn1(arg: string) {
return !!arg;
}
}
// 抽象类的子类可以实例化
const cs4 = new CS4();
cs4.attr1; // "111"
cs4.attr2;
cs4.fn1('1'); // true
cs4.fn2(); // "111"
函数
/**
* 函数类型定义
*/
function fn5(arg: string): boolean {
return arg === "";
}
// 匿名函数
let fn6 = function (arg: string): boolean {
return arg === "";
};
// 箭头函数
fn6 = (arg: string): boolean => arg === "";
// 箭头式类型定义
// 箭头前为参数类型,后为返回值类型
let fn61: (arg: string) => boolean;
fn61 = (arg) => arg === '';
/**
* 可选参数
*/
function fn7(arg1: string, arg2?: string): boolean {
return arg2 ? arg2 === "" : arg1 === "";
}
// 可选参数必须放到必输参数后面
// function fn8(arg2?: string, arg1: string): boolean {
// return arg2 ? arg2 === "" : arg1 === "";
// }
fn7('a');
fn7('a', 'b');
// 参数多了会报错
// fn7('a', 'b', 'c');
/**
* 参数默认值
*
* 加默认值的参数会成为 可选参数
*/
function fn9(arg: string = 'abc'): boolean {
return arg === '';
}
fn9(); // false
fn9(''); // true
// 默认值参数可以放到必输参数前面,但是必须传入 undefined 才能获取到默认值,不传报错
function fn10(arg1 = 'abc', arg2: string): boolean {
return arg1 === arg2;
}
// fn10(''); // Error
fn10('', 'abc'); // false
fn10(undefined, 'abc'); // true
/**
* 剩余参数
*
* 剩余的参数可以一个没有或多个,即被当成可选参数
*/
function fn11(arg1: string, ...args: (string | number)[]): boolean {
return arg1 === args[0];
}
fn11('a', 'b', 123);
fn11('a');
/**
* 重载(overload)
*
* 函数根据传参的不同会有不同的返回类型
*/
// 重载手段是为一个函数提供多个类型定义
function fn12(x: number[]): number;
function fn12(x: string): string;
function fn12(x: boolean): boolean;
function fn12(x: any): any {
// 上面一行只是函数的实现签名,为了兼容上面两个重载签名,不能被直接调用,
// 同时它也并不算作一个重载,真正的重载签名只有最上面的三个
if (typeof x === 'object') {
return x[0];
} else {
return x;
}
}
// 都能通过类型检查
fn12([1, 2, 3]);
fn12('abc');
// 没在重载定义中的类型会报错
// fn12(undefined); // Error
泛型
// 函数返回类型与传参类型一致
function fn13(arg: number): number {
return arg;
}
fn13(123);
// 如果传参支持多种类型,就需要写多个重载定义,如果使用 any,
// 虽然能包含所有情况,但不能保证参数和返回值类型一致;
function fn14(arg: any): any {
return arg + '';
}
fn14(123);
fn14('abc');
/**
* 类型变量
*/
// 使用 类型变量 可以解决这个问题
// 变量 T 用于捕获并存储用户传入的类型
// 这里的函数 fn15 便是泛型(泛型函数),即适用于多个类型的函数
function fn15<T>(arg: T): T {
return arg;
}
// 使用泛型时在类型变量的位置传入类型值
fn15<number>(123);
// 通常是使用时省略,直接利用编译器的 类型推断 来判断类型
fn15('abc');
// 但是一些复杂类型情况下,编译器可能不能自动推断出类型,
// 这时就任然需要使用 <> 来指定类型
interface IObj2 {
a: number;
b: string;
};
fn15<IObj2>({ a: 123, b: 'abc' });
/**
* 使用
*/
// 由于 T 表示任意类型,所以不能直接访问某些属性
function fn16<T>(arg: T): T {
// return arg.toString(); // Error
return arg
}
// 如果是复合类型,则可以使用某些固有属性
function fn17<T>(arg: T[]): string {
return arg.toString();
}
// 类型变量可以使用其他字母或者单词(通常使用 T)
// 也可以存在多个变量
function fn18<M, My, other>(arg: M): M {
let one: My;
let two: other;
return arg;
}
// 存在多个类型变量时依次指定
fn18<string, number, boolean>('abc');
/**
* 泛型接口
*/
// 接口里的泛型
interface IGeneric {
<T>(arg: T): T;
}
let fn19: IGeneric = function(arg) {
return arg;
// 和接口申明不一致会报错
// return arg + '';
}
// 针对整个接口的泛型
interface IGeneric2<T> {
a: T;
b: T[];
c(arg: T): T;
}
const obj5: IGeneric2<number> = {
a: 123,
b: [1, 2, 3],
c: (arg) => arg + 1
};
/**
* 泛型类
*/
class CS5<T> {
constructor(public attr: T) {}
fn(): T {
return this.attr;
}
// 静态成员不能使用泛型类型
// static a: T = '';
}
const cs5 = new CS5<number>(123);
cs5.fn(); // 123
/**
* 泛型约束
*
* 需要约束泛型具有某些属性,可以 extends 指定接口
*/
interface IObj3 {
length: number; // 约束为具有 length 属性的任意类型
}
function fn20<T extends IObj3>(arg: T): number {
return arg.length;
}
fn20('abc'); // 3
// 报错,因为数字没有 length 属性
// fn20(123); // Error
// 泛型约束中使用类型参数
// 表示第二个参数需要是第一个参数对象的属性
function fn21<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
fn21({ a: 1 }, 'a');
// b 不是第一个参数对象的属性
// fn21({ a: 1 }, 'b'); // Error
高级类型
/**
* 交叉类型
*
* 多个类型合并为一个类型,合并类型需同时满足多种类型
* 类似类型的 与(&)运算
*/
interface IStr {
str: string,
}
interface INum {
num: number,
}
// 合并后的交叉类型
type StrAndNumType = IStr & INum;
const obj6: StrAndNumType = {
// 缺少属性会报错
str: 'abc',
num: 123,
// 多余属性也会报错
// bol: true,
};
/**
* 联合类型
*
* 类似类型的 或(|)运算,需至少满足多种类型的一种
*/
type StrOrNumType = IStr | INum;
let strOrNum: StrOrNumType;
// 满足联合类型中任意一种 即可通过
strOrNum = { str: 'abc' };
strOrNum = { num: 123 };
/**
* 类型守卫
*
* 在运行时 检查以确保某个作用域类型 的表达式
*/
// 如果函数入参是联合类型,那么内部访问该参数时,用任意一种类型访问参数都会报错,
// 因为 ts 判断联合类型时只要有一个类型不满足,就不会通过,所以导致每个都不通过;
function fn22(arg: number | number[]): number {
// if (arg > 0) {
// return arg + 1; // Error
// } else if ( arg.length > 0) { // Error
// return arg.pop(); // Error
// } else {
// return 0;
// }
// 为了避免这种情况的报错,需要使用 类型断言
if (arg as number > 0) {
return arg as number + 1;
} else if ( (<number[]>arg).length > 0) {
return (<number[]>arg).pop() as number;
} else {
return 0;
}
}
// 由于多次书写类型断言很麻烦,所以 ts 出现了 类型守卫 的机制:
/**
* typeof 类型守卫:
*/
function fn23(arg: number | number[]): number {
// ts 会识别 typeof 语句,并将当前 if 区块中的 arg 都当成 number,
// 类似于加上了隐式的类型断言
if (typeof arg === 'number' && arg > 0) {
return arg + 1;
// 其他 if 区块也能识别为指定类型
} else if (typeof arg === 'object' && arg.length > 0) {
return arg.pop() as number;
} else {
return 0;
}
}
/**
* truthiness 类型守卫
*/
function fn24(arg: number[] | null): void {
if (typeof arg === 'object') {
// 不能直接使用数组的特性,即使使用了 typeof,因为 null 也是 'object' 类型,
// 并且 null 不能使用 for...of...
// for (let i of arg) { i; } // Error
// 使用 truthiness 类型守卫可以进一步限制变量为 数组类型,
// !!arg 或者 Boolean() 等涉及布尔运算的操作都能触发这种类型守卫;
if (arg) {
for (let i of arg) { i; }
}
}
}
/**
* 等式类型守卫
*/
function fn25(a: string | number, b: string | boolean): void {
// 直接使用参数的方法会报错,因为可能是 number 类型
// a.toUpperCase(); // Error
// b.toUpperCase(); // Error
// 使用了等式判断后,ts 会自动取二者的交集类型,即 string,就可以正常调用方法了,
// 也可以使用类似 a === null 的直接与基本类型对比的判断触发类型守卫;
if (a === b) {
a.toUpperCase();
b.toUpperCase();
}
}
/**
* instanceof 类型守卫
*/
function fn26(arg: Number | String): void {
//(x instanceof y) 用于检查 x 的原型链中是否包含 y.prototype
// 直接调用方法会报错,Number 类型不存在该方法
// arg.toUpperCase(); // Error
// instanceof 表达式会触发类型守卫,限制 arg 为 String 类型
if (arg instanceof String) {
arg.toUpperCase();
}
}
fn26('abc');
// fn26(true); // Error
/**
* 自定义类型守卫
*
* 首先自定义一个函数来判断参数类型,返回值类型具有固定格式:
* param is type
* 其中 param 必须是参数,type 是类型
*/
function isStr(arg: string | number): arg is string {
// 内部可以使用其他 类型守卫
return typeof arg === 'string';
// 也可以直接使用类型断言
// return (arg as string).toUpperCase !== undefined;
}
function fn27(arg: string | number): void {
// 像使用其他类型守卫一样引用 自定义函数,触发类型守卫
if (isStr(arg)) {
arg.toUpperCase();
} else {
arg += 1;
}
}
/**
* 类型别名
*
* 为一些基本类型或复杂类型定义一个别名
*/
type MyString = string;
type StrOrNum = string | number;
type StrAndNum = string & number;
type callback = (arg: string) => boolean
// 像使用基本类型一样使用类型别名
let myString: MyString;
// 类型别名可以是 泛型
type TypeArr<T> = T[];
const strArr: TypeArr<string> = ['a'];
// 类型别名可以 自己引用自己(嵌套)
type NestObj = {
str: string,
obj?: NestObj,
}
const nestObj: NestObj = {
str: 'abc',
obj: { str: 'def' },
}
/**
* 字面量类型
*
* 使用该类型的变量值需要等于字面值
*/
type OneOrTwo = 'one' | 'two' | 3 | true;
let num: OneOrTwo;
num = 'one';
num = 'two';
num = 3;
num = true;
// 取类型中不存在的值会报错
// num = 'three'; // Error
/**
* 可辨识联合(Discriminated Union)
*
*
*
*
* 当联合类型中的每一个类型,都包含一个为字面量类型的共有属性时,
* ts 将会把这个联合类型识别为 可辨识联合
*/
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
// 该联合类型每个接口类型都含有 kind 属性,并且是字符串字面量类型,
// kind 被称为可辨识联合 Shape 的 可辨识特征(Discriminant)
type Shape = Square | Circle;
function area(s: Shape): number {
// 直接访问会提示 Circle 没有 size 属性
// return s.size ** 2; // Error
// 使用 switch 或者 if 判断 kind 属性后,ts 会自动限制为指定类型
switch (s.kind) {
case "square":
return s.size ** 2;
// 但是仍然存在一个问题,漏写判断时 ts 并不会提示
// case "circle":
// return Math.PI * s.radius ** 2;
default:
return 0;
}
}
// 解决提示漏写 case 的问题,可以利用 ts 类型守卫的特性,
// 即代码中判断了所有联合类型的可能性之后,该联合类型会被限制为 never 类型,
// 所以在最后尝试将联合类型赋值给一个 never 类型,如果报错就说明还有情况没有判断到;
function area2(s: Shape): number {
switch (s.kind) {
case 'square':
return s.size ** 2;
case "circle":
return Math.PI * s.radius ** 2;
default:
// 如果上面的 case 漏写一个,下面就会报错,
// 提示 不能将 xxx 分配给 never;
let result: never = s;
return result;
}
}
/**
* 索引类型
*/
interface IObj4 {
name: string,
age: number,
}
// keyof 为索引查询操作符,表示类型是接口所有属性的 联合类型
// 等价于:
// type Key4 = 'name' | 'age';
type Key1 = keyof IObj4;
let key1: Key1;
key1 = 'name';
key1 = 'age';
// key4 = 'hobby'; // Error
// 或者获取普通对象的类型的索引
let obj7 = { name: '', age: 0 };
type Key2 = keyof typeof obj7;
let key2: Key2 = 'name';
/**
* 映射类型
*
* 将类型中每个属性进行映射转变,类似数组的 map 方法
*/
interface IObj5 {
name: string;
age: number;
}
type Map1 = {
// 为接口中每个属性添加 只读与可选 标识
readonly [K in keyof IObj5]?: IObj5[K];
// 无法新增额外的属性
// hobby: string; // Error
};
// 等同于:
// type Map1 = {
// readonly name?: string;
// readonly age?: number;
// }
// 也可以写一个通用的转变泛型
type TMap2<T> = {
readonly [K in keyof T]?: T[K];
}
type Map2 = TMap2<IObj5>;
// ts 也内置了一些条件类型,如:
/**
* Readonly - 属性全转换为只读:
*/
type Map3 = Readonly<{ a: string }>; // { readonly a: string }
/**
* Partial - 属性全转换为可选:
*/
type Map4 = Partial<{ a: string }>; // { a?: string | undefined }
/**
* Pick - 属性拷贝
*/
type Map5 = Pick<{ a: string; b: number }, 'a'>; // { a: string }
/**
* Exclude<T, U> - 从 T 中剔除可以赋值给 U 的类型
*/
type Map6 = Exclude<'a' | 'b', 'b'>; // 'a'
/**
* Extract<T, U> - 从 T 中提取可以赋值给 U 的类型
*/
type Map7 = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
/**
* NonNullable<T> - 从 T 中剔除 null 和 undefined
*/
type Map8 = NonNullable<'a' | null | undefined>; // 'a'
/**
* ReturnType<T> - 获取函数返回值的类型
*/
type Map9 = ReturnType<() => string>; // string
装饰器
/************************************************************
* 装饰器
*
* 用于标注或修改类及其成员,是一种特殊类型声明,
* 支持被附加到类声明、方法、访问符、属性或参数上
************************************************************/
/**
* 基本使用
*/
// 声明装饰器函数:
// 函数一共接收三个参数:
// 1. 装饰器所装饰的目标(target);
// 2. 装饰目标的属性名(key);
// 3. 装饰目标的属性描述值(descriptor);
function decBasic(target: unknown, key: string, descriptor: object): void {
console.log('decorator');
}
console.log('=============== Basic ===============');
// 使用装饰器,直接写在装饰目标的上方,
// 这里装饰目标是类的 getNum 方法;
class DecBasicCS {
@decBasic
getNum(): void {
console.log('class');
}
}
const decBasicCS = new DecBasicCS();
decBasicCS.getNum();
// "decorator"
// "class"
/**
* 装饰器工厂
*
* 可自定义如何装饰一个目标(允许为装饰器传参)
*/
function decFactory(str: 'add' | 'plus') {
// 这里可写自定义装饰逻辑
let returnFn;
if (str === 'add') {
// 装饰器工厂函数最后需要返回一个装饰器函数
returnFn = function(target: unknown, key: string): void {
console.log('add');
}
} else {
returnFn = function(target: unknown, key: string): void {
console.log('plus');
}
}
return returnFn;
}
console.log('=============== Factory ===============');
class DecFactoryCS {
// 装饰器工厂函数可以像普通函数一样传参执行(运行时执行)
@decFactory('add')
useAdd(): void {}
// 使用不同参数针对不同装饰目标,或使用不同装饰逻辑
@decFactory("plus")
usePlus(): void {}
}
const decFactoryCS = new DecFactoryCS();
decFactoryCS.useAdd(); // "add"
decFactoryCS.usePlus(); // "plus"
/**
* 装饰器组合
*
* 一个装饰目标可同时被多个装饰器叠加装饰
*/
function decMultiOne(target: unknown, key: string): void {
console.log('one');
}
function decMultiTwo(target: unknown, key: string): void {
console.log('two');
}
function decMultiOneFact() {
console.log('one fact');
return function(target: unknown, key: string): void {
console.log('one');
}
}
function decMultiTwoFact() {
console.log('two fact');
return function(target: unknown, key: string): void {
console.log('two');
}
}
console.log('=============== Multi decorator ===============');
class DecMultiCS {
// 多个装饰器叠加时,会从装饰目标开始,依次向上执行,
// 类型于执行 decMultiOne(decMultiTwo(getNum))
@decMultiOne
@decMultiTwo
getNum(): void {}
// 装饰器也可以横向书写
@decMultiOne @decMultiTwo getNum1(): void {}
@decMultiOne @decMultiTwo
getNum2(): void {}
// 使用装饰器工厂也能叠加,但是执行顺序是:
// - 先自上而下执行工厂函数的逻辑
// - 然后自下而上执行装饰器的逻辑
@decMultiOneFact()
@decMultiTwoFact()
getNum3(): void {}
}
const decMultiCS = new DecMultiCS();
decMultiCS.getNum();
// "two"
// "one"
decMultiCS.getNum1();
// "two"
// "one"
decMultiCS.getNum2();
// "two"
// "one"
decMultiCS.getNum3();
// "one" "fact"
// "two" "fact"
// "two"
// "one"
/**
* 多种装饰目标类型
*
* 执行顺序:
* 1. 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器 应用到每个 实例成员。
* 2. 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器 应用到每个 静态成员。
* 3. 参数装饰器应用到构造函数。
* 4. 类装饰器应用到类。
*/
function decClass() {
console.log('class fact');
return function(target: Function): void {
console.log('class');
}
}
function decAttr() {
console.log('attr fact');
return function(target: unknown, key: string): void {
console.log('attr', key);
}
}
function decMethod() {
console.log('method fact');
return function(target: unknown, key: string): void {
console.log('method', key);
}
}
function decGetter() {
console.log('getter fact');
return function(target: unknown, key: string): void {
console.log('getter');
}
}
function decSetter() {
console.log('getter fact');
return function(target: unknown, key: string): void {
console.log('getter');
}
}
function decArg() {
console.log('arg fact');
return function(target: unknown, key: string, index: unknown): void {
console.log('arg');
}
}
console.log('=============== Multi target ===============');
// 类装饰器,实际是作用于类的构造函数,
// 即装饰器的 target 入参是构造函数(并且只能定义这一个入参)
// 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明
@decClass()
class MultiDecCS {
/**
* 属性装饰器
*
* target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
* 属性装饰器没有第三个入参 属性描述符;
*/
// 公共属性
@decAttr()
public publicAttr: number = 0;
// 私有属性
@decAttr()
private privateAttr: number = 0;
// 受保护属性
@decAttr()
protected protectedAttr: number = 0;
// 静态属性
@decAttr()
static staticAttr: number = 0;
/**
* 方法装饰器
*
* target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
* 如果方法装饰器返回一个值,它会被用作方法的属性描述符
*/
@decMethod()
getVar(): void {}
// 静态方法
@decMethod()
getStaticVar(): void {};
/**
* getter 修饰器
*
* target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
* 但不能同时向同一个属性的 getter 和 setter 设置修饰器
* 如果访问器装饰器返回一个值,它会被用作方法的属性描述符
*/
@decGetter()
get var(): undefined { return; }
/**
* setter 修饰器
*/
@decSetter()
set var1(val: unknown) {}
/**
* 参数装饰器
*
* target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
* 入参第三个是当前参数在入参数组中的索引
*/
getArg<T>(@decArg() arg: T): T { return arg; }
}
const multiDecCS = new MultiDecCS();