想学ts嘛?来这呀~

234 阅读21分钟

哪里有理解的不对的地方请各位大佬指出,感谢!

接口

接口定义

语法:interface 关键字

// 这就是一个接口,里面有两个属性,一个name是string类型,一个age是number类型
interface PersonType {
    name: string,
    age: number
}

可选属性

语法:?,意思是这属性写也可以,不写也没事,一些不是必要的参数就可以使用可选属性

interface PersonType {
    name: string,
    age?: number
}
// 没有写age属性也是可以的
let xy:PersonType = {
    name: '小杨'
}

只读属性

语法:readonly ,如果你不希望之后修改此属性,你就可以加上 readonly

image.png

TS当中提供了一种可读的数组的类型ReadonlyArray,此时你可以看到被ReadonlyArray修饰的数组则不能重新赋值~,但是

image.png

  • 此时我想把ReadonlyArray修饰的ro数组给到另外一个变量,会报错,但是可以用断言(as)的方式解决(断言慎用)

image.png

  • 此时可以看到,类型“readonly number[]”为“readonly”,不能分配给可变类型“number[]”。但是我们可以用 断言 的方式解决~

image.png

readonly 和 const

嗯,被 const 定义的是常量也是不可以被修改的(对象类型除外(对象的属性还是可以被修改的),我这里指的基本类型),如果你是定义的变量就用 const 修饰,如果是属性 就可以使用 readonly

类型断言

嗯,怎么说呢,比如你拿到一个any类型的数据,但是你清楚的知道它就是string类型, 这时候你就可以通过类型断言的方式跟编辑器说:“嘿!我知道我在干什么!”

尖括号语法

<类型>变量名

  • 这里很奇怪哈,我看文档和网上别人的资料 尖括号语法确实如此,我也不知道哪里的问题,可能是不支持?哈哈,确实更推荐下面的as语法,这种尖括号写法貌似会和jsx写法里面冲突~ image.png

as 语法

变量名 as 类型

let msg: any = "hello world"
let a: string = msg as string

更加推荐使用 as 的方式使用类型断言~~~~

非空断言

语法:在变量后面加上一个! , 当你明确知道某个值不可能为 undefined 和 null 时,你可以用非空断言的方式跟编辑器说: ”嘿,它是一定有值的,相信我!“

function fun(val: string | null | undefined) {
    // error: 输入'字符串| 空 | undefined' 不可分配给类型 'string'。类型“undefined”不可分配给类型“string”。
    const str: string = val
    // success
    const str2: string = val!

    // error: 'val' 可能是 'null' 或 'undefined'
    const num = val.length
    // success
    const num2 = val!.length
}

确定赋值断言

语法:可以在变量或者实例后面加上!,如果你非常确定该属性会一定被赋值,则可以告诉编译器:“我会为它赋值的,相信我,你别报错了,放心!!!”

  • 话不多说,上案例!!!
let num:number
// 在分配之前使用变量“num”
console.log("num===>",num * 2) // Variable 'num' is used before being assigned.
initialize()
function initialize() {
    num = 10
}

image.png

很明显看到提示了吧,因为num还没赋值呢,你就去使用它做运算了~,此时可以使用确定赋值断言解决该问题

let num!:number
console.log("num===>",num * 2) // NaN
initialize()
function initialize() {
    num = 10
}

image.png

  • 可以看到已经没有出现红色波浪线,但是结果需要注意可不是20哦。他依然是先执行的输出语句 再往下执行的~

接口表示函数类型

// 定义接口
interface FuncC {
    (test01: string,test02:string):boolean
};
let test01: FuncC;
test01 = (param1,param2)=> {
    return true // 此处也必须返回boolean类型
};
test01('1','2') // 这里必须传入2个参数且必须是string类型

注意:这里的参数名称接口定义的参数名称可以不一样,类型个数匹配即可

可索引的类型

个人理解:比如一个对象你不知道它里面的属性且类型不确定,就可以使用索引类型
两种索引签名:string索引number索引

string签名索引

// 这里定义了objType,它具有索引签名,索引签名为string,
interface ObjType{
    [propName:string]: any
}

const obj:ObjType = {
    name: "金金金",
    age: 23,
    sex: "男",
    isPerson: true
}  // 这里可以写任何类型的属性值

number类型索引

