Typescript 规范及最佳实践

踩坑经验汇总

阅读前请先熟读阅读Typescript HandBook

使用 Typescript 一定要清楚哪些是运行时代码哪些是类型代码

类型

Type 别名

普通、简单类型声明一律使用type

与 interface 两者的关键区别是 type 不能被重复使用加入新属性,但 interface 可以一直扩展

// type 不支持 extends,使用 = 设置类型
export type MicroAppState = {
  isError: boolean
  mounted: boolean
  loading: boolean
  resetting: boolean
}

// type 可以与其他type扩展出新类型 如
export type Result = {
  context: {
    warnings: HTMLElement[]
    references: HTMLElement[]
    elementsById: HTMLElement[]
  }
} & OtherType

interface

复杂的、可扩展的、多重继承使用interface

interface Diagram {
  // 构造函数可以这么写
  constructor(options: anyType)
}
// interface的扩展使用extends关键字
interface BaseViewer extends Diagram {
  // 构造函数也可以这么写
  new (options: anyType)

  newMethod: () => void
}

interface 没法实现 Utility type 如以下

type Person = {
  name: string
  age: number
}
// 没有age的Person类型实现
const onlyNamePerson: Omit<Person, 'age'> = { name: 'xaoMing' }
// 没有名字和age的Person类型实现
const smPerson: Omit<Person, 'name' | 'age'> = {}

any

Typescript 中使用做多的类型,任何不确定的类型都会用 any 表示

实际开发中我们尽量不实用 any,大量的使用 any 就没有必要使用 ts 开发了

// any会让下面的代码执行都成立
let obj: any = { x: 0 }
obj.foo()
obj()
obj.bar = 100
obj = 'hello'
const n: number = obj

as

类型断言,就是告诉编译器我确定这家伙是这种类型

  • as 断言
// as 语法
let someValue: unknown = 'this is a string'

let strLength: number = (someValue as string).length
  • 尖括号断言(无法与 jsx 配合使用)
let someValue: unknown = 'this is a string'

let strLength: number = (<string>someValue).length

unknown

unknown 指的是不可预先定义的类型
unknown 的一个使用场景是,避免使用 any 作为函数的参数类型而导致的静态类型检查 bug
unknown 类型必须先转为一个确定类型后才能调用其属性或方法

return (
  lang || (window as unknown as GLocaleWindow).g_locale || navigator.language
)

void

返回类型,一个方法没有任何返回值时

// 一个接受字符串的不返回任何值的函数
type someMethod = (type: string) => void

never

返回类型,没法正常结束返回的类型

一般用在报错、死循环、switch 拖底中做返回类型,新手小白唔用

// throw error 返回值是never
function foo(): never {
  throw new Error('error message')
}
// 这个死循环的也会无法正常退出
function foo(): never {
  while (true) {}
}
// Error: 这个无法将返回值定义为never,因为无法在静态编译阶段直接识别出
function foo(): never {
  let count = 1
  while (count) {
    count++
  }
}

// Exhaustiveness checking 穷尽性检查
interface Triangle {
  kind: 'triangle'
  sideLength: number
}

type Shape = Circle | Square | Triangle

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.sideLength ** 2
    default:
      const _exhaustiveCheck: never = shape
      // Type 'Triangle' is not assignable to type 'never'.
      return _exhaustiveCheck
  }
}

Literal Types

字面量类型, 使用了特定字符串或数字的类型

// let 和 const 声明的类型不同

// changingString的类型为 string;
let changingString = 'Hello World'
// constantString 的类型为 'Hello World';
const constantString = 'Hello World'
// 无法通过编译
constantString = 'howdy'

字面量类型在处理字符串数字枚举值时很有效,但需要做转换来去掉类型问题

// 字面量类型无法识别两个GET字符串是相同的
const req = { url: 'https://example.com', method: 'GET' }
// 报错 Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
handleRequest(req.url, req.method)

// 修改方法1 使用 as 转换
const req = { url: 'https://example.com', method: 'GET' as 'GET' }
handleRequest(req.url, req.method as 'GET')

// 修改方法2 使用 const 转换整个对象为字面量类型
const req = { url: 'https://example.com', method: 'GET' } as const
handleRequest(req.url, req.method)

操作符

?

可选变量

对象可不包含该 可选变量

interface PaintOptions {
  shape: Shape
  xPos?: number
  yPos?: number
}

function paintShape(opts: PaintOptions) {
  // ...
}

const shape = getShape()
paintShape({ shape })
paintShape({ shape, xPos: 100 })
paintShape({ shape, yPos: 100 })
paintShape({ shape, xPos: 100, yPos: 100 })

可选参数

不传也不会报错

function f(x?: number) {
  // ...
}
f() // OK
f(10) // OK

!

非空断言操作符

function liveDangerously(x?: number | null) {
  // 使用!告诉编译器我是真的
  console.log(x!.toFixed())
}

keyof

keyof 可以获得一个类型所有的键并返回一个联合类型

type Person = {
  name: string
  age: number
}
// PersonKey得到的类型为 'name' | 'age'
type PersonKey = keyof Person

// 使用案例
function getValue(p: Person, k: PersonKey) {
  // 如果k不如此定义,则无法以p[k]的代码格式通过编译
  return p[k]
}

typeof

typeof 获取一个实际对象的类型

const me: Person = { name: 'gzx', age: 16 }
// { name: string, age: number | undefined }
type P = typeof me
// 可以通过编译
const you: typeof me = { name: 'mabaoguo', age: 69 }

in

