【TypeScript历险记】

1,695 阅读18分钟

记录从零开始的TypeScript基础
简要概括在学习过程中的重点难点
作为今后复习的文章 ✨

引入

typeScript 是 javaScript 的超集:TypeScript = JavaScript + type + (some other stuff) 其他特性

typescript中文文档

特性

  • 面向对象(prototype、Function、Object)
    • Class interface
  • 类型检查(静态类型、强类型)
    • typeScript 编译成 javaScript时便检查(提前避免bug)
  • 参数自带文档内容
  • IDE或者是编译工具的良好支持(自动完成提示)

安装

  • 创建typeScript-demo文件 -> index.ts文件

  • 全局安装typeScript

npm install typescript -g

  • 编译typeScript代码文件

tsc index.ts -> 产生编译后的index.js文件


类型定义

类型定义

定义变量: [数据类型]

自动编译模式

tsc -w index.ts -> 会自动编译代码

array 数组

定义数组

let myArr: string[] 表示数组中每个元素均为字符串
let myArr: Array<number> 表示该变量为数组且每个元素均为数值型

继承变量类型

  • 变量赋于过类型后
  • 另一变量再赋于后
  • 自动获取该变量类型
let s: String = '123'
let s1 = s
// s1变量类型 -> String

tuple 元组

定义

该集合中有多种数据类型

let my_tuple: [string, number] 第一位为字符串、第二位为数值型

也不能为空、顺序不能颠倒、仅能为两个

编译出来的javaScript为数组

function 函数

常规

// 定义add函数
function add(a: number, b: boolean) 
    { 
        return a + b 
    }  
// 所传值为相对应的数据类型、所传的个数为两个

箭头函数

const add = (a: number, b: boolean) => {
     return a + b
    }

function return value

函数指定return返回值的数据类型 const add = (a: number, b: boolean): number => { return a + b }

返回内容的数据类型为number,否则报错

const add = (a: number, b: boolean):void => { return a + b }

不返回任何值(否则报错)

优势: 明返回值类型可以方便人员处理操作

function 可选参数

一般默认值、可选参数不放在第一位参数

默认值(param = value)

const add = (a: number, b: number = 10): number => { return a + b }

  • 设置b默认值为10

  • 可只传一个参数赋予a

  • 传两个参数赋予a,覆盖b

  • 少传、多传报错

可选参数(?)

const add = (a: number, b?: number = 10): number => { return a }

  • b参数可选传或不传
  • 函数内部需要判断b传或不传的情况

function Rest Rarameters

剩余的参数(…) -> 一般为数组

const add = (a: number, ...num: number[]): number => { 
	return num.reduce(function(total, num) {
		return total + num
	}, a)	// a为初始值
}
let sum = add(20, 30, 40, 50)
// 返回第一个参数与之后参数的总和
// 该…Rest Rarameters数据类似要一致

any 数据类型

let a: any a变量为任何类型的值

可以利用typeof 来判断数据类型来处理操作

let b: any[] b数组内元素均为any

null、undefined 数据类型 | 连环类型

TypeScript自带的判断数据类型

const type = (value: any): string => {
    return `your value is number` 
}

const type1 = (value: any): string => {
    return `your value is number` 
}

// 可传入value数据类型为string或者number
const typ2 = (value: string | number) => {
    return `your value is string or number` 
}

null、undefined为一种数据类型
null、undefined -> 为所有数据类型的子集 -> 就是所有数据类型都可以传入null、undefined

执行命令

  • tsc - -strictNullChecks 文件
    • null、undefined不为子集

class 类

class(模板)new 对象
数据(data)行为(action)

//定义class
class Person {
	firstName: string
	lastName: string
}

//生成对象
let aPerson = new Person()

//赋予对象属性
aPerson.firstName = "xxxx"

methods 方法 constructor 构造器

class Movie {
	name: string;
	private play_count:any;
	
	constructor(name: string = 'xxx') {	// 通过构造器设置初始化内容
		this.name = name;
	}

	display_play_count() {			// methods 方法
		return `${this.play_count} 次数`
	}
}

Inheritance and Polymorphism 继承和多态

继承(extends)

