TypeScript 進階經驗總結

前言

使用 TypeScript 也快一年了,本文主要分享一些工做經常使用的知識點技巧和注意點。前端

本文適合瞭解 TypeScript 或有實際使用過一段時間的小夥伴。git

若是對 TypeScript 基礎知識不熟悉的能夠看下我這篇文章:TypeScript 入門知識點總結github

操做符

類型別名

用來給一個類型起個新名字api

type Props = TextProps

keyof

用於獲取某種類型的全部鍵,其返回類型是聯合類型。數組

interface Person {
  name: string
  age: number
}

type PersonKey = keyof Person // "name" | "age"

聯合類型

和邏輯 "||" 同樣都是表示其類型爲多個類型中的任意一個編輯器

好比當你在寫Button 組件的時候:函數

export type ButtonSize = 'lg' | 'md' | 'sm'

交叉類型

將多個類型合併成一個類型工具

好比當你給一個新的 React 組件定義 Props 類型,須要用到其它若干屬性集合post

type Props = TypographyProps & ColorProps & SpaceProps

extends

主要做用是添加泛型約束單元測試

interface WithLength {
  length: number
}
// extends 來繼承
function logger<T extends WithLength>(val: T) {
  console.log(val.length)
}
logger('hello')
logger([1, 2, 3])
// logger(true) // error 沒有length屬性

typeof

typeof 是獲取一個對象/實例的類型

interface Person {
  name: string
  age: number
}
const person1: Person = { name: 'monkey', age: 18 }
const person2: typeof person1 = { name: 'jacky', age: 24 } // 經過編譯

泛型工具類型

Partial

將某個類型裏的屬性所有變爲可選項。

// 實現:所有變可選
type Partial<T> = {
  [P in keyof T]?: T[P]
}

例子

interface Animal {
  canFly: boolean
  canSwim: boolean
}

// 變可選,能夠只賦值部分屬性
let animal: Partial<Animal> = {
  canFly: false,
}

Readonly

它接收一個泛型 T,用來把它的全部屬性標記爲只讀類型

// 實現:所有變只讀
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}
interface Person {
  name: string
  age: number
}

let person: Readonly<Person> = {
  name: 'jacky',
  age: 24,
}
person.name = 'jack' // Cannot assign to 'name' because it is a read-only property.

Required

將某個類型裏的屬性所有變爲必選項。

// 實現:所有變必選
type Required<T> = {
  [P in keyof T]-?: T[P]
}
interface Person {
  name?: string
  age?: number
}

// Property 'age' is missing in type '{ name: string; }' but required in type 'Required<Person>'.
let person: Required<Person> = {
  name: 'jacky',
  // 沒寫 age 屬性會提示錯誤
}

Record

// 實現:K 中全部屬性值轉化爲 T 類型
type Record<K extends keyof any, T> = {
  [P in K]: T
}

Record 生成的類型具備類型 K 中存在的屬性,值爲類型 T

interface DatabaseInfo {
  id: string
}

type DataSource = 'user' | 'detail' | 'list'

const x: Record<DataSource, DatabaseInfo> = {
  user: { id: '1' },
  detail: { id: '2' },
  list: { id: '3' },
}

Pick

// 實現:經過從Type中選擇屬性Keys的集合來構造類型
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

用於提取接口的某幾個屬性

interface Animal {
  canFly: boolean
  canSwim: boolean
}

let person: Pick<Animal, 'canSwim'> = {
  canSwim: true,
}

Exclude

// 實現:若是 T 中的類型在 U 不存在,則返回,不然不返回
type Exclude<T, U> = T extends U ? never : T

將某個類型中屬於另外一個的類型移除掉

interface Programmer {
  name: string
  age: number
  isWork: boolean
  isStudy: boolean
}

interface Student {
  name: string
  age: number
  isStudy: boolean
}

type ExcludeKeys = Exclude<keyof Programmer, keyof Student>
// type ExcludeKeys = "isWork"

Omit

// 實現:去除類型 T 中包含 K 的鍵值對。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

做用與 Pick 相反,仍是直接看代碼容易理解

interface Animal {
  canFly: boolean
  canSwim: boolean
}

let person1: Pick<Animal, 'canSwim'> = {
  canSwim: true,
}

let person2: Omit<Animal, 'canFly'> = {
  canSwim: true,
}

ReturnType

// 實現:獲取 T 類型(函數)對應的返回值類型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

獲取函數返回值類型

function bar(x: string | number): string | number {
  return 'hello'
}
type FooType = ReturnType<typeof bar> // string | number

運算符

可選鏈運算符

可選鏈操做符(?.)容許讀取位於鏈接對象鏈深處的屬性的值,而沒必要明確驗證鏈中的每一個引用是否有效。若是遇到 null 或 undefined 就能夠當即中止某些表達式的運行

這個在項目中比較經常使用,也很好用。