// 这个索引签名表示了当用 `number`去索引`ObjType`时会得到`string`类型的返回值
interface ObjType{
    [propName:number]: string
}
const arr:ObjType = ["1","2","3"]
console.log(arr) // ["1", "2", "3"] 
  • 可以同时两种索引类型一起使用

注意:两种类型同时使用的时候,number索引的返回值必须是string索引返回值类型的子级,这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。

// 动物
class Animal {
    name: string;
}
// 小狗
class Dog extends Animal {
    breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

// 正确
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Animal;
}
// 正确
interface NotOkay {
    [x: number]: Dog;
    [x: string]: Animal;
}

索引类型和其他属性同时存在

// error
interface NumberDictionary {
    [index: string]: number;
    length: number;
    name: string;  // 这里会报错
    age: number;
}

// success
// 这是因为字符串可索引类型的返回值为number类型,存在string类型的返回值就会报错!必须是number类型或者number子类型。
interface NumberDictionary {
    [index: string]: number;
    length: number;
    name: number;
    age: number;
}

// success
interface NumberDictionary {
    [index: string]: number|string;
    length: number;
    name: string;
    age: number;
}

使用场景:比如有一个对象,你不知道属性名属性个数确定,此时可以使用可索引签名

// 例如:定义一个person类型,名字,年龄,性别字段是必须的,其余不确定,此时就可以使用可索引签名
interface PersonType {
    name: string,
    age: number,
    sex: string,
    [propName:string]: string | number
}
const person1:PersonType =  {
    name :'金金金',
    age: 22,
    sex: '男'
}

字符串索引和数字索引区别

感觉就是为了区分吧。
如果你写的是string索引签名,就说明是通过 .属性 方式读取数据。
如果你写的是number索引签名,则是通过 test[0] 方式。

// number索引签名 strat -------------------------
// success
interface MyObj {
  [index: number]: string;
}
let myObj: MyObj = ['hello', 'world'];
console.log(myObj[0]); // hello
console.log(myObj[1]); // world

// error
interface MyObj {
  [index: string]: string; // 这里换成了string索引签名
}
// 类型“string[]”不可分配给类型“MyObj”。“string[]”类型中缺少“string”类型的索引签名。
let myObj: MyObj = ['hello', 'world'];
// number索引签名 end -------------------------


// string索引签名 strat -------------------------
// success
interface MyObj {
  [index: string]: string;
}
let myObj: MyObj = {
    left: 'hello',
    right: 'world'
};
console.log(myObj.left); // hello
console.log(myObj.right); // world

// error
interface MyObj {
  [index: number]: string; // 这里换成了number索引签名
}
let myObj: MyObj = {
    // 会报错,说不存在left
    left: 'hello',
    right: 'world'
};

// 需要注意!!! 如下方式是可以的!!!
interface MyObj {
  [index: number]: string;
}
// 因为 TypeScript 能够自动将字符串字面量转换为对应的数字类型
let myObj: MyObj = {
    '0': 'hello',
    '1': 'world'
};
console.log(myObj[0]); // hello
console.log(myObj[1]); // world
console.log(myObj['0']); // hello
console.log(myObj['1']); // world
// string索引签名 end -------------------------

类类型

我理解的意思就是 嗯,我想想,就是强制一个类符合某种契约吧

interface TestInterface {
    test: string
}
// 实现TestInterface接口,需要注意这里需要写个构造函数(constructor)给test赋值,不然会提示没赋值初始值以及没有在构造函数中未明确分配
class TestInterfaceImpl implements TestInterface {
    test: string;
    constructor(test: string) {
        this.test = test
    }
}
  • 你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime方法一样
interface Test2Interface {
    test: string;
    setTime(d: Date):Date
}
class Test2InterfaceImpl implements Test2Interface {
    test: string;
    constructor(test: string) {
        this.test = test
    };
    setTime(d:Date): Date{
        return new Date(d.getTime())
    };
}

继承接口

接口也可以互相继承,这样能很方便的共用一些属性。

  • extends 关键字
interface Animal {
    name: string
}

interface Dog extends Animal{
    age: number
}

const xiaohuang:Dog = {
    name: '',
    age: 0
}
xiaohuang.name = "小黄"
xiaohuang.age = 12
console.log(xiaohuang.name) // "小黄"

也可以同时继承多个接口

// 宽度
interface ShapeWidth {
    width: number
}

