TS

292 阅读16分钟

什么是ts

  • ts就是typescript,是在js的基础上增加了一些限制,属于js的超集
  • 因为js的规范很松散

使用ts

  • npm install typescript -g 将ts下载到全局
  • 在使用目录下执行tsc --init 生成ts的配置文件tsconfig.json
  • ts是无法在浏览器直接运行的,需要进行编译

编译ts

  • tsc 将目录下的所有ts都单独编译为对应的js后缀文件,比如less编译成css,会新建一个文件
  • tsc --watch 监听当前目录下所有的ts,如果修改就会自动编译
  • tsc index.ts 只编译index.ts

类型

  • 给某个值使用类型限制,在变量名后加:类型即可
  • 使用多个的话,: 第一类 | 第二类,这样也称之为联合类型
  • null和undefined可以为任意类型的子类型,也就是可以将他俩赋值给其他数据类型的变量,但是会爆红,这时候将tsconfig.json里的strictNullChecks改为false即可

string

  • 字符串

number

  • 数字

boolean

  • 布尔

any

  • 所有类型

HTMLElement

  • dom节点

null

  • 可以为其他类型的子类型

undefined

  • 未定义
  • 可以为其他类型的子类型

void

  • 函数没有返回值,或者为return (null/undefined/不返回)

never

  • 永远都不会出现的值,比如函数里面一直执行中途死循环或异常
    // 在这里面可以看到,end永远不会执行,但是奇怪的是使用if则不行
    function fn(): never {
    console.log(1);
    throw new Error('error');
    console.log('end');
    }

    function fn(): never {
        while(true){
            console.log(1);
        }
        console.log('end');
    }

字面量类型

  • 在一个变量初始化时候,指定几个值,在后续赋值的时候,该变量只能赋值开始指定的值中的一个
  • 使用方法 let 变量名: 值 | 值;
    let Gender: '男' | '女';
    Gender = '男';
    Gender = '女';
    Gender = '不男不女';//爆红

声明指定参数类型的数组

  • 两种方法
  • 方法1:类型[]
  • 方法2:Array<类型>
    // 方法1
    const ary: string[] = ['one','two'];
    // 方法2
    const ary2: Array<string> = ['one','two'];

声明元组

  • 元组就是一个指定好长度和类型的数组,例如react的useState会返回固定的两个值
  • 声明方法[string, number]
    const arg: [string, number] = ['文字', 1]
枚举 enum
  • 用法:生成一个可枚举的对象
  • 如果不赋值的话,会默认将索引赋值给属性名
  • 可以通过属性值拿到属性名,也可以通过属性名拿到属性值
  • 使用方法 enum 名字 {key=name,key=name},注意赋值的时候用=而不是:
  • 如果不同的属性赋值为一个值的话,那么我们通过他的值访问到的将会是最后一个属性名
    enum Stu {
        age,
        name
    };
    console.log(stu[0], stu[1], stu['age'], stu['name']); // age name 0 1

    // 编译结果,可以看出他是将一个键值对赋值两次,正着一次,反着一次
    var stu;
    (function (stu) {
        stu[stu["age"] = 0] = "age";
        stu[stu["name"] = 1] = "name";
    })(stu || (stu = {}));
    ;
    console.log(stu[0], stu[1], stu['age'], stu['name']);

声明一个类 type

  • 可以使用type声明一个类,来减少重复的书写类似的限制
  • 格式 type 类名 = (参数:类型,参数:类型)=>返回值的类型
  • 注意的是,=>后如果为{},那是需要返回一个对象,而不是像箭头函数一样执行
  • 如果是可选参数,那么就在声明的时候再:前加?
    function fn(name: string, age?: number): { name: string } {
    return { name: '' };
    }

    type Stu = (name: string, age?: number) => { name: '' };
    let fn2: Stu = function (name, age) {
        return { name: '' };
    }

    fn2('')

断言

  • 非空断言,在变量名后使用!表示一定存在
  • 类型断言,将一个联合类型的变量,指定为一个具体的类型,但是不能指定为联合类型中不存在的类型,用法为(变量 as 类型)
    // 因为dom可能拿不到,那么root会爆红,那么使用的时候在root后加上! 表示我知道他肯定存在就ok
    let root: HTMLElement | null = document.querySelector('#root');
    root.style.color = 'red';//爆红
    root!.style.color = 'red';//OK

    // 类型断言
    let age: string | number = 1;
    (age as number).toFixed(1);

