TS小白的学习日记

423 阅读10分钟

基础类型

1、unknown和as类型转换

// 当有字符串类型变量需要转换为数值类型时
let string : string = '999'
let num = string as unknown as number

同时,使用unknown类型声明的变量的方法也不能被使用

let unk : unknown = 'this is an unknown variable'
unk.slice(1) // error

unknown虽然不是任何类型,但是也不允许赋值给任何类型变量

let unk : unknown = 'this is an unknown variable'
let str : string = unk // error

2、void

当函数没有明确返回值(即返回nullundefinded时),允许使用void类型约束函数

function v : void (){}
function v : void (){ return }
function v : void (){return null}
function v : void (){return undefined}
function v : void (){ console.log() }
function v : void (){return true} // error

void也允许用来约束变量,但只允许分配nullundefindedvoid类型的值

let val : void
let val : void = null
let val : void = undefined

3、never

never指永不可被赋值的变量,或永不会有返回结果的函数。但是什么情况下才会有不会返回结果的函数?

function ne : never(){
    throw new Error('程序错误')
    // 或是永不退出的循环
    while(true){}
} 
// 但是当函数有几乎到达结束时,never将会报错
function ne : never(){
    if(num>10){
        throw new Error('程序错误')
    }
} // error

函数

1、函数参数定义

当多个函数的参数定义重复时,使用type来定义参数是个很好的选择

// 定义公共参数
type userInfo = { name:string , age:number , sex?:string|number } // 性别为选填,且可为数字或字符串

function showUser (user:userInfo){
    ...
}
    
function updateUser (user:userInfo){
    ...
}

2、函数结构约束

type不仅可以用来约束函数的参数,也可以用来约束函数的结构

type userInfo = { name:string , age:number , sex?:string }

type showResult = ( user:userInfo ) => boolean
// 至此,就可以用showResult来约束函数的结构必须和其相同
let updateUserInfo:showResult = ( u:userInfo ):boolean =>{
    ...
    return true
}

3、剩余参数

JS中,可以使用...语法来接收长度不确定的参数,TS中也同样可以,而且也可以约束其类型

function log( ...args:number[] ){
    return args.reduce( (p,n) => p+n ,0 )
}
log( 1,2,3,4,5 )

4、元组

在某些情况下,可能需要对数组中的某个参数进行类型约束,这时就可以使用元组

type tuple = [ string , number ]

let arr = [ '男' , 23 ] // true
let arr = [ 23 , '男' ] // false

5、枚举

TS中,允许为一组数据命名,以方便查找对应的映射

enum color { red:1 , blue , green , yellow }
let c = color[2] // blue
let g =  color.green // 3

6、断言 as const

TS中,使用断言可以将变量约束为某一类型甚至某一个值,而as const则更加强力,可以约束变量的同时使其无法被修改

let str = '这是const' as const
str = '这是let'  // error

哪怕是将变量作为值赋值给对象,也不能被修改

let str = '这是const' as const
let obj = {
    name : str
} // name的值为`这是const`
obj.name = '这是let' // error

如果变量没有被设置为as const的话,将作为类型赋值给属性

let str:string = '这是let'
let obj = {
    name : str
} as const // name没有被赋值,为string类型,且不允许被赋值

如果对象或数组使用了as const,就是将其设置为了只读,不允许修改

let arr = [ 1 , '2' , true ] as const
arr[2] = '修改第二个' // error

假如不对数组进行as const限制的话,如果变量从数据中取值的类型将会是不确定的

let arr = [ 1 , '2' , false ]
let a = arr[1]
// 此时a的类型是[string|number|boolean],因此可以给他赋三个类型中的任意值
a = 2 // true
a = 'str' // true
a = true // true

但是如果给数据进行as const限制的话,变量从数据中取值就会被指定类型

let arr = [ 1 , '2' , true ]
let a = arr[2]
// 此时a的类型被指定为boolean类型
a = 2 // false
a = 'str' // false
a = false // true

as cosnt的实际运用

当方法有多个返回值且被集成为一个数组时,可以使用断言结合解构语法来准确的获得值