// 高度
interface ShapeHeight {
    height: number
}

// 形状
interface Shape extends ShapeWidth,ShapeHeight {
    name: string
}

let round:Shape = {
    name: "圆形",
    width: 220,
    height: 220
}

混合类型

  • 一个对象可以同时做为函数对象使用,并带有额外的属性

这个后面补========================================================

  • 类(Class):定义了一件事物的抽象特点,包含它的属性方法

  • 对象(Object):类的实例,通过 new 生成

简单使用

class Dog {
    name: string;
    // 这里是构造函数,一般用来初始化对象的
    constructor(cName:string) {
        // 这里的this代表我们访问的当前类的成员,也就是name属性。
        this.name = cName
    }

    say() {
        return 'hello,我叫' + this.name
    }
}

const xiaohuang = new Dog('小黄')
console.log(xiaohuang)
const msg = xiaohuang.say()
console.log(msg)

image.png

我自己的理解:类就是形容一件事物的抽象特点,比如一只小狗,你可以给它取名字(name属性),今年多大了(age属性)以及它会叫主人跟人打招呼(方法)等等

  • 定义:用class 关键字定义一个类,
  • 命名类名首字母大写驼峰命名
  • 使用:用new关键字,会得到一个实例,使用这个实例对象就可以获取其中的属性、方法等。

类的继承

  • 先放代码,后解释。
class Person {
    wear(parameter: string = '') {
        return parameter
    }
}
class Chinese extends Person{
    drink() {
        console.log('我喜欢喝阔落~ ~ ~')
    }
}
const xiaoming = new Chinese()
const wearMsg = xiaoming.wear('我今天穿了一件白色的衣服')
console.log(wearMsg)
xiaoming.drink()

image.png

如上所示,Chinese(派生类)继承了Person(基类),从而也可以使用他的方法,new出来的Chinese实例可以直接访问到Person上的方法属性等。
其实我更喜欢叫 子类(派生类) 和 父类(基类) 哈哈

  • 使用:用extends 关键字代表继承
  • 看一个更复杂的例子
// 动物
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name
    };
    move(action:string){
        console.log(this.name + ':' +action)
    };
}
// 龙
class Dragon extends Animal {
    constructor(name:string) {
        super(name)
    }
    move() {
        console.log('Dragon ~ move')
        super.move('我是在天上飞的~')
    }
}
// 兔
class Rabbit extends Animal {
     constructor(name:string) {
        console.log('Rabbit ~ move')
        // this.name = name // 这里是不行的!!!super语句要放在this语句前面!!!  在派生类的构造函数中访问“this”之前必须调用“super”。(17009)  
        super(name)
    }
    move() {
        super.move('我是在地上跑的~')
    }
}

const tuzi =  new Rabbit('小白兔')
const long =  new Dragon('大黑龙')
tuzi.move()
long.move()

运行结果

image.png

解释一下上面代码的意思:

  • 首先有一个Animal类,有name名字属性和mover移动方法,接着两个类分别继承Animate,并且在构造函数里面使用了super()以及重写了父类的move方法

  • super()
    1. 构造函数里面的super():代表调用基类(父类)的构造函数,必须调用一次!!!
    2. 方法里面的super():代表调用基类里面的方法,语法是 super.方法名(),不是必须!!!
    3. 特别注意:super语句一定要写在this语句前面,不然会报错。
    4. 在构造函数里面访问 this 属性之前,我们一定要调用super(),这是ts的规则

  • ts中文网是这么解释的。

image.png

公共、私有、受保护的修饰符。

公共(public)

class Dog {
    public name: string;
    public age: number;
    public constructor(name:string, age:number) {
        this.name = name;
        this.age = age;
    }
}
------------------这两个其实是等价的。你不写默认就是public---------------------
class DogCopy {
    name: string;
    age: number;
    constructor(name:string, age:number) {
        this.name = name;
        this.age = age;
    }
}

ts里面,你不写默认就是public

私有(private)

class Person {
    private name: string;
    constructor(name: string) {
        this.name = name
    }
}

const xiaoming  = new Person('小明')
console.log(xiaoming)
// Property 'name' is private and only accessible within class 'Person'.(2341)
console.log(xiaoming.name) 