class A extends class B A类继承B类

  • 会覆盖父类 -> 非要用时 -> 父类的 super(指向父类)

多态

let B: classB = new classB( ) classB可以当作一个类型

当B继承于A类时候可以采用 let B: classA = new classB( ) 来进行子实例化父

public、private 成员可见性

成员的可见性 -> public、private -> 对方法或者属性进行修饰

  • 默认为public

    • public 任何属性和方法都可以在实例化的对象中调用
  • private 私有的,只有在内部对象内才能访问,生成的对象无法调用

  • 使用get、set方法(通过class内部定义public的方法来调用)

  • 继承来的private属性和方法 和 原本父类没有区别

protected 成员可见性

  • 权限范围 public > protected > private
    • 意思是public可以调用protected和private
  • 用protected对方法或者属性进行修饰
    • 类似保护一个基石、在基石上的就不管
    • 跟private一样需内部调用
    • 还可以通过继承子类调用
  • 子类均可继承成员可见性属性
  • 若是继承调用一个为private、一个为protected
    • 利用set、get来操作private再调用

constructor 成员可见性

可被继承

成员可见性修饰构造方法

  • 在constructor中调用super( ) -> 重写父类的构造函数
    • 必要时需传入相关的参数
  • protected或private修饰constructor时
    • 当前类不能new
  • 当父类申明protected时
    • 子类通过super( ) 重写后new

作用

  • 当不想被实例化时

    • 用protected修饰
    • 类似于模版
  • 都不想被子类和父类实例化或继承

    • 用private修饰
    • 私有的

Abstract Classes 抽象类

// 定义抽象的class
abstract class Person {
	name: string;
	constructor(name: string) {
		this.name = name;
	}

	display(): void {
		console.log(this.name);
	}
	
	// 抽象方法 没有方法体 -> 由后面继承的来定义 -> 子类必须重写
	abstract find(string): Person;

	// 也有抽象属性 -> 同理子类必须定义该抽象属性
}

class Employee extends Person {
	// 除了继承的属性和方法体 -> 还可以自定义扩展自身
	// 调用父类的方法属性时 -> this指自身类 -> super指父类
	constructor(name: string) {
		super(name);
	}

	find(name: string): Person {    //必须有抽象方法
		//重写方法体
        return 
	}
}

// let p: Person = new Person('xxx');  // 报错不能实例化抽象类
let p: Person = new Employee('xxx');

// 抽象前 -> 也可以定义成员可见属性

static 静态属性和方法

  • 静态属性和方法(可以添加成员可见性修饰)

  • static age: number = 10 -> 调用

    • 静态属性
    • className.age(在内部调用静态属性)
  • static getStaticAge( ) { return my age is ${ className.age }}

    • 静态方法 -> 可用来对应数据库的一个表模型
    • className:getStaticAge -> 调用静态方法

readonly 只读修饰符

  • 在类中 readonly name: string = 'lindada'
    • 修饰name为只读
    • 只读属性不能修改
    • 类似于常量

enum 枚举

enum DaysOfTheWeek { 
	SUN, MON, TUE, WED, THU, FRI, SAT
}
// 定义枚举 -> 是一个集合

let day: DaysOfTheWeek
day = DaysOfTheWeek.MON
// 使用提取出MON -> 值为 1 ->  从 0 开始的number
// 定义时枚举默认值 -> SUN = 100, MON, …

作用

  • 数据库存的类型的number
    • 表现出来是一个有意义的字符串
  • 利用有意义可读性的字符串
    • 更方便操作处理数据

ts-node、nodemon自动化

ts-node

TS在git官方文档

  • npm install -D ts-node

    • 安装 ts-node
  • ts-node ts文件名 -> 运行该文件 -> 自动编译和运行ts

    • 把编译后的js文件放在内存中
    • 若要编译后的js文件 -> 还是需要tsc 文件名

nodemon

nodemon在git官方文档

nodemon: 监控改变并自动化重启

  • npm install nodemon -S -D
    • 安装 nodemon 插件
  • nodemon —exec 文件名 文件目录
    • nodemon —exec ts-node ./index.ts

