初学TypeScript中.....

356 阅读11分钟

[TOC]

一、 获取Typescript

1. 安装Typescript

npm install -g typescript

2. IDE

  • IDE推荐使用 VS Code

  • VS Code是开源的,跨终端的轻量级编辑器,内置TS

  • 本身就是TS编写的,支持力度较好

3. 如何编译TS

  • 新建一个index.ts文件

  • 执行代码(每次修改都要执行命令)

    tsc index.ts
    
  • 如果要一直监视,首先在当前目录下执行

    tsc --init // 初始化TS
    
  • 生成一个tsconfig.json文档,一般会将配置文档中的输出outDir的改一下位置,这个按照自己的要求来做

     "outDir": "./js" 
    
  • 可以开启VS Code的watch, 位置

    终端---运性任务 --- typescript --- tsc:监视tsconfig.json

二、基础数据类型

  • Javascript的类型分为两种:原始数据类型和对象类型
  • 原始数据类型:BooleannumberstringnullundefinedSymbolBigInt

1. Boolean类型

let flag: boolean = false;
// 编译结果:var flag = false;

2. 数字类型number

let num:number = 0
// 编译结果:var num = 0;

3. 字符串string

let name:string = '张三'
// 编译结果:var name = '张三';

4. Void空值

// Void表示没有任何返回值的函数
function fn():void{
	console.log('没有返回值')
}

5. Array类型

// 方式一
let arr: number[] = [1, 2, 3]
// 编译结果 var arr = [1, 2, 3];

// 方式二 (泛型方式)
let arr: Array<number> = [1,2,3]
// 编译结果 var arr = [1, 2, 3];

6. Tuple元组类型

  • javascript中不存在元组,这是TS特有的类型
  • 元组中每一个属性都有一个关联类型,初始化元组时必须提供每个属性的值
  • 当元素越界,也就是给数组添加元素时,它的类型会被限制为元组中每个类型的联合类型
// 联合类型[string, number]
let tom: [string, number] = ['Tom', 25];
tom.push('1111') // 成功
tom.push(1111)// 成功
tom.push(null)// 成功
tom.push(undefined)// 成功
tom.push(true) // 失败 Argument of type 'true' is not assignable to parameter of type 'string | number'.

7. 枚举类型Enum

  • 用于取值被限定在一定范围内的场景
  • 默认情况枚举开始于其成员编号0,然后自增
  • 也可以通过设置其值来进行更改,也可以设置所有值
// TS
enum ColorEnum{ red, green, blue }
// ES5 编译结果
var ColorEnum;
(function (ColorEnum) {
    ColorEnum[ColorEnum["red"] = 0] = "red";
    ColorEnum[ColorEnum["green"] = 1] = "green";
    ColorEnum[ColorEnum["blue"] = 2] = "blue";
})(ColorEnum || (ColorEnum = {}));

// TS
enum ColorEnum1{ red = 1, green = 3, blue }
// ES5 编译结果
var ColorEnum1;
(function (ColorEnum1) {
    ColorEnum1[ColorEnum1["red"] = 1] = "red";
    ColorEnum1[ColorEnum1["green"] = 3] = "green";
    ColorEnum1[ColorEnum1["blue"] = 4] = "blue";
})(ColorEnum1 || (ColorEnum1 = {}));

8. Any任意类型值

  • 如果定义为any类型,则工作方式变为js模式😓
  • 任何东西都可以赋值为any
  • top type/ bottom type ,放弃了类型检查
let anyNumber: any = '任意类型值'
anyNumber = 0

9. Unknown类型

  • TS3.0 引入
  • top type 和 any一样,所有类型都可以分配给unknown,但unknown不能赋值给所有,只能赋值给any和unknown
  • unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) , 这导致 any 基本上就是放弃了任何类型检查.
/*任何值都可以为unknown类型*/
let un: unknown
un = true // ok
un = 0 // ok
un = 'string' // ok
un = [] // ok
un = {} // ok
un = null // ok
un = undefined // ok
un = Symbol('111') // ok

/* unknown只能复制给any类型和unknown类型,其他类型编译不成功 */
let value1:unknown = un // ok
let value2:any = un // ok
let value3:string = un // error
let value4:boolean = un // error
let value5:number = un // error
let value6:any[] = un // error
let value7:null = un // error
let value8:undefined = un // error

10. Null 和 Undefined

TypeScript里,undefinednull两者有各自的类型,分别为undefinednull,与void的区别是undefinednull为所有类型的子类型。

let u:undefined = undefined
let n:null = null

/*前提条件tsconfig.json中 strictNullChecks 设置为false*/
let numb: number = null // 编译通过

/*前提条件tsconfig.json中 strictNullChecks 设置为true*/
let numb: number = null // 编译未通过,Type 'null' is not assignable to type 'number'

11. Never类型

  • 表示的是那些永不存在的值的类型

  • 可以用来使得异常的处理更加安全

  • TypeScript中的never类型具体有什么用?

    function listen() : never{
        while(true){
            let conn = 1111
        }
    }
    listen()
    console.log("!!!")  //Error
    

三、 函数

1. 函数的定义

  • 函数是javascript的一等公民

  • 定义函数的方式:函数声明函数表达式

    // Javascript
    // 函数声明
    function getInfo(name, age) {
    	return `My name is ${name}, age is ${age}`
    }
    // 函数表达式
    let getInfo = (name, age) => {
        return `My name is ${name}, age is ${age}`
    }
    
  • Ts中函数的输入和输出是有约束的,输入多余的或者少于要求的参数是不被允许的

    // 函数声明
    function getInfo(name:string, age:number):string {
    	return `My name is ${name}, age is ${age}`
    }
    // 函数表达式
    let getInfo:(name:string, age:number)=> string = (name:string, age:number):string => {
        return `My name is ${name}, age is ${age}`
    }
    console.log(getInfo('张三', 20)) // My name is 张三, age is 20
    