image.png

  • private修饰的不能在外部访问,只能在本类中访问
  • 你可以看到右侧还是输出了 小明,没错,因为TypeScript 编译器会将 TypeScript 代码编译JavaScript 代码,而在 JavaScript并没有访问修饰符的概念,因此实际上 JavaScript 运行时并不会对私有属性进行限制,所以这也是为什么会输出的原因,我们应该避免掉这种情况,报红色波浪线你就应该注意
  • 也是有办法滴~一般都是在构造函数当中赋值,或者存取器(set/get)当中去赋值,后面会讲到!!!

受保护的(protected)

这个和private很像

  • 不同之处
  • private:只能在本类中访问
  • protected:可以在派生类(子类)当中访问,(new出来的实例访问不行哦,会出现红色波浪线)
class Person {
   protected name: string;
   constructor(name: string) {
    this.name = name
   }
}
class Employee extends Person {
   private employeeName: string;
   constructor(name: string,employeeName:string) {
    super(name)
    this.employeeName = employeeName 
   }
   say() {
    // protected 修饰的可以在派生类当中访问。
    return `hello, 我叫${this.name},我在${this.employeeName}工作!`
   }
}
let xiaoming = new Employee('小明', '销售部')
console.log(xiaoming.say())
  • 构造函数也可以使用protectedprotected修饰的构造函数不能够被直接实例化,但是可以被继承!!!

image.png

  • 错误示范

image.png

readonly修饰符

readonly修饰的意思 代表是只读的。只能在属性初始化、构造函数当中赋值。

class Person {
    readonly name: string;
    constructor(name: string) {
        this.name = name
   }
}
let xiaohuang = new Person('小黄')
xiaohuang.name = '小明' // Error: Cannot assign to 'name' because it is a read-only property.(2540)



class Person2 {
    name: string = '小明';
    constructor(name: string) {
        this.name = name
   }
}
let xiaoming = new Person2('小杨')
xiaoming.name = '小红'

参数属性

构造函数里的参数前面加上修饰符。比如在构造函数的一个参数前面加上private,意思就是声明并初始化了一个私有属性。对于 public和 protected来说也是一样。

  • 下面我写一个例子,一看就明白了。
  • 两个类,一个构造函数里面没使用参数属性,一个使用了参数属性(用的是private修饰的。代表是私有的)
class Person{
    constructor( name:string) {
        console.log(name)
    }
}

let xiaoming = new Person('小明');
console.log(xiaoming)



class Person2{
    constructor(private name:string) {
        console.log(name)
    }
}

let xiaohuang = new Person2('小黄');
console.log(xiaohuang)
xiaohuang.name='私有属性不可以在外部被修改'

image.png

存取器

语法:在函数的前面加上 set / get
我的理解是 这个存取器相当于就是一个类内部暴露出setget方法供外部使用的。set和get处理逻辑都是在本类内部处理的。外部调取相对应的方法,相当于逻辑都封装在内部吧,只暴露方法供外部使用。安全性很好!
注意:存取器要求你将编译器设置为输出ECMAScript 5或更高。不支持降级到ECMAScript 3。

  • 如下例子你可以看到,当访问name属性的时候给出了一个提示,因为name属性是被private修饰的,外部不能修改。存取器就很好的解决了这个问题。
class Person {
    private name: string;
    public age: number;
    constructor(name:string, age:number) {
        this.name = name
        this.age = age
    };
}
let xiaoMing = new Person('小明',22)
xiaoMing.name = '小黄' // Property 'name' is private and only accessible within class 'Person'.(2341)
xiaoMing.age  = 20
  • 存取器写法
class Person {
    private name: string;
    public age: number;
    constructor(name:string, age:number) {
        this.name = name
        this.age = age
    };
    
    // 注意set这里不能指定返回类型,你可以在这里面写逻辑判断。
    set setName(sName: string) {
         if (sName==='小黄') {
            this.name = sName;
        } else  {
            alert('信息不合法!')
        }
    };
    get getName():string {
        return this.name
    };
}
let xiaoMing = new Person('小明',22)
xiaoMing.setName = '小黄'; // 将 '小黄' 赋值给 name 属性
console.log(xiaoMing.getName) // "小黄"
xiaoMing.age  = 20
/** 
  Person: {
   "name": "小黄",
   "age": 20
  } 
*/
console.log(xiaoMing) 

image.png

静态属性