function show() {
    let a = '这是一个string'
    let b = (x: number, y: number): number => x + y
    return [a,b] as const
    /*
    也可以使用其他方法
    1. return [ a , b ] as [ typeof a , typeof b ]
    2. return [ a , b ] as [ String , Function ]
    */
}
let [ str , fnc ] = show()
/*
如果在函数返回值中没有使用`as const`,也可以在函数执行的时候用元组规定返回值类型
1. show() as [ string , function ]
2. show() as [ string , ( x:number , y:number)=>number ]

注意!!!不能使用 show() as const
由于as const只能对明确类型的变量使用,在函数返回值中的类型是不明确的,无法准确地约束类型
*/
fnc( 1 , 2 ) // 3

7、非空断言

在某些情况下,当获取的值被编译器判定不确定是否存在时,会自动为变量约束一个null类型

<!-- 有一个div需要被更改内容 -->
<div id="fake-div">
    这是一个span
</div>
/* 如果直接获取的话,变量就会同时拥有null和Element两种类型 */
let div:HTMLDivElement = document.querySelector('.fake-div')
/* 
如果开启了严格校验不允许赋值为null,可以设置两种类型
但是如果这样设置的话,在接下来使用div变量时,编译器将不会自动补全HTML语法
*/
let div:HTMLDivElement | null = document.querySelector('.fake-div')

非空断言应使用于无法判断是null或者具体类型的情况下,用来为获取不确定是否存在值的对象指定具体类型

// 可以使用两种方法来约束其只有一种类型
// 1.断言
let div:HTMLDivElement = document.querySelector('.fake-div') as HTMLDivElement
// 2.非空断言
let div:HTMLDivElement = document.querySelector('.fake-div')!
// 此时,div变量的类型就只有HTMLDivElement了

8、构造函数断言

在构造函数中,也可以对实例的属性进行类型约束

class person {
    el:HTMLDivElement
    constructor(el:HTMLDivElement) {
        this.el=el
    }
}
let man = new person('str') // error

let div = document.querySelector('div') as HTMLDivElement
let girl = new person(div) // true

小tips

使用TS时,尽可能为变量约束具体的类型,这样方便编译器提供更具体的代码补全功能

1、类型约束类

JS的类也可以使用类型进行属性约束

class person{
    name:string
    age:number
    constructor( n:string , a:number ){
        this.name=n
        this.age=a
    }
    showInfo(){
        return `${this.name}的年龄为${this.age}`
	}
}
// 还可以用一个约束实例对象的数据,来装载特定的势力对像
let man :person[] = [ pereson('hqh',21) , person('jxy',21) ] // 只允许由person创建的实例对象插入

2、泛型

之前所使用的都是指定的类型(str:string),但是如果需要动态的判断类型呢?这时就可以使用泛型

function show <a> ( arg:a ):a{
    return arg
}
let str = show <string>('string')
let num = show <number>(123)
let boo = show <boolean>(true)
let boo2 = show (false) // 自动判断为boolean类型

注意!泛型并不能准确的判断出每一种类型,还是需要调用时详细指明类型!

3、泛型约束

当函数返回的值是某个类型的特征时(如length属性),此时需要为泛型指定包含该特征的类型

function show<t extends String | number[]>(para: t): number {
	return para.length;
}

show([1, 2, 3]) // 3
show('this is a string') // 16
show(123) // error number类型没有length属性

// 也可以自定义需要约束的特征,也有同样的效果
type newType = { length: number }
function show<t extends newType>(para: t): number {
	return para.length;
}

// 还可以约束形参为某一特定类型,如数组,再通过泛型来约束形参的具体类型(字符串数组等)
function show<t>(para: t[]): number {
	return para.length;
}

show<string | number>([1, 2, 3, "4"]) // 4

4、泛型结合类的使用

泛型可以用来约束函数,自然也可以用来约束类。泛型结合类的使用,使得可以更加方便地管理特定类型的数据

