跳至主要內容

TypeScript

俞文健大约 6 分钟

常用类型

接口 Interface

继承。interface 可以继承,并可以扩展一些属性。

interface Person {
  name: string
  age: number
}

interface Student extends Person {
  sex: string
}

const student: Student = {
  name: "Minji",
  age: 18,
  sex: "female"
}

索引签名。如果后端返回的字段太多了,而我们只需要其中几个,那么就可以使用索引签名。

如下,Response 类型中只有 name 和 age 字段是必需的,其他字段就不再强校验了。

interface Response {
  name: string
  age: number
  [key: string]: any // key 可以为任意名称
}

const response: Response = {
  name: "Minji",
  age: 18,
  sex: "female",
  subject: "TypeScript"
}

只读属性。常用于函数类型。

interface Info {
  name: string
  age: number
  readonly id: number
  readonly fn: () => string
}

函数 Function

函数重载。根据参数的类型执行不同的函数。

// 重载
function fn(num: number): number
function fn(str: string): number
function fn(arr: number[]): number[]

// 实现
function fn(value: any) {
  if (typeof value === "number") {
    return value + 1
  }
  else if (typeof value === "string") {
    return value.trim()
  }
  else if (Array.isArray(value)) {
    return [...value, 1]
  }
}

定义 this 的类型。必须在第一个参数定义 this 的类型,调用时忽略该参数。

interface This {
  nums: number[]
  append: (this: This, num: number) => void
}

const ctx = {
  nums: [1, 2, 3],
  append(this: This, num: number) {
    this.nums.push(num)
  }
}

类 Class

类型约束。对类进行约束,使该类的实例对象满足这个外部类型。

implements 后面可以是 interface,也可以是一个类。

super 原理:调用父类的 prototype.constructor.call()。

interface Options {
  el: string | HTMLElement
}

interface VueCls {
  options: Options
  init: () => void
}

// implements 约束 Class
class Vue implements VueCls {
  options: Options
  init(): void {
    // 初始化 ...
  }
}

抽象类。通过 abstract 定义的类称为抽象类,抽象类不能被实例化,只能被继承。

通过 abstract 定义的方法称为抽象方法,抽象方法只能进行描述,不能实现。

继承抽象类的类,称为派生类,派生类可以被实例化。在派生类中必须对抽象方法进行实现。

abstract class Vue {
  name: string
  abstract init(name: string): void
}

class VueComponent extends Vue {
  init(name: string) {
    // ...
  }
}

泛型

函数泛型

我们希望一个函数可以支持多种类型的数据,但是又不想使用 any,那么可以使用泛型来定义函数。

function request<T>(url: string, data: T) { }

interface Params {
  name: string
  age: number
}

request<Params>("127.0.0.1:8000", {
  name: "Minji",
  age: 18
})

接口泛型

如果类型需要在调用接口的时候传递,可以定义接口泛型。

interface Response<T = any> {
  code: number
  data: T
  message: string
}

interface User {
  id: number
  nickname: string
}

const response: Response<User> = {
  code: 200,
  data: {
    id: 10000,
    nickname: "Minji"
  },
  message: "success"
}

const error: Response<null> = {
  code: 5001,
  data: null,
  message: "Invalid Token"
}

泛型约束

对泛型参数进行约束。

function fn<T extends object, K extends keyof T>() { }

keyof & in

keyof:返回对象的 key 组成的联合类型。

in:(只能)遍历联合类型。

type User = { name: string, age: number }

type UserKey = keyof User // => "name" | "age"


type Custom<T extends object> = { readonly [K in keyof T]: T[K] }

type CustomUser = Custom<User> // => { readonly name: string, readonly age: number }

infer

infer 就是推导泛型参数。并且 infer 声明只能出现在 extends 子语句中。

注意

暂时没看懂。

泛型工具

Partial

将对象中的所有属性变成可选的。

interface User {
  name: string
  age: number
  address: string
}

type PartialUser = Partial<User> // => { name?: string, age?: number, address?: string }

实现原理。

type CustomPartial<T> = {
  [P in keyof T]?: T[P]
}

Required

将对象中的所有属性变成必选的。

interface User {
  name?: string
  age?: number
  address?: string
}

type RequiredUser = Required<User> // => { name: string, age: number, address: string }

实现原理。

type CustomRequired<T> = {
  [R in keyof T]-?: T[R]
}

Record

约束对象的 key 和 value。

interface Status {
  type: "success" | "info" | "warning" | "danger"
  value: "待审核" | "已上架" | "已拒绝" | "已下架"
}

const tabs: Record<string, Status> = {
  inReview: {
    type: "warning",
    value: "待审核"
  },
  onShelf: {
    type: "success",
    value: "已上架"
  },
  offShelf: {
    type: "danger",
    value: "已拒绝"
  },
  rejected: {
    type: "info",
    value: "已下架"
  }
}

实现原理。

keyof any => string | number | symbol

type CustomRecord<K extends keyof any, T> = {
  [P in K]: T
}

Pick

提取对象中的部分属性。与 Omit 相反。

interface User {
  name: string
  age: number
  address?: string
}

type PickUser = Pick<User, "name" | "age"> // => { name: string, age: number }

