TypeScript | 青训营笔记

102 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

本节课所学

  • TypeScript 及其发展历史
  • TypeScript 基本语法

TypeScript

发展历史

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

为什么是 TypeScript

TypeScript 是 Javascript 的语法 超集,它添加了静态类型,优点在于可读性增强(基于语法解析TSDoc,ide 增强),以及可维护性增强(在编译阶段暴露大部分错误,在多人合作的大项目中,获得更好的稳定性和开发效率)。

所谓 超集 就是包含于兼容所有 JS 特性,支持共存,支持渐进式引入与升级。

基本语法

TypeScript 区别于其他静态类型语言,它使用 类型后置 声明变量的类型。

一、基础数据类型

  • 字符串
    const str: string = 'string';
    
  • 数字
    const num: number = 1;
    
  • 布尔
    const bol: boolean = true;
    
  • null
    const n: null = null;
    
  • undefined
    const un: undefined = undefined;
    

二、引用类型

2.1 array

  • 数组元素只有一种类型
    • type[]
    let arr: number[] = [1,2,3,4,5];
    
    • Array<>
    let arr: Array<number> = [1,2,3,4,5];
    
  • 若数组有多种类型的元素:
    • 使用 | 联合类型
    let arr: (number | string)[] = [1,2,3,4,'a','b'];
    
  • 二维数组
    let arr: number[][] = [[10],[20]]
    let arr2: Array<Array<number>> = [[10],[20]]
    

2.2 object

指定对象中属性和属性值的类型

interface objI {
    name: string,
    age: number,
    fn: (a: string) => string
}

let person: objI = {
    name: 'aaa',
    age: 20,
    fn(a){
        return a
    }
}

2.2.1 可选属性

  某个对象中的某个属性可有可无,此时定义类型时可以在属性名后面使用 ? 表示该属性为可选属性。

interface IPerson {
    name: string
    age: number
    hobby?: string
}
let p1: IPerson = {
    name: 'aa',
    age: 20,
    hobby: 'sing'
}
let p2: IPerson = {
    name: 'bb',
    age: 19,
}

2.2.2 索引签名

  有时候对象中的某些属性不会 提前 知道,但是却知道值的类型,此时可以使用 索引签名 描述可能值的类型

interface IReq {
  id: string
  src: string
  author: string
  [propName: string] : any
}

let r: IReq = {
  id: '1',
  src: './*/',
  author: 'jack',
  imgSrc: './img1.png',
  fn: () => {}
}

2.3 function

对函数的 输入输出 声明类型

  • function 关键字定义函数 :

    function test(str1: string, str2: string): string {
        return str1 + str2
    }
    
  • const 关键字定义函数

    let fn1: (str1: string, str2: string) => string = (str1,str2) => {
        return str1 + str2
    }
    

2.3.1 可选参数、默认参数、剩余参数

  • 可选参数

    在声明函数时,可以通过 ? 来定义可选参数,比如 age?: number 这种形式。可选参数必须要放在普通参数的后面,不然会导致编译错误。

    function userInfo(name: string, id: number, age?: number): string {
        return name + id;
    }
    
  • 默认参数

    Js 一样可以对函数的参数设置默认值,在类型后直接复制即可

    // 为 name 设置默认值
    function userInfo(name: string = "jack", id: number, age?: number): string {
        return name + id;
    }
    
  • 剩余参数

    在参数最后一位使用 ...,并且声明类型

    function add(x: number, ...items: number[]): number {
      let num = x;
      items.forEach((item) => {
        num += item;
      });
      return num;
    }
    

2.3.2 函数重载

函数有不同类型的 参数,根据不同的参数返回不同类型的结果。为解决这个问题就要为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据函数的调用去列表中进行匹配并处理。

  • 重载签名

    函数调用时根据所传参数的类型匹配不同的参数类型的函数

  • 实现签名

    函数调用类型匹配后的具体逻辑实现

    // 重载签名
    function test(x: number): number
    function test(x: string): string
    // 实现签名
    function test(x: number | string): number| string | void{
        ...
        return ... 
    }
    
    // 函数调用
    test(10);
    test('aa');
    

三、TS 额外提供的类型

3.1 Enum 枚举类型

使用枚举类型可以定义一些 带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持 数字 的和基于 字符串 的枚举

// 定义枚举时,如果常量不赋初值,默认从 0 开始递增,即 NORTH = 0,SOUTH = 1...
enum Direction { 
    NORTH, 
    SOUTH, 
    EAST, 
    WEST,
} 
let dir: Direction = Direction.NORTH;

// 一般使用枚举都会赋初值
enum Direction { 
    NORTH = "NORTH", 
    SOUTH = "SOUTH", 
    EAST = "EAST", 
    WEST = "WEST",
} 