Interfaces 接口介绍 、鸭子类型

  • duck typing -> 鸭子类型
    • 看起来像鸭子,我们就认为它是鸭子
  • Interface -> 接口 -> 定义一个函数 -> 函数中设定一定规范 -> 操作一些特定的事
// 函数 -> value是接口类型,这个接口类型要有name属性
const sayName = (value) => {
	console.log(value.name)
}

// 对象
const person = {
	age: 17,
	name: "dada"
}

// 对象
const bottle = {
	litres: 1,
	name: "漂流瓶"
}

sayName(person)	// sayName(bottle)
// 有个name属性的对象就可以传过去

Interfaces 接口

//定义接口 -> 跟规则性质差不多
interface Named {
	// 属性
	name: string
}

// 定义为接口类型 -> 设置规则
const sayName = (value: Named) => {
	console.log(value.name)
}

// 对象
const person = {
	age: 27,
	name: "dada"
}

// 有多个重名的接口 -> 内部规则会合并在一起

在编译时候就知道是否符合规则,输入需要有name属性


Interfaces 方法体

interface Named {
	// 方法体
	print(): void;
}

const sayName = (value: Named) => {
	value.print()
}

// 对象
const person = {
    name: "dada",
	print: ()=> { console.log(this.name) }
}

sayName(person)

需要符合具有该方法体


type alias 类型别名

定义类型

type myName = string;

也可以定义为数组对象的数据类型,具有方法体等

let my_name: myName = 'dada'

class、type、interface

interface Person {
	name: string;
	greet( ): void;
}
// 类连接接口 -> 必须有name属性和greet( )方法体 -> 可以接多接口
class Employee implements Person {
	name: string
	greet( ): void { console.log('I am employee') }
}

// 实例化的对象本身也可以定义一个类型来new class

一般是 interface -> class(implements) -> new object

? 可选属性

// 可以用class 和 变量来接接口 -> 设置可选属性
interface Person {
	first_name?: string
	last_name: string
}


interface readonly 只读

interface Person {
	readonly first_name: string
	last_name: string
}
// Person接口的first_name属性为只读的 -> 当使用了这个接口时 -> 无法修改该属性
  • ReadonlyArray

    • 只读数组
  • readonly 和 const 的区别

    • const是常量 readonly是接口、类中使用

interface function type 函数类型

interface PrintClassBack {
	// 可以简单理解为匿名函数 -> 没有函数名
	(success: boolean): void
}

let printCallBack: PrintClassBack
printCallBack = ( suc: boolean ): void => {	// 函数名可以加上 类型需一致
	console.log("callback", suc)
}

<数据类型> - 断言

断言概念

  • typeScript中编译时 -> 编译时告诉编译器变量是哪种类型的方法

    • 程序员操作时 -> 更可能知道变量是哪种类型
  • 跟类型转换有点相似 -> 但只用于编译时期 -> 让操作时可能变换方法

    • 但是不改变数据类型 只告诉编译器可以当作某数据类型操作
  • let x: any = "dada"

    • 任意一个类型 -> 不能明确变量是哪种类型
    • <string> 表示编译时 -> 断言为字符串 -> 可以才可以使用字符串方法操作
    • let s = (<string>x).substring(0, 3) -> 截取出从0开始三个字符串

extends 接口继承

interface Person {
	name: string
}

// 继承另一个接口Person -> 有name和age属性
interface Person1 extends Person {
	age: number
}

extends 接口继承 - 类

// 定义一个类
class Component {
	private width: number
	private height: number

	constructor(width: number, height: number) {
		this.width = width
		this.height = height
	}
	
	display( ): void {
		console.log(this.width)
	}
}

// 接口继承类
// 可重写属性和方法 -> 对应接的class也要
interface Widget extends Component { 
	hide( ): void
}

// 类接接口 -> 需要继承有接口继承后的属性和方法体 -> 才能接该接口
class Button extends Component implements Widget {
	hide( ): void {
		console.log('hiding')
	}
}

// 调用对象 -> 类接接口 -> 重写属性和方法 -> new对象操作
let w: Widget = new Button(1, 2);
w.display( )
w.hide( )

Indexable Types 索引型类型 - 1