2. 可选参数

  • 输入多余的或者少于的参数是不被允许的

  • ?来表示可选参数

    let getInfo: (name: string, age?: number) => string = (name: string, age?: number): string => {
      if (age) {
        return `My name is ${name}, age is ${age}`
      } else {
        return `My name is ${name}, age is 30`
      }
    }
    console.log(getInfo('张三')) // My name is 张三, age is 20
    console.log(getInfo('张三', 50)) // My name is 张三, age is 50
    
  • 可选参数必须在参数后面

    let getInfo: (age?: number, name: string ) => string = (age?: number, name: string ): string => {
      if (age) {
        return `My name is ${name}, age is ${age}`
      } else {
        return `My name is ${name}, age is 30`
      }
    }
    console.log(getInfo('张三')) // A required parameter cannot follow an optional parameter
    
    // 多个可选参数
    let getInfo: (name: string, age?: number, sex?: string) => string = (name: string, age?: number, sex?: string): string => {
      if (age) {
        return `My name is ${name}, age is ${age}, sex is ${sex}`
      } else {
        return `My name is ${name}, age is 30, sex is ${sex}`
      }
    }
    console.log(getInfo('张三')) // My name is 张三, age is 30, sex is undefined
    console.log(getInfo('张三', undefined, '男')) // My name is 张三, age is 30, sex is 男
    

3. 默认参数

  • TS的默认值设置和ES6的默认值设置是一样的

    let getInfo: (name: string, age?: number) => string = (name: string, age?: number): string => {
      if (age) {
        return `My name is ${name}, age is ${age}`
      } else {
        return `My name is ${name}, age is 30`
      }
    }
    console.log(getInfo('张三')) // My name is 张三, age is 30
    console.log(getInfo('张三', 20)) // My name is 张三, age is 20
    

4. 剩余参数

  • TS 的剩余参数和ES6的剩余参数是一致的

  • 剩余参数只能作为最后一个参数,

    let add: (...rest: number[]) => number = (...rest: number[]): number => {
      let sum = 0
      rest.forEach(item => {
        sum += item
      })
      return sum
    }
    
    console.log(push(1, 2, 3)) // 6
    console.log(push(1, 2, 3, 4)) // 10
    
    // 将剩余参数后面加一个参数,编译的时候会报错,A rest parameter must be last in a parameter list
    let add: (...rest: number[], a: number) => number = (...rest: number[], a: number): number => {
      let sum = 0
      rest.forEach(item => {
        sum += item
      })
      return sum
    }
    

5. 类型推断

  • 当在赋值语句的一边指定类型,但另一边没有指定的时候,TS会经过类型推断,自动识别出类型

  • 建议当写的时候两边都指定类型,增加可读性

    // 未左右指定类型
    let getInfo:(name:string, age:number)=> string = (name, age) => {
        return `My name is ${name}, age is ${age}`
    }
    let getInfo = (name:string, age:number):string => {
        return `My name is ${name}, age is ${age}`
    }
    // 左右都指定类型
    let getInfo:(name:string, age:number)=> string = (name:string, age:number):string => {
        return `My name is ${name}, age is ${age}`
    }
    console.log(getInfo('张三', 20)) // My name is 张三, age is 20
    

6. 函数重载

  • js中没有函数重载概念,当有相同函数命名时,只会执行最后一个相同名称的函数

    function testJs (num) {
        return num
    }
    function testJs(num) {
        return `数字是${num}`
    }
    console.log(testJs(1)) // 数字是1
    
  • 在TS中,具有相同函数命名时,会出发函数的重载。

  • 函数重载让编译器根据函数的输入决定函数的输出,从而推断出更准确的类型。。

  • 将签名更加具体的重载放上面,不那么具体的放后面

    /**
     * @description 将鼠标放到num和str上的函数上会得到不同的结果
     * num function getInfo(age: number): number (+1 overload)
     * str function getInfo(name: string): string (+1 overload)
     */
    function getInfo(age: number): number;
    function getInfo(name: string): string;
    function getInfo(str: any): any {
      if (typeof str === 'string') {
        return 'string'
      } else {
        return 0
      }
    }
    const num = getInfo(1)
    const str = getInfo('hello')
    

四、类和继承