type userType = { name: string; age: number };
class collection<T extends userType> {
    // 约束构造函数的传值类型必须为userType的结构
	data: T[];
	constructor(...args: T[]) {
		this.data = args;
	}
	showName() {
		const arr: string[] = [];
		for (const item of this.data) {
			arr.push(item.name);
		}
		return arr;
	}
	showAge() {
		const arr: number[] = [];
		for (const item of this.data) {
			arr.push(item.age);
		}
		return arr;
	}
}

let hqh: userType = { name: "hqh", age: 21 };
let jxy: userType = { name: "jxy", age: 21 };
let collectionUser = new collection(hqh, jxy);
console.log(collectionUser.showName());
console.log(collectionUser.showAge());

5、public、protected和private

  • public允许所有实例对象使用
  • protected仅允许构造函数本身及其子类使用
  • private仅允许构造函数本身使用

6、接口

接口interface和类型type都可以用来约束类型

// 接口中也可以动态设置类型
interface article<lockType, cType> {
	title: string;
	detail: string;
	isLock: lockType;
	comments: cType[];
}

interface commentsType {
	userId: string | number;
	content: string;
}

// 动态地将article中的isLock属性设置为boolean类型
let articleA: article<boolean, commentsType> = {
	title: "月亮与六便士",
	detail: "这是一本非常优美的书",
	isLock: false,
	comments: [
		{
			userId: 1212,
			content: "这真是一本好书!",
		},
		{
			userId: "我是好人",
			content: "这本书太好看了!",
		},
	],
};

7、只读属性readonly

class axios {
	readonly site: string = "https://www.baidu.com";
    /*
    protected | private readonly site:string = "https..."
    如果为readonly属性加上protected或private的话,实例化对象就不能进行访问
    */
	constructor(site?: string) {
        /*
        在构造函数中,readonly的属性是可以被修改值的
        这里是如果实例化对象时传值了,则取传的值,否则取默认值
        */
		this.site = site || this.site;
	}
	get() {
		return this.site;
	}
}

let Axios = new axios("https://www.sina.com");
// 此时site属性是无法被修改的,只允许读取
Axios.site='123' // error

8、构造函数在TS中的使用

class User {
    /*
    如果不使用`public name: string`方法定义的话就需要:
    name: string
     constructor(name:string) {
         this.name = this.completeName(name);
     }
    */
	constructor(public name: string) {
        // 在构造函数内部可以使用实例方法
		this.name = this.completeName(name);
	}
	completeName(name): string {
		return `${name} -- 这是一个帅哥`;
	}
}

使用typeinterface约束类

1、单例模式

有些情况下(如网络请求方法对象),可能只需要构造函数生产出一个实例对象

class Axios {
	private static instance: Axios | null = null;
	private constructor(private site: string = "https://www.baidu.com") {
		this.site = this.site;
		console.log("创建了一个Axios实例");
	}
	static createAxios(site: string): Axios {
		if (Axios.instance == null) {
			Axios.instance = new Axios(site);
		}
        // 这样每次调用都只会将已经缓存的实例对象返回,就不会重复创建消耗内存
		return Axios.instance;
	}
}

let requestObj = Axios.createAxios("https://www.sina.com");
// 这种模式适合应用于仅需要一个请求地址的场景中

2、抽象类约束子类属性

当需要子类中都具有相同的属性时,可以使用抽象类对子类进行约束

abstract class animation {
    // 抽象属性和方法,可以没有,但是如果要使用必须添加
    // 且抽象类不能被实例化
	abstract name: string;
	abstract move(): void;
	protected getPos(): { x: number; y: number } {
		return { x: 100, y: 200 };
	}
}

class player extends animation {
	constructor() {
		super();
	}
	name: string = "玩家";
	move(): void {
		console.log(`${this.name}向X轴移动了${super.getPos()["x"]}米,向Y轴移动了${super.getPos()["y"]}米`);
	}
}

class enemy extends animation {
	name: string = "敌方坦克";
	move(): void {
		console.log(`${this.name}移动了`);
	}
}

3、抽象类+接口约束类