语法:static 关键字修饰。

  1. 可以 写在属性或者方法前面
  2. 不可以 写在构造函数前面
  3. static修饰的方法或者属性是直接存在上面的,new出来的实例不存在被static修饰的方法以及属性
  4. static修饰的方法以及属性的调用方式是:类名.属性/方法 来调用
  • 还有一个注意点:被static修饰的属性不可以使用name等,会和一些其他的内置属性存在冲突!!!
class Person {
    /**
     * Error:Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'Person'.(2699)
     
     * 翻译:静态属性“name”与构造函数“Person”的内置属性“Function.name”冲突。(2699)
     
     * 解释:因为 定义的 Person 类的静态属性名 name 冲突了 JavaScript 内置类型 Function 的构造函数属性 name,因为 name 已经是构造函数的属性之一, 所以给 Person 类添加 name 静态属性时就会产生冲突
     */
    static name: string; // Error

    
    static myName: string;
    age: number;
    constructor(name: string, age: number) {
        this.age = age;
        Person.myName = name; // static修饰的属性需要 类名.属性 调用。
    };
    static say() {

    }
}

const xiaoyang = new Person('小杨',23)
console.log(xiaoyang)
console.log(xiaoyang.myName) // Error
console.log(Person.myName) // success

image.png

抽象类

语法: abstract关键字,一般是给派生类(子类)继承 实现的。
可以在方法前面加abstract

  1. abstract修饰的方法所在的类必须也加上abstract
  2. abstract所修饰的方法和接口的方法有点相似。两者都是定义签名但不包含方法体。且派生类需要实现所有的抽象方法
  3. 接口(interface)不同,抽象类里面可以包含成员的实现细节。接口则不行。
  4. 抽象方法必须包含 abstract 关键字并且可以加访问修饰符(private、public等),接口方法也可以加访问修饰符。
abstract class Person {
    private name: string;
    constructor(name: string) {
        this.name = name;
    };
    set setName(name: string) {
        this.name = name;
    };
    get getName() {
        return this.name;
    };
    abstract say():void;
    jump():void {
        console.log(`${this.name}会跳舞`)
    };
}
class XiaoMing extends Person {
    constructor(name: string) {
        super(name)
    };
    say() {
        console.log(`${this.getName}:嗨!`)
    }
}
const xiaoming = new XiaoMing('小明')
console.log(xiaoming)
xiaoming.say() // "小明:嗨!" 
xiaoming.jump() // "小明会跳舞" 

image.png

构造函数

语法constructor(){}
构造函数会在我们使用 new创建类实例的时候被调用。并且会得到一个实例

  • 基础使用
class Person {
    private name: string;
    constructor(name:string) {
        this.name = name;
    };
    say():string {
        return `哈喽:我叫${this.name}`
    }
}
let xiaoming: Person
xiaoming = new Person('小明')
const msg = xiaoming.say()
console.log(msg) // "哈喽:我叫小明"
  • typeof 妙用

你可以看见一句这样的代码let Person2: typeof Person = Person;
这里创建了一个叫做 Person2的变量。 这个变量保存了Person这个类或者说保存了Person这个类的构造函数。 然后我们使用 typeof Person,意思是取Person类的类型,而不是实例的类型。 或者更确切的说,"告诉我 Person标识符的类型",也就是构造函数的类型。 这个类型包含了类的所有成员和构造函数。其实就是两个变量名引用了同一处地址。
好处:当我们在其他地方需要引用 Person 类型时,可以使用 Person2 替代,这样就不需要直接使用 Person 了。如果以后我们需要修改 Person 类型的名称,那么只需要修改一处,即将 Person 修改为新的名称,而无需在代码的所有引用处都进行修改。

class Person {
    static msg = '上午好';
    name: string="";
    
    getMsg() {
        if (this.name) {
            return this.name
        } else {
            return Person.msg
        }
    };
}

let xiaoming:Person = new Person();
console.log('xiaoming.getMsg--->',xiaoming.getMsg());

let Person2: typeof Person = Person;
Person2.msg = '下午好';
console.log(Person2.name); // 注意这里输出的是类名。===》 "Person" 

let xiaohuang:Person = new Person2();
console.log('xiaohuang.getMsg--->',xiaohuang.getMsg());

console.log(Person === Person2);  // true

  • 假如person改名字了,typeof这样赋值类型的好处就体现出来了。看代码。
