【记一忘三二】TS笔记

369 阅读28分钟

安装学习环境

安装步凑

全局安装typeScript

npm install -g  typescript

新建一个项目文件夹

mkdir ts_test
cd ./ts_test

初始化项目

tsc --init

新建index.ts编写typeScript代码

const num:number = 10

在vsCode中按下 Ctrl+shift+B 快捷键,选择一种运行模式

image-20221108140239945

在vsocde编写TS可以在插件库中下载Error Lens插件,用于提供编译前报错

image-20221108162355455

常见问题

如果在运行的时,提示下面的错误

image-20221108162014948

解决方法

TS工作方案

TSC编译器会把ts文件编译为js文件,并且在编译时,检验变量、函数、对象的类型,需要注意的是,TSC编译器并不会执行js代码。配合vsocde等工具,在编写代码阶段给予类型提示

类型

类型分类

值类型

let d: 1 | "name" | false
d = false
d = true //Type 'true' is not assignable to type 'false | 1 | "name"'.

numbere类型

let num: number = 10

boolean类型

let bool: boolean = true

string类型

let str: string = "abc"

undefined类型

let und: undefined = undefined

null类型

let nul: null = null

any类型

可以理解为去除了TS类型的限制,可以为任意类型

let an: any
an = "李白"
an = 1

但是这里注意,any类型变量不仅仅可以赋值为任意类型,还可以把变量本身可以赋值给类型

let an: any
let num: number
an = "李白"
num = an 

不仅如此,any类型变量还可以使用其他类型的所专属的方法,并且在TS编译阶段不会提示错误

let an: any
an = "李白"
an.toFixed()  

上面的使用方法本身已经脱离了TS定义类型的初衷,所以不建议使用any类型

unknown类型

如果在开发过程不确定或者不知道变量的类型,更推荐使用unknown类型,而不是采用any类型,因为unknown类型避免了any所暴露出来的问题

let an: unknown
let num: number
an = "李白"
num = an    // Type 'unknown' is not assignable to type 'number'.
let an: unknown
an = "李白"
an.toFixed()  //Object is of type 'unknown'.
特点
  • unknown类型的变量可以赋值为任意类型
let num: number = 18
let str: string = "love"
let un: unknown;

un = num
un = str
let num: number = 18
  • unknown类型的变量不能赋值给其他类型
let un: unknown;
let num: number;
let str: string;

un = 18;
num = un   //不能将类型“unknown”分配给类型“number”。
un = "love"
str = un  // 不能将类型“unknown”分配给类型“string”。
  • unknown类型和任意类型的联合类型都是任意类型
type T1 = number & unknown     // 类型为number
type T2 = string & unknown	   // 类型为string
  • unknown类型和类型的交叉类型是unknown类型,除any类型外
type T1 = number | unknown    // 类型为unknown
type T2 = string | unknown	  // 类型为unknown
type T3 = any | unknown	      // 类型为any
  • never类型是unknown类型的子类型
type T1 = never extends unknown ? true : false   // true

void类型

void类型可以理解为没有任何类型,一般使用在函数的返回值中,当函数没有返回值时使用

function show(name: string, age: number): void {
    console.log(`${name}--${age}`);
}
show("李白", 18)

在js中函数没有返回值,默认返回的值是undefined,所以void类型的变量可以接受undefined作为值

let v:void
v = undefined

never类型

never类型比void类型还极端,代表了不可达到,不能为任何值,一般用来代码判错

function test(foo: number | string) {
    if (typeof foo === "string") {
    } else if (typeof foo === "number") {
    } else {
        const check: never = foo;
    }
}

如果test代码走到了else,也说明代码本身就存在错误,所以never代表不可达到

Array类型

//语法1
let arr: string[] = ["a", "b", "c"]
//语法2
let arr: Array<string> = ["a", "b", "c"]
Array签名
interface IfArray {
    [key: number]: string
}
let arr: IfArray = ["抽烟", "喝酒"]

tuple类型

在数组中只能使用一种类型的值,但有的时候数组类型中的值不确定类型,就采用元祖类型

let tu: [string, number]
tu = ["李白", 18]

object类型

let obj: object = {
    name: "李白",
    age: 18
}

还可以指定对象的值类型和值数量

//限制值类型
let obj: { name: string, age: number } = {
    name: "李白",
    age: 18
}
//对象属性存在性
let obj: { name: string, age?: number } = {
    name: "李白"
}
// 有任意数量的属性,属性名称为string类型,值也是string类型
let obj: { [propName: string]: string } = {
    name: "李白"
}

enum类型

数字枚举
enum Color {
    RED,
    YELLOW,
    WHITE
}
console.log(Color.RED);   // 0
console.log(Color[Color.RED])  // RED
let c: Color = 1

数字枚举类型的变量本质也是数字,所以可以赋值为数字

字符串枚举
enum Color {
    RED = "red",
    YELLOW = "yellow",
    WHITE = "white"
}
let c: Color = Color.RED

function类型

let add: (a: number) => number
add = function (a) {
    return a++
}

