Typescript基础学习 | 青训营

65 阅读9分钟

基础入门

1. 类型

你应该声明类型,只要存在变量的地方

1.1 变量的声明和赋值

1.1.1 声明 后 赋值

首先声明变量和它的类型,然后进行赋值,赋值只能采用你声明的类型,否则会报错。

 let a: number
 a = 1 

1.1.2 声明 + 赋值

声明的时候,同时赋值,且只能赋值声明的类型

 let c: string = "nihao"

1.1.3 直接赋值

TS会自动识别赋值的类型,然后将变量的类型设置为对应的类型

 let d = 1233

1.2 函数参数类型

类似于Python声明参数的类型是什么,返回值的类型是什么。

与上面类似,如果类型不一致会报错。

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

1.3 类型的种类

1.3.1 类型分类

1.3.2 特殊类型详解

字面量
 // 1. 使用字面量来设置类型
 let a: "male" // 表示a的类型为male,且值只能为"male"
 a = "male"
 a = "123" 错误 ×
any

表示任意类型 等价于 js默认的变量

相当于关闭类型检测;

最好避免

  • any的表示方式
 // 显式any
 let c: any
 c = 1
 c = "aa"
 c = true 
 ​
 // 隐式any
 let d;  // 解析器识别为该变量类型为any
  • any的弊端之一

    any类型的变量可以赋值给任意类型的变量并且不报错

     let c: any
     c = 1
     let d: string
     d = c // 不报错
    

    返过来时可以的,任意类型可以赋值到any类型,因为any本身的类型就决定了这个性质!

unknown

一种安全的any

  • 像any一样使用

     let e: unknown
     e = 1
     e = "a"
     e = true
    
  • 比any更安全的赋值给其他变量

    不能直接赋值,尽管e的类型实际上和f相同。

     let f: number = 1
     f = e // 不能将类型unknown分配给类型number
    

    e = f 是可以的,理由同上

  • 实现unknown的赋值-> 类型断言

void
  • 在变量类型声明时,表示undefined

     let k: void
     k = undefined
     k = null   // 报错
    
  • 用于函数返回值的声明

    返回为空,或者不返回,或者返回undefined都行 返回null也不对

     function fn(): void{
         return 
     }
    
object
  1. 直接用,比较少:
 let a: object;
  1. 字面量+对象,来设置对象的属性和属性的类型
 let b: {     
     name: string,
 ​
 };
 ​
 b = {
     name: "a"
 }
 ​
 // 不合法:
 b = {}
 b = {name: 1}
  1. 其他常用的?:

    • 可选属性,age写与不写都行

       let b: {     
           name: string,
           age?: number
       };
      
    • 必须有指定属性,其他有没有都行

       let c: {name: string, [propName: string]: any}
      
  2. 设置函数的类型声明

    整体上就是变量的类型声明,但是是作用到函数上;

    有个疑问,如果是写和上面类似,任意多个参数的怎么写?

     let d: (a: number, b: number) => number;
     ​
     d = function(n1:number, n2:number): number{
         return n1 + n2
     }
    
array

设置数组的类型

  1. 类型后+[]

    • let f: string[];
    • let f: string[] = ["1", "2"]
  2. Array<类型名>

    let i: Array<string>

tuple

固定长度+指定类型的数组

语法:[类型, 类型]

 let h: [string, number]
 h = ["hello", 1]
enum

枚举类型用来方便选择的

 enum Gender{
     Male,
     Female
 }
 ​
 let i: {name: string, gender: Gender}
 i = {
     name: "孙悟空",
     gender: Gender.Male
 }

1.3.3 一些特殊的用法

类型相或 | :联合类型

表示类型的并集,多重类型可以使用

 let b: "male" | "famale"
 b = "male"  √
 b = "famale" √
 b = "c"  ×
 ​
 // 不同常规类型
 let a: "string" | "number"
类型断言

在unknown类型的赋值时,尽管变量e的类型已知,但是还是不能赋值给其他类型。

我们这时候可以给编辑器解释当前变量的类型:类型断言

方式1:变量 as 类型

 let g: unknown
 g = 1
 ​
 let h: number
 h = g as number

方式2:<类型名>变量

 let l: number
 l = <number>g
类型别名
 type MyType = string
 type MyNumber = 1 | 2 | 3 | 4
 let l: MyNumber;

2. 编译选项

面向对象

1. 类

创建类

定义类

 class Person{
     
 }

定义属性

