Typescript实用指南

356 阅读5分钟

[TOC]

没有TS时的困扰

张三: 你知道下面的这个test方法要如何传参吗?
胖虎: 传a,b两个参数
张三: 具体呢?你知道a,b是什么类型吗?
胖虎: 你一没注释,二又看不到代码具体内容,我怎么知道ab什么类型?你是在为难我胖虎啊
张三: 要注释? 没问题,继续往下看
function test(a,b){
    ...
}
张三: 再看看这个,注释,代码都有了,告诉我ab如何传参
胖虎: 找茬呢,这注释有和没有有区别吗?还有这代码,还要我通读,还要我根据代码推敲这个参数的意思和类型,不想写就别写啊
/**
* @params {Object} a 第一个参数
* @params {Object} b 第二个参数
*/
function test(a,b){
    const {flag,good} = b
    ...省略若干代码
    if(b.test){
        ...省略若干代码
    }
}

没错这就是前端项目在没有TS时,大概率会遇到的问题,别人写的方法具体如何调用?后端接口返回的参数到底是什么接口? 不知道,在不运行到具体代码行,不一行行阅读源码,大概率是不知道的

基础

与JS基本数据类型对应的TS类型定义

字符串

let str:string = ''

数值

let num:number = 1

布尔值

let flag:boolean = true

数组

const arr1: Array<string> = []
const arr2: Array<string|number> = []
const arr3: [string,number] = ['a',1]

日期

const date: Date = new Date()

null与undefined

const x1: null = null
let x2: undefined;

symbol

const sym: Symbol = Symbol()
const symb: Symbol = Symbol('abc')

Map类型

const map = new Map<string,number>()

对象

const obj: Record<string, any> = {}

interface Person {
  name: string
  age: number
}

const p:Person = {
    name:"张三",
    age:12
}

函数

function test(a: number, b: number): number {
  return a + b
}
function test(a: string, b: string): number
function test(a: number, b: number): number

function test(a: number | string, b: number | string): number {
  const numA: number = typeof a === 'string' ? Number(a) : a
  const numB: number = typeof b === 'string' ? Number(b) : b
  return numA + numB
}

test('1', '2')
test(4, 3)
type TestFun = (a: number, b: number) => number
const fun: TestFun = (a, b) => {
  return a + b
}
interface TestFun {
  (x: number, y: number): number
}
const fun: TestFun = (x, y) => {
    return x+y
}

复合类型

interface PartA {
  name: string
  fatherName: string
}
interface PartB {
  age: number
  male: boolean
}

type PartC = PartA & PartB
type PartD = PartA | PartB

const partC: PartC = {
  name: '',
  fatherName: '',
  age: 0,
  male: false
}

const partD: PartD = {
  name: '',
  fatherName: ''
}

获取对象中所有key作为类型定义

interface PartA {
  name: string
  fatherName: string
}

type PartAKeys = keyof PartA

const keyFahter: PartAKeys = 'fatherName'
const keys: PartAKeys = 'name'

抽取特定接口定义的部分属性,作为一个新类型

interface Person {
  name: string
  age: number
  male: boolean
}

// 使用Person接口的name,male形成一个新接口
type NewPerson = Pick<Person, 'name' | 'male'>

const person: Person = { name: '张三', age: 12, male: true }
const newPerson: NewPerson = { name: '张三', male: true }

排除特定接口定义的部分属性,作为一个新类型

interface Person {
  name: string
  age: number
  male: boolean
}

// 将Person接口,排除age属性之后,形成一个新接口
type NewPerson = Omit<Person, 'age'>

const person: Person = { name: '张三', age: 12, male: true }
const newPerson: NewPerson = { name: '张三', male: true }

以特定接口为基础进行扩展

interface Person {
  name: string
  male: boolean
}

interface NewPerson extends Person {
  age: number
}

const p: Person = {
  name: '',
  male: true
}

const p1: NewPerson = {
  name: '',
  male: true,
  age: 0
}

泛型

指定this类型

type Service<R, P extends unknown[]> = (...args: P) => Promise<R>
function wrapper<R, P extends unknown[]>(service: Service<R, P>) {
  return function (this: any, ...args: P) {
    const method = service.bind(this)
    return method(...args)
  }
}