字面量类型

enum类型相似,都是指定某一组特定的常量

type Method = "POST" | "GET" 

类型推断

  • 变量不在声明时不定义类型,并且不赋值

    let num   // any类型
    let str   // any类型
    
  • 变量不在声明时不定义类型,但赋值

    let str = "abc"  // string类型
    let num = 12	// number类型
    

断言

因为TS的目的值在编写阶段规范代码,在开发过程中就会遇到这种情况,在编写阶段TS无法确定变量的情况,但是我们知道的情况,这个时候就要使用变量断言了,告诉TS编译器

类型断言

//“尖括号”语法
let str: unknown = "this is a string"
let len: number = (<string>str).length             
//“as”语法
let str: unknown = "this is a string"
let len: number = (str as string).length          
双重类型断言

可以处理任何类型不兼容问题。

type Color = "red" | "green" | "blue"
let color: Color = "red"
color = 1 as unknown as Color

const断言

const断言告诉TS编译器为表达式推断出它能推断出的最窄或最特定的类型

function req(url: string, method: "POST" | "GET") {

}
/**
 * param断言之后的的type: {
 *      readonly url: "www.bai.com";
 *      readonly method: "POST";
 *    }    
 */
let param = {
    url: "www.bai.com",
    method: "POST"
} as const
req(param.url, param.method)

非空断言

let a: number | undefined
let b: number
b = a //type 'undefined' is not assignable to type 'number'

因为a的类型可能是undefined类型,所以不能直接赋值给b,但是这里我们知道a不可能为undefined,所以要告诉TS编译器

let a: number | undefined
let b: number
b = a!

确认赋值断言

let num: number;
const init: () => void = () => {
    num = 10
}
init()
console.log(num * 2);  //Variable 'num' is used before being assigned.

因为在TS编译阶段并不知道init执行会给num赋值,所以在输出num时,TS编译器认为num没有赋值

let num!: number;
const init: () => void = () => {
    num = 10
}
init()
console.log(num * 2);

类型守卫

in关键字

interface PeopleInterface {
    walk: string
}
interface AnimalInterface {
    climb: string
}

function handle(people: PeopleInterface | AnimalInterface): void {
    if ("walk" in people) {
        console.log(people.walk);
    } else if ("climb" in people) {
        console.log(people.climb);
    }
}

typeof关键字

function padLeft(value: string, padding: number | string): string {
    let str: string
    if (typeof padding === "number") {
        str = value + Array(padding + 1).join(" ") + value;
    } else {
        str = value + padding
    }
    return str
}

padLeft("a", "d")

instanceof关键字

interface PeopleInterface {
    walk: string
}
interface AnimalInterface {
    climb: string
}

class People implements PeopleInterface {
    walk: string = "走路"
}
class Animal implements AnimalInterface {
    climb: string = "爬行"
}
function handle(people: PeopleInterface | AnimalInterface): void {
    if (people instanceof People) {
        console.log(people.walk);
    } else if (people instanceof Animal) {
        console.log(people.climb);
    }
}

类型谓词 is

function isNil<T>(v: T | undefined | null): boolean {
    return v === undefined || v === null
}
function test(v: number | undefined | null) {
    if (!isNil<number>(v)) {
        console.log(Math.round(v))   // 类型“number | null | undefined”的参数不能赋给类型“number”的参数。
    }
}

使用is谓词进行类型缩小

function isNil<T>(v: T | undefined | null): v is undefined | null {
    return v === undefined || v === null
}
function test(v: number | undefined | null) {
    if (!isNil<number>(v)) {
        console.log(Math.round(v)) 
    }
}

联合类型

可以为联合类型中的任一类型

let a: number | string | false | { name: string } | (() => void)
a = 9
a = "abc"
a = false
a = { name: "李白" }
a = () => { }

交叉类型

let a: { name: string } & { age: number }
a = {
    name: "李白",
    age: 18
}

TS编译器推断类型

变量声明

变量的类型为any

let age;    // type是any

变量声明和定义

根据定义的值类型推断出变量的类型

let age = 18; // type是number
let name = "李白"  // type是string

函数的行参数

形参的类型为any类

function sum(a, b) {   // 形参a和b的type是number
    console.log(a + b);
}

函数的返回值

会根据函数主体return的值推出类型

function sum(a: number, b: number) {  // 返回值的type为number
    return a + b
}

函数上下文类型

根据forEach函数的特性自动推断函数类型

let colors: Array<string> = ["red", "yellow", "white"]
colors.forEach((color) => {    // color的类型为string

})

泛型推导

根据执行时的类型自动推断

function print<T>(data: T) {
}
print(2)  // T的类型是number

函数

类型

声明和定义分开

let sum: (a: number, b: number) => number;
sum = (a: number, b: number): number => a + b;

声明和定义一起

function sum(a: number, b: number): number {
    return a + b
}

类型别名创建