实例属性

 class Person{
 name: string = "孙悟空";
 }
 const per = new Person()
  • 通过对象实例来访问

静态属性

属性前面 + static

 class Person{
 // 对象包含两个部分:
 // 属性
 // 实例属性: 通过实例来访问
 name: string = "孙悟空";
 // 在属性前使用static关键字可以定义类属性(静态属性)
 static age: number = 18
 // 方法
 }
 ​
 const per = new Person()
 console.log(per)
 console.log(Person.age)
  • 通过类访问(Person.age)访问
  • 打印per只会显示name属性

只读属性

  • randonly 属性名
  • randonly static 属性名
 class Person {
     readonly name: string = "孙悟空";
     static age: number = 18
 }
 ​
 const per = new Person()
 per.name = "猪八戒"

报错:无法为name赋值,因为它是只读的。

定义方法

实例方法

实例调用

 class Person {
     name: string = "孙悟空";
     age: number = 18
     sayHello(){
         console.log("hello 大家好!")
     }
 }
 const per = new Person()
 per.name = "猪八戒"

静态方法

类调用

 class Person {
     name: string = "孙悟空";
     age: number = 18
     static sayHello(){
         console.log("hello 大家好!")
     }
 }
 Person.sayHello()

2. 构造函数

构造函数用于动态地为类中属性赋值

基本使用

  • 在类中定义属性
  • constructor中为this赋值
  • 创建实例的时候,传递参数
 class Dog{
     
     name: string
     age: number
     
     constructor(name: string, age: number){
         this.name = name
         this.age = age
         console.log("构造函数执行了")
     }
 }
 const dog = new Dog("小黑", 2)
 const dog1 = new Dog("小bai", 2)
 console.log(dog)
 console.log(dog1)
  • 在构造函数中,当前对象就是当前新建的对象
  • 创建谁,this就是谁
  • 通过this向新建的对象中添加属性

3. 继承

和其他语言类似,继承可以复用父类的代码

     class Animal {
         name: string;
         age: number;
         test = '这是一个测试'
         constructor(name: string, age: number) {
             this.name = name
             this.age = age
         }
         sayHello() {
             console.log("动物在叫")
         }
     }
     class Dog extends Animal {
 ​
         run() {
             console.log(`${this.name}在跑`)
         }
         sayHello() {
             console.log('汪汪汪')
         }
     }
 ​
 ​
     class Cat extends Animal {
         sayHello() {
             console.log('汪汪汪')
         }
     }
 ​
 ​
     const dog = new Dog("旺财", 19)
     const cat = new Cat("咪咪", 29)
     dog.sayHello()
     cat.sayHello()
     dog.run()

使用extends进行继承

Dog类继承Animal类

 class Dog extends Animal

子类使用父类的属性和方法

 const dog = new Dog("旺财", 19)
 const cat = new Cat("咪咪", 29)
 console.log(cat.test) 

子类添加和重写方法

直接添加、修改即可

     class Dog extends Animal {
 ​
         run() {
             console.log(`${this.name}在跑`)
         }
         sayHello() {
             console.log('汪汪汪')
         }
     }
     dog.sayHello()  // 汪汪汪
     dog.run()       // 旺财在跑

使用super调用父类

简单调用方法

该例中,super可以理解为父类(父类的实例),因此当我们调用dog.sayHello()时,实际上是调用了父类的sayHello

     class Dog extends Animal{
         sayHello(): void {
             // super,表示父类的实例
             super.sayHello()
            
         }
         
     }
     const dog = new Dog('tony', 19)
     console.log(dog)

在构造函数中调用super

当我们想在子类中添加新的属性的时候,需要使用constructor来为动态为属性赋值。但是如果直接使用constructor,会导致重写父类的constructor。

为了解决上述问题,我们需要再子类的constructor中,调用父类,也就是super(),并且要传入父类构造函数需要的参数。

     class Dog extends Animal{
         age: number  // 新属性
         constructor(name: string, age: number){
             super(name)  // 调用父类
             this.age = age   // 为属性赋值
         }
         sayHello(): void {
             super.sayHello()
         }
     }
     const dog = new Dog('tony', 19)
     console.log(dog) // Dog {name: 'tony', age: 19}

抽象类

抽象类:

  • 抽象类不能用来创建实例对象,只用来被继承;
  • 子类中的super可以使用

抽象方法:

  • 抽象方法使用abstract开头,没有方法体
  • 只能定义在抽象类中,子类必须对抽象方法进行重写

