1、配置环境以及项目初始化
typescript的环境配置参考node中的nodemon配置typescript, 并且配置parcel-bundler,具体配置如下
{
"name": "ts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "parcel ./index.html",
"watch": "nodemon --watch src/ -e ts,tsx --exec ts-node ./src/index.ts",
"tsc-watch": "tsc --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"parcel-bundler": "^1.12.5",
"typescript": "^4.4.3"
}
}
注意: 在index.html中,需要引入 <script src="./src/index.ts"></script>
2、ts函数的方法重载
ts的函数重载比较特殊, 和很多的其他后端语言的方法重载相比,多了不少的规则。学习函数重载,先要了解什么是函数签名,定义如下
函数签名: 函数签名=函数名称 + 函数参数 + 函数参数类型 + 返回值类型四者合成。在TS函数重载中,包含了实现签名和重载签名,实现签名是一种函数签名, 重载签名也是一种函数签名
定义: 包含了以下规则的一组函数就是TS函数重载
1、由一个实现签名 + 一个或多个重载签名合成;
2、外部调用函数重载定义的函数时,只能调用重载签名, 不能调用实现签名,这看似矛盾的规则, 其实是TS的规定:实现签名下的函数体是给重载签名编写的,实现签名只是在定义时起到了统领所有重载签名的作用, 在执行调用时就看不到实现签名了;
3:调用重载函数时,会根据位于新人参数来判断你调用的是哪一个函数;
4、只有一个函数体,只有实现签名配备 了函数体,所有的重载签名都只有签名, 没有配备函数体。
例子:
//方法的重写
function getMessage(info: string): string
function getMessage(info: number): number
function getMessage(info: number | string): string | number {
if (typeof info === 'number') return info + 1
return info
}
//类的方法的重写
class Check {
public sendMsg(msg: string): string
public sendMsg(msg: Array<string>): string
public sendMsg(msg: string | Array<string>): string {
if (Array.isArray(msg)) return msg.join('')
return msg
}
}
构造器的方法重载
type IData = { name: string; age: number }
class Test {
private name: string
private age: number
constructor(name: string, age: number)
constructor(info: IData)
constructor(info: string | IData, age: number = 0) {
if (typeof info === 'string') {
this.name = info
this.age = age
} else {
let { name, age } = info
this.name = name
this.age = age
}
}
public log(): void {
console.log(this.name, this.age)
}
}
let first = new Test('aaa', 12)
let second = new Test({ name: 'aaa', age: 13 })
first.log()
second.log()
3、类型断言与类型转换
定义:把两种能有重叠关系 的数据类型进行相互转换的一种TS语法,把其中的一种数据类型转换成另外一种数据类型。类型断言和类型转换产生的效果一样,但语法格式不同。
格式: A数据类型的变量 as B数据类型。娄数据类型和B数据类型必须具有重叠关系
class People {
public name!: string
public age!: number
public say() {
}
}
class Stu extends People {
public study() {}
}
//类型断言
let p = (new Stu() as People) // 这时就不会有学生的方法
let s = new People() as Stu // 这时断言成学生的类,就会有学生的方法
//类型转换
let p1 = new Stu();
<People>p1 // 这个使用的是类型转换,就不会联想到学生的方法
注意:需要使用断言与转换时,有几种情况,情况一: 两者之间是互相继承关系; 情况二: 两个类之间的属性是互相重叠或都互为子集的关系; 情况三: 联合类型之间的转换
类型的非空断言
建议打开tsconfig.js中的 "strictNullChecks": true 配置
never类型的三种情况
//情况一
let test = (msg: number | string) => {
if (typeof msg === 'number') {
console.log(msg)
} else if (typeof msg === 'string') {
console.log(msg)
} else {
console.log(msg) //这里的msg是never类型,因为它无法到达
}
}
//情况二
let test2 = (msg: string): never => {
throw new Error(msg)
}
//情况三
let test3 = (): never => {
while (true) {}
}
void 与never的区别:
1、前者可以被赋值为null(strictNullChecks为false时), undefined, 但never不能包含任何类型;
2、返回类型为void的函数还能正常执行, 但是返回never的函数无法正常执行
never是unknown的子集
type isNever = never extends unknown? true: false; //显示的是true
type keys = keyof unknown //显示的是never
4、装饰器
在使用类的装饰器前,需要在tsconfig中进行配置experimentalDecorators,将其值改为true, emitDecoratorMetadata的值为true
类装饰器: 在类声明之前声明, 用来监视,修改, 替换类定义
// 注意:这里接收的是类的构造器,与方法装饰器不一样的是方法装饰器接收的是一个实例化后的类, 这是实现的一个日志打印的功能
function LoggerDecorator<T extends { new (...args: any): any }>(targetClass: T) {
// class LoggerClass extends targetClass {
// public constructor(...args: any) {
// super(...args)
// console.log('日志信息...targetClass')
// }
// }
// return LoggerClass
// 使用匿名类进行实现
return class extends targetClass {
public constructor(...args: any) {
super(...args)
console.log('日志信息...targetClass')
}
}
}
@LoggerDecorator
class Person {
public constructor(private name: string, private age: number) {}
public getInfo(): string {
return `${this.name}, ${this.age}`
}
}
let P = new Person('aaa', 12)
console.log(P.getInfo())
属性装饰器以及方法装饰器:可以用来装饰方法,或者类里的属性
//装饰属性,target是表示类的一个实例, 第二个参数表示属性的名称
function property(target: any, propertyName: string) {
let value = target[propertyName]
const getter = () => value
const setter = function (newVal: string) {
//用这种方式的函数,this是指向实例
value = newVal.toUpperCase()
}
if (delete target[propertyName]) {
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
configurable: true,
enumerable: true,
})
}
}
//装饰方法, 第一个参数是实例化后的类, 第二个参数表示方法名,第三个参数表示该方法的属性
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let oldMethods = descriptor.value
descriptor.value = function (...rest: any[]) { //使用这种写法,那么该this是指向实例化后的类
console.log(this)
return oldMethods.apply(this, rest)
}
}
class Person {
@property
private name: string
public constructor(name: string, private age: number) {
this.name = name
}
@method
public getInfo(): string {
return `${this.name}, ${this.age}`
}
}
let p = new Person('bbb', 12)
console.log(p.getInfo())
注意:如果修饰的是属性,那么只接收两个参数,第一个参数是实例化后的类,第二个参数是属性的名称,如果修饰的是方法,那么第三个参数是该方法的属性,如果装饰器需要传参数,那么需要返回一个function接收这些参数如下
function method(msg: string) {
return function (target: any, property: string, descriptor: PropertyDescriptor) {
let oldMethods = descriptor.value
descriptor.value = function (...rest: any[]) {
//使用这种写法,那么该this是指向实例化后的类
console.log(this)
return oldMethods.apply(this, rest) + msg
}
}
}
class Person {
private name: string
public constructor(name: string, private age: number) {
this.name = name
}
@method('addInfo')
public getInfo(): string {
return `${this.name}, ${this.age}`
}
}
let p = new Person('bbb', 12)
console.log(p.getInfo())
注意:如果修饰的是静态的属性或者静态的方法,那么,第一个参数则不是实例化后的类,而是该类的构造函数
参数装饰器: 用于装饰类里方法的参数
//接收三个参数,一个是实例化后的类,第二个是方法名,第三个是参数的顺序
function param(...rest: any[]) {
console.log(rest)
}
class Person {
private name: string
public constructor(name: string, private age: number) {
this.name = name
}
public getInfo(@param info: string): string {
return `${this.name}, ${this.age}, ${info}`
}
}
注意:装饰器的执行顺序: a、类装饰器是最后执行,后写的类装饰器先执行; b、参数装饰器先于方法装饰器执行; c、属性装饰器与方法装饰器,谁在前面谁先执行, 所以总体的顺序是 属性装饰器 => 方法参数装饰器(一) => 方法装饰器(一) => 方法参数装饰器(二) => 方法装饰器(二)=> ... => 类装饰器
5、知识点拾遗
泛型
class Person {}
//表示需要传入一个类,那么返回的是一个实例化的类
function test<T>(type: { new (): T }): T {
return new type()
}
let p = test<Person>(Person)
//泛型在接口中的定义,其中T是表示全局的,U是表示局部的
interface ICheck<T> {
sum(type: T): T
test<U>(type: U): U
}
//在类在进行泛型的定义
class Check<U> {
public constructor(prop: U) {}
}
//泛型继承
interface IWise {
length: number
}
//这里可以继承一个接口也可以继承一个类
function logger<T extends IWise>(type: T): number {
return type.length
}
类型
type aa = string | number
type bb = number | boolean
type cc = aa & bb //这个时候cc的类型就是 number
interface IA {
name: string
}
interface IB {
age: number
}
type IC = IA & IB // 这个时候IC的类型就是IA与IB的交集如下例子
let obj: IC = {
name: 'yes',
age: 12,
}
但是如果遇到下面的情况,那么就需要求内部的交集
interface IA {
name: string|number,
age: number
}
interface IB {
name: string,
sex: string
}
type IC = IA & IB
let obj: IC = {
name: 'aaa', //这个时候name就是上面IA与IB的name的交集,则为string
age: 1,
sex: 'man'
}
6、类型守卫
定义:在语句的块级作用域【if语句内或条目运算符表达式内】缩小变量的一种类型推断的行为。
类型判断: typeof
属性或者方法或者函数判断: in
实例判断:instanceof
字面量相等判断: ==, ===, !=, !==
typeof的使用
let obj = {
name: 'aaa',
age: 12,
fav: {
sport: 'football',
others: ['computer', 'singing'],
},
}
let t: typeof obj //这个时候是把obj对象转成一个类型,相当于,{name: string, age: number, fav: {sport: string, other: Array<string>}}
keyof的使用
type IA = ['aaa', 'bbb', 'ccc', 'ddd']
let ia: keyof IA = 0 //这个时候的keyof相当于下标0, 1, 2, 3
type IB = { name: 'aaa'; age: 12 }
let ib: keyof IB = 'age' //相当于IB类型里的所有key的子集
interface IInfo {
name: string
age: number
fav: string
}
//相当于在IInfo里的所有属性加了?可选标识
type subInfo1 = {
[key in keyof IInfo]?: IInfo[key] //注意:这里的key也可以用其他的单词代替
}
//该效果与上面的subInfo1是等效的
type subInfo2 = Partial<IInfo>
//typescript系统内置了Partial的声明,相当于如下的效果
// type Partial = {
// [key in keyof IInfo]?: IInfo[key]
// }
分布式有条件类型
interface A {
name1: string
}
interface B {
name2: string
}
interface AA {
name3: string
}
interface BB {
name4: string
}
//分布式有条件类型,如下,即T为裸类型的时候,那么类型会被推导出来
type Condition<T> = T extends AA ? A : B
let t: Condition<AA | B> = { name2: 'aaa' } // let t: A|B
//如果 type Condition<T> = {type: T} extends {type: AA}? A: B //那么类型t将会变成 let t: B
排除与包含的使用
type Diff<T, U> = T extends U ? never : T
let R: Diff<'a' | 'b' | 'c', 'a' | 'b' | 'c' | 'd'> //相当于let R: never
//针对上面的声明typescript内置了一个方法Exclude, Exclude的声明相当于Diff的逻辑
let T: Exclude<'e' | 'f', 'e' | 'g' | 't'> //这个时候相当于 let T: 'f'
type Filter<T, U> = T extends U ? T : U
let F: Filter<'a' | 'b' | 'c', 'a' | 'b' | 'c' | 'd'> //相当于let F: "a"| "b"| "c"
//针对上面的声明typescript内置了一个方法Extract, Extract的声明相当Filter的逻辑
let E: Extract<'a' | 'b' | 'c', 'a' | 'b' | 'c' | 'd'> //相当于let E: "a"| "b"| "c"
其他内置类型一
// type NonNullable<T> = T extends null | undefined ? never : T
type A = NonNullable<'a' | undefined | null> //相当于 type A = 'a'
// 以函数的返回值作为类型 ReturnType
interface IFun {
(): { name: string; age: number }
}
let func: IFun = () => {
return {
name: 'aaa',
age: 12,
}
}
type ReturnTypeCopy<T extends (...args: any) => any> = T extends (...args: any[]) => infer R ? R : any
let res: ReturnType<IFun> = {
name: 'yes',
age: 1,
}
//以函数的参数作为返回值 Parameters
interface IFunParam {
(name: string, age: number): void
}
type ParametersCopy<T extends (...args: any) => any> = T extends (...args: infer R) => any ? R : never
let param: Parameters<IFunParam> = ['aaa', 12]
// 实例化的类型 InstanceType 以及构造函数的参数 ConstructorParameters
class Person {
public constructor(private name: string, private age: number) {}
public getInfo() {
return { name: this.name, age: this.age }
}
}
type ConstructorParametersCopy<T extends new (...args: any[]) => any> = T extends abstract new (...args: infer P) => any? P: never;
let consParam: ConstructorParameters<typeof Person> = ['name', 12]
type InstanceTypeCopy<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
let ins: InstanceType<typeof Person> = new Person('aaa', 12)
infer的应用
// infer的应用案例
type Ttuple = [string, number, boolean]
type ElementOf<T extends Array<any>> = T extends Array<infer P> ? P : never // 这里的infer是表示数组里的内容
let test: ElementOf<Ttuple> // 返回的类型是 let test: string|number|boolean
//联合类型转交叉类型
type T1 = { name: string }
type T2 = { age: number }
type ToInterSection<T> = T extends { a: (x: infer P) => void; b: (x: infer P) => void } ? P : never
type T3 = ToInterSection<{ a: (x: T1) => void; b: (x: T2) => void }> // 返回的类型 type T3 = T1 & T2
其他内置类型二
type TCompany = {
name: string
count: number
}
interface json {
name: string
age: number
company: TCompany
}
// 纵向进行递归操作
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object ? DeepPartial<T[U]> : T[U]
}
let T: DeepPartial<json> = {
name: 'aaa',
company: {
count: 12,
},
}
//该接口等同于系统内置的Required的接口
type RequiredCopy<T> = {
[key in keyof T]-?: T[key]
}
//该接口等同于系统内置的Readonly的接口
type ReadonlyCopy<T> = {
readonly [key in keyof T]: T[key]
}
注意:上面的RequiredCopy的写法,'-' 表示不可选 ‘+’表示可选的操作
其他内置类型三
// Pick的用法 这里表示提取指定的属性作为接口
interface json {
name: string
age: number
avatar: string
}
//注意: 这里的extends的作用在实际类型声明的时候,相当于in的作用
type PickCopy<T, K extends keyof T> = {
[P in K]: T[P]
}
type PartJson = Pick<json, 'name' | 'age'> //相当于提取了两个属性作为自己类型的属性
// Omit的用法, 这里表示排除指定的属性作为接口
type PartJson1<T, K extends keyof T> = Omit<T, K>
let part: PartJson1<json, 'name'> = {
age: 12,
avatar: 'avatar',
}
type a = {
name: string
age: number
}
type b = {
name: string
sex: string
}
//获取两个对象的差异
type Diff<T extends object, K extends object> = Pick<T, Exclude<keyof T, keyof K>> &
Pick<K, Exclude<keyof K, keyof T>>
//获取两个对象的相同部份
type Intersect<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>>
type A = Intersect<a, b>
type B = Diff<a, b>
let bb: B = {
age: 12,
sex: 'man',
}
注意: 这里的extends的作用在实际类型声明的时候,相当于in的作用
7、typescript的命名空间
在使用ts进行编程的时候,除了模块外,还有一个可以使用独立的作用域,那就是命名空间
export namespace A {
const test = 'ok' //注意:在命名空间中,如果没有使用export,那么外部是无法访问到该属性或方法的
export const fun = (): string => { //在外部可以被访问
return 'are you ok???'
}
}
export namespace B {
export const fun = (): string => {
return 'today is goods day'
}
}
外部引用的时候
import { A, B } from './check'
console.log(A.fun())
console.log(B.fun())
8、类型声明
在typescript进行编译的时候,如果需要自动生成d.ts的文件,那么需要配置 "declaration": true
在typescript中,对类型进行声明,declare,但这个只是声明,而不是实现,这个方法可适用于对js项目转化成ts项目中使用
declare let str: string;
declare function func(str: string): void;
declare class Person {
private name;
private age;
constructor(name: string, age: number);
getInfo(): Record<string, string | number>;
}
注意:如果需要扩展全局的方法,并且该文件里有类型导出,那么就需要进行全局类型的修饰如下
export const a = 'aaa'
//这个声明的这个方法就是全局的,因为上面的模块的导出,所以需要用global进行修饰
declare global {
interface String {
check(): void
}
}
String.prototype.check = function () {
return this.toString()
}
注意: 这个时候就会把check这个方法写入到String的原型方法中,以供使用
9、配置项目访问路径
"baseUrl": "./src",
"paths": {
"@app": [
"./"
],
"@app/*": [
"./*"
]
}
这样添加这个配置后,那么在项目中访问就可以用@app来指定到./src目录下