函数重载

  • 声明一个函数的参数以及返回值类型
  • 用法,与函数保持同名,且中间不能有其他的js语句,否则会报错
    const obj: any = {};
    function fn(val: any) {
        if (typeof val === 'string') {
            obj['name'] = val;
        }
        if (typeof val === 'number') {
            obj['age'] = val;
        }
    }
    fn('name');
    fn(21);
    fn({});//不报错

    // 使用函数重载
    function fn(val: number): void;
    function fn(val: string): void;
    function fn(val: any) {
        if (typeof val === 'string') {
            obj['name'] = val;
        }
        if (typeof val === 'number') {
            obj['age'] = val;
        }
    }
    fn('name');
    fn(21);
    fn(null);//报错
    // 也可以使用type或直接在参数上标记为联合类型
    // 但是复杂需求可以使用这个,比如说传入number输出number,传入string输出string,使用(val: number | string): number | string 显然达不到需求效果,没有那么精准

class 类

  • 内置了get与set方法,用的是Object.defineProperty来监听
  • 属性名前使用readonly可以将值设置为只读
  • 可以使用在constructor的()中使用public将参数放在实例上
  • 如果想在类里面初始化的时候只限制类型,但不赋值,会爆红,如(name:string;),解决方法:在tsconfig.json里将strictPropertyInitialization改为false即可
    class A {
    // myname: string = '空';如果使用public的话,这句话就可以省略
    readonly age: number = 1;
    constructor(public myname: string = '空', public readonly money: number = 100) {

    }
    get name() {
        return this.myname;
    }
    set name(val: string) {
        this.myname = '名字' + name;
    }
    }
    let newA = new A;
    console.log(newA);

    console.log(newA.name);
    newA.name = 'name';
    console.log(newA.name);

继承

  • 属性名前使用public己以及自己子类或孙等类都能访问
  • 属性名前使用protected受保护的,只有自己和自己的子类可以访问
  • 属性名前使用private私有的,只有自己能访问
  • 假设A类一个属性设置了pricate,子类B拿不到,但是可以在A类上声明一个方法,这个方法内部可以拿到这个属性,而这个方法不是私有的,那么B可以间接的拿到这个属性
    class A {
        // 公开的
        public name: string;
        // 受保护的
        protected age: number;
        // 私有的
        private sexs: '男' | '女' = '男';
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;        
        }
        getSexs(){
            console.log(this.sexs);
        }
    }
    class B extends A {
        money: number;
        constructor(name: string, age: number, money: number) {
            super(name, age);
            this.money = money;
            console.log(this.age,'儿子可以访问');
            console.log(this.sexs,'儿子也不可以访问');//爆红,私有的
        }
    }
    let newB = new B('大大泡泡', 21, 0);
    newB.getSexs();
    console.log(newB.sexs);//爆红,因为是私有的,孙子不可以访问
    console.log(newB.age);//爆红,因为是受保护的,孙子不可以访问
    console.log(newB.name);
    newB.getSexs();

装饰器 @

  • 使用@+名字来使用装饰器
  • 使用前需要先声明一个函数
  • 如果直接使用,那么紧贴需要使用的属性或方法,且不用加()
  • 如果加()那么声明的函数就需要在内部进行return一个新的函数
  • 执行顺序是谁距离目标近就先执行谁,然后将结果给到下一个
类装饰器
  • 可以重构类,起到增加属性的作用
  • target就是传递进来的类
    function addA(target: any) {
        console.log(target);//A
        return class B extends target {
            public name: string = 'cuicui';
            money: number = 100;
        }
    }
    @addA
    class A {
        
    }
    let newA = new A;
    console.log(newA);//{name: 'cuicui', money: 100}