interface States {
	[index: string]: boolean;	// 可以指定索引的类型
}

let s: States = {
    'enabled': true,
    'maximized': false 
}
// 使用中括号代表索引
// s['enabled'] -> true
// s['maximized'] -> false

interface States {
	[index: number]: boolean;   // s[0] 调用 
}

Indexable Types 索引型类型 - 2

let s2: number[] = [1, 2, 3] 
// 也定义为索引类型为number的数组
// 为数组类型 -> 有其属性和方法
// s不为数组类型 -> 无其属性和方法

// 接口需要自己添加属性和方法
interface States2 {
	[index: number]: boolean;	// 可以指定索引的类型number
	length: number;
	push(): void;
	pop(): boolean
}

let s3: States2 = [true, false, false]
s3.length   // 4
s3.pop()    // false	// 数组最后一位

// 嵌套复杂结构接口
interface NestedCss {
	color?: string,
	nest?:{
		[selector: string]: NestedCss		// 调用自身
	}
}

let example: NestedCss = {
	color: 'red',
	nest:{
		'example': {
			color: 'bule',
			nest: {}        // 嵌套
		}
	}
}

list 定义列表

interface Todo {
	userId: number;
	id: number;
	used: boolean
}

let todo: Todo[ ] = [
	{
		"userId": 1,	
		"id": 1,	
		"used": false
	},
	{
		"userId": 2,	
		"id": 2,	
		"used": false
	},
	{
		"userId": 3,	
		"id": 3,	
		"used": false
	}
]

Parameter Properties - 参数属性

class Person {
	private _name: string;
	private _age: number;
	constructor(name: string, age: number) {
		this._name = name;
		this._age = age;
	}
}

let p: Person = new Person("dada", 18);

class Person {
	constructor(private _name: string, private _age: number) {
		// 等效于上面类的代码
	}
}

getter、setter - 类中读写方法

class Person {
	constructor(private _name: string, private _age: number) {
	}
	// 读取
	getName( ): string{ return this._name; }
	// 写入
	setName(name: string): void{ this._name = name; }	
	// 需要调用setName 和 getName方法


	// 读取
	get name( ): string{ return this._name; }
	// 写入
	set name(name: string) { this._name = name; }	// 不需要return type
	// 直接可以Person.name
}
	// get 和 set会报错 -> tsc - -target es5 使用这个命令编译
let p: Person = new Person('dada', 29);

noImplicit This - this 指向

class Rectangle {
	constructor(private w: number, private h: number) {}

	getAreaFunction() {
		return function(): number {	// 返回一个匿名的function
			return this.w * this.h; // this指向调用上下文
		}
	}
}

let rectangle: Rectangle = new Rectangle(2, 5);
// 返回function
let areaFunction = rectangle.getAreaFunction;
// 得到面积
let area = areaFunction( );
console.log(area);  //NaN -> 与想得到的值不同
// this 是指向调用的上下文 -> 导致this不明确

// 解决this指向问题
// function改造成箭头函数
return function(): number {	// 返回一个匿名的function
	return this.w * this.h; // this指向对象
}

function type - 函数类型

// 定义一个函数的类型
// 1.
let a: any;
a = function(): void {
	console.log('dada');
}

// 2.
let c: Function;
c = function(): void {
	console.log('dada');
}

// 3.
let d: (params: string) => string;  // 规定类型是一个function、参数为string、返回值为string

// 4.
type fun = (params: string) => string;  //类型别名
let a: fun  // 定义function类型

// 5.
interface fun { // function 类型的接口
	(params: string): string;
}
let a: e = (pass: string) => pass;  // 定义function类型

Function Overloading - 函数重载1

  • 函数重载允许用相同的名字与不同的参数来创造多个函数
    • 在javaScript中会被覆盖
  • 先提供没有实现的函数定义列表
  • 须提供所有函数列表组合的实现
// 定义函数体 -> 参数不同
function sum(x: number, y: number): number;
function sum(x: number, y: number, z: number): number;  // 重载

// 上面两种函数定义的组合实现
function sum(x: number, y:number, z?:number): number {
	return z ? x+y+z : x+y;
}

Function Overloading - 函数重载2