抽象类可以用来约束子类的属性和方法,但是如果所有约束都加到抽象类中就会很臃肿,因此可以使用抽象类+接口的方式来约束子类

interface AnimationInterface{
	name: string
	move():void
}

abstract class animation {
	protected getPos(): { x: number; y: number } {
		return { x: 100, y: 200 };
	}
}

// 使用`implements`关键字即可使用接口,可以同时使用多个接口约束
class player extends animation implements AnimationInterface {
	constructor() {
		super();
	}
	name: string = "玩家";
	move(): void {
		console.log(`${this.name}向X轴移动了${super.getPos()["x"]}米,向Y轴移动了${super.getPos()["y"]}米`);
	}
}

class enemy extends animation implements AnimationInterface {
	name: string = "敌方坦克";
	move(): void {
		console.log(`${this.name}移动了`);
	}
}

4、接口约束对象

interface objInterface {
	name: string;
	age?: number;
	info?(): void;
	// 允许添加多个`key`为string类型,`value`为any类型的值
	[key: string]: any;
}

const person: objInterface = {
	name: "hqh",
	school: "华广",
	class: "网工一班",
};

5、enum枚举配合接口和数组

enum chooseSex {
	boy,
	girl,
}

interface userInfo {
	name: string;
	age: number;
    // sex属性的值只允许从枚举对象中选择
	sex: chooseSex;
}

const hqh: userInfo = {
	name: "hqh",
	age: 21,
	sex: chooseSex.boy,
};
const jxy: userInfo = {
	name: "jxy",
	age: 21,
	sex: chooseSex.girl,
};

const userList: userInfo[] = [hqh, jxy];

6、接口约束箭头函数

interface show{
    (name:string,age:number):string
}
const showName:show=(name:string,age:number):string=>`${name}-${age}`

7、接口合并

TS中,只需要将接口重命名,就可以实现接口合并

interface UserInfo{
    name:string
}
interface UserInfo {
	school: string;
	showStudentId(studentName: string): string;
}

const student:UserInfo={
    name: "hqh",
    school: "GCU",
    showStudentId: function (studentName: string): string {
        return studentName
    },
}

8、interfacetype

不同点

  1. interface可以直接通过重命名来合并,type需要 通过&操作符合并

    interface user{
        name:string
    }
    interface user{
        age:number
    }
    
    type name={ name:string }
    type age={ age:number }
    type user= name & age
    
    // 如果同时声明两个重名type,先声明的将会被覆盖
    type user={ name:string } // 被覆盖
    type user={ age:number }
    
  2. type可以直接对联合类型起别名,interface只能对属性声明联合类型

    interface UserInfo {
    	name: string| number;
    }
        
    type UserType = string | number;
    
  3. interfacetype都可以继承

    interface user{ name:string }
    interface userAge extends user{ age:number }
    
    type user={ name:stirng }
    type userAge ={ age:number } & user
    
    // interface 继承 type
    type user={ name:string }
    interface userAge extends user { age:number }
    
    // type继承interface
    interface user{ name:string }
    type userAge = { age:number } & user
    
  4. type可以用typeof获取类型

    let UserName:string
    type UserType = typeof UserName
    
  5. type可以声明元组

type ArrayType = [ string , number , boolean ] // 每个位置只能填入特定类型的值

6.type可以结合三元表达式和泛型进行赋值类型

type userType<t> = t extends string ? string : number

装饰器

1. 类装饰器

  1. 配置

使用装饰器,首先需要在tsconfig.json文件中打开"experimentalDecorators""emitDecoratorMetadata"配置以确保装饰器被引擎解析

  1. 类装饰器基本使用

装饰器本质其实是函数,可以为所修饰的添加属性

const myDecorate = (target: Function) => {
	target.prototype.getPos = (posiObj: { add: string; meter: number }) => {
		console.log(`当前的位置是${posiObj.add}${posiObj.meter}米处`);
	};
};

@myDecorate
class person {
	public getPos(posiObj: { add: string; meter: number }) {}
}

let hqh = new person();
hqh.getPos({ add: "佛山", meter: 2000 });