属性装饰器
  • 在class里面使用@名字,然后在类外面声明一个同名函数
  • 如果作用在属性上,接受两个参数,第一个是target,第二个是name
  • 如果作用在函数上,接受三个参数,第一个是target,第二个是name,第三个是config(代表的是他的配置,是一个对象)
  • target代表的是prototype,name代表的是属性名
  • 访问static静态属性的时候,target就是类本身,但是需要先在类本身上先声明属性,哪怕不赋值
  • 一个属性装饰器可以被多个属性使用
    function upperCase(target: any, propertyName: string) {
        let value: string = target[propertyName];
        const getter = () => value;
        const setter = (newVal: string): void => {
            value = newVal.toUpperCase();
        };
        delete target[propertyName];
        Object.defineProperty(target, propertyName, {
            get: getter,
            set: setter
        });
    }
    function getMethods(target: any, propertyName: string, ada: any) {
        console.log(target,propertyName,ada,999);   
    }
    class A {
        @upperCase
        name: string = 'cuicui';
        money: string = 'money';
        @getMethods
        getMoney() {
            console.log('get');
        }
        @upperCase
        static age: string;
    }
    let newA = new A;
    newA.name = 'haha';
    console.log(newA.name);//'HAHA'
    console.log(A.age);//age
    A.age = 'age';
    console.log(A.age);//Age

参数装饰器
  • 三个参数:目标(prototype),函数名字,参数索引
    function addAge(target: any, name: string, index: number) {
        console.log(target, name, index);//A.prepertype 'login' 1    
    }
    class A {
        login(username: string, @addAge password: string) {
            console.log(username, password);

        }
    }
    let newA = new A;
    newA.login('cuicui', 'password');

抽象类 abstract

  • 在未定义的类和属性前加上abstract
    abstract class All {
        name: string;
        abstract getName(): string;
    }
    class Cat extends All {
        getName(): string {
            return this.name;
        }
    }
    let cat = new Cat;
    cat.name = '猫';
    console.log(cat.getName());//猫
    let dog = new Cat;
    dog.name = '狗';
    console.log(dog.getName());//狗

接口 interface

  • 可以当做一种抽象规范来限制
  • 一个类可以实现多个接口,以,间隔即可,使用inmpements实现
    //给对象限制数据结构 
    interface Stu {
        name: string,
        age: number
    };
    let cui: Stu = { name: 'cui', age: 21 };
    // 给函数限制
    interface fn {
        (val: number): number;
    }
    let myFn: fn = function (age) {
        return 1;
    }
    myFn(1);//正常
    myFn('1');//爆红

    // 类实现2个接口
    interface FnOne {
        getOne(): string
    }
    interface FnTwo {
        getTwo(): void
    }
    class A implements FnOne, FnTwo {
        getOne() {
            return ''
        };
        getTwo() { };
    }
    // 限制构造函数
    class A {
    constructor(name: string) {

        }
    }
    interface bindA {
        // 这个new限制的是A的constructor
        new(name: string): A
    }
    function creatA(clazz: bindA, name: string) {
        return new clazz('');
    }
interface常用方法
  • 通常定义了一个接口,包含了a,b两个值,但后续赋值的时候,会有一些额外的值,会ts抛异常
interface I{
    a: string
    b: number
}

const test1:I = {a:'', b:1};//OK
const test1:I = {a:'', b:1, c:1};//异常

解决办法1
const test2 = {a:'', b:1, c:1};
test1 = test2;

解决办法2

interface I2 extends I{
   c: number 
}
const test2:I2 = {a:'', b:1, c:1};

解决办法3
//相加
const test2:I1 & {c: number} = {a:'', b:1, c:1};

解决办法4 快速

const test1:I = ({a:'', b:1, c:1}) as I;

解决办法5  推荐
//[key:string]:any 表示所有未知的key只要是string类型,且值是any都可以,所以可以有多个key,省事
interface I{
    a: string
    b: number
    [key:string]:any
}
const test1:I = {a:'', b:1, c:1};

泛型

  • 泛型就是广泛的类型
  • 定义的时候,放在括号前面
  • 使用方法是:在变量名字后面<自定义泛型名>,使用的时候就可以直接使用 泛型名了
  • 可以理解为泛型是在变量调用的时候才传递过来的一个值