类型创建
type Fun = (a: number, b: number) => number;
let sum: Fun
sum = (a: number, b: number): number => a + b;
签名创建
type Fun = {
    (a: number, b: number): number
}
let sum: Fun
sum = (a: number, b: number): number => a + b;
接口创建
interface Fun {
    (a: number, b: number): number
}
let sum: Fun
sum = (a: number, b: number): number => a + b;

参数

可选参数

function sum(a: number, b?: number): number {
    if (typeof b === "number") {
        return a + b
    } else {
        return a
    }
}

需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。

默认参数

function sum(a: number, b: number = 10): number {
    return a + b
}

默认参数不能可选参数一起用

剩余参数

function sum(...arg: Array<number>): number {
    return arg.reduce((a: number, b: number): number => a + b)
}

函数签名

type Fun = {
    description: string,
    (): boolean
}
let fun: Fun = <Fun>((): boolean => !!window)
fun.description = "描述"

function show(fn: Fun) {
    console.log(fn.description + "--" + fn());
}
show(fun)

用于声明函数体上的方法或者属性

重载

function joint(a: number, b: number): string
function joint(a: string, b: string): string
function joint(a: string, b: number): string
function joint(a: number, b: string): string
function joint(a: any, b: any): any {
    return String(a) + String(b)
}

特殊的函数返回值

  • 使用类型别名定义一个返回值是void的函数,并非一定不能有返回值,相反我们在函数中写了返回值也是有效的
type voidFun = () => void
let fun: voidFun = () => true
  • 定义函数的返回值是void,那么除undefined以外的值进行返回都会报错
function show(): void {
    return undefined
}

对象

类型别名

type objType = {
    name: string,
    age: number,
    like?: Array<string>
}
let obj: objType = {
    name: "李白",
    age: 18,
    like: ["抽烟", "喝酒"]
}

可以使用可选属性,但是不能使用默认属性值

对象签名

interface IfObject {
    [param: string]: string
}
let obj: IfObject = {
    name: "李白",
    sex: "男"
}

构造签名

接口

基本使用

interface objType {
    name: string,
    age: number,
}

let obj: objType = {
    name: "李白",
    age: 18
}

类实现接口

interface IParent {
    name: string;
}

class Parent implements IParent {
    public name: string;
    public sex: string;
    constructor(name: string, sex: string) {
        this.name = name
        this.sex = sex
    }
}

可选属性

interface objType {
    name: string,
    like?: Array<string>,
}

let obj: objType = {
    name: "李白",
    like:["抽烟","喝酒"]
}

其他属性

interface objType {
    name: string,
    [paramName: string]: any
}
let obj: objType = {
    name: "李白",
    age: 18,
    like: ["抽烟", "喝酒"],
    sex: "男"
}

只读属性

interface objType {
    readonly name: string
}
let obj: objType = {
    name: "李白"
}
obj.name = "杜甫"  //Cannot assign to 'name' because it is a read-only property.

扩展

interface nameType {
    name: string
}
interface ageType extends nameType {
    age: number
}
let obj: ageType = {
    name: "李白",
    age: 18
}

接口和类型别名

声明函数类型

接口也可以声明函数的类型

interface funType {
    (a: number, b: number): number
}
let fun: funType = (a: number, b: number) => {
    return a + b
}

可以相互扩展

interface interType {
    name: string
}
type objType = { age: number } & interType
let obj: objType = {
    name: "李白",
    age: 18
}

类实现

类实现类型别名

type PointType = {
    x: number
    y: number
}
class Point implements PointType {
    x: number = 1
    y: number = 2
}

类实现接口

interface PointInterface {
    x: number
    y: number
}
class Point implements PointInterface {
    x: number = 1
    y: number = 2
}

类可以以相同的方式实现接口或类型别名,但类不能实现使用类型别名定义的联合类型

type PointType = {
    x: number
    y: number
} | { x: number }
// A class can only implement an object type or intersection of object types with statically known members.
class Point implements PointType { 
    x: number = 1
    y: number = 2
    z: number = 3
}

声明合并

同一名称的多次接口声明,会自动合并接口,

interface objType {
    name: string
}
interface objType {
    age: number
}
let obj: objType = {
    name: "李白",
    age: 18
}

但是类型别名不可以合并

type objType = {
    name: string
}
type objType = {   //标识符“objType”重复
    age: number
}

属性只读修饰符readonly

只可以在初始化语句和构造函数内赋值

class Item {
    readonly country: string = "中国";
}
let item = new Item()
item.country = "美国"   // 无法分配到 "country" ,因为它是只读属性。

可访问性修饰符

公开的(public)

允许在任何地方使用和修改,属性的默认的可访问性修饰符

class Item {
    public country: string = "中国";
}
let item = new Item()
item.country = "美国"
私有的(private)

只能在类内部使用

class Item {
    private country: string = "中国";
    getCountry() {
        return this.country
    }
}
let item = new Item()
item.getCountry()
受保护的(protected)

当前类和继承了该类的子类

class Parent {
    protected country: string = "中国";

}
class Son extends Parent {
    getCountry() {
        return this.country
    }
}

let son = new Son()
son.getCountry()