function test(a: number, b: number): Promise<number> {
  return Promise.resolve(a + b)
}

const wrapperTest = wrapper(test)

;(async () => {
  console.log(await wrapperTest(1, 2))
})()
/**
 *
 * @param this 指定this类型,必须为第一个参数
 * @param str 从这个参数开始,才是这个方法的实际参数
 */
function doAdd(this: Plus, str: string) {
  const ret = this.a + this.b
  console.log(str, ret)
}
class Plus {
  a: number = 0
  b: number = 0

  constructor(a: number, b: number) {
    this.a = a
    this.b = b
  }

  add = doAdd
}

new Plus(1, 3).add('hello')

any, unknow

any和unknow都表示任意类型,但unknow要求,在使用unknow声明的变量时,必须先转换为某个具体的类型

void, never

可变参数类型

不确定的可变参数类型通过此种方式指定

type Service<R, P extends unknown[]> = (...args: P) => Promise<R>
function wrapper<R, P extends unknown[]>(service: Service<R, P>) {
  return function (this: any, ...args: P) {
    const method = service.bind(this)
    return method(...args)
  }
}

function test(a: number, b: number): Promise<number> {
  return Promise.resolve(a + b)
}

const wrapperTest = wrapper(test)

;(async () => {
  console.log(await wrapperTest(1, 2))
})()

禁止TS报错

单行忽略
// @ts-ignore

忽略全文
// @ts-nocheck

取消忽略全文
// @ts-check
/* eslint-disable */
const watermark = require("watermark-dom"); // @ts-ignore
/* eslint-enable */

便捷操作

const obj: Record<'x1' | 'x2' | string, string | number> = {
  x1: '你好',
  x2: '世界',
  name: '张三'
}

高级

对象中的一个属性的属性值,决定另一个属性的类型

interface BaseSeriesType {
  name: string
  color: [string] | [string | string]
}
interface BarSeriesType extends BaseSeriesType {
  exceptionColor: string
}
interface OtherSeriesType extends BaseSeriesType {
  flag: boolean
}
interface SeriesType {
  bar: BarSeriesType
  other: OtherSeriesType
}

type Better = {
  [SubType in keyof SeriesType]: {
    type: SubType
    config: SeriesType[SubType]
  }
}[keyof SeriesType]

Better的实际类型会是这个

image.png

参考资料:

TypeScript 根据「实际」字段推断类型 - 知乎 (zhihu.com)

typescript - 如何在TypeScript中为对象属性定义基于另一个属性值的类型约束? - SegmentFault 思否

索引访问类型

type Person = { age: number; name: string; alive: boolean };

type AgeType = Person["age"]; // 此时AgeType实际为number类型

实际项目快速应用TS定义

从json字符串生成TS

JSON to TS - Visual Studio Marketplace

从swagger生成TS

tswagger - Visual Studio Marketplace

swagger-to-ts-fix - Visual Studio Marketplace

swagger-to-ts - Visual Studio Marketplace

JS To TS - Visual Studio Marketplace

vue3项目中的TS

组件事件: defineEmits

TypeScript 与组合式 API | Vue.js (vuejs.org)

泛型组件

在js项目使用ts

如果有使用eslint,可能需要先通过.eslintignore配置文件,去除对所有d.ts文件的检查

.eslintignore

*.d.ts

在项目根目录下创建types目录,并创建types.d.ts

types.d.ts

declare interface TypeA {
  /**
   * xml字符串
   */
  xml: string
  /**
   * json字符串
   */
  json: string
}

declare interface TypeB {
  /**
   * 测试数字
   */
  test: number
  /**
   * 回调方法
   * @param num 测试数字
   * @returns 是否大于10
   */
  call: (num: number) => boolean
}

declare interface Good {
  /**
   * 分子
   */
  fz: string
  /**
   * 分母
   */
  fm: string
  /**
   * 日期
   */
  zdate: string
}

效果

image.png

image.png

image.png

image.png

如何在JS项目中使用TS类型检查? - 掘金 (juejin.cn)

如何在JavaScript项目中使用TypeScript的能力 - 掘金 (juejin.cn)

TS类型定义详解:types/typeRoots/@types,以及命名空间namespace