1. ES5类和继承

  • 最简单的类

    function Person() {
    	this.name = '张三'
        this.age = 10
    }
    const p = new Person()
    console.log(p.name) // 张三
    
  • 构造函数和原型链上定义属性和方法

    function Person() {
    	this.name = '张三' // 定义构造函数的属性
        this.age = 10  // 定义构造函数的属性
        this.run = function() {  // 定义构造函数的方法
            console.log(`${this.name}在跑步`)
        }
    }
    
    Person.prototype.sex = '男' // 原型链上的属性
    Person.prototype.work = function() { // 原型链上的方法
        console.log(`${this.name}在工作`)
    }
    const p = new Person()
    p.run() // 张三在跑步
    p.work() // 张三在工作
    
  • 类的静态方法,创建的是整个class的方法

    function Person() {
    	this.name = '张三' // 定义构造函数的属性
        this.age = 10  // 定义构造函数的属性
        this.run = function() {  // 定义构造函数的方法
            console.log(`${this.name}在跑步`)
        }
    }
    Person.getInfo = function() {
        console.log('我是静态方法')
    }
    Person.getInfo() // 我是静态方法
    
    let p = new Person()
    p.getInfo() // undefined (因为创建的是整个class的方法,如果想要使用,可以再次进行声明 p.getInfo = Person.getInfo 即可继承)
    
    // 例如
    // Vue源码中原型链继承和静态方法继承
    const Sub = function VueComponent(options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    
    //静态方法继承
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
    
  • ES5的继承一共分为两种:对象冒充继承 和原型链继承

    • 对象冒充继承可以继承构造函数里面上的属性和方法, 不能继承原型链上面的属性和方法
    • 原型链继承可以继承构造函数里面和原型链上的属性和方法,但是实例化子类的时候没法给子类传递参数
    // 对象冒充继承
    function Person() {
    	this.name = '张三' // 定义构造函数的属性
        this.age = 10  // 定义构造函数的属性
        this.run = function() {  // 定义构造函数的方法
            console.log(`${this.name}在跑步`)
        }
    }
    Person.prototype.sex = '男' // 原型链上的属性
    Person.prototype.work = function() { // 原型链上的方法
        console.log(`${this.name}在工作`)
    }
    function Web() {
        /*对象冒充实现继承*/
        Person.call(this) 
    }
    let w = new Web()
    
    w.run() // 张三在跑步
    w.work() // w.work is not a function 不能继承原型链上的属性和方法
    
    // 原型链实现继承, 但是不能给父类传值
    /*在不传递参数的情况下,即name:'张三'和 age: 10 规定后*/
    function Person() {
    	this.name = '张三' // 定义构造函数的属性
        this.age = 10  // 定义构造函数的属性
        this.run = function() {  // 定义构造函数的方法
            console.log(`${this.name}在跑步`)
        }
    }
    
    Person.prototype.sex = '男' // 原型链上的属性
    Person.prototype.work = function() { // 原型链上的方法
        console.log(`${this.name}在工作`)
    }
    function Web() {}
    Web.prototype = new Person()
    let w = new Web()
    
    w.run() // 张三在跑步
    w.work() // 张三在工作
    
    /*在传递参数的情况下,即name和 age需要传值时*/
    function Person(name, age) {
    	this.name = name // 定义构造函数的属性
        this.age = age  // 定义构造函数的属性
        this.run = function() {  // 定义构造函数的方法
            console.log(`${this.name}在跑步`)
        }
    }
    
    Person.prototype.sex = '男' // 原型链上的属性
    Person.prototype.work = function() { // 原型链上的方法
        console.log(`${this.name}在工作`)
    }
    function Web() {}
    Web.prototype = new Person()
    let w = new Web('张三', 20)
    
    w.run() // undefined在跑步
    w.work() // undefined在工作
    
  • 原型链 + 构造函数的组合继承模式

    function Person(name, age) {
    	this.name = name // 定义构造函数的属性
        this.age = age  // 定义构造函数的属性
        this.run = function() {  // 定义构造函数的方法
            console.log(`${this.name}在跑步`)
        }
    }
    
    Person.prototype.sex = '男' // 原型链上的属性
    Person.prototype.work = function() { // 原型链上的方法
        console.log(`${this.name}在工作`)
    }
    function Web(name, age) {
    	// 对象冒充继承 实例化子类给父类
        Person.call(this, name, age)
    }
    Web.prototype = new Person() // (这段代码也可以替换为 Web.prototype = Person.prototype 因为对象冒充的时候已经继承构造函数的属性和方法,所以只需要继承父类原型链上的属性和方法即可)
    let w = new Web('张三', 20)
    
    w.run() // 张三在跑步
    w.work() // 张三在工作
    

2. Ts中的类和继承

  • 类的定义

    • 定义类的关键词class
    • 字段---类声明的变量,表示对象的相关数据
    • 构造函数---实例化时调用,可以为类的对象分配内存
    • 方法 --- 为对象要执行的操作
    class Person {
        name: string // 字段
        constructor(name: string) { // 构造函数
            this.name = name
        }
        run():void{ // 方法
            console.log(`${this.name}在跑步`)
        }
    }
    const p = new Person('张三')
    p.run() // 张三在跑步
    
  • 继承

    • 继承使用关键词extends、super
    • 子类除了不能继承父类的私有成员(属性和方法)和构造函数,其它的都能继承
    • TS一次只能继承一个类,不能继承多个类,但是支持多重继承(A继承B,B继承C)
    • 子类可以定义自己的属性和方法
    • 当子类定义具有和父类相同名称的属性和方法时,将会执行子类的属性和方法
    class Person {
        name: string // 字段
        constructor(name: string) { // 构造函数
            this.name = name
        }
        run():void{ // 方法
            console.log(`${this.name}在跑步`)
        }
    }
    
    class Web extends Person{
        /*
        	此处构造函数可以省略, 
        	当没有构造函数的时候TS会自动创建
        */
        num: number
        constructor(name: string, num: number) {
            super(name)
            this.num = num
        }
        // 子类可以定义自己的属性和方法
        work():void{
            console.log(`${this.name}工作了${this.num}小时`)
        }
    }
    const p = new Web('张三', 10)
    p.run() // 张三在跑步
    p.work() // 张三工作了10小时
    
  • 类的修饰符 publicprotectedprivatereadonly

    名称作用
    public当未指明类型的时候的默认类型,可以在任何地方访问
    protected只能被类内部,继承类的内部访问,不能被外部访问
    private只能被内部访问,其它都不能访问
    readonly只读属性,相当于const,只能在声明时或构造函数里被初始化赋值,其它地方不允许赋值
    class Person {
        private name: string
        protected age: number
        public sex: boolean
        readonly height: number = 100
        constructor(height) {
            this.height = height
        }
        setHeight() {
            this.height = 200 // Cannot assign to 'height' because it is a read-only property.
        }
    }
    
    /*外部访问*/
    const p = new Person()
    console.log(p.name) // Property 'name' is private and only accessible within class 'Person'.
    console.log(p.age) // Property 'age' is protected and only accessible within class 'Person' and its subclasses.
    console.log(p.sex) // success
    
    class Web extends Person{
        getInfo():void{
           	console.log(this.name) // Property 'name' is private and only accessible within class 'Person'.
         	console.log(this.age) // success
         	console.log(this.sex) // success
        }
    }
    
  • 静态 static, 只能通过类名来进行调用, 静态类型的方法,只能访问静态类的属性, 不能通过实例化来获取

    class Person {
        name: string
        static age: number = 10
        constructor(name:string) {
    		this.name = name        
        }
        static getAge():void {
            console.log(this.age)
            // console.log(this.name) // Property 'name' does not exist on type 'typeof Person'
        }
    }
    console.log(Person.age) // 10
    Person.age = 1
    Person.getAge() // 1
    
  • 多态

    • 父类定义一个方法不去实现,让继承它的子类来实现,而且每一个子类都有不同的表现
    • 多态属于继承
    class Animal {
      name: string
      constructor(name: string) {
        this.name = name
      }
      eat() {
        return this.name + '吃东西'
      }
    }
    class Dog extends Animal{
      constructor(name: string) {
        super(name)
      } 
      eat() {
        return this.name + '吃狗粮'
      }
    }
    class Cat extends Animal {
      constructor(name: string) {
        super(name)
      }
      eat() {
        return this.name + '吃猫粮'
      }
    }
    const d = new Dog('小狗')
    console.log(d.eat()) // 小狗吃狗粮
    const c = new Cat('小猫')
    console.log(c.eat()) // 小猫吃猫粮
    
  • 抽象类abstract

    • 抽象类做为其它子类的基类使用,一般不会被实例化
    • abstract 用于定义抽象类和在抽象类内部定义抽象方法
    • 在子类的构造函数中必须调用super()方法
    abstract class Person {
        name: string
        constructor(name: string) {
            this.name = name
        }
        abstract run():unknown // 必须在子类中实现
    }
    
    class Web extends Person{
        run():void{
            console.log(`${this.name}在运动`)
        }
    }
    
    
  • 存取器

    • TS 支持 通过getters/setters来截取对象成员的访问
    • set 中定义的方法不能定义返回值
    class Person {
    	name: string
        constructor(name: string) {
            this.name = name
        }
        get userName():string {
            console.log(`get ${this.name}`)
            return this.name
        }
        set userName(name: string) {
            console.log(`set ${this.name}`)
            this.name = name
        }
    }
    
    const p = new Person('李四')
    p.userName // get 李四
    p.userName = '王五' // set 王五
    p.userName // get 王五
    

五、接口

接口主要是定义行为和动作的规范 作用是为这些类名和你的代码或者第三方代码定义契约 传入的对象参数实际上好汉很多属性,但是编译器只会检查哪些必需的属性是否存在 只要传入的对象满足上面提到的必要条件那么他是被允许的

1. 属性接口(对json的定义)

  • 对批量传入参数的写法进行约束

    interface LabelObj {
        label: string
    }
    
    function printLabel(labelObj: LabelObj){
        console.log(labelObj.label)
        console.log(labelObj.size) // 类型“labelObj”上不存在属性“size”
    }
    let myObj = {
        size: 10, // 在接口层未定义,即使传入进去,在方法层printLabel() 也不能使用
        label: 'Size 10 Object'
    }
    
    printLabel(myObj)
    
    
    // 当另一个函数的传参和这个函数的传参相同时,所定义的interface是相同的, 因此可以共用一个interface
    

2. 可选属性

  • 接口中的属性不全都是必需的, 有些只是在某些条件下,或根本不存在
  • 带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义后面加上?符号
  • 好处:一是对可能存在属性进行预定义,二是可以捕获引用了不存在属性时的错误
interface SquareConfig{
	color?: string
    width?: number
}
function createSquare(config: SquareConfig): { color: string, area: number } {
    let newSquare = {
        color: 'white',
        area: 100
    }
    if (config.width) {
        newSquare.width = config.width
    }
    if (config.width) {
        newSquare.area = config.width * config.width
    }
    return newSquare
}

let mySquare = createSquare({ color: 'black' })
console.log(mySquare) // { color: 'black', area: 100 }
  • 可选参数封装ajax
$.ajax({
	type: 'GET',
    url: 'test.json',
    data: { username: $('#username').val(), content: $('#content').val()},
    dataType: 'json'
})
interface Config{
 	type: string
  	url: string
    data?: string
    dataType: string
}

function ajax(config: Config) {
    const xhr = new XMLHttpRequest()
    xhr.open(config.type, config.url, 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({
    type: 'GET',
    url: 'xxxx.com',
    data: 'name = 1111',
    dataType: 'json'
})

3. 只读属性readonly

  • 一些对象属性只能在对象刚刚创建的时候修改其值
interface Point{
	readonly x: number
    readonly y: number
}
let p: Point = { x: 10, y: 20 }
p.x = 100 // 无法分配到 "x" ,因为它是只读属性

4. 函数类型参数

  • 对于函数类型的类型检查,函数的参数名可以不与接口里定义的参数名一致
interface Encrypt{
    (key: string, value: string):string
}
let md5: Encrypt = function(key: string, value: string):string{
    return key + value
}
// console.log(md5('哈哈哈', '呵呵呵')) // 哈哈哈呵呵呵

// 对于函数类型的类型检查,函数的参数名可以不与接口里定义的参数名一致
let sha1: Encrypt = function(a: string, b: string):string{
    return a + b
}
// console.log(sha1('哈哈哈', '呵呵呵')) // 哈哈哈呵呵呵

5. 混合类型

interface Counter{
  (start: number): string
  interval: number
  reset(): void
}

function getCounter(): Counter{
  const counter = <Counter>function (start: number) {}
  counter.interval = 1
  counter.reset = () => {}
  return counter
}

6. 可索引接口

// 对数组类型的约束
interface UseArr{
    [index: number]: string
}
const arr: UseArr = ['111', '2222']
const arr: UseArr = [111, 222] // 不能将类型“number”分配给类型“string”

// 对对象类型的约束
interface UseObj{
    [index: string]: string
}
const obj: UseObj = {
  name: '张三',
  age: 10 // 不能将类型“number”分配给类型“string”
}

7. 类类型接口的约束

  • 当类类型的interface被定义的时候,class类中必须包含interface中的属性和方法

  • 使用implements关键字来链接

    interface Animal{
        name: string
        eat(str: string): void
    }
    class Dog implements Animal{
        name: string
        constructor(name: string) {
            this.name = name
        }
        eat() {
            console.log(`${this.name}哈哈哈`)
        }
    }
    const d = new Dog('小狗')
    d.eat() // 小狗哈哈哈
    
    class Cat implements Animal{
        name: string
        constructor(name: string) {
            this.name = name
        }
        eat() {
            console.log(`${this.name}啦啦啦`)
        }
    }
    const c = new Cat('小猫')
    c.eat() // 小猫啦啦啦
    

8. 接口的扩展,接口可以继承接口extends

interface Animal {
    eat(): void
}
// 当类继承多个接口时 interface Person extends Animal AAA,BBB
interface Person extends Animal{
    work():void
}
    
// 当类继承多个接口时 class Web implements Person, AAA,BBB
class Web implements Person {
    name: string
    constructor(name) {
        this.name = name
    }
    // eat 和 work 必须要实现
   eat() {
       console.log(`${this.name}东吃西`)
   }
    work() {
        console.log(`${this.name}东吃西`)
    }
}
 const p = new Web('小明')
 p.eat() // 小明吃东西
 p.work() // 小明在工作

9. 实现类的继承和接口继承

interface Animal {
    eat(): void
}
interface Person extends Animal{
    work():void
}
class Web {
    name: string
    constructor(name: string) {
        this.name = name
    }
    coding() {
        console.log(`${this.name}喜欢写代码`)
    }
}

class Programer extends Web implements Person {
    constructor(name: string) {
        super(name)
    }
    eat() {
        console.log(`${this.name}东吃西`)
    }
    work() {
        console.log(`${this.name}在工作`)
    }
}
const p = new Programer('小明')
p.eat() // 小明吃东西
p.work() // 小明在工作
p.coding() // 小明喜欢写代码

六、类型别名

1. 定义

  • 类型别名是给一个类型起一个新的名字,并不会建立新的类型

  • 类型别名可以用于原始值、联合类型、交叉类型、元组和其它任何需要手写的类型

  • 错误信息、鼠标悬停时,不会使用别名,而是直接显示为引用类型

    const greet = (uid: string | number, name: string) => {
        console.log(`${name} --- ${uid}`)
    }
    
    const greetUser = (user: { uid: string | number, name: string }) => {
        console.log(`${user.name} --- ${user.uid}`)
    }
    
    // 这两个函数中重复的有 string | number和 {uid: string | number, name: string}, 因此可以定义类型别名
    // 当类型需要重复时, 或者多行写,就需要类型的别名来定义
    
    type Uid = string | number
    type User = {
        uid: Uid
        name: string
    }
    const greet = (user: User) => {
        console.log(`${user.name} --- ${user.uid}`)
    }
    const obj = {
        uid: 11111111,
        name: '李四'
    }
    greet(obj) // 李四---11111111
    
    

2. 关键字

  • extends可以继承一个类,也可以用来继承interface, 还可以用来判断类型

    T extends U ? X : Y
    // 解读: 如果T能赋值给U, 则类型为X 否则为Y
    
    // 简单例子
    type Words = 'a' | 'b' | 'c'
    type W<T> = T extends Words ? true | false
    type WA = W<'a'> // true
    type WD = W<'D'> // false
    
    // 联合类型
    type A<T> = T extends 'x' ? 'a' : 'b'
    type B = 'x' | 'y'
    type C = A<B> // 'a' | 'b'
    
  • typeof判断一个变量的基础类型,在TS中可以获取一个变量的声明类型,如果不存在,则获取该类型的推论类型

    // 声明类型
    interface Person {
        name: string
        age: number
        location?: string
    }
    const jackt: Person = {
        name: 'jack',
        age: 10
    }
    type jack = typeof jack // Person
    
    // 推论类型
    function foo(x: number):number[] {
        return [x]
    }
    type F = typeof foo // (x: number) => number[]
    
    /* 可以用来取一个对象接口的所有key值 */
    type K1 = keyof Person // 'name' | 'age' | 'location'
    type K2 = keyof Person[] // number | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice".....等等
    type K3 = keyof { [x: string]: Person } // string | number
    
    /* 对基本类型的判断*/
    let key1: keyof boolean // "valueOf"
    let key2: keyof number // "valueOf" | "toString" | "toFixed" | "toExponential" | "toPrecision" | "toLocaleString"
    let key3: keyof string // number | "valueOf" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring"....
    let key4: keyof null // never
    let key5: keyof undefined // never
    let key6: keyof symbol // "valueOf" | "toString"
    let key7: keyof bigint // never
    
    /* 对引用类型的判断*/
    let key8: keyof Array<any> // number | "toString" | "toLocaleString" | "concat" | "indexOf" | "lastIndexOf" | "slice" | "length" | "includes" | "pop" | "push" | "join" .....
    let key9: keyof Function // "toString" | "length" | "apply" | "call" | "bind" | "prototype" | "arguments" | "caller" | "name"
    let key10: keyof Object // "valueOf" | "toString" | "toLocaleString" | "constructor" | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable"
    
    // typeof 和 keyof 的结合
    const Colors = {
      red: "11",
      blue: "22",
    }
    type Color = keyof typeof Colors // 'red' | 'blue'
    let color: Color
    color = 'red' // success
    color = 'blue' // success
    color = 'black' // error
    
    // 先执行typeof Colors 获取到变量类型, 然后通过keyof 获取该类型的所有值
    
  • in 遍历枚举类型

    type Keys = 'a' | 'b'
    type obj = {
        [P in Keys]: number
    } // { a: number; b: number; }
    
  • infer只能在extends条件类型语句中使用, 可以声明一个类型变量并且对它进行使用,可以用来获取函数的返回值类型,

    type ParamType<T> = T extends (param: infer P) => any ? P : T;
    interface User {
      name: string;
      age: number;
    }
    type Func = (user: User) => void;
    type Param = ParamType<Func>; // User
    

3.TS内置类型别名 文档

  • 源码位置node_modules/typescript/lib/lib.es5.d.ts

  • Partial 将某个类型里面的属性全部变为可选项?

    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    /从源码中可以看到 keyof T 是先拿到T的所有属性, 然后in进行遍历, 将赋值其赋值给P, 然后T[P]获取到相应的属性值,结合?则变为可选项
    
    type A = {
        name: string
        age: number
    }
    type B = Parital<A> // { name?:string, age?: number }
    
  • Required的作用和Partial的作用相反,将所有类型变为必选项

    type Required<T> = {
        [P in keyof T]-?: T[P];
    };
    // -?代表的是移除?标识,所有的为必填项
    type A = {
        name?: string
        age?: number
    }
    type B = Parital<A> // { name:string, age: number }
    
  • Readonly 将传入的值变为只读选项

    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    };
    
    type A = {
      name: string
      age?: number
    }
    
    type B = Readonly<A> // {readonly name: string; readonly age?: number;}
    
  • Pick 将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型

    // K必须是T的key
    // 选出T类型中的K  为T 的子类型
    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    };
    
    type A = {
      name: string
      age: number
    }
    
    type C = Pick<A, 'name'> // { name: string; }
    
  • Record将K中所有的属性的值转化为T类型

    type Record<K extends any, T> = { [P in K]: T; }
    
    type T = {
      name: string,
      age: number
    }
    
    type K = string | number
    type C = Record<K, T> // { [x: string]: T; [x: number]: T;}
    
    type K = '1' | 2 | 1
    type C = Record<K, T> //  { 1: T, 2: T };
    // 注意:实践中发现数字1会和字符串1合并
    
  • Exclude 将某个类型中属于另一个的类型移除掉

    // 去除T中和U相同的类型,返回T
    type Exclude<T, U> = T extends U ? never : T;
    
    type A = '1' | '2' | '4'
    type B = '2' | '5' | '6'
    type C = Exclude<A, B> // "1" | "4"
    
    type A = string | number
    type B = string | bigint
    
    type C = Exclude<A, B> // number
    
  • Extract 中提取出T包含在U的元素, 也就是 T 和 U 的交集

    type Extract<T, U> = T extends U ? T : never;
    
    type A = '1' | '2' | '4'
    type B = '2' | '5' | '6'
    type C = Extract<A, B> // "2"
    
    type A = string | number
    type B = string | bigint
    
    type C = Extract<A, B> // string
    
  • ReturnType 获取函数的返回类型

    type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
    
    function A(x: number): number[] {
      return [x]
    }
    type B = typeof A // (x: number) => number[]
    type fn = ReturnType<typeof A> //  number[]
    
  • InstanceType获取构造函数类型的返回类型

    type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
    
    class A {
      name: string
      age: number
      constructor(name: string, age: number) {
        this.name = name
        this.age = age
      }
    }
    type B = InstanceType<typeof A> // A
    
  • ThisType上下文此类型的标记 必须启用--noImplicitThis标志才能使用此实用程序

    interface ThisType<T> { } 
    
    /* 例子 */
    type A<D, M> = {
      data?: D,
      methods?: M & ThisType<D & M>
    }
    function makeObj<D, M>(desc: A<D, M>): D & M {
      let data: object = desc.data || {}
      let methods: object = desc.methods || {}
      return { ...data, ...methods } as D & M
    }
    let obj = makeObj({
      data: {
        x: 0,
        y: 0
      },
      methods: {
        add(dx: number, dy: number) {
          this.x += dx
          this.y += dy
        }
      }
    })
    // 经过类型推导可以发现 
    // D ---> { x: 0, y: 0 }
    // M ----> { add(dx: number, dy: number) { this.x += dx; this.y += dy;}}
    
    // 所以最终合并的应该是对象合并
    // { x: 0, y: 0, add(dx: number, dy: number) { this.x += dx; this.y += dy;} }
    obj.x = 10
    obj.y = 5
    obj.add(5,5)
    console.log(obj) // {x: 15, y: 10, add: ƒ}
    
  • Uppercase将字符串文字类型转换为大写

  • Lowercase将字符串文字类型转换为小写

  • Capitalize将字符串文字类型的第一个字符转换为大写

  • Uncapitalize将字符串文字类型的第一个字符转换为小写

    type Uppercase<S extends string> = intrinsic;
    type Lowercase<S extends string> = intrinsic;
    type Capitalize<S extends string> = intrinsic;
    type Uncapitalize<S extends string> = intrinsic;
    
    type A = 'aaa'
    type B = Uppercase<A> // "AAA"
    type C = Lowercase<B> // "aaa"
    type D = Capitalize<C> // "Aaa"
    type E = Uncapitalize<B> // "aAA"
    
  • NonNullable这个类型可以用来过滤类型中的 nullundefined类型

    type NonNullable<T> = T extends null | undefined ? never : T;
    
    type A = string | undefined | null | bigint | number
    type B = NonNullable<A> // string | number | bigint
    
  • Parameters可以获得函数的参数类型组成的元组类型

    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
    
    function Fn(name: string): string{
      return name
    }
    type A = Parameters<typeof Fn> // [name: string]
    
  • ConstructorParameters在元组中获取构造函数类型的参数

    type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
    
    class Person {
      name: string
      age: number
      constructor(name: string, age: number) {
        this.name = name
        this.age = age
      }
    }
    
    type B = ConstructorParameters<typeof Person> // [name: string, age: number]
    

4. 自定义类型别名

  • Omit构造一个类型为T的,但是类型K除外

    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
    
    type A = {
      name: string
      age: number
    }
    type B = 'name'
    
    type C = Exclude<keyof A, B> // "age"
    type D = Pick<A, C> // age: number;
    
    type E = Omit<A, B> // age: number;
    
  • Mutable将T中所有属性的readonly移除

    type Mutable<T> = {
      -readonly [P in keyof T]: T[P]
    }
    
    type A = {
      readonly name: string
      readonly age: number
      flag: boolean
    }
    
    type B = Mutable<A> // { name: string; age: number; flag: boolean;}
    
  • PowerPartial 就是通过类型递归,将对象类型中的每一个 key ,都变成了不必须, 主要是为了解决内置的Partial的局限性,只能处理第一层的属性

    type PowerPartial<T> = {
      // 如果是Object,则递归
      [U in keyof T]?: T[U] extends object? PowerPartial<T[U]> : T[U]
    }
    
    // 使用Partial时
    // 虽然已经写了C类型的属性可选但是当多层嵌套的时候,在第二层如果name、age或者list未写会报错
    type A = {
      name: string
      age: number
      list: A[]
    }
    type C = Partial<A>
    const a: C = {
      name: '1',
      age: 0,
      list: [
        {
          name: '2',
          age: 0,
          list: [] 
        }
      ]
    }
    
    // 使用PowerPartial则可以将子集属性也变成可选属性
    type D = PowerPartial<A>
    const b: D = {
      name: '1',
      age: 0,
      list: [
        {
          name: '2',
          list: []
        }
      ]
    }
    
  • Defferred 相同的属性名称,但使值是一个Promise,而不是一个具体的值:

    type Deferred<T> = {
        [P in keyof T]: Promise<T[P]>;
    };
    
    type A = {
      name: string,
      age: number
    }
    type C = Deferred<A> // { name: Promise<string>; age: Promise<number>;}
    
  • Proxify为 T 的属性添加代理 增加一个get set方法

    type Proxify<T> = {
        [P in keyof T]: { get(): T[P]; set(v: T[P]): void }
    };
    
    type A = {
      name: string,
      age: number
    }
    type C = Proxify<A> 
    // {
    //   name: {
    //         get(): string;
    //         set(v: string): void;
    //     };
    //     age: {
    //         get(): number;
    //         set(v: number): void;
    //     };
    // }
    

5. interface VS type

  • Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

  • An interface can have multiple merged declarations, but a type alias for an object type literal cannot.

  • 文档

  • 相同点

    • 都可以描述一个对象或者函数

      interface User {
          name: string
      }
      interface Fn {
          (name: string, age: number): void
      }
          
       type User = {
           name: string
       }
      type Fn = (name: string, age: number) => void
      
    • 都可以进行扩展

      // interface 扩展 interface
      interface A {
          name: string
      }
      interface A {
          age: number
      }
      
      // interface 扩展 type
      type A {
          name: string
      }
      interface B extends A {}
      
      // type 扩展 type
      type A = {
          name: string
      }
      type B = {
          age: number
      }
      type C = A & B
      
      // type 扩展 interface
      interface A {
          name: string
      }
      
      type B = {
          age: number
      }
      type C = A & B
      
  • 不同点

    • 类型别名不能参与声明合并,但是接口可以, 即类型别名不可以重复命名,但是接口可以

      // interface
      interface A {
          name: string
      }
      interface A{ // ok
          age: number
      }
      
      // type类型
      type A = {
          name: string
      }
      type A = { // 标识符“B”重复
          age: number
      }
      
    • 接口仅仅可以声明对象类型的,不能声明基础类型的数据

      // 声明对象类型
      interface A {
          name: string
      }
      type B = {
          name: string
      }
      
      type C = string // ok
      interface A extends string{} // // “string”仅表示类型,但在此处却作为值使用 
      
    • type 可以声明基本类型、联合类型和 元组等类型, 接口不行

      type A = string // 基本类型
      type B = string | number // 联合类型
      type C = [string, number] // 元组
      
    • type可以根据typeof来判断出实例类型

      let div = document.createElement('div')
      type A = typeof div // HTMLDivElement
      

七、泛型

1. 泛型定义

  • 创建一个可重用的组件,支持多种类型的数据
/* 希望定义一个传递的参数和返回的参数相同的函数 */

// number
function identity(arg: number):number{
    return arg
}

// string
function identity(arg: string): string{
    return arg
}

// 使用any也可以达到以上目的, 但是缺失了ts代码检查的功效, 变成anyscript
function identity(arg: any): any{
    return arg
}
  • 在上述例子中会造成多个函数 或者在函数中增加很多类型判断,因此在Ts中引入了泛型概念,用于捕获传入的类型,然后使用当做返回值的类型
  • 具体什么类型是调用这个方法的时候决定的
// 方式一
// 定义参数属性
function identity<T>(arg: T):T{
    return arg
}
console.log(identity<number>(111)) // 111
console.log(identity<string>('哈哈哈')) // 哈哈哈

// 方式二
// 直接使用类型推论
console.log(identity(111)) // 111
console.log(identity('哈哈哈')) // 哈哈哈
  • 用法: 当你的函数、接口或者类
    • 需要作用到很多类型的时候
    • 需要被用到很多地方的时候

2. 泛型变量

  • 使用泛型创建泛型函数时,编译器要求函数体必须使用通用的类型,也就是这些参数必须是任意的或者所有类型

    function identity<T>(arg: T): T {
       // console.log(arg.length) //类型“T”上不存在属性“length”
       // 并不是所有的类型都是具有length属性的,比如number类型, 因此会报错
       return arg
    }
    
  • 如果想要传入一个数组类型格式的

    function identity<T>(arg: T[]): T[]{
        console.log(arg.length) // 数组格式是有length属性的
        return arg
    }
    
    // 等价于
    
    function identity<T>(arg: Array<T>): Array<T>{
        console.log(arg.length) // 数组格式是有length属性的
        return arg
    }
    
    console.log(identity(123)) // 类型“number”的参数不能赋给类型“unknown[]”的参数
    console.log(identity([123]))
    console.log(identity('123')) // 类型“string”的参数不能赋给类型“unknown[]”的参数
    

3. 泛型接口

// 定义接口
/* 方式一 */
interface IdentityFn{
    <T>(arg: T):T
}
/* 方式二 */
interface IdentityFn<T>{
    (arg: T): T
}
// 定义函数
function identity<T>(arg: T):T{
    return arg
}
const myIdentity: IdentityFn = identity
console.log(myIdentity(123)) // 123

4. 泛型约束

  • 使用关键字extends实现约束
interface Obj{
    length: string
}
function identity<T extends Obj>(arg: T):T{
    console.log(arg.length)
    return arg
}
identity(10) // 类型“number”的参数不能赋给类型“Obj”的参数
identity([1, 2, 3]) // ok
identity({length: 10}) // ok

5. 泛型类

// 定义一个公共的泛型类MySqlDB, 然后User和Article可以一起使用公共的泛型
class MySqlDB<T>{
    // 定义一个新增接口
    add(info:T):boolean {
        console.log(info)
        return true
    }
}
// 定义User类
class User{
    username: string | undefined
    password: string | undefined
}
let u = new User()
u.username = '张三'
u.password = '123456'

let db = new MySqlDB<User>()
db.add(u) // User {username: "张三", password: "123456"}

class Article{
    title: string | undefined
    desc: string | undefined
    status: number | undefined
    constructor(params: {
        title: string | undefined,
        desc: string | undefined,
    	status: number | undefined
    }) {
        this.title = title
        this.desc = tesc
        this.status = status
    }
}

const a = new Article({
    title: 'Test',
    desc: '123465',
    status: 1
})

let dbA = new MySqlDB<Article>
dbA.add(a) // Article {title: "Test", desc: "123465", status: 1}

6. 泛型函数嵌套

7. 泛型递归

8. Bonus接口智能提示

// 可以尝试以下此段代码,会得到不一样的效果
interface Seal{
    name: string
    age: number
}
interface API{
    '/user': { name: string, age: number }
	'/seal': { seal: Seal }
}
const api = <URL extends typeof API>(url: URL): Promise<API[URL]> => {
    return fetch(url).then((res) => res.json())
}

api('/seals').then(res => res.seal)

八、namescpace命名空间

  • 为了解决重名问题

  • 定义了标识符的可见范围,一个标识符可以在多个命名空间中定义,它在不通命名空间是互不相同的,这样一个新的命名空间中可以定义任何标识符,他们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中

    namespace A {
        export function B(arg:string):void {
            console.log(arg)
        }
        export function C(arg:string):void {
            console.log(arg)
        }
    }
    A.B('B') // B
    A.C('C') // C
    
  • namespace 可以嵌套

    namespace A {
        export function B(arg:string):void {
            console.log(arg)
        }
        export function C(arg:string):void {
            console.log(arg)
        }
        export namespace D{
            export function B(arg:string):void {
            	console.log(`D---${arg}`);
            }
            export function C(arg:string):void {
                console.log(`D---${arg}`);
            }
        }
    }
    A.B('B') // B
    A.C('C') // C
    A.D.B("B"); // D---B
    A.D.C("C") // D---C 
    
  • 当在namespace调用函数时

    // 没有相同的函数,会执行上级的函数
    namespace A {
        export function B(arg:string):void {
            console.log(arg)
        }
        export function C(arg:string):void {
            console.log(arg)
        }
        export namespace D{
    		B('BBBBBB') // BBBBBB      
        }
    }
    
    // 具有相同函数会执行命名空间的函数
     namespace A {
        export function B(arg:string):void {
            console.log(arg)
        }
        export function C(arg:string):void {
            console.log(arg)
        }
        export namespace D{
    		B('BBBBBB') // D---BBBBBB 
            export function B(arg: string): void {
              console.log(`D---${arg}`);
        	}
        }
    }