可访问性修饰符使用在constructor

  • private:只能类内部创建该类的对象,单例模式的使用场景
class Parent {
    static objectItem: Parent;
    private constructor() { }
    static getObject(): Parent {
        if (this.objectItem) {
            return this.objectItem
        }
        return this.objectItem = new Parent()
    }
}

let parent: Parent = Parent.getObject()
  • protected:可以通过创建子类对象来创建当前类的对象
class Parent {
    protected constructor() { }
}
class Son extends Parent {

}

let parent = new Parent() // 类“Parent”的构造函数是受保护的,仅可在类声明中访问
  • public:无使用范围限制,任何地方都可以创建类对象

方法重写

class Animal {
    setInfo(name: string, age: number): boolean {
        return true;
    }
}

class Cat extends Animal {

    setInfo(name: string): boolean {
        super.setInfo(name, 1);
        return true;
    }
}
  • 子类的重写方法参数类型和个数必须兼容父类方法的参数类型和个数,子类重写方法的参数个数必须小于或者等于父类的方法,参数类型必须一致。
  • 子类的方法返回值类型和父类一样,父类方法返回值为void 除外。

原型属性

class Animal{
    get name () {
        return "名称";
    }
}

抽象类

使用 abstract 关键字声明的类,被称为抽象类,抽象类是普通类中包含抽象方法,抽象类不能被实例化,因为内部有抽象方法,只能被继承,让子类去实现抽象方法

abstract class Parent {
    public abstract a: number;
    public b: number = 10
    abstract handle(): void
}

class Son extends Parent {
    public a: number = 20
    handle() {
        console.log(this.a + this.b);
    }
}

let son = new Son()
son.handle()
注意点
  • private修饰符不能与abstract修饰符一起使用

    abstract class Parent {
        private abstract name: string;   // “private”修饰符不能与“abstract”修饰符一起使用
    }
    

方法重载

class Item {
    handle(a: number, b: number): number;
    handle(a: number, b: string): number;
    handle(a: number, b: Array<number>): number;
    handle(a: any, b: any): any {
        if (Array.isArray(b)) {
            return a + b.reduce((c: number, p: number): number => c + p)
        } else {
        }
    }
}
let sum = new Item().handle(1, [2, 3])

静态属性、方法

可继承
class Animal {
    public static readonly type = "animal";
}
class Cat extends Animal {

}
console.log(Cat.type);

类的类型

在TS中有Class两种类型:

  • 静态侧类型(tyopof Class)

    class A {
      name = '李白'
    
      static age = 18
    }
    
    let a: typeof A = A
    a.age  // 使用静态属性
    
  • 实例侧类型(Class)

    class A {
      name = '李白'
    
      static age = 18
    }
    
    let a: A = new A()
    a.name  // 使用实例属性
    
实例
class A {
  name = '李白'
}
function createInstance<T extends new (...args: any[]) => any>(Ctor: T): T {
  return new Ctor()
}
const a = createInstance(A)  // a的类型是静态侧类型,提示只能点出静态方法

这里的TS给出a的类型静态侧类型,这显然是不是我们想要的,需要将a 从为静态侧类型转为实例侧类型

断言
class A {
  name = '李白'

  static age = 18
}

function createInstance<T extends new (...args: any[]) => any>(Ctor: T): T {
  return new Ctor()
}
const a = createInstance(A) as A  // 断言为实例侧类型
泛型

interface IAnimal<T> {

    new(): void

}

  


class Cat { }

  


class Dog { }

  


function getInstance<T>(clazz: { new(): T }) {

    return new clazz()

}

  
  


let cat = getInstance(Cat)

let dog = getInstance(Dog)

InstanceType
function createInstance<T extends new (...args: any[]) => any>(Ctor: T): InstanceType<T> {
  return new Ctor()
}
const a = createInstance(A)

原理
type InstanceType<T> = T extends { new (...args: any[]): infer U } ? U : never

泛型

泛型类似于类型函数,在指定类型的地方传入对应的类型参数,然后返回不同的类型

函数

function getFillArr<T>(value: T, num: number): Array<T> {
    return Array(num).fill(value)
}

getFillArr<string>("李白", 5)
getFillArr<number>(18, 5)

class Parent<T>{
    name: T;
    constructor(name: T) {
        this.name = name
    }
}

new Parent<string>("李白")

接口

interface IParent<T> {
    name: T;
    getName(): T;
}

let obj: IParent<string> = {
    name: "李白",
    getName() {
        return this.name
    }
}

默认值

interface IParent<T1 = string, T2 = number> {
    name: T1
    age: T2
}

let parentObject: IParent = {
    name: "李白",
    age: 18
}

约束

function getArrLength<T extends { length: number }>(data: T): number {
    return data.length
}

getArrLength([1, 2, 3])
getArrLength({ length: 88 })

使用参数进行约束

function getProperty<T, V extends keyof T>(obj: T, key: V): any {
    return obj[key]
}
getProperty({ a: 1, b: 2 }, 'a')

泛型作为类型函数