实现原理。

type CustomPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

Omit

排除对象中的部分属性。与 Pick 相反。

interface User {
  name: string
  age: number
  address?: string
}

type OmitUser = Omit<User, "name"> // => { age: number, address?: string }

实现原理。先用 Exclude 排除不需要的属性,再用 Pick 提取剩下的属性。

type CustomOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

Exclude

排除联合类型中的部分属性。

type E = Exclude<"a" | "b" | "c", "b"> // => "a" | "c"

实现原理。

never 在联合类型中会被排除。

type CustomExclude<T, U> = T extends U ? never : T

type CE = CustomExclude<"a" | "b" | "c", "a" | "c"> // => never | 'b' | never => 'b'

ReturnType

获取函数返回值的类型。

const fn = () => [1, 2, 3, true]

type ReturnFn = ReturnType<typeof fn> // (number | boolean)[]

实现原理。

type CustomReturnType<F extends Function> = F extends (...args: any[]) => infer Res ? Res : never

配置文件 tsconfig

{
  "compilerOptions": {
    /* https://aka.ms/tsconfig */
    
    /* Projects */
    "incremental": true,                  /* TS 在初次编译时会创建缓存文件, 再次编译会读取这个缓存文件 */
    "tsBuildInfoFile": "./.tsbuildinfo",  /* 缓存文件的存储位置 */
    "diagnostics": true,                  /* 打印诊断信息 */
    
    /* Language and Environment */
    "target": "es2016",                   /* 目标语言的版本 */
    "lib": [],                            /* TS 需要引用的声明文件, ES5 默认引用 DOM、ES5、ScriptHost */
    "jsx": "preserve",                    /* JSX 解析器 */
    "jsxFactory": "",                     /* JSX 语法的解析器 'React.createElement' or 'h' */
    
    /* Modules */
    "module": "commonjs",                 /* 生成代码的模板标准 */
    "rootDir": "./",                      /* 根目录 */
    "moduleResolution": "node",           /* 模块解析策略, TS 默认使用 node 解析策略, 即相对的方式导入 */
    "baseUrl": "./",                      /* 基本路径, 默认为当前目录 */
    "typeRoots": [],                      /* node_modules 的声明文件, 默认为 '/node_modules/@types' */
    "types": [],                          /* 加载的声明文件包 */
    "paths": {                            /* 路径映射 */
      "@/*": ["./src/*"]
    },
    
    /* JavaScript Support */
    "allowJs": false,                     /* 允许编译 JS, JSX 文件 */
    "checkJs": false,                     /* 允许在 JS 文件中报错, 通常与 allowJs 一起使用 */
    
    /* Emit */
    "declaration": false,                 /* 自动生成声明文件 */
    "declarationDir": "./types",          /* 声明文件存放目录 */
    "declarationMap": true,               /* 为声明文件生成 sourceMap */
    "emitDeclarationOnly": false,         /* 只生成声明文件, 不生成 JS 文件 */
    "sourceMap": true,                    /* 生成目标文件的 sourceMap 文件 */
    "inlineSourceMap": true,              /* 生成目标文件的 inline sourceMap */
    "outDir": "./",                       /* 输出目录 */
    "removeComments": true,               /* 删除注释 */
    "noEmit": false,                      /* 编译后不生成任何 JS 文件 */
    "noEmitOnError": true,                /* 发生错误时不输出任何文件 */
    "downlevelIteration": true,           /* 如果目标源是 ES3/ES5, 那么遍历器会有降级的实现 */
    
    /* Type Checking */
    "strict": true,                       /* 开启所有严格的类型检查 */
    "alwaysStrict": true,                 /* 在代码中注入 'use strict' */
    "noImplicitAny": true,                /* 不允许隐式的 'any' 类型 */
    "strictNullChecks": true,             /* 不允许把 'null' 和 'undefined' 赋值给其他类型的变量 */
    "strictFunctionTypes": true,          /* 不允许函数参数双向协变 */
    "strictBindCallApply": true,          /* 严格的 'bind', 'call', 'apply' 检查 */
    "strictPropertyInitialization": true, /* 类的实例属性必须初始化 */
    "noImplicitThis": true,               /* 不允许 'this' 有隐式的 'any' 类型 */
    "noUnusedLocals": true,               /* 检查声明了但未使用的局部变量(只提示不报错) */
    "noUnusedParameters": true,           /* 检查未使用的函数参数(只提示不报错) */
    "noFallthroughCasesInSwitch": true,   /* 防止 switch 语句贯穿(如果没有 break 语句后面不会执行) */
  },
  
  "include": [],                          /* 指定包含的编译列表 */
  "exclude": [],                          /* 指定排除的编译列表 */
  "files": [],                            /* 指定使用该配置的文件 */
}

声明文件 d.ts

在使用第三方库时,我们需要引用它的声明文件,才能获取对应的代码补全、接口提示等功能。

下面是一个 declare express 声明文件的例子。

declare module "express" {
  interface App {
    get(path: string, callback: (req: any, res: any) => void): void
    
    listen(port: number, callback?: () => void)
  }
  
  interface Express {
    (): App
  }
  
  const express: Express
  
  export default express
}