在函数中使用
    //箭头函数定义泛型
    const fn = <T>(props:T)=>{};
    
    
    
    
    function createArray(len: number, val: any): Array<any> {
        const result: Array<any> = [];
        for (let i = 0; i < len; i++) {
            result[i] = val;
        }
        return result;
    }
    createArray(2, 'x');
    // 换成泛型  函数
    function createArray1<T>(len: number, val: T): Array<T> {
        const result: Array<T> = [];
        for (let i = 0; i < len; i++) {
            result[i] = val;
        }
        return result;
    };
    createArray1<string>(2, 'x');
    createArray1<number>(2, 'x');//爆红
    createArray1<number>(2, 9);
在类里面使用泛型
    class A<T>{
        constructor(name: T) {

        }
        num: T;
    }
    new A<string>('');
接口里使用泛型
  • 接口中分内部声明与外部声明
  • 在内部声明的话,那么泛型只针对声明泛型的自己
  • 在外部声明针对的是整个接口
// 内部
    interface Num {
        <T>(a: T, b: T): T
    };
    let add: Num = function <T>(a: T, b: T): T {
        return a;
    };
    add<number>(5, 5);

    // 外部
    interface Num<T> {
        name: T,
        age: T
    };
    let add: Num<number> = {
        name: 1,
        age: 2
    }
多个泛型
  • 以,间隔即可
    function nums<A, B>(arg: [A, B]): [B, A] {
        return [arg[1], arg[0]];
    }
    nums<number, string>([1, '22']);
赋默认值
  • 其实如果不传递的话,会根据自己传递的参数来判断泛型
  • 也可以像给形参赋默认值一样泛型=类型
    function nums1<A = number, B = string>(arg: [A, B]): [B, A] {
        return [arg[1], arg[0]];
    }
    nums1([1, '22']);
泛型的约束
  • 因为泛型在执行前还没定义,所以会导致我们无法使用未定义的值身上的方法,哪怕我们知道他的类型,但是ts不知道
  • 解决方法:让泛型继承一个接口,接口内部声明我们需要使用的属性或方法
    interface Len {
        length: number
    };
    function args<T extends Len>(val: T) {
        console.log(val.length);
    }
泛型类型别名
  • 配合type来使用
    type List<T> = { list: T[] } | T[];
    let list1: List<string> = { list: ['1'] };
    let list2: List<string> = ['1'];
泛型接口和泛型类型别名的区别
  • 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
  • 类型别名不能被extends和implements,这个时候我们应该使用接口来代替类型别名
  • 当我们需要使用联合类型或元组类型的时候,类型别名会更合适
  • type可以做 循环和逻辑判断,interface不可以

兼容性

基本数据类型的兼容性

  • 可以在原型上找到就不报错
    let tos: {
        toString(): string
    };
    tos = {
        toString() {
            return '';
        }
    };//ok
    tos = {
        toString() {
            return '';
        },
        x: ''
    };//爆红,因为限制了只有一个
    tos = '';//成功,因为string原型上有toString方法
接口的兼容性
    interface A {
        name: string,
        age: number,
    }
    interface B {
        name: string,
        age: number,
        money: number
    }
    function getName(a: A): string {
        return a.name
    }

    getName({ name: '', age: 10, money: 10 });//爆红
    let b: B = { name: '', age: 10, money: 10 };
    getName(b);//不爆红了,一个新的接口,里面的内容包含他就ok
类的兼容
  • 可以看出,只要是赋值的结果和new的结果结构一致即可
  • 或者,找一个新的变量,其内部的值大于等于原有的值,也可以成功
    lass A {
        name: string;
    }
    class B extends A {

    }
    class C extends A {
        age: number = 10;
    }
    let a: A;
    let b: B;
    let c: C;
    a = new A;//ok
    a = new B;//ok
    b = new B;//ok
    b = new A;//ok
    c=new A;//爆红
    c=new B;//爆红
    c=new C;//ok
    c={age:10,name:''};//ok
    let d={age:10,name:'',money:10};
    c=d;//ok

函数的兼容
  • 形参可以少,但是不能多
  • 返回值可以多,但是不能少
  • 参数的类型可以多,但是不能少
    type sumType = (name: string, age: number) => number;
    let sum: sumType;
    let fn1 = function (name: string, age: number, money: number): number {
        return 1;
    }
    sum=fn1;//爆红
    let fn2 = function (name: string, age: number): number {
        return 1;
    }
    sum=fn2;//ok
    let fn3 = function (name: string): number {
        return 1;
    }
    sum=fn3;//ok
    let fn4 = function (): number {
        return 1;
    }
    sum=fn4;//ok