// 定义函数体 -> 参数类型不同,返回类型也不同
function divide(x: number, y: number): number;
function divide(str: string, y: number): string[ ];
function divide(x: any, y: number): any {
	// 判断类型不同时 -> 不同操作
}
  • 放在class中的静态方法和实例方法都可以重载方法
  • 实例方法 -> new之后调用
  • 静态方法 -> 直接className调用

Guards 1 守卫 - typeof

// 1. 利用 typeof 来进行守卫操作
function show(x: number | string): void {
	console.log(typeof x);
	if(typeof x === 'number') {
		console.log('a number' + x);
	} else {
		console.log('a string' + x);
	}
}
show('test string');
show(4);

  • typeof
  • undefined -> undefined
  • null -> object

Guards 2 守卫 - instanced

  • 利用类型断言 as 来判断该类型(class)中有无相关方法和属性
  • 再抽出isClass方法 -> 方法中理由类型断言

最好使用 instanceof

  • 判断是否为new出来的实例,无需断言。
if(vehicle instanceof Car) {  
	console.log('typ is Car');  
}

strictNullChecks - 严格空值检查

  • undefined -> 没有值 -> 类型为undefined -> 可以为任意类型的子类型
  • null -> 值为空 -> 类型为null -> 可以为任意类型的子类型
function show(x: number | undefined | null): void {
	if (x === undefined) {
		console.log('value is undefined');
	} else if (x === null) {
		console.log('value is null');
	} else {
		console.log('value is number');
	}
}

let x = 10;
let y;
let z = null;

show(x);    // 10
show(y);    // value is undefined
show(z);    // value is null
  • 当编译时 tsc index.ts - -strictNullChecks
    • undefined和null类型不为其他任意类型的子类型

Assertion Operator - 非空检查

  • 将传入字符串分为两半
  • return str!.substring(0, str!.length / 2); -> ! 告诉编译器该字符串不能为空(编译时)
  • 编译时需加严格空值检查

never - never类型

// 告诉编译器没有返回值的类型
function loopForever( ): never {
	// 无限循环
	while(true) {

	}
}
// void -> 返回的类型为空 undefined
// 用于 1. 无限循环时(执行不到底部) 2.扔出异常throw时 -> 不会有返回值

ajax封装

// 定义请求数据接口
interface Config {
	type: string;
	url: string;
	data?: string;
	dataType: string;
}
// 定义ajax请求方法
function ajax(config: Config) {
	let xhr = new XMLHttpRequest();	// 建立一个http对象
	xhr.open(config.type, config.url, true);	// 创建连接 -> true为异步
	xhr.send(config.data);		// 发送连接
	xhr.onreadystatechange = function() {	// 当状态改变时
		if(xhr.readyState===4 && xhr.status===200) {
			console.log('成功!');
			if(config.dataType==='json') {
				console.log(JSON.parse(xhr.responseText))
			} else {
				console.log(xhr.responseText);
			}
		}
	}
}

// 调用ajax请求
ajax({
	type: 'get',
	url: 'www.baidu.com',
	data: 'xxx',
	dataType: 'json'
})

加密的函数类型接口

作用: 对方法传入的参数 以及返回值进行约束

// 接口约束
interface encrypt{
	(key: string, value: string):string;
}

// 定义方法
let md5: encrypt = function(key: string, value: string):string{
	// 模拟操作加密算法
	return key+value;
}

// 使用方法
console.log(md5('name', 'zhangsan'));

let sha1: encrypt = function(key: string, value: string):string{
	// 模拟操作加密算法
	return key + '————' + value;
}

console.log(sha1('name', 'zhangsan'));

可索引接口 / 类类型接口

  • 可索引接口 -> 对数组、对象的约束(不常用)
  • 通常ts定义数组约束方法
  • let arr: number[ ]
    • 数组内的类型必须为number
  • let arr1: Array<string>
    • 数组内的类型必须为string
// 定义可索引接口
// 对数组的约束
interface UserArr {
	[index: number]:string; // 索引值类型必须为number / 且内容必须为string
}

// 对对象的约束
interface UserObj {
	[index: string]:string; // 索引值类型必须为string / 且内容必须为string
}