in 只能用在类型的定义中,可以对枚举类型进行遍历

// 这个类型可以将任何类型的键值转化成number类型
type TypeToNumber<T> = {
  [key in keyof T]: number
}

const obj: TypeToNumber<Person> = { name: 10, age: 10 }

泛型

泛型可以用在普通类型定义,类定义、函数定义上,如下:

// 普通类型定义
type Dog<T> = { name: string; type: T }
// 普通类型使用
const dog: Dog<number> = { name: 'ww', type: 20 }

// 类定义
class Cat<T> {
  private type: T
  constructor(type: T) {
    this.type = type
  }
}
// 类使用
const cat: Cat<number> = new Cat<number>(20) // 或简写 const cat = new Cat(20)

// 函数定义
function swipe<T, U>(value: [T, U]): [U, T] {
  return [value[1], value[0]]
}
// 函数使用
swipe<Cat<number>, Dog<number>>([cat, dog]) // 或简写 swipe([cat, dog])

泛型默认值

type Dog<T = string> = { name: string; type: T }
const dog: Dog = { name: 'ww', type: 'hsq' }

泛型约束

在方法内需要调用泛型内的特定属性方法,需要使用 extends 执行泛型约束

interface Lengthwise {
  length: number
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  // 使用extends 继承自含有length的对象就不会报错了
  console.log(arg.length)
  return arg
}

泛型条件

高级玩法

Conditional Types

方法泛型

type off = <T, V>(events: T | string | string[], callback: V) => void

JSX 泛型

import {Select} from 'antd'
export default (props) => {
  return <Select<string> {...props}>
}

泛型工具

常用于表单提交,将已经定义好的类型抽像成表单提交的实际内容定义

// 将类型属性全部变为可选的
Partial<T>

// 将类型属性全部变为必选的
Required<T>

// 将 T 类型中的 K 键列表提取出来,生成新的子键值对类型。
Pick<T, K>
const bird: Pick<Animal, "name" | "age"> = { name: 'bird', age: 1 }

// 在 T 类型中,去除 T 类型和 U 类型的交集,返回剩余的部分
Exclude<T, U>

// 去除类型 T 中包含 K 的键值对
Omit<T, K>

// 获取 T 类型(函数)对应的返回值类型
ReturnType<T>

用于声明普通对象

Record<K, T>
type ELDrawerPropsType = {
  onOk: (fieldsValue: Record<string, string>, index: number) => void;
};

声明

声明文件

Typescript 中使用.d.ts结尾的文件作为声明文件

声明文件应放置于项目跟路径的typings目录内

delare

声明关键字,声明结果均为全局声明

// 全局声明变量
declare var
declare let
declare const
// 全局声明方法
declare function
// 全局声明类
declare class {}
// 全局声明枚举
declare enum
// 全局声明类型
declare type
// 全局声明接口
declare interface {}
// 全局声明命名空间
declare namespace
// 全局声明模块
declare module

namespace

namespace 用来解决工程过大命名冲突的问题,可以跨文件使用

对外可通过 export 向外导出作为模块使用(deprecated)

// a.d.ts
declare namespace FescoTechTao {
  type A;
}

// b.d.ts
declare namespace FescoTechTao {
  type B;
}

module

module 用来定义外部 ES 模块,可跨文件使用

// canvas.d.ts
declare module 'diagram.js' {
  export interface Canvas {}
}
// eventBus.d.ts
declare module 'diagram.js' {
  export interface EventBus {}
}

// main.ts
import { Canvas, EventBus } from 'diagram.js'

module 可以用来定义静态模块

declare module 'slash2'
declare module '*.css'
declare module '*.less'
declare module '*.scss'
declare module '*.sass'
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare module 'omit.js'
declare module '*.xml' {
  // Change this to an actual XML type
  const value: string
  export default value
}

module 可以用来改写 npm 包中的模块定义

type PutResolveType = {
  resolve: <A extends AnyAction>(action: A) => Promise<void>
}
type PutType = <A extends AnyAction>(action: A) => any

declare module 'dva' {
  export interface EffectsCommandMap {
    put: PutResolveType & PutType
    call: Function
    select: Function
    take: Function
    cancel: Function
    [key: string]: any
  }
}

module 可以补充声明没有编写声明文件的 npm 包

declare module 'dva-model-extend'
declare module 'bpmn-js-properties-panel'
declare module 'bpmn-js-properties-panel/lib/provider/camunda'
declare module 'camunda-bpmn-moddle'
declare module 'camunda-bpmn-moddle/resources/camunda'

module 可以扩展其他全局对象的属性方法

declare global {
  interface Window {
    ga: (
      command: 'send',
      hitType: 'event' | 'pageview',
      fieldsObject: GAFieldsObject | string
    ) => void
    reloadAuthorized: () => void
    bpmnInstances: BpmnInstancesType
  }
  interface Console {
    logModel: (namespace: string, expand?: boolean) => void
    logModelState: (namespace: string, expand?: boolean) => void
  }
}

declare module NodeJS {
  interface Global {
    host: string
    href: string
    _cookies: string
    _navigatorLang: string
  }
}

使用 module 给 vue 扩展原型链

import type { AxiosInstance } from 'axios'

declare module 'Vue/types/vue' {
  interface Vue {
    $axios: AxiosInstance
  }
}

/// 三斜线指令

///<reference types=“UMDModuleName/globalName” /> ts 早期模块化的标签, 用来导入依赖, ES6 广泛使用后, 在编写 TS 文件中不推荐使用,使用 import 代替

  • 25
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值