type Parent<T> = T | number    

type TP = Parent<string>      // string|number类型

类型操作符

typeof类型获取

typeof 操作符可以用来获取一个变量声明、对象、类的类型。

let a: string
type T1 = typeof a   // type T1 = string

type TParent = {
    name: string
}
let c: TParent = {
    name: "杜甫"
}
type T3 = typeof c  // type T3 = { name: string; }

获取数组值类型

const validateState = ['success', 'error'] as const

type ValidateState = typeof validateState[number] // "success" | "error"

注意必须as const,否则得到类型是string

keyof

keyof 操作符可以用来获取某种类型的所有键,返回类型一般联合类型

基本类型

会把基本类型当做封装类型,所以会返回内部的属性和方法

type TBasic = string
type T = keyof TBasic    // type T3 = number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" 

引用类型

interface IParent {
    name: string,
    age: number
}
type T = keyof IParent   // type T = name | age

in

in 操作符可以用来循环联合类型

type Keys = "name" | "sex"

type TParent = {
    [p in Keys]: string
}                          // type TParent = { name: string; sex: string; }

extends

用于判断类型是否属于其他类型的子类型

泛型约束

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束

function getArrLen<T extends { length: number }>(arr: T): T["length"] {
    return arr.length
}

getArrLen([1, 2, 3])   // 3

子类型判断

判断是否继承自某个类型

interface IParent {
    name: string
}
interface ISon extends IParent {
    age: number
}

type T = ISon extends IParent ? true : false

infer

type T1 = string;
type T2 = number;

type K2<T> = T extends { a: infer U, b: infer U } ? U : string;

interface Props {
    a: T1;
    b: T2;
}

type K3 = K2<Props>  // T1 | T2
type T1 = {
    name: string
};
type T2 = {
    age: number
};

type K2<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : string;

interface Props {
    a: (x: T1) => void;
    b: (x: T2) => void;
}

type K3 = K2<Props>     // T1 & T2

泛型工具方法

映射类型

Pick<T,K>挑选映射

挑选 T 类型中字符串字面量 K 属性后构成的新类型

原理
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}
实例
interface User {
    name: string;
    age: number;
    sex: string;
}

type PickUser = Pick<User, 'name' | 'sex'> 
// 等价于
type PickUser = {
    name: string;
    sex: string;
}

Partial<T>可选映射

将类型T中所以属性转为可选属性

原理
type Partial<T> = {
    [P in keyof T]?: T[P]
}
实例
interface User {
    name: string;
    age: number;
    sex: string;
}

type PartialUser = Partial<User>
// 等价于
type PartialUser = {
    name?: string | undefined;
    age?: number | undefined;
    sex?: string | undefined;
}

Required<T>必选映射

将类型T中所以属性转为必选属性

原理
type Required<T> = {
    [P in keyof T]: T[P]
}
实例
interface User {
    name: string;
    age: number;
    sex: string;
}

type RequiredUser = Required<User>
// 等价于
type RequiredUser = {
    name: string;
    age: number;
    sex: string;
}

Readonly<T>只读映射

将类型T中所以属性转为只读属性

原理
type Readonly<T> = {
    readonly [P in keyof T]: T[P]
}
实例
interface User {
    name: string;
    age: number;
    sex: string;
}

type ReadonlyUser = Readonly<User>
// 等价于
type ReadonlyUser = {
    readonly name: string;
    readonly age: number;
    readonly sex: string;
}

Record<K,T>属性映射

将字符串字面量类型 K 中所有字串变量作为新类型的 Key 值, T 类型作为 Key 的类型

原理
type Record<K extends keyof any, T> = {
    [P in K]: T
}
实例
type RecordUser = Record<'name' | 'age', string>
// 等价于
type RecordUser = {
    name: string;
    age: string;
}

条件类型

Extract<T,U>公共类型

Extract<T, U> 将联合类型 T 和联合类型 U 中共有的成员组成的新联合类型,作用类似于求 TU 类型的交集

原理
type Extract<T, U> = T extends U ? T : never
实例
type ExtractType = Extract<"name" | "age", "sex" | "name">
// 等价于
type ExtractType = "name"

Exclude<T,U>排除类型

Extract<T, U> 将联合类型 T 排除联合类型 U 中的成员,类型T剩余的成员组成新的联合类型

原理
type Exclude<T, U> = T extends U ? never : T
实例
type ExcludeType = Extract<"name" | "age", "sex" | "name">
// 等价于
type ExcludeType = "age"

类型兼容性

判断集合或类型系统时,会进行变型判断

类型的兼容性

类型范围大的变量可以接受子范围类型的变量

let a: number
let b: 1

a = b

任意类型(unknown、any)>基本类型(number、string、boolean)>字面量类型(1、“abc”、false)

对象的兼容性

  • TS是根据类结构进行判断的,只要两个对象结构相处于包含关系,就认为少熟悉的那个类型是父类型,多的是子类型
  • TS中父类型的变量是可以直接接受子类型的变量
