TypeScript 入门 | 青训营笔记

70 阅读8分钟

image.png

这是我参与「第四届青训营」笔记创作活动的第5天

一、TypScript 简介

1.1 为什么是TypeScript

TypeScript 是 JavaScript 的一个超集。TypeScript在集成JavaScript的所有特性的同时,还拥有自己所独有的特性,TypeScript 是一门不同于JavaScript 的新语言,但它可以编译成 JavaScript 在浏览器中运行。

1.2 TypScript 发展史

  • 2012-10∶微软发布了TypeScript 第一个版本(0.8)
  • 2014-10: Angular发布了基于TypeScript的2.0版本
  • 2015-04: 微软发布了Visual Studio Code
  • 2016-05: @types/react发布,TypeScript可开发React
  • 2020-09: Vue发布了3.o版本,官方支持TypeScript
  • 2021-11: V4.5版本发布

1.3 为什么要学习TypScript

  • 可读性增强: 基于语法解析TSDoc,ide增强
  • 可维护性增强:在编译阶段暴露大部分错误,多人合作的大型项目中,获得更好的稳定性和开发效率
  • 包含于兼容所有Js特性,支持共存
  • 支持渐进式引入与升级

二、基本语法

下面都是一些基础语法,根 js 相比没有太多的区别,只是在书写过程多,需要写类型声明,更多的基础类型声明可以自己试一下,比如对象类型,数组类型等

    // 限制类型为 number
    let a : number
    a = 123
    // a = 'hello'  // 报错,a的值的类型只能是 number
    
    // 也可以使用字面量进行类型限制
    // 通过字面量进行限制之后,该变量的值只能是 : 后面的那个值,不能进行更改
    let b : 10
    b = 10
    // b = 123  // 报错 b 的值只能是10,并且类型只能是数字
    
    // any 表示该变量可以为任意类型,也就相当于去掉了ts的限制
    let e : any
    e = 12
    e = 'hello'
    e = true
    
    // unknown 也表示改变了可以为任意类型,但是它相对于 any 要安全一些
    let f : unknown
    f = 123
    f = true
    f = 'hello'
    
    // 下面就是 unknown any 的一些区别
    let g : number
    // 将 any 的变量赋值给一个 类型为number变量时,
    // 这个类型为number变量的将无法检测类型
    // 所以下面的赋值并不会报错
    g = e
    // 此时将类型为 unknown 赋值给 number类型的变量时,
    // 就会报错,无法将 字符串 赋值 给数字类型  所以下面就会报错
    // g = f

    // 总结 : any 与 unknown 的使用 any 尽量不要使用  
    // unknown 在类型不确定的时候可以使用  因为它 不会影响 已经确定类型的变量

2.1、函数类型

ts 函数基本也于 js 基本一致,不同的是 ts 比 js 新增一个特性 函数重载,下面我们来简单介绍一下函数重载

为什么要使用函数重载呢?

因为使用函数重载我们可以确保函数返回值的类型,并且也不需要也过多的类型判断来缩小类型,从而得到我们想得到的结果,下面就是不使用函数重载的方式

2.1.1 不使用函数重载

function add(a1: number | string, a2: number | string) {
  if (typeof a1 === "number" && typeof a2 === "number") {
    return a1 + a2
  } else if (typeof a1 === "string" && typeof a2 === "string") {
    return a1 + a2
  }
}
add(10, 20)

在不使用函数重载时,ts 类型推断也无法给我们一个确定的类型,而是一个多个类型的联合类型

image.png

在给出多个类型的联合类型有什么坏处呢?

简单点来说,本来我们已经确定它肯定会返回一个 string 类型,但是我们无法调用 string 类型上的一些方法,如果我们直接调用那么 ts 会直接报错,但是我们又确定它一定是 string,那么就需要将其强制转换为 string 类型,如下所示

    // 还是上面的函数
    const sum = add(10, 20)
    // 1. 对象可能为“未定义”。
    // 2. 类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”
    console.log(sum.length)

image.png 由于 ts 推断出来函数返回值是 string | number | undefined 三种类型的联合类型,所以才会报这样的错误消息

说明一下,如果函数没有返回值可以使用 void 来指定函数没有返回值,但是指定 void 之后,函数还是可以返回 undefined。上面 ts 推断出来 undefined 是因为 当 if 与 else 都没有进入时,此时函数并没有返回值,我们都知道,js 的函数没有返回值,默认会返回 undefined

2.1.2 使用函数重载