// 类类型接口:对类的约束 -> 与抽象类有点相似
interface Animal{	// 多态 -> 可让子类均可重写
	name: string;
	eat(str: string):void;
}

class Dog implements Animal{	// 对类进行接口约束
	name: string;
	constructor(name: string){
		this.name = name;
	}

	eat(): void{
		console.log(this.name + 'is eating…');
	}
}

let d: Animal = new Dog('小黑');
d.eat('骨头');

接口的扩展、接口的继承

// 扩展 -> 接口可以继承接口
interface Animal{
	eat():void;
}

interface Person extends Animal{	// extends 接口继承
	work(): void;
}


// 父类
class Programmer{
	public name:string;
	constructor(name: string) {
		this.name = name
	}
	
	coding(){
		console.log(this.name + 'is coding…')
	}
}

// 继承父类和接Person接口
class Web extends Programmer implements Person{
	public name:string;
	constructor(name: string) {
		this.name = name
	}
	
	// Person接口的方法体
	eat(){
		console.log(this.name + 'eating');
	}
	
	work(){
		console.log(this.name + 'working');
	}

	// 继承了Programmer类的方法体 -> coding()
}

泛型接口

// 方法1
interface ConfigFn{
	// 泛型方法约定
	<T>(value1:T, value2:T):T;
}

// 实现泛型方法接口
let setData:ConfigFn = function<T>(value1:T, value2:T):T{
	return value2+value2;
}

// 调用时指定方法
setData<string>('name', '张三');	// 指定是string类型


// 方法2
interface ConfigFn<T>{
	// 泛型方法约定
	<T>(value1:T, value2:T):T;
}

// 定义泛型方法
function getData<T>(value1:T, value2:T):T{
	return value2+value2;
}

// 实例化指定类方法 并 接接口
let myGetData:ConfigFn = getData;

// 调用方法
myGetData<string>('20', '30')

把类作为参数类型的泛型类

泛型:可以帮助我们避免重复的代码以及对不特定数据类型的支持

// 比如有个最小堆算法 普通类型 -> T泛型类
class MinClass<T>{
	public list:T[] = [];
	add(value:T):void{
		this.list.push(value);
	}
	min():T{
		let minNum = this.list[0];
		for(let i = 0; this.list.length; i++) {
			if(minNum > this.list[i]) {
				minNum = this.list[i];
			}
		}
		return minNum;
	}
}

// 调用泛型类 -> 指定传入类型
let m1 = new MinClass<number>( );		// 实例化类,指定类的T代表类型为number
m1.add(11);
m1.add(22);

let m2 = new MinClass<string>( );		// 实例化类,指定类的T代表类型为string
m2.add('a');
m2.add('c');


// 定义一个User类 -> 映射数据库表字段
class User{
	userName: string | undefined;
	password: string | undefined;
}

// 定义一个文章分类类
class ArticleCate{
	title: string | undefined;
	desc: string | undefined;
	
	constructor(params: {		// 要求实例化时必须传入参数
		title:string | undefined,
		desc:string | undefined
	}) {
		this.title = params.title;
		this.desc = params.desc;
	}
}

// 定义一个MySqlDb类 -> 操作数据库的泛型类
class MySqlDb<T> {
	add(value:T):boolean{		// 将类当作参数类型来验证
		console.log(value);
		return true;
	}
}

// 实例化后再传值
let user = new User( );
user.userName = '张三';
user.password = '123456';

let Db = new MySqlDb<User>(); 	// 校验传入的是User类
// 将User类作为参数传入MySqlDb中
Db.add(user)

// 实例化articleCate时传值
let a = new ArticleCate({
	title: '分类',
	desc: '泛类型类'
});

let Db = new MySqlDb<ArticleCate>(); // 校验传入的是ArticleCate类
Db.add(a)

TypeScript 封装统一操作数据库

// 定义一个操作数据库的库 -> 可以支持Mysql、Mssql、Mongodb
// 需要约束统一规范、以及代码重用 -> 接口(统一规范)、重用(泛型)