let obj1 = {
    name: "李白"
}
let obj2 = {
    name: "杜甫",
    age: 18
}
obj2 = obj1  // 错误
obj1 = obj2  // 正确 多属性值可以赋值给少熟悉值(可多不可少)

函数类型的兼容性

  • 函数的参数时逆变,函数的返回值是协变

参数类型

参数类型不一致时,会采用类型的兼容性进行比较

let fun1: (a: number) => void
let fun2: (a: string) => void

fun2 = fun1   // 错误,因为number类型和string类型不兼容

参数个数

let sum: (a: number, b: number) => void;
let show: (c: number) => void;

sum = show  // 正确 
show = sum  // 错误 参数时逆变的,所以多参数函数类型可以接受少参数函数类型

返回值类型

返回值类型不一致时,会采用类型的兼容性进行比较

let fun1: () => number
let fun2: () => string

fun2 = fun1  // 错误,因为number类型和string类型不兼容
fun1 = fun2  // 错误,因为number类型和string类型不兼容

函数重载

function sum(a: number, b: number): number;
function sum(a: string, b: string): string;
function sum(a: any, b: any): any {
    return a + b
}

function dev(a: number, b: number): number;
function dev(a: any, b: any): any {
    return a - b
}

let fun = sum
fun = dev   	// 错误

剩余参数

function fun(callback: (...args: any[]) => void) { }

fun((x, y, z) => x + y + z)
fun((x, y, z?) => x + y + z)
fun((x, y?, z?) => x + y + z)

枚举的兼容性

数字枚举和数字类型兼容

数字枚举本身也是数字类型

enum Num {
    ONE,
    TWO
}

let a: Num
a = 2          // 合理
let b: number
b = Num.ONE    // 合理

数字枚举和数字枚举不兼容

enum Num1 {
    ONE
}
enum Num2 {
    TWO
}

let a: Num1
let b: Num2
a = b			// 不兼容

字符串枚举和字符串不兼容

enum Str {
    NAME = "name"
}

let a: Str
a = "abc"

类的兼容性

class Parent {
    name: string;
}
class Son {
    name: string;
    age: number;
}

let a: Parent
let b: Son
a = b			// 合理,和类型的兼容性类似,进行结构化比较
  • 在类中熟系是受保护类型时,会影响兼容性
class Parent {
    private name: string;
}
class Son {
    name: string;
    age: number;
}

let a: Parent
let b: Son
a = b         // 不合理,因为Parent的name属性是受保护的

泛型的兼容性

TS是结构类型的编程语言,所以只是使用了泛型传参,并且没有影响属性的类型,那么就不会有兼容问题

type Empty<T> = {}

let a: Empty<number>;
let b: Empty<string>;
a = b					// 合理
b = a					// 合理

当传入的类型参数影响到熟悉,就会影响泛型的兼容性

type Empty<T> = {
    data: T
}

let a: Empty<number>;
let b: Empty<string>;
a = b
b = a

装饰器

若要启用实验性的装饰器特性,必须tsconfig.json里启用experimentalDecorators编译器选项

特性

本质

装饰器本质就是函数,把需要修饰的目标(类、函数、属性、访问器、参数)作为参数传入装饰器函数

传参

function setName(name: string) {
    return function <T extends new (...arg: any[]) => any>(constructor: T) {
        constructor.prototype.name = name
    }
}

@setName("李白")
class Test {
    constructor() { }
}

装饰器需要在装饰器函数外层包装一层函数,包装的函数被称为工厂函数

执行顺序

先从上到下执行工厂函数,再从下到上执行装饰器

function Two() {
    console.log("Two工厂函数被执行");
    return function <T extends new (...arg: any[]) => any>(constructor: T) {
        console.log("Two装饰器被执行");
    }
}
function One() {
    console.log("One工厂函数被执行");
    return function <T extends new (...arg: any[]) => any>(constructor: T) {
        console.log("One装饰器被执行");
    }
}

@Two()
@One()
class Test {
    constructor() { }
}
/**
 *    Two工厂函数被执行
 *    One工厂函数被执行
 *    One装饰器被执行
 *    Two装饰器被执行
 */

类装饰器

参数

  1. target:类的构造函数

返回值

如果存在返回值,那么就用该值覆盖原本的类

实例

function addRun<T extends new (...arg: any[]) => any>(target: T) {
    return class extends target {
        public age: number = 18;
        public show() {
            console.log(`我是${this.name},今年${this.age}岁!!!`);
        }
    }
}

@addRun
class Person {
    public name: string = "李白";

}
(new Person() as any).show()

属性装饰器

参数

  1. target:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  2. propertyName:属性名称

返回值

如果存在返回值,返回值会被会当做属性的修饰符,对于静态成员,就是在构造函数上根据属性修饰符修改属性;对于实例成员,就是在原型对象上根据属性修饰符添加属性

实例

function show(target: any, propertyName: string): any {
    target.show = function () {
        console.log(this[propertyName]);
    }
}

function add(target: any, propertyName: string) {
    target[propertyName]++
}