特别说明:函数重载就是定义多个 函数名称相同函数类型 ,并且函数重载,函数类型不能实现该函数的具体功能,具体的函数实现需要单独写另外一个函数来实现,并且在实现函数时,该实现函数的一些参数类型返回值类型等,需要比定义函数重载时的类型要更加宽泛,也就是最少就是需要包含函数重载时的所有类型,所以下面我采用了 any 类型

    function add(num1: number, num2: number): number // 没函数体
    function add(num1: string, num2: string): string

    function add(num1: any, num2: any): any {
      if (typeof num1 === 'string' && typeof num2 === 'string') {
        return num1.length + num2.length
      }
      return num1 + num2
    }

    const result = add(20, 30)
    const result2 = add('abc', 'cba')
    console.log(result)
    console.log(result2)

此时 ts 就正确的推断出了我们所需要的结果

image.png

image.png

当然即使我们的实现函数的类型都是 any 我们在调用实现函数的时候,参数类型也必须符合重载类型的参数类型约束

image.png

以上差不多是一些基础的类型声明该如何去写,当然还有更多的特性,比如 泛型, 枚举等,这里就不一一演示了,可以去官网查阅学习

三、高级类型

3.1 联合交叉类型

  • 联合类型: A | B 联合类型表示一个值可以是几种类型之一
  • 交叉类型: A & B 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
  • 联合类型是 或者 的关系类似于 逻辑或 ||
  • 交叉类型是 并且 的关系类似于 逻辑与 &&

例子:当我们给一个书籍定义类型,书籍当中有一个相同属性,但是其余的属性类型不一致

    const booklist: IBookList = [
      {
        author: 'xiaoming',
        type: 'history',
        range: '2001-2010'
      },
      {
        author: 'zhangsan',
        type: 'story',
        theme: 'love'
      }
    ]

我们很有可能会这样写, 下面的写法很繁琐,重复定义的属性也有很多,我们对此可以进行优化改进

    interface IHistoryBook {
      author: string
      type: string
      range: string
    }

    interface IStoryBook {
      author: string
      type: string
      theme: string
    }

    type IBookList = Array<IHistoryBook | IStoryBook>

优化:我们可以使用联合交叉类型进行优化, 下面的写法会比上面稍微简介了一点

    type IBookList = Array<
      {
        author: string
      } & (
        | {
            type: 'history' | 'story'
            range: string
          }
        | {
            theme: string
          }
      )
    >

3.2 is 类型谓词

主要介绍类型谓词 is,它是用于来判断一个变量或者是接口是否属于一种类型

下面的 foo 函数,需要根据 传入的参数来打印不同的变量值

    interface IA {
      a: 1
      a1: 2
    }
    interface IB {
      b: 1
      b1: 2
    }
    
    function foo(arg: IA | IB) {
      if (arg.a) {
        console.log(arg.a1)
      } else {
        console.log(arg.b1)
      }
    }

如果直接进行判断 if(arg.a) 这样 ts 会直接报错

image.png

此时我们就可以来使用 类型谓词 is 来帮助我们判断

    // arg is IA 这个就是 来判断 arg的类型 是否 为类型 IA
    function getIsIA(arg: IA | IB): arg is IA {
     // 将 arg 转换为 IA 类型,然后获取当中的 a 属性
     // !! 表示两次取反
      return !!(arg as IA).a
    }

完整写法

    interface IA {
      a: 1
      a1: 2
    }
    interface IB {
      b: 1
      b1: 2
    }

    // 类型守卫:定义一个函数,它的返回值是一个类型谓词
    function getIsIA(arg: IA | IB): arg is IA {
      return !!(arg as IA).a
    }
    function foo(arg: IA | IB) {
      if (getIsIA(arg)) {
        console.log(arg.a1)
      } else {
        console.log(arg.b1)
      }
    }

image.png

3.3 Recoed

Recoed 它是 ts 的内置类型,它会接受两个泛型作为参数,并且第一个泛型的属性都转换为第二个泛型类型

下面我们来看看它的源码实现

    /**
     * Construct a type with a set of properties K of type T
     * 构造一个具有类型T的属性集K的类型
     */
    // keyof 表示 获取 K 的所有键值(key)
    type Record<K extends keyof any, T> = {
        // in 类似于遍历 它会依次取出 字符串字面量类型,然后作为 T类型的 键值
        [P in K]: T;
    };

keyof 演示:它会依次取出该对象的所有的键值,使用联合类型进行保存 image.png

in 演示:它会取出上面 keyof 遍历的所有键值,作为类型 T 的键值

image.png

Recoed:会将传入的第一个泛型类型的所有的键作为第二个参选参数的 key(键) 值,生成一个新的类型

    type petsGroup = 'dog' | 'cat' | 'fish'

    interface IPetInfo {
      name: string
      age: number
    }

    type IPets = Record<petsGroup, IPetInfo>

image.png

上面就是 ts 的一些基本使用,以及一些高级类型的使用,ts的内容还有很多很多,所以还是需要自己通过查阅官方文档来进行学习,啊哈哈哈哈

总结

学习了一些 ts 的高级类型的使用以及 ts 函数重载的使用以及为什么需要使用函数重载它能帮助我们解决什么样的问题