// 定义数据库操作接口
interface DBI<T>{   //泛型接口
	add(info:T):boolean;
	update(info:T, id:number):boolean;
	delete(id:number):boolean;
	get(id:number):any[];
}

// 要接泛型接口 -> 该类也应该是泛型类
// 定义一个操作mysql数据库的类
class MysqlDb<T> implements DBI<T>{
	add(info:T):boolean{
		return true;
	}
	update(info:T, id:number):boolean{
		return true;
	}
	delete(id:number):boolean{
		return true;
	}
	get(id:number):any[]{
		return [true];
	}
}

// 同理定义MsSqlDb和MongoDb的类


// 增加Mysql数据库
// 定义一个User类和数据库表做映射
class User{
	userName:string | undefined;
	password:string | undefined;
}

let u = new User();
u.userName = '张三';
u.password = '123456';

let mysql = new MysqlDb<User>();		// 类作为参数来约束数据传入的类型
mysql.add(u);		// 传入u实例数据

// 切换对应数据库就切换类

TypeScript模块化

// 文件1
let dbUrl = 'xxxx';

// 暴露方法1
export function getData():any[]{
	console.log('获取数据库的数据')
	return [
		{
			title: 'aaa'
		}
	]
}

// 暴露方法2
export{
	dbUrl,
	getData
}

// 文件2
import { getData as get } from './xxx文件' // 引入方法
getData( );
get( );     // 相同重命名

// export default getData -> 默认暴露
// import getData from './文件1' -> 默认引入

命名空间模块化

命名空间:避免各变量命名冲突,可将相似功能的函数、类、接口等放置命名空间。

命名空间和模块的区别:

  • 命名空间
    • 内部模块
    • 主要用户组织代码,避免命名冲突
  • 模块
    • ts外部模块,侧重代码的复用
    • 一个模块里可能会有多个命名空间
// 模块文件A
export namespace A {    // 命名空间默认私有 -> 可暴露
	export interface Animal{ };	// 使用内部接口和方法需要暴露
	
	export class Dog implements Animal{};
}

// 不同命名空间
namespace B {
	interface Animal{ };
	
	class Dog implements Animal{};
}

// 引入模块文件A
import { A, B } from './modules/a';
let a = new A.Dog('小黑');
a.eat();    // 调用

类装饰器

  • 装饰器
    • 是JS过去几年中最大的成就之一,是ES7标准特性之一。
    • 实际是一个方法,可以注入到类、方法、属性参数上来扩展相关的功能。
  • 常见的装饰器 -> 类装饰器、属性装饰器、方法装饰器、参数装饰器
// 定义普通装饰器 -> 不能传入参数(默认为当前类)
function logClass(params:any){
	console.log(params);		// params 就是当前类
	params.prototype.apiUrl = 'xxxx';		// 给这个类动态扩展一个apiUrl属性
	params.prototype.run = () => {};		// 给这个类动态扩展一个run方法
}


@logClass			// 调用装饰器 -> 动态扩展类的功能(不能加分号)
class HttpClient {
	constructor() {
	
	}
	getData() {
		
	}
}

// 实例化
let http:any = new HttpClient();
console.log(http.apiUrl);


// 定义装饰器工厂 -> 可传入参数
function logClass(params:string){

	return function(target:any){    // 类装饰器接收当前类的一个参数
		console.log(target);    // 当前类
		console.log(params);    // 'hello'
	}
	
	// 同样可以动态扩展target当前类的功能
}


@logClass('hello')			// 传入参数
class HttpClient {
	constructor() {
	
	}
	getData() {
		
	}
}

// 实例化
let http:any = new HttpClient();
console.log(http.apiUrl);


// 类装饰器 -> 用装饰器修改当前类的构造函数
function logClass(target:any){
	console.log(target);
	return class extends HttpClient {
		apiUrl:any = '我是装饰器修改后的apiUrl';
		getData(){
			console.log(this.apiUrl);
		};
	}
}

@logClass
class HttpClient {
	public apiUrl:string | undefined;
	constructor() {
		this.apiUrl = '我是构造函数里的apiUrl';
	}
	getData() {
		console.log(this.apiUrl);
	}
}

let http = new HttpClient();
http.getData(); // '我是装饰器修改后的apiUrl'