class Chinese {
    static msg = '上午好';
    name: string="";
    
    getMsg() {
        if (this.name) {
            return this.name
        } else {
            return Chinese.msg
        }
    };
}

let Person2: typeof Chinese = Chinese; // 只需要修改这里的名称,其它引用Person2地方的代码无需修改
Person2.msg = '下午好';
console.log(Person2.name);

image.png

把类当作接口使用

类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类
这个我也不好形容。理解的不到位, 类也可以被接口继承。

  • 查询了一波,供参考

image.png

class Person {
    name: string = "";
}

interface XiaoMing extends Person {
    age: number;
    sex: string
}

const xiaoming:XiaoMing = {name: '小明',age:22,sex:'男'}

泛型

基础使用

function test<T>(num: T) : T {
    return num;
}
// 显示指定泛型为string类型
const result = test<string>('指定了<string>,只能写string类型的数据')
console.log('result值--->',result)
console.log('result值--->',typeof result)

// 自动推导出类型为number
const result2 = test(1)
console.log('result2值--->', result2)
console.log('result2类型---->', typeof result2)

image.png

使用泛型变量

// error!!!
function test<T>(num: T) : T {
    // 假设此时我们想看长度,因为T没有明确类型,不确定是否有length这个属性,则会爆红给出提示
    console.log(num.length);
}

// success!!! 可以这样写
function test2<T>(num: T[]) : T[] {
    console.log(num.length);
    return num; 
}

// success!!! 也可以这么写
function test3<T>(num: Array<T>) : Array<T> {
    console.log(num.length);
    return num; 
}

image.png

泛型函数类型

泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样

function test<T>(num: T) : T {
    return num;
}

// 使用
let demo: <T>(num: T) => T = test;

// 也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
let demo2: <U>(num: U) => U = test;

// 调用签名的对象字面量方式
let demo3: {<T>(num: T): T} = test;

泛型接口

// 定义泛型接口
interface GenericIdentityFn {
    <T>(age:T) :T,
}
function identity<T>(arg: T): T {
    return arg;
}
// 这里可以随便写什么类型
let test:GenericIdentityFn = identity;
test(1) // success
test('1') // success
test(true) // success


// 锁定参数类型方式!!!
// 我们可能想把泛型参数当作整个接口的一个参数
interface GenericIdentityFn2<T> {
    (age:T) :T
}
function identity2<T>(arg: T): T {
    return arg;
}
// 这里需要显性的给指定类型(指定了string,就只能传入string类型的数据)
let test2:GenericIdentityFn2<string> = identity2;
test2('测试') // success
test2(1) // error: Argument of type 'number' is not assignable to parameter of type 'string'.(2345)

注意,无法创建泛型枚举和泛型命名空间。

泛型类

泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

class GenericNumber<T> {
    zeroValue: T;
    addAndSub: (x: T, y: T) => T;
    constructor(zeroValue: T, addAndSub: (x: T, y: T) => T) {
        this.zeroValue = zeroValue;
        this.addAndSub = addAndSub
    }   
}
// 加法
const number = new GenericNumber<number>(0, (x, y) => x + y);
number.zeroValue = 1;
const result = number.addAndSub(1, 111)
console.log('result', result)


// 减法也行,把操作的参数抽出来
// const option =  (x: number, y: number) => x + y; // 加法操作
const option =  (x: number, y: number) => x - y; // 减法操作
const number2 = new GenericNumber<number>(0, option);
const result2 =  number2.addAndSub(2,1);
console.log('result2', result2);


// 也可以使用字符串类型
const stringAdd = new GenericNumber<string>('0', (x, y) => x + y);
const result3 = stringAdd.addAndSub('11', '22')
console.log('result3', result3)

image.png

泛型约束

先看下这个例子

function test<T>(agrs: T): T {
    console.log(agrs.length)
    return agrs;
}

image.png

  • 你会发现,报了一个错误,大概意思是说T类型上面不存在length这个属性,因为不确定T是什么类型的,此时就可以用泛型约束解决这个问题
  1. 我这里定义了一个接口规范
  2. 泛型函数T继承了这个接口(相当于就继承了它的属性length)
  3. 使用泛型函数,传入对应参数
    注意: 传入数字,布尔值等会报错,只能传入 有length属性的 或者 对象含有length属性的。
interface LengthInterface {
     length : number
}