定义抽象类和抽象方法

 abstract class Animal {
     name: string;
     constructor(name: string) {
         this.name = name
     }
     abstract sayHello(): void
 }

继承抽象类并重写

 class Dog extends Animal{
 ​
     sayHello(): void {
         console.log('旺旺汪')
     }
 ​
 }

为什么要定义呢?直接重写不就得了?

  • 定义抽象类的抽象方法可以起到一种约束作用,即强制要求子类实现这些方法,以确保每个子类都具有相同的行为和特性,从而提高代码的可维护性和可读性。

4. 接口

接口用于规定类的结构,也就是类中要包括哪些属性和方法。

也可以用于对象的结构定义

接口的定义

 interface myInter {
     name: string;
     sayHello(): void;
 }

要求:

  • 接口中的所有属性都不能有实际的值
  • 接口中所有的方法都是抽象方法(没有方法体)

接口的使用-类

只需要按照接口要求,编写属性和方法即可。

使用implements关键字

 class MyClass implements myInter {
     name: string;
     constructor(name: string){
         this.name = name
     }
     hello: string = "hell0"
     sayHello(): void {
         throw new Error("Method not implemented.");
     }
 }

接口的使用-对象

定义接口之后,可以将其作为对象的类型,其中对象的属性和方法必须是满足接口要求,同时不能添加或者删除属性(方法)。

 interface MyInter {
     name: string,
     age: number,
     sayHello(): void 
 }
 ​
 let inter: MyInter = {
     name: "111",
     age: 19,
     sayHello: () => {console.log(1)}
 }

5. 属性的封装

使用修饰符来配置属性,使得属性在指定的地方被读取和修改;

public

public是默认的修饰符,可以在任意位置访问(修改),包括子类;

private

private:只能在当前类内部进行访问(修改),子类中也不能访问和修改

 class Person {
     private _name: string
     private _age: number
     constructor(name: string, age: number) {
         this._name = name
         this._age = age
     }
 const person = new Person()
 console.log(person._name) // 报错,提示为私有属性

protected

protected: 只能在当前类和子类中使用(修改)

 class A{
     number: number
     constructor(number: number){
         this.number = number
     }
 }
 ​
 class B extends A{
     sayHello(){
         console.log(this.number)
     }
 }
     console.log(n.number)  // 报错
     n.sayHello()         // 可以打印

TS中设置getter和setter

其实和js没什么区别

  • 使用get attr(){} 访问属性attr的值
  • 使用set attr(value){} 设置属性attr的值
     class Person {
         private _name: string;
         private _age: number;
         constructor(name: string, age: number) {
             this._name = name
             this._age = age
         }
         get name(): string {
             console.log('get name执行了')
             return this._name
         }
 ​
         set name(val: string) {
             this._name = val
         }
         get age(): number{
             return this._age
         }
 ​
         set age(val){
             if (val >= 0){
                 this._age = val
             }
         }
     }
 ​
     console.log(person.name)
     person.name = "猪八戒"
     console.log(person.name)
     person.age = -44
     console.log(person.age)

简写类的语法糖

不用额外再写属性的定义和类型

 class C{
     constructor(public name: string, public age: number){
         this.name = name
         this.age = age
     }
 }

等价于:

 class C{
     name: string
     age: number
     constructor(name: string, age: number){
         this.name = name
         this.age = age
     }
 }

6. 泛型

定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

为什么需要泛型

  •  function test(arg: any): any{
         return arg;
     }
    
  • 上例中,test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型

使用泛型

基本使用

  •  function test<T>(arg: T): T{
         return arg;
     }
    
  • 这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。

  • 那么如何使用上边的函数呢?

    • 方式一(直接使用):

      •  test(10)
        
      • 使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
    • 方式二(指定类型):

      •  test<number>(10)
        
      • 也可以在函数后手动指定泛型

指定多个泛型

可以同时指定多个泛型,泛型间使用逗号隔开:

  •  function test<T, K>(a: T, b: K): K{
         return b;
     }
     ​
     test<number, string>(10, "hello");
    

注意:在使用泛型函数的时候,必要时要指定泛型的类型变量。

类中使用泛型

使用泛型时,完全可以将泛型当成是一个普通的类去使用

  •  class MyClass<T>{
         prop: T;
     ​
         constructor(prop: T){
             this.prop = prop;
         }
     }
    

泛型的范围进行约束

  •  interface MyInter{
         length: number;
     }
     ​
     function test<T extends MyInter>(arg: T): number{
         return arg.length;
     }
    
  • 使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。