属性装饰器

  • 属性装饰器接受 2 个参数
    • 1.当前原型对象(实例化后的);
    • 2.属性名称
// 定义属性装饰器
function logProperty(params:any) {

	return function(target:any, attr:any){  // 属性装饰器接收两个参数
		console.log(target);    // http(实例化的对象)
		console.log(attr);  // apiUrl

		target[attr] = params;  // 将对象的attr属性修改成xxxxx
			// target[attr] === params.prototype.attr
				// 因为target为当前原型对象
	}
}

@logProperty		// 调用类装饰器 
class HttpClient {
	@logProperty('xxxxx')
	public apiUrl:string | undefined;
	constructor() {
		this.apiUrl = '我是构造函数里的apiUrl';
	}
	getData() {
		console.log(this.apiUrl);
	}
}

let http = new HttpClient();
http.getData(); // '我是装饰器修改后的apiUrl'

方法装饰器

方法装饰器:用来监视,修改或者替换方法定义

方法装饰器3个参数

  • 类的构造函数/类的实例化原型对象
  • 成员名字
  • 成员的属性描述符
// 定义方法装饰器  
function logMethod(params:any) {
	return function(target:any, methodName:any, desc:any){  // 接收三个参数
		console.log(target);    // 类的原型对象
		console.log(methodName);    // getData
		console.log(desc);  // {方法描述信息}

		// 扩展当前类的属性和方法
		target.apiUrl = 'xxx';
		target.run = function() {
			console.log('run');
		}

		// 修改装饰器的当前方法
		let oMethod = desc.value;
		desc.value = function(...args:any[]){     // 替换且将接收到的参数放入数组
			args = args.map((value) => {
				return String(value);   // 修改该方法转换为String类型
			})

			// 再替换回原来方法		-> 变为修改当前方法
			oMethod.apply(this, args);
		}
	}
}


class HttpClient {
	@logMethod('xxxxx')
	public apiUrl:string | undefined;
	constructor() {
		this.apiUrl = '我是构造函数里的apiUrl';
	}

	@logMethod('www.baidu.com')
	getData(...args:any[]) {
		console.log(args)   // ["123", "xxx"]	// 均修改为string类型
		console.log(this.apiUrl);
	}
}

let http = new HttpClient();
http.getData(123, 'xxx');   // '我是装饰器修改后的apiUrl'
  • 还有方法参数装饰器:为方法参数动态增加元素
    • 类的构造函数/类的实例化原型对象
    • 参数名字
    • 参数再函数参数列表中的索引
  • 调用 -> getData(@logParams a:any, b:any) { };
  • 可以装饰多个装饰器
    • 多个同样的装饰器 -> 由后至前
  • 装饰器执行顺序
    • 属性 > 方法 > 方法参数 > 类

keyof

key:提供正确的属性名称

class A {
	x: number = 5; 
}

let y: keyof A;
y = 'x';    // y只能是class A的一个属性名

class Test {
	x: number = 6; 
}

function getProp(a: keyof Test, test: Test): any {
	return test[a];
}
let t: Test = new Test();
let prop = getProp('x', t);
console.log(prop);  // 6


class B {
	y: keyof A; // y只能是x
}

tsconfig.js - 配置文件

  • 配置文件:约束ts文件
  • 在终端初始化:tsc - -init
  • 在根目录生成了 tsconfig.json配置文件
  • 使用该配置文件,在终端 tsc,不会忽略代码 查看是否符合配置。

lodash - 使用第三方插件

引入 lodash 现代化的工具库:操作数组、函数等

  • 初始化项目 npm init
    • 主要生成package.json文件
  • npm install lodash -S
  • 进入文件中导入 import * as _ from 'lodash';
    • 若是第三方插件不是用ts编写
      • 编译器不会提示和补全代码
    • 去找相关库的ts文档(是声明相关库的ts源码)
      • 可能还需要引入源码库

结尾

简单写一下个人在学习TypeScript遇到的重点和难点,若有错误的地方,还望各位小伙伴指出噢。

要是这篇文章对您有那么一丢丢的帮助,点一个赞👍 吧

博客原文✨