接口的兼容性
  • 不同泛型,但值固定,可以相互赋值
  • 不同泛型,值不固定,不可以相互赋值
    interface A<T> {
        data:T
    }
    let a:A<string>;
    let b:A<string>;
    let c:A<number>;
    a=b;//ok
    a=c;//爆红

    interface B<T> {
        data:1
    }
    let a:B<string>;
    let b:B<string>;
    let c:B<number>;
    a=b;//ok
    a=c;//ok
枚举的兼容
  • 枚举对象内有,就可以赋值,否则不行
    enum Color {
        Red, Yellow
    };
    let c: Color;
    c = Color.Red;//ok
    c = 1;//ok
    c = 0;//ok
    c = Color.Yellow;//ok
    c = '哈哈';//爆红

类型保护

  • 使用判断,来更精准的识别数据类型或指定的类,这样就可以使用对应的方法
  • 可使用typeof、instanceof、in逻辑判断来给ts进行明确的指引
type All = string | number | boolean;
function all(val: All) {
    val.toFixed();//爆红
    if(typeof val === 'number'){
        val.toFixed();//ok
    }
};
all(1);

//demo 2
class A {
    public name: string = '1';
}
class B{
    public age: number = 1;
}
function getName(a: A) {
    a.age;//爆红
    if(a instanceof B){
        a.age;//ok
    }
}
//------------
class A {
    name: string
}
class B { }

function getVal(v: A | B) {
    v//A|B
    if (v instanceof A) {
        v;//A
    } else {
        v;//B
    }
};

function getVal2(v: A | B) {
    if ('name' in v) {
        v;//A
    } else {
        v;//B
    }
}

链式写法

  • a?.b
  • 先判断a是否存在,如果为null或undefined就返回null或undefined,如果存在就找里面是否存在b,如果b存在就返回b的值,如果不存在,返回undefined
  • a?.b等价于a == null? a: a.b
    let a;
    console.log(a?.b);//undefined 
    a={b:1};
    console.log(a?.b);//1
    a=1;
    console.log(a?.b);//undefined
    a?.b();//如果b不是函数的话会抛出错误

自定义的类型保护

  • 如果使用2个类来限制类型,可以通过定义一个函数来当前判断使用的是哪个类
  • 函数返回值为布尔
    interface A {
        name: string
    }
    interface B {
        age: number
    }
    // 判断函数,return的必须是一个布尔值
    function isA(x: A | B): x is A {
        return (x as A).name == '';
    }

    function getA(x: A | B) {
        if(isA(x)){
            return x.name;
        }
        return x.age;
    }

交集&

  • 可以使用&将两个类进行合并,相同的值,后面会覆盖前面
    interface A {
        name: string
        money:number
    }
    interface B {
        age: number
        name: number
    }
    type All = A & B;
    let p: All = {
        name:'',//爆红
        age:1,//ok
        money:1,//ok
    }

typeof

  • ts对typeof进行了二次封装
  • 在原来的基础上,还可以识别ts类,但识别的ts类需要用type来接收(虽然使用let不报错),且接收后可以当做类型给其他值使用
    interface A {
        name: string
        money: number
    }
    function a(x: A) {
        type B = typeof x;
        let a = typeof '';
        console.log(a);//string 不影响旧的功能
        console.log(B);//爆红,因为B是一个接口类,不能输出
        let D: B = {
            name: '',
            money: 1
        };//ok
    }
    a({ name: '', money: 1 });
Exclude
  • 排除
type I = Exclude<number|string|boolean, number|boolean>;//string
//会将第一项的挨个去第二项找,有就排除,没有留下
Extract
  • 筛选
  • 查看参数2中是否存在参数1,存在就返回,没有就never

type R = Extract<string, number | string>;//string

索引操作符
  • 可以拿到子级的类型给到其他地方用
    interface A {
        name: string,
        job: { name: string }
    }
    let haha: A['name'] = '';//ok
    let dada: A['name'] = 1;//爆红
    let xiaoxiao: A['job'] = { name: '' };//ok

索引集合 keyof

  • 可以通过keyof 接口 拿到当前接口内的所有集合,且把它当做一个类