3.2 any

表示任何类型,当一个变量声明为 any 类型,任何类型的值都可以赋给它,它也可以赋值给任何类型的变量。并且 any 类型的值可以执行任何操作而无需事先执行任何形式的检查。使用 any 就会关闭类型检查,因此尽量不使用。

// a 为 any 表示任何类型的值都可传入,但会关闭类型检查,编译期间不会判断出类型错误
fucntion fn(a: any): void {}

3.3 unknown

any 一样表示任何类型,任何类型的值都可以赋给它。但比 any 更安全,因为 unknown 类型的值不能赋给其他类型的变量。

3.3.1 any 和 unknown 的区别

  • 任何类型的值都可以赋值给 any 类型,any 类型也可以赋值给任何其他类型(除 never 等个别类型)。
  • 任何类型的值同样可以赋值给 unknown 类型,但 unknown 类型的值能被赋值给 any 类型和 unknown 类型本身
  • any 类型的值在编译期间 可以执行任何操作 而不做检查,unknown 类型 禁止 做任何操作,因为不知道会赋什么样的值,对 未知 的变量做某一 具体的操作错误 的。

3.4 Tuple 元组类型

另一种类型的数组,可以指定数组中元素的个数以及每个元素的类型

type CustomTuple = [string, number]
let t1: CustomTuple = ['1' ,1]
t1[2] = 1; // 报错,因为 CustomTuple 只有两个索引,0 和 1,但是可以通过 push 添加元素,且必须是元组的某个类型
let t2: CustomTuple = ['1' ,1, 2] // 报错,因为 CustomTuple 指定了数组只有两个元素

3.5 void

JS 中没有显式的使用 return 声明返回值的函数,默认返回值 undefinedTS 中不应该是 undefined,应该显式的声明返回值的类型为 void

// 函数 fn 没有任何返回值
type Fn = () => void
let fn: Fn = () => {}

3.6 字面量类型

当变量是某几个具体的字符串或数字中的一个时,可以对变量声明字面量类型。常搭配联合类型使用,限定 变量(变量限定为某一个值用处不大,一般不会对变量使用字面量类型) 或 函数参数 的值

// 对变量使用字面量类型,用的比较少
type strType = 'a' | 'b' | 'c'
let str1: strType = 'a'
let str2: strType = 'b'
let str3: strType = 'c'
let str4: strType = 'd' // 报错,'d' 不能分配给类型 strType,因为 strType 已经限定为 'a' 'b' 'c' 中的某一个

// 对函数参数使用字面量类型
function printText(s: string, alignment: "left" | "right" | "center") {
    // ...
}

printText("Hello, world", "left");
printText("G'day, mate", "centre"); // 报错,centre 不能分配给类型 "left" | "right" | "center"

3.7 联合类型

某个变量可以有多个类型时,可以使用联合类型,使用 | 进行类型拼接。

// x 既可以是 number 类型,也可以是 string 类型
function fn(x: number | string, y: boolean): void{}

四、类型别名 & 接口

4.1 类型别名

当某个变量的类型声明过长或多个变量使用相同的类型,这时可以使用 类型别名 为类型起一个名字

type Message = string | string[]; 
let greet = (message: Message) => {
    // ... 
}

4.2 接口

既可用于对象的描述,也可用于对类的的行为抽象

interface IPoint {
    x: number;
    y: number
}
let p: IPoint = {
    x: 10,
    y: 10
}

4.3 interface 与 type 的异同

  1. 都可以用来描述对象 / 函数类型
    interface IPoint {
        x: number
        y: number
    }
    interface IFn {
        (x: number, y: number) : number
    }
    
    let p1: IPoint = {
        x: 10,
        y: 10
    }
    let fn1: IFn = (x, y) => x
    
    type Point = {
        x: number
        y: number
    }
    type Fn = (x: number, y: number) => number
    
    let p2: Point = {
        x: 10,
        y: 10
    }
    let fn2: Fn = (x, y) => x
    
  2. interface 用来定义 对象 / 函数type 可以用来定义 原始类型联合类型元组类型 等等
    type CustomNum = number | string
    type CunstomTuple = [string, number]
    
    let n: CustomNum = 10;
    let t: CunstomTuple = ['a', 10]
    
  3. interface 允许定义 多个同名 接口,且默认 合并为一个,type 不允许定义多个同名类型别名
    interface IPerson {
        name: string
    }
    interface IPerson {
        age: number
    }
    interface IPerson {
        sex: string
    }
    // 默认合并为一个
    // interface IPerson {
    //     name: string
    //     age: number
    //     sex: string
    // }
    
    // 如果声明多个同名函数接口,合并后会当做同一函数的重载
    interface IPoint { 
        (x:number, y:number, z?:number) : number | void
    }
    interface IPoint { 
        (x:string, y:string) : string | void
    }
    interface IPoint { 
        (x:boolean, y:boolean) : boolean | void
    }
    
    // 合并
    // interface IPoint { 
    //     // 同名的函数会当做同一函数的重载
    //     (x:boolean, y:boolean, z?:number) : boolean | void
    //     (x:string, y:string) : string | void
    //     (x:number, y:number) : number | void
    // }
    
  4. 继承 / 拓展方式不同
    // interface 通过 extends 关键字继承/拓展,且只能使用一次 extends
    interface IPonitX { x: number }
    interface IPonitY extends IPonitX { y: number }
    
    let p1: IPonitY = {
        x: 10,
        y: 10
    }
    // type 通过 & 拓展,且可以使用多次
    type PointX = { x: number }
    type PointY = PointX & { y: number }
    let p2: PointY = {
        x: 10,
        y: 10
    }
    