function test<T extends LengthInterface>(agrs: T): T {
    console.log(agrs.length)
    return agrs;
}

test({length:100});
test([1,2,3,4]);
test('11111111111111');

image.png

在泛型约束中使用类型参数

  • 我这里是限制了value参数必须为obj参数当中的属性,利用extends keyof T
function test<T, V extends keyof T>(obj: T, value: V) {
    return obj[value];
}

const obj = {
    a: 'a1',
    b: 'b1',
    c: 'c1',
    d: 'd1'
}

const result = test(obj , 'a');
console.log('result--->', result); // 'a1'

const result2 = test(obj , 'a1'); // error

image.png

在泛型中使用类类型

  • 指定了Numbr只能输入number类型的数据,string同等。
class Person<T> {
    private name: T;
    constructor(name: T) {
        this.name = name;
    }

    set setName(name: T) {
        this.name = name;
    }
    get getName(): T {
        return this.name;
    }
}

const obj = new Person<string>('小丽');
const objName = obj.getName;
console.log('objName====>', objName);


const obj2 = new Person<number>(11);
const obj2Name = obj2.getName;
console.log('obj2Name====>', obj2Name);

使用泛型创建工厂函数

  • 需要引用构造函数的类类型
// function create<T>(c: {new(): T}): T{
//     return new c();
// }
 // 也可以这么写
function create<T>(c: new()=> T): T{
    return new c();
}

class Person {
   
}
const person = create<Person>(Person);
console.log('person',person)

image.png

枚举

  • 使用场景:例如你列表有一个字段 需要展示订单状态,状态为1需要展示 已支付,状态为2显示 未支付等等,你就可以使用枚举将他们统一管理起来,方便管理以及服用。

数字枚举

  • 不给值,值则从0开始依次递增
enum OrderType {
    One,
    Two,
    Three,
    Four
}
console.log(OrderType.One) // 0
console.log(OrderType['Two']) // 1
console.log(OrderType['Three']) // 2
console.log(OrderType['Four']) // 3
  • 第一个给默认值,其余不给,其余值则在第一个上面依次递增
enum OrderType {
    One = 1,
    Two,
    Three,
    Four
}
console.log(OrderType.One)  // 1
console.log(OrderType['Two']) // 2
console.log(OrderType['Three']) // 3
console.log(OrderType['Four']) // 4

字符串枚举

enum StrngType {
    No = "0",
    Yes = "1",
}

异构枚举(不推荐这么写)

enum BooleanLikeHeterogeneousEnum { 
    No = 0, 
    Yes = "YES" 
}

枚举成员做为类型

enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square,
    //    ~~~~~~~~~~~~~~~~ Error!
    radius: 100,
}

此时这里的kind 只能是ShapeKind.Circle

反向映射

  • 注意:字符串枚举成员不可反向映射。
enum Enum {
    A = 123
}
let a = Enum.A;
console.log('a---->', a) // 123
let nameOfA = Enum[a]; 
console.log(nameOfA) // "A"

const枚举

const枚举

const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = Directions.Up

image.png

非const枚举

enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = Directions.Up

image.png

-   常量枚举在编译阶段会被删除
-   普通枚举并不会 

不需要操作枚举对象本身,而是直接使用枚举成员的值进行比较或其他操作,这时使用 const 枚举更加合适。它将避免创建额外的枚举对象,使得代码更加简洁和高效

高级类型

交叉类型

  • 语法: &
// 人
interface Person {
    name: string;
    age: number;
}
// 衣服
interface Clothing {
    color: string;
    clothing: string;
}
// 这里用了交叉类型,需要传递四个属性,少或者多都不行
function test(params: Person & Clothing) {
    console.log('params' ,params)
}
const from = {
    name: "小明",
    age: 23,
    color: "红色",
    clothing: "花纹"
}
test(from);

此时有两个接口,人和衣服,我想要一个穿着衣服的人,此时就可以用交叉类型。
交叉类型必须包含所有的属性

联合类型

  • 语法: |
function add(num1: number, num2: number | string) {
    if (typeof num2 === "number") {
        console.log(num1 - num2)
    } else if (typeof num2 === "string") {
        console.log(num1 + num2)
    }
}

add(1, 10)

通过传入不同类型执行不同的操作,联合类型可限制符合某几种其中的一种
联合类型 符合其中一个类型 即可