TypeScript学习笔记与应用 | 青训营

599 阅读4分钟

本文是青训营课程《深入浅出 Typescript》以及平时阅读和项目实践中遇到的TypeScript常见问题总结。将从以下几点描述,并在最后给出一个运用以上知识点封装的接口实践作为举例:

导航

  1. TypeScript基础知识
  2. TypeScript实用知识:泛型,高级类型
  3. TypeScript应用案例:组件参数/函数声明,接口约束等

TypeScript基础知识

a. anyneverunknownnull & undefinedvoid

  • any: 不检查。很好用,但要小心变成anyScript
  • never: 永不存在的值的类型。常用于会抛出异常或不会有返回值的函数表达式或箭头函数表达式的返回值类型。
  • unknown: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。
  • null & undefined: 默认情况下 null 和 undefined 是所有类型的子类型。
  • void: 常用于函数返回类型声明。

b. typeinterface

相同点:允许拓展(extends)

不同点:

  1. type 可以声明基本类型,联合类型(union: X | Y),元组(tuple: [X, Y])
  2. type 可以使用 typeof 获取实例的类型进行赋值
  3. 多个相同的 interface 声明可以自动合并
  4. type 能使用 in 关键字生成映射类型,但 interface 不行
  5. inerface 支持同时声明,默认导出 而type必须先声明后导出

c. keyoftypeof

keyof  获取索引类型的属性名,构成联合类型。
typeof 获取一个变量或对象的类型。

d. implementsexpends

继承,常用于复杂接口的封装和自定义组件入口设计。

extends:用来实现继承 接口与接口接口与类,不可以重写属性,但可以重写方法 implements:用来实现类实现接口,同时可以重写属性和方法,包含一些新的功能

type A = { a: number }

interface AB extends A { b: string } // 更规范常用
// 等价写法
type TAB = A & { b: string }

// 类类型接口,类可以实现多个接口 implements
interface Stu1 {}
interface Stu2 {}
class Stu implements Stu1, Stu2 {}

// 混合类型接口 extends
interface Fn {}
interface Mixed extends Fn {}
function myFn(): Fn {}

e. 断言

类型断言,只在编译阶段起作用

! x! 非空断言运算符。将从 x 值域中排除 nullundefined

!. 在变量名后添加,可以断言排除 undefinednull 类型。

f. 数组定义方式

type Ex1= Array<string>;
interface Man { man: Array<{ name: string, age: number}>}

type Ex2 = string[];
interface Man { man : { name: string, age: number }[] }

其他

重写(override) vs 重载(overload)

override:重写是指子类重写“继承”自父类中的方法。

overload:重载是指为同一个函数提供多个类型定义。

TypeScript实用知识

泛型

<T, K, V>泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。解决类/接口/方法的复用性,以及对不确定数据类型的使用。

泛型字母可以随意指定,但一个范型字母在同一个函数中每次使用时只会代表一种数据类型。 常用规定:

  • T (Type) 表示类型;
  • K (Key) 表示对象中键的类型;
  • V (Value) 表示对象中值的类型;
  • E (Element) 表示元素类型。

高级类型

介绍比较常用的四种高级类型,主要用在接口约束上

a. Partial

一个接口的字段可能有几百个,用 Partial可以只声明我们需要的那几个重要字段

/**
 * Make all properties in T optional
 */
type Partial<T> = { [P in keyof T]?: T[P] }

interface Person {
    id: number;
    name: string;
    age: number;
    gender: number;
    email?: string;
}

type optional = Partial<Person>

b. Required

某些字段是可选的,但当我们需要这个字段时,就可以将字段?可选字段变成必须字段。

// Make all properties in T required
type Required<T> = { [P in keyof T]-?: T[P] }

type student = Required<Person>

c. Pick

一般用在组件参数接口上。用于需要从一个已声明的类型中抽取出一个子类型,在子类型中包含父类型中的部分或全部属性。即利用已声明接口的一部分定义。

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}
 
type PickUser = Pick<Person, "id" | "name" | "gender">;

d. omit

属性忽略,用于忽略掉我们不需要关心的属性

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

type OmitUser = Omit<Person, "age" | "email">;

TypeScript应用案例

开发项目基本上使用的都是TypeScript,对于维护项目和保护项目都是非常有益的。在项目中,我们会在传递参数时对参数做类型约束,例如对组件propsstates的声明,这对于其他开发同学接触项目时是非常友好的。除此之外最常见的场景就是对接口的封装。举例如下:

declare namespace API {
  type BaseRes<T = unknown> = {
    success: boolean;
    code: string;
    message: string;
    data: T;
  };
   type TestConfig = {
    id: string;
    userName: string
  };
 }
 
declare namespace Test {
  type CommonConfig = Partial<API.TestConfig> & { time?: [string, string] };
}

export async function queryTest(configData: Test.CommonConfig,): Promise<API.BaseRes<{ list: API.TestConfig[] }>> {
  const { time } = configData || {};
  const [ createFrom, createTo ] = time || [];
  const data: Partial<API.TestConfig> = { 
    createFrom,
    createTo,
    ...configData
  };
  return request(`/config/test/query`, {
    method: 'POST',
    data,
  });
}

参考资料

  1. 《深入浅出Typescript》
  2. juejin.cn/post/721135…
  3. juejin.cn/post/684490…