class Person {
    @show
    name: string = "李白"
    @add
    static age: number = 18;
}

方法装饰器

参数

  1. target:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  2. propertyName:方法名称
  3. descriptor:方法的修饰符

返回值

如果存在返回值,返回值会被会当做属性的修饰符,对于静态成员,就是在构造函数上根据属性修饰符修改方法;对于实例成员,就是在原型对象上根据属性修饰符修改方法

实例

function addRun(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    let _value = descriptor.value
    descriptor.value = function () {
        console.log("我开始加速");
        _value()
    }
}

class Person {
    @addRun
    run() {
        console.log("我正在狂奔");
    }
}

访问器装饰器

本质和方法是相同的,所以参数和返回值也是相同的

参数

  1. target:类的构造函数
  2. propertyName:访问器名称
  3. descriptor:访问器的修饰符

返回值

如果存在返回值,返回值会被会当做属性的修饰符,对于静态成员,就是在构造函数上根据属性修饰符修改方法;对于实例成员,就是在原型对象上根据属性修饰符修改方法

实例

function show(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    
    descriptor.get = function (this: Person) {
        return `我的名字叫${this._name}`
    }
}

class Person {
    _name: string = "李白";

    @show
    get name() {
        return this._name
    }
    set name(value) {
        this._name = value
    }
}

console.log(new Person());

参数器装饰器

参数

  1. target:类的构造函数
  2. propertyName: 方法名
  3. paramIndex:该参数在方法中入参中所在的位置

实例

function show(target: any, propertyName: string, index: number) {
    
}

class Person {
    show(name: string, @show age: number) { }
}

命名空间

基本使用

声明使用

  1. 使用namespace关键字定义命名空间
  2. 命名空间内部需要暴露的成员需要使用export关键字暴露
namespace User {
    export const name: string = "李白"
}

let userName = User.name

编辑成JS后的代码

var User;
(function (User) {
    User.name = "李白";
})(User || (User = {}));
let userName = User.name;

和枚举很相似

多层嵌套

可以命名空间内部再使用命名空间,内部命名空间的也要用export暴露

namespace User {
    export namespace Name {
        export const name: string = "李白"
    }
}

let userName = User.Name.name

快捷使用

如果使用多层嵌套命名空间,那么在每次访问内部命名空间成员时会写重复代码

namespace User {
    export namespace Name {
        export const name: string = "李白"
    }
}

import Name = User.Name
let userName = Name.name

使用其他模块的命名空间

  • import进行模块导入

    // cs.ts
    export namespace Cs {
        export interface IParent{
            name: string;
        }
    }
    
    // index.ts
    import { Cs } from "./cs"
    let user: Cs.IParent = {
        name: "李白"
    }
    
  • 使用reference标签进行导入

    // cs.ts
    namespace Cs {
        export interface IParent{
            name: string;
        }
    }
    
    // index.ts
    
    /// <reference path="./cs.ts" />
    let user: Cs.IParent = {
        name: "李白"
    }
    

声明重复

命名空间和类

class Parent { }

namespace Parent {
    export const name: string = "李白"
}

编译之后的JS代码

class Parent {
}
(function (Parent) {
    Parent.name = "李白";
})(Parent || (Parent = {}));
  • 类的声明必须在命名空间声明的前面
  • 将命名空间的成员定义到类上,成为类的静态成员

命名空间和函数

function Show() { }

namespace Show {
    export const name: string = "李白"
}

编译之后的JS代码

function Show() { }
(function (Show) {
    Show.name = "李白";
})(Show || (Show = {}));
  • 函数的声明必须在命名空间声明的前面
  • 将命名空间的成员定义到函数上

命名空间和枚举

enum Color {
    RED,
    YELLOW
}

namespace Color {
    export const name: string = "李白"
}

编译之后的JS代码

var Color;
(function (Color) {
    Color[Color["RED"] = 0] = "RED";
    Color[Color["YELLOW"] = 1] = "YELLOW";
})(Color || (Color = {}));
(function (Color) {
    Color.name = "李白";
})(Color || (Color = {}));

模块

在TS中的模块化和JS模块化是不一样的,任何含有exportimport语句的文件的都是模块,相应的,如果一个文件不包含exportimport语句语句,这就是一个全局脚本文件

模块属于一个作用域,并不是全局作用域,模块文件中的成员并不能直接被其他文件直接访问,如需将模块中的成员暴露给外部代码,需要使用exportimport语法进行导出

如果一个文件不包含exportimport语句,但是希望把它当作一个模块(内部变量不对外访问),可以在脚本头部加一行

export {}

TS对于非模块化的文件,都是会全局加载,但必须是include包含的文件

注意:这里特指的TS编译器,并不是说在JS执行环境中未exportimport语句就是全局环境

image-20240818013041866

基础语法

在TS中导出和引入成员使用的是EsModuld语法

interface IPerson {
    name: string,
    age: number
}

export { IPerson }
import  { IPerson } from "./index"

语法扩展

在导出和引入类型时,TS还扩展了其他写法