五、泛型

声明时不指定类型,只在调用时指定类型,允许同一个函数接收不同类型参数的一种模板,相比较于 any泛型 的可复用性更好。

5.1 泛型的使用

泛型可以说是提供 可扩展性,变量不会只拘泥于一种类型

  • 函数使用泛型
    // 函数调用时使用 <> 传递 类型参数,会自动的向参数以及返回值传递类型
    function fn1 <T>(param: T): T{}
    function fn2 <T, U>(param: T, mass: U): T{}
    
    fn1<string>();
    fn2<string, number>();
    
  • 泛型接口
    interface IInfo<T> {
        data: T
    }
    let info: IInfo<string> = { data: 'aa' }
    
  • 泛型类型别名
    type Point<T> = {
        x?: T
        y?: T
    }
    let p: Point<number> = { x: 10 }
    
  • 泛型类
    class 
    
  • 泛型默认参数 : 同默认值一样,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。
    function fn1 <T = string>(param: T): T{}
    

5.2 泛型变量

任何的 大写字母 都可以作为泛型变量,为了规范会使用一些特定的字母:T、K...

  • T(Type):表示一个 TypeScript 类型
  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型
  • ......

5.3 泛型会用到的操作符

5.3.1 keyof 操作符

keyof 某个对象类型的声明,并结合声明中的键,返回 联合类型,如果类型中有索引签名,会返回索引签名的类型

type Point = { x: number; y: number };
type P = keyof Point // 此时 type P = 'x' | 'y'
let imp1: P = 'x';
let imp2: P = 'y';

type CustomPoint = {
  x: number; 
  y: number;
  [key: string]: any
}
type P2 = keyof CustomPoint // 此时 type P2 = 'x' | 'y' | string
let imp3: P2 = 'x'
let imp4: P2 = 'y'
let imp5: P2 = 'a'

5.3.2 typeof 操作符

引用某个变量或属性的类型,类似于使用 typeof 取出某个 变量或属性类型,转而赋给另一个变量或属性,可以理解为一个取值赋值(取的值是 类型)操作

type Point = { x: number; y: number };
let o: Point = {
  x: 10,
  y: 10
}
type CustomPoint = typeof o & {
  [key: string]: any
}

5.3.3 in 操作符

用于遍历联合类型,相当于一个 循环赋值 操作

type CustomKeys = 'data' | 'methods' | 'computed' | 'watch'

type CustomOptions = {
  [p in CustomKeys] : any
}
// CustomOptions = {
//     data: any;
//     methods: any;
//     computed: any;
//     watch: any;
// }

5.3.4 [ ] 索引访问

索引的方式 查找属性的类型。类似于数组索引取值,TS 取的是值的 类型

type Test = {name: string;age: number;}
type TestItem1 = Test['name'] // 取 Test 中 name 的类型,也就是 string
let t1: TestItem = 'aa'

// 也可以联合 keyof 或其他类型使用
type TestItem2 = Test[keyof Test]; // 取 Test 中 name 和 age 的属性,也就是 string | number
let t2: TestItem2 = "n";
let t3: TestItem2 = 10;

5.4 泛型约束

限制类型变量所接受的类型数量,比如 T 只接受 string数组 类型

function identity <T> (param: T): T{
  console.log(param.length); // 报错,T 不存在 length 属性,此时就需要为 T 做约束
  return param;
}

// 使用 extends 关键字对 T 进行约束
type Length = {
  length: number
}
// T extends Length 表示传入给 T 的类型必须要有 Length 中的属性,否则报错
function identity <T extends Length> (param: T): T{
  console.log(param.length);
  return param;
}

总结

通过本节课学习,学到了 TypeScript 在开发中的各种类型的声明和使用方式,了解了在大项目的开发中 TypeScript 所体现的优势(对变量进行约束,在代码编译期间就能暴露一些声明或语法错误)。