/*
 还可以这样写
 但是下面的写法,将不会校验getPOs方法中形参的格式,不建议使用
*/
@myDecorate
class person {
	public getPos() {}
}

let hqh = new person();
(hqh as any).getPos({ add: "佛山", meter: 2000 });
  1. 装饰器也可以叠加使用

const myDecorate: ClassDecorator = (target: Function) => {
		...
};

const sucessDecorate: ClassDecorator = (target: Function) => {
	target.prototype.successLog = (msg: string) => {
		console.log(msg);
	};
};

@myDecorate
@sucessDecorate
class person {
    // 如果不声明则提示错误,但是不影响使用
	successLog: Function;
	public getPos(posiObj: { add: string; meter: number }) {}
	public requestSuccess() {
		this.successLog("获取地址操作成功");
	}
}
  1. 类装饰器工厂
function myDecorateFac(type: string): ClassDecorator {
switch (type) {
	case "学校":
		return (target: Function) => {
			target.prototype.showSchoolName = (): string => "GCU";
		};
		break;

	case "公司":
		return (target: Function) => {
			target.prototype.showCompanyName = (): string => "Tencent";
		};
		break;
	default:
		return (target: Function) => {
			target.prototype.nothing = (): string => "没有信息";
		};
	}
}

2、函数装饰器

  1. 基本使用

    const myFncDecorate: MethodDecorator = (
    	target: Object,
    	propertyKey: string | symbol,
    	descriptor: PropertyDescriptor,
        // 也可以写成`descriptor: TypedPropertyDescriptor<any>`,一样的结果
    ) => {
    	descriptor.value = (): void => {
    		console.log("这是函数装饰器修改后的结果");
    	};
    };
    
    class show {
    	@myFncDecorate
    	public showMsg(params: string): void {
    		console.log(params);
    	}
    }
    
    let me = new show();
    me.showMsg("这是原始方法输出的结果");
    
  2. 利用descriptor参数对方法静态方法做限制

    // 在默认情况下,由构造函数创建出来的实例方法或者静态方法都是可以修改的
    class show {
    	public showMsg(params: string): void {
    		console.log(params);
    	}
    	public static showName(): void {
    		console.log("这是默认静态方法");
    	}
    }
    
    let me = new show();
    me.showMsg = (): void => {
    	console.log("这是修改后的showMsg方法");
    };
    show.showName = (): void => {
    	console.log("这是修改后的静态方法");
    };
    me.showMsg("12"); // 这是修改后的showMsg方法
    show.showName(); // 这是修改后的静态方法
    
    // 利用装饰器的descriptor参数,可以修改方法为可读属性
    const myFncDecorate: MethodDecorator = (
    	target: Object,
    	propertyKey: string | symbol,
    	descriptor: PropertyDescriptor,
    ) => {
    	descriptor.writable = false;
    };
    
    class show {
    	@myFncDecorate
    	public showMsg(params: string): void {
    		console.log(params);
    	}
    	public static showName(): void {
    		console.log("这是默认静态方法");
    	}
    }
    
    let me = new show();
    me.showMsg = (): void => { // error
    	console.log("这是修改后的showMsg方法");
    };
    show.showName = (): void => { // error
    	console.log("这是修改后的静态方法");
    };
    
    

3、装饰器实现内容高亮

在页面中实现按键高亮,首先需要获取内容,然后根据内容创建新的DOM节点,最后替换原来的节点

<body>
    <!-- 要加上defer属性,确保DOM解析完毕后执行JS脚本 -->
	<script defer src="./highlight.js"></script>
	<div id="value-box">这是需要高亮的div</div>
	<button onclick="changeDiv()">开始替换</button>
</body>
<script lang='ts'>
    const myNewDecorate: MethodDecorator = (
	arget: Object,
	propertyKey: string | symbol,
	descriptor: TypedPropertyDescriptor<any>,
) => {
	const method = descriptor.value;
	const targetDiv = document.querySelector("#value-box") as HTMLDivElement;
	const resDiv = document.createElement("div");
	resDiv.innerHTML = method();
	resDiv.style.color = "red";
	resDiv.id = "value-box";
	descriptor.value = () => {
		targetDiv.replaceWith(resDiv);
	};
};