interface IPerson {
    name: string,
    age: number
}

export type { IPerson }
import type { IPerson } from "./index"

使用type关键字利于阅读代码,当然不使用也不影响

接口的默认导出

export default interface IPerson {
    name: string,
    age: number
}

export type { IPerson }

接口既能当表达式,也能当语句

类型声明文件.d.ts文件

第三方库无论是用TS编写还是JS编写的,最终都是打包成JS代码,但是JS代码是没有类型声明的,造成TS使用第三方库没有类型声明提示,而TS中的类型声明文件就是专门解决这个问题的

方式使用文件容只能包含类型声明,最终编译还是.d.ts文件

declare关键字

它告诉 TypeScript 编译器某些实体(如变量、函数、类等)已经存在,并且提供了这些实体的类型定义

declare const count: number

基础类型

declare const count: number

declare const config: {
    name: string,
    age: num
}

模块

declare module 'axios' {
    export declare const version: string
}

使用形式

定义基础类型

interface IPerson {
    name: string;
}

export { IPerson }

代码内不含export导出就会声明到全局

已有JS成员设置类型

TS编译器,环境中存在一个 config变量,现在赋予他类型为Config,主要是为JS文件提供类型介绍

interface Config {
    name: string,
    age: number
}

declare const config: Config

// 具名导出
export { config }
// 默认导出
export default config

为已有包补充类型

declare module 'axios' {
    export declare const version: string
}

会直接替换掉第三方的声明,可以写在多个地方,文件必须包含于include

无论写在那个文件都会被读取(但仅限制第三方模块,比如:axiosvue等)

declare module 'axios';

设置axios第三方包为any类型

image-20240819001910275

如需扩展模块,需要将代码放在TS模块(有exportimport语法)中


import "axios";

declare module "axios" {
     const version: number
}

TS内置的类型声明文件

image-20240817234608620

根据lib配置TS编译器默认导入,也可以通过types配置

image-20240817235104817

第三方库类型声明文件

库自带的类型声明文件

image-20240817234748968

在导入第三方库时提供自动导入

image-20240817235240895

DefinitelyTyped提供类型文件

如果使用第三方库没有自带的类型声明文件,那么由DefinitelyTyped提供的类型声明文件

image-20240818000322231

在安装之后,引入第三方库会自动引入类型声明文件

单独创建使用

全局环境

不用EsModule模块化导出,会默认为全局导入,可以在TS编辑器任意位置使用

// global.d.ts
declare const count: string

interface IPerson {
    name: string;
}
// main.ts
// 不用引入直接使用
const P: IPerson = {
    name: "11"
}

可以用于全局环境类型声明、第三方库类型补充

需要在tsconfig.json包含

image-20240818013041866

局部环境
// util.d.ts
interface IPerson {
    name: string;
}

export type { IPerson }
// main.ts
import type { IPerson } from "./util";


const person: IPerson = {
    name: "李白"
}

用于类型声明模块化

配合JS文件使用

一般配合JS使用,可以为已有的JS代码补充类型声明

const count = 110;

这里的count在全局环境是没有类型的,所以使用时会报错

但是在某个.d.ts文件提供声明,那么就可以正常使用了

declare const count: string

在上面模块化中提及过,在TS编译器中,如果没有提供export语法,那么在TS编译时默认是全局环境

.js文件和.d.ts文件都是全局环境
// util.js
const count = 110;
// utilType.d.ts
declare const count: string
// main.ts
console.log(count)

因为两种文件都是处于全局环境,所以在TS编译环境中都会加载,比如DOM执行环境的类型声明

.js文件和.d.ts文件都是模块化

使用import语法导出JS文件或者.d.ts文件,TS编译器都会自动追踪同名的JS文件或.d.ts文件

// util.js
const count = 110;

export { count };
// utilType.d.ts
declare const count: string

export { count };
// main.ts
import { count } from "./util"

在导入JS文件时会自动读取.d.ts文件,会将类型声明文件和JS模块内容进行合并,这里需要注意JS模块和类型声明文件都采用ESModule

  • JS模块和类型声明都导出实体,在导出JS模块时为实体添加类型声明
  • JS模块导出,但是类型声明没有对应导出,在导出JS模块时置为any类型
  • JS模块未导出,但是类型声明文件导出,在导出JS声明文件时,可以导出该类型
.js文件是模块化,.d.ts文件是全局环境

报错!!!

同名文件

image-20240818190446049

非同名文件

image-20240818190626728

.js文件是全局环境,.d.ts是文件模块化
// util.d.ts
declare const count: number

interface IPerson {
    name: string;
}

export { count, IPerson }
// util.js
const count = 110;
// main.ts
import { count } from "./util";

导出的是声明,并无法正常使用

三斜杠命令

三斜杠命令用于TS加载类型声明文件

可以读取tsconfig.json中incliue配置包含之外的声明文件,但引入的什么文件不能包含模块化

加载指定文件的类型声明

/// <reference path="..." /> 

加载某个包的类型依赖

/// <reference types="..." />