interface A {
    name: string,
    age: number
}
type Akeys = keyof A;
// 此时的Akeys相当于 'name' | 'age'
let b: Akeys = 'age';
特殊记忆
keyof any; //symbol | number | string 代表任何能作为key的类型
映射类型

在定义的时候用in操作符去定义

        interface A {
            name: string,
            age: number
        }
        type AKeyType = { [key in keyof A]?: A[key] };
        let a: AKeyType = { name: '' };//ok
        let b: AKeyType = { age: 1 };//ok
        let c: AKeyType = { name: '', age: 1 };//ok
        let d: AKeyType = { name: '', age: 1, money: 1 };//爆红
遍历
//声明了一个K来遍历交集的keys集合,取值的时候使用K来取,有点像js的key in {}
interface Btn {
    size: "mini" | "large"
    type: "primary" | "success"
}

interface Event {
    click: (eventName: string) => void;
}

//type BtnType = Btn & Event;//鼠标悬浮BtnType,提示Btn & Event 不够清晰,声明的时候extends也是一样,提示不清晰


type Compute<T> = { [K in keyof T]: T[K] }
type BtnType = Compute<Btn & Event>;//有size、type、click 清晰
Partial
  • 将所有参数变为可选的
  • 内部还是遍历
//实现 type Partial<T> = { [K in keyof T]?: T[K] }
type I = Partial<{ a: string, b: number }>;// {a?:string,b?:string}
Required
  • 将所有参数变为必填的
//实现 type Required<T> = { [K in keyof T]-?: T[K] }
type I2 = Required<{ a?: string, b: number }>;//{a:string,b:string}
Omit
  • 用于object
  • 移除某一项
//实现type Omit<T extends object, K extends keyof T> = { [X in Exclude<keyof T, K>]: T[X] };

//Compute只是为了看起来清晰
type I3 = {
    name: string
    age: number
    address: string
}

type I3OPtions = Compute<Omit<I3, 'address'>>//{ name: string,age: number}

Pick
  • 用于object
  • 拿到object中的key,组成新的类
//实现type Pick<T, K extends keyof T> = {[P in K]: T[P];};

type I3 = {
    name: string
    age: number
    address: string
}
type I4 = Pick<I3, 'name' | 'age'>;//{name: string, age: number}

条件限制

  • 通过泛型和三元运算符来计算type
  • 此时的extends可以理解为是不是,而不是继承
    interface A {
        name: string
    }
    interface B {
        age: number
    }
    interface C {
        money: number
    }
    type D<T> = T extends A ? B : C;
    let b: D<A> = {
        name: ''
    };//爆红
    b = {
        age: 1
    };//ok
Record
  • 限制对象的key和value类型
  • Record要求第一个值只能是 number | string | symbol
let obj: Record<symbol, number> = { [Symbol(1)]: 1 }

命名控件 namespace

  • 会形成一个单独的作用域,可以理解为是闭包
  • 在内部可以使用export导出一些内容
  • 两个namespace互不影响
    namespace A {
        console.log(1);
        let c = 1;
        export let a: number = 1;
    }
    console.log(A.c);//undefined
    console.log(A);//{a:1}

声明 declare

declare namespace Jquery {
    function ajax(path: 'string', config: any): any
    let name: string
}
console.log(Jquery.ajax);//Jquery is not defined

declare let a: string = 1;//报错,因为只能声明
declare let myname: string;//ok

声明文件

  • 以.d.ts为后缀的都是声明文件
  • 在这里面我们可以将一些常用的方法和变量用declare声明,起到全局声明的效果

常见问题

引入第三方模块,爆红,显示缺少声明文件
  • 比如juqery缺少@types/juqery
  • 可以安装Types Auto Installer插件来自动补全@types,也可以手动安装对应的声明文件
判断一个参数是否可以被new
class C {}

//行间写法
function get<T>(clazz:new ()=> T) {
    return new clazz();
}
get<C>(C);//OK
get(C);//OK ts自动进行类型推导,可以得到返回值是C的实例

//接口写法
interface Clazz<T> {
   new ()=> T
}
function get<T>(clazz:Clazz<T>) {
    return new clazz();
}
get<C>(C);//OK