class changeDic {
	@myNewDecorate
	public static getDivValue() {
		let value = document.getElementById("value-box")?.innerHTML;
		return value;
	}
}

function changeDiv() {
	changeDic.getDivValue();
}
</script>

装饰器小tips

装饰器会在DOM节点加载之前执行一次,提前创建出其中的变量以及方法的重写(重写后的方法第一次不会执行),如果包含对DOM节点的操作就会失败,因此需要为JS脚本加上defer属性确保DOM解析完成才执行脚本

4. 延时装饰器

在某些情况下需要装饰器延迟执行,这时候可以使用工厂装饰器来实现可复用的装饰器

const sleepDecorate =
	(time: number): MethodDecorator =>
	(...args: any[]) => {
		const [, , descriptor] = args;
		const method = descriptor.value;
		descriptor.value = () => {
			setTimeout(() => {
				method();
			}, time);
		};
	};

class printTimeAfterTime {
	@sleepDecorate(2000)
	public static getTime() {
		let date = new Date().toLocaleString();
	}
}

printTimeAfterTime.getTime();

还是装饰器

1、全局错误装饰器

当需要定制错误信息时,可以通过全局错误装饰器来实现

const errorDecorate: MethodDecorator = function (
	target: Object,
	propertyKey: string | symbol,
	descriptor: PropertyDescriptor,
) {
	const method = descriptor.value;
	descriptor.value = () => {
		try {
			method();
		} catch (error: any) {
			console.log(`%c出现错误!`, "color:red;font-size:30px");
			console.log(`%c${error.message}`, "color:blue;font-size:26px");
		}
	};
};

class RequestObj {
	@errorDecorate
	get() {
		throw new Error("请求失败!");
	}
}

2、全局装饰器工厂(进阶,可定制版)

const ErrorDecorate =
	(title: string = "出现错误!", color: string = "red", fontSize: number = 20): MethodDecorator =>
	(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
		const method = descriptor.value;
		descriptor.value = () => {
			try {
				method();
			} catch (error: any) {
				console.log(`%c${title}`, `color:${color};font-size:${fontSize}px`);
				console.log(`%c${error.message}`, `color:blue;font-size:26px`);
			}
		};
	};

class RequestObj {
	@ErrorDecorate("这是自定义的错误提示", "#111111", 30)
	get() {
		throw new Error("请求失败!");
	}
}

3、检测用户是否登录

const userState = {
	userName: "hqh",
	isLogin: false,
};

const AuthorizeArticle: MethodDecorator = (
	target: Object,
	propertyKey: string | symbol,
	descriptor: PropertyDescriptor,
) => {
	const method = descriptor.value;
	if (userState.isLogin) {
		return method();
	}
	alert("请登录后操作");
	location.href = "login.html";
};

class ArticleControl {
	@AuthorizeArticle
	ShowArticle() {
		console.log("展示文章");
	}
	@AuthorizeArticle
	StoreArticle() {
		console.log("保存文章");
	}
}

4、管理用户权限

type UserType = {
	userName: string;
	isLogin: boolean;
	userPermission: string[];
};

const userState: UserType = {
	userName: "hqh",
	isLogin: true,
	userPermission: ["store"],
};

const AuthorizeArticle =
	(permission: string[]): MethodDecorator =>
	(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
		const method = descriptor.value;
		const validate = () =>
			permission.every(key => {
				return userState.userPermission.includes(key);
			});

		descriptor.value = () => {
			if (userState.isLogin === true) {
				if (validate()) {
					return method();
				} else {
					alert("权限不足,请联系管理员");
					return;
				}
			} else {
				alert("请登录后操作");
				location.href = "login.html";
			}
		};
	};

class ArticleControl {
	@AuthorizeArticle(["show"])
	ShowArticle() {
		console.log("展示文章");
	}
	@AuthorizeArticle(["store", "manage"])
	StoreArticle() {
		console.log("保存文章");
	}
}