好比咱們數據都是從 api 返回的,假設有這麼一組數據

// 請求 api 後才返回的數據
{
  data: {
    name: 'jacky',
    age: 24,
    address: {
      city: 'shenzhen',
    },
  },
}

前端接收後拿來取值,apiRequestResult.data.address.city,正常狀況取獲得值,若是後臺 GG 沒有返回 address 這個對象,那麼就會報錯:

TypeError: Cannot read property 'city' of undefined

這個時候須要前端處理了,好比最直接的就是

apiRequestResult && apiRequestResult.data && data.address && data.address.city

或者 lodash 庫方法也能解決

import * as _ from 'lodash'
const city = _.get(apiRequestResult, 'apiRequestResult.data.address.city', undefined)

項目中若是都是這麼處理,未免太過於繁瑣,特別對象不少的狀況下,這時就可使用 TS 的可選鏈操做符,

apiRequestResult?.data?.address?.city

上述的代碼會自動檢查對象是否爲 null 或 undefined,若是是的話就當即返回 undefined,這樣就能夠當即中止某些表達式的運行,從而不會報錯。

當你在 React 中使用 ref 封裝判斷元素是否包含 XX 元素時,也會用到這個操做符

const isClickAway = !childRef.current?.contains?.(e.target)

空值合併運算符

空值合併操做符(??)是一個邏輯操做符,當左側的操做數爲 null 或者 undefined 時,返回其右側操做數,不然返回左側操做數。

||操做符相似,可是 || 是一個布爾邏輯運算符,左側的操做數會被強制轉換成布爾值用於求值。如0, '', NaN, null, undefined都不會被返回。

const foo1 = null
const foo2 = 12
const foo3 = 'hello'

const res1 = foo1 ?? 'value' // 'value'
const res2 = foo2 ?? 'value' // 12
const res3 = foo3 ?? 'value' // 'hello'

實際開發也能夠混用?.??

const title = document.getElementById("title")?.textContent ?? "";

非空斷言運算符

在上下文中當類型檢查器沒法判定類型時,這個運算符(!)能夠用在變量名或者函數名以後,用於斷言操做對象是非 null 和非 undefined 類型。不過通常是不推薦使用的

這個適用於咱們已經很肯定知道不會返回空值

function print(name: string | undefined) {
  let myName: string = 'jacky'
  // Type 'string | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'
  myName = name
}

要解決這個報錯,能夠加入判斷

function print(name: string | undefined) {
  let myName: string = 'jacky'
  if (name) {
    myName = name
  }
}

但這樣寫代碼就顯得冗長一點,這時你很是肯定不空,那就用非空斷言運算符

function print(name: string | undefined) {
  let myName: string = 'jacky'
  myName = name!
}

從而能夠減小冗餘的代碼判斷

項目中的細節應用

儘可能避免 any

避免 any,但檢查過不了怎麼搞?能夠用 unknown(不可預先定義的類型)代替

給個不是很好的例子,由於下面例子是自己不對還強行修正錯誤。大概狀況是有些未知數據的狀況,須要強定義類型才能過,能夠用(xx as unknown) as SomeType的形式,固然這裏只是例子,項目中類型檢查逼不得已的狀況下可能會使用

const metaData = {
  description: 'xxxx',
}
interface MetaType {
  desc: string
}

function handleMeta(data: MetaType): void {}

// 這兩種寫法都會報錯
// handleMeta(metaData)
// handleMeta(metaData as MetaType)

// 這裏能夠經過類型檢查不報錯,替代 any 的功能同時保留靜態檢查的能力,
handleMeta((metaData as unknown) as MetaType)

可索引的類型

通常用來約束對象和數組

interface StringObject {
  // key 的類型爲 string, 表明對象
  // 限制 value 的類型爲 string
  [key: string]: string
}
let obj: StringObject = {
  name: 'jacky',
  age: 24, // 此行報錯:Type 'number' is not assignable to type 'string'.
}
interface StringArr {
  // key 的類型爲 number, 表明數組
  // 限制 value 的類型爲 string
  [key: number]: string
}
let arr: StringArr = ['name', 'jacky']

註釋

經過 /** */ 形式的註釋讓 TS 類型作標記提示,編輯器會有更好的提示

/** Animal common props */
interface Animal {
  /** like animal special skill */
  feature: string
}

const cat: Animal = {
  feature: 'running',
}

image.png

image.png

ts-ignore

仍是那樣,儘量用 unknown 來代替使用 any

若是大家項目禁用 any,那麼 ts-ignore 是一個比 any 更有潛在影響代碼質量的因素了。

禁用了但有些地方確實須要使用忽略的話,最好註釋補多句理由。

好比有些函數單元測試文件,測試用例故意傳入不正確的數據類型等,有時是逼不得已的報錯。

// @ts-ignore intentionally for test


相關文章
相關標籤/搜索