5、装饰器模拟网络请求后调用方法执行

interface info {
	name: string;
	id: number;
}

const requestDecorate =
	(url: string): MethodDecorator =>
	(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
		const method = descriptor.value;
		// 模拟网络请求后返回的promise
		new Promise<info[]>(resolve => {
			setTimeout(() => {
				resolve([
					{ name: "hqh", id: 1212 },
					{ name: "jxy", id: 121212 },
				]);
			}, 1000);
		}).then(value => {
			method(value);
		});
	};

class getUserInfo {
	@requestDecorate("https://www.baidu.com")
	private get(value: info[]) {
		value.forEach(item => {
			console.log(item.name);
		});
	}
}

6、属性装饰器

使用属性装饰器,动态地把属性变为key:value格式

const propertyDecorate: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
	let value: string;
	Object.defineProperty(target, propertyKey, {
		get() {
			return value;
		},
		set(v: string) {
			this.value = `${propertyKey as string}${v}`;
		},
	});
};

class propertyClass {
	@propertyDecorate
	name: string;
	constructor(parameters: string) {
		this.name = parameters;
	}
}

随机产生一个十六进制颜色,并赋值给属性

function getRandomColor() {
	var str = "#";
	var arr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
	for (var i = 0; i < 6; i++) {
		var num: number = parseInt((Math.random() * 16).toString());
		str += arr[num];
	}
	return str;
}

const colorDecorate: PropertyDecorator = (target: object, key: string | symbol) => {
	var color = getRandomColor();
	Object.defineProperty(target, key, {
		get() {
			return color;
		},
	});
};

class colorDiv {
	@colorDecorate
	public color: string;

	colorfulDiv() {
		document.body.insertAdjacentHTML(
			"beforeend",
			`<div style="background:${this.color}">这是一个div</div>`,
		);
	}
}

7、使用reflect-metadata验证参数

reflect-metadata相当于为参数添加了一个标记属性,而这个标记属性可以通过重写后的Reflect中的Reflect.getMetadata来获取

import "reflect-metadata";

const paramDecorate: ParameterDecorator = (
	target: Object, // 构造函数或实例对象
	propertyKey: string | symbol, // 方法名
	parameterIndex: number, // 需要标记的形参下标
) => {
	let requiredParamIndexArr: number[] = [];
	requiredParamIndexArr.push(parameterIndex);
	Reflect.defineMetadata("required", requiredParamIndexArr, target, propertyKey);
};

const methodDecorate: MethodDecorator = (
	target: Object,
	propertyKey: string | symbol,
	descriptor: PropertyDescriptor,
) => {
	const method = descriptor.value;
	descriptor.value = function () {
		const requiredArr = Reflect.getMetadata("required", target, propertyKey) || [];
		let keyIndex: number;
		if (
			requiredArr.some((item: number) => {
				return (
					(item > arguments.length && (keyIndex = item)) ||
					(arguments[item] == undefined && (keyIndex = item))
				);
			})
		) {
			console.error(`第${keyIndex + 1}个参数没有填写,请完整填写参数!`);
			return;
		}
		method(...arguments);
	};
};

class paramTest {
	@methodDecorate
	showMsg(title: string, content: string, @paramDecorate index: number) {
		console.log(`${title} -- ${content} -- ${index}`);
	}
}

以上是我个人在TS学习过程中的笔记,如果有错误的地方恳请大佬指出,磕头了咚咚咚

笔记还有很多没有完善的地方,如泛型的工具类型inkeyof等等,如果有很不幸看到这里的朋友(狗头),可以到大佬的文章《一份不可多得的 TS 学习指南》洗洗眼 :)

PS:推荐一位我学习JS和TS的老师向军大叔,老师的JS课程非常详细,也适合新手学习(不是广告),建议如果是对JS不是特别熟悉的朋友先过一遍JS的课程再学习TS,会轻松很多

还有就是一个帮助TS学习的训练场:TS类型体操。强烈建议在学习TS的过程中,每天刷几题(附上掘金大佬的答案集),对加深TS类型印象非常有帮助