Typescript型別常用使用技巧1

MangoGoing 發表於 2022-05-05

列舉型別怎麼定義?

有這麼一個常量FieldTypes,代表表單項型別,是輸入框還是開關還是其他等等,只列舉兩個

enum FieldTypes {
  INPUT = 'input',
  SWITCH = 'switch'
  // ...
}

此時子元件需要接收一個typeprops,型別的值為FieldTypes中的定義的值,即'input' | 'switch'等等

解法一:

typescript中enum可以作為型別約束某個變數,如果這麼做,那麼給這個propstype賦值時,必須從enum中去取,保證了資料的來源以及資料型別的統一性,缺點是在某些場景下不是特別靈活

const _type: FieldTypes = FieldTypes.INPUT // ✅ correct
const _type: FieldTypes = 'input' // ❎ error

解法二:

使用只讀的 object 來替代enum的作用

const FieldTypesObj = {
  INPUT: 'input',
  SWITCH: 'switch'
} as const // <-- 關鍵技巧 1: as const

type Type = typeof FieldTypesObj // <-- 關鍵技巧 2: 用 typeof 關鍵字從 FieldTypesObj 反向建立同名型別

const _type1: Type[keyof Type] = 'input' // ✅ correct
const _type2: Type[keyof Type] = FieldTypes.SWITCH // ✅ correct

常量斷言(語法寫作 as const)是 TypeScript 3.4釋出的新特性,這裡對它進行簡單的解釋:

先看下面例子:

let str = 'ghostwang' // str: string
const str = 'ghostwang' // str: 'ghostwang'
let str = 'ghostwang' as const // str: 'ghostwang'

const obj1 = { name: 'ghostwang' } // const obj: { name: string; }
const obj2 = { name: 'ghostwang' } as const // const obj: { readonly name: "ghostwang"; }

const arr = [1, 2] // const arr: number[]
const arr2 = [1, 2] as const // const arr: readonly [1, 2]

看出區別來了麼,使用as const會告訴編譯器為表示式推斷出它能推斷出的最窄或最特定的型別。如果不使用它,編譯器將使用其預設型別推斷行為,這可能會導致更廣泛或更一般的型別,並且此時他的屬性是隻讀的(當然還是有辦法能修改)

事件型別該怎麼定義?

相信有人在定義事件的時候有時候不知道怎麼去定義,例如下面的場景,div點選時阻止冒泡

const onClick = (e)=>{// 這裡e的型別是什麼?
    e...//
}

<div onClick={onClick}><div>

這裡很多會定義為e:any 然後冒泡的函式是啥,阻止冒泡stop什麼什麼,再去查一下。

其實想一想,如果能知道<div>的props的型別定義,我可以直接定義onClick這個函式,從而e就有型別了,不僅可以檢查程式碼,還可以得到友好的提示。JSX提供了這樣一個查詢元件props的泛型:

// 獲得div標籤的props的型別 (內建元件,例如 div、a、p、input等等)

const DivPropsType = JSX.IntrinsicElements<'div'>

// 獲得自定義元件的props

const CustomPropsType = JSXElementConstructor<CustomComponent>

為了減少記憶的負擔,React對這2個泛型又進行了一步包裝:

type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =

        T extends JSXElementConstructor<infer P>

            ? P

            : T extends keyof JSX.IntrinsicElements

                ? JSX.IntrinsicElements[T]

                : {};

所以上面的事件型別定義就變得非常簡單了:

import { ComponentProps } from 'react'

const onClick: ComponentProps<'div'>['onClick'] = (e)=>{// e的型別被自動推導

    e.//會得到程式碼提示

}

<div onClick={onClick}><div>

同樣,我們在引入自定義元件時,也不需要單獨引入它的props型別,直接使用這個泛型即可:

import { type ComponentProps } from 'react'

const onClick: ComponentProps<typeof Custom>['onClick'] = (e)=>{// e的型別被自動推導

    e.//會得到程式碼提示

}

<Custom onClick={onClick}></Custom>

怎樣定義一個字串型別,既有列舉,又可以自由輸入?

這種應用場景非常常見,我這樣寫標題,可能表達得不清晰,舉一個例子:

我需要定義一個color型別,在開發者輸入時,可以提示輸入 "red" 和 "blue",但是除了red和blue也可以自由輸入其他字串

img

但是這樣定義型別的話,除了red和blue,其他都輸入不了。都會報錯。

如果加上string,直接什麼都不提示了

img

所以需要定義一個既包含red和blue,又包含除了red和blue之外的字串

img

我輸入了white,也不會報錯

img

ref的型別該怎麼定義?

ref的應用場景常用來儲存一些改變不會引起重新渲染、用來引用forwardRef的元件、引用內建元件使用。

import { useRef } form 'react'



const valueRef = useRef<number>(0)

這種方式定義的valueRef的型別是MutableRefObject 可變的引用物件

除了這種方式,還有一種不可變的,對應的型別是RefObject 只讀的引用物件 感覺這倆就是const和let一樣

看一下區別

import { useRef, type MutableRefObject, type RefObject } form 'react'



const valueRef1: MutableRefObject<number> = useRef(0)

const valueRef2: RefObject<number> = useRef(0)



valueRef1.current = 1; // 正常

valueRef2.current = 1; // 報錯,不能賦值: 無法分配到 "current" ,因為它是隻讀屬性。

所以我們在定義幾種場景時,應區分是手動賦值還是自動賦值,並使用不同的型別

例如用來封裝一個useUUID的hook

import { useRef, type RefObject } form 'react'



// 定義只讀的ref

export const useUUID = ()=>{

    const uuidRef: RefObject<string> = useRef('uuid' + Date.now().toString(16));

    

    return uuidRef.current

}

例如引用一個div的ref

import { useRef, type RefObject } form 'react'



const divRef: RefObject<HTMLDivElement> = useRef();



<div ref={divRef}></div>

forwardRef的型別該怎樣定義以及引用時型別該怎樣定義?

根據官方的推薦,在定義forwardRef時,將型別定義在高階函式中(注意⚠️props的型別和ref型別位置相反)

const ComA = forwardRef<{dddd: string}, {age?: number}>((props, ref)=>{
  useImperativeHandle(ref, ()=>{
    return {
      dddd: "1"
    }
  })
  return <div></div>
})

在引入時,typeof ComA 得到的是一個ref和props的交叉型別,所以只需訪問出ref的型別即可

const ComB = ()=>{
  const tRef: ComponentProps<typeof ComA>['ref'] = useRef();
  
  return <ComA ref={tRef}></ComA>
}

React在此處設計的型別是ComponentProps<typeof ComA>['ref'] 返回的是一個React.Ref泛型

type Ref<T> = RefCallback<T> | RefObject<T> | null;

正好相容了ref的3種情況,使用函式接收(createRef),useRef引用,和初始空值。而且還是個只讀的ref 👍

然後訪問tRef時,此時tRef即是已經收窄的型別,具有友好的提示和取值限制。

img

寫在最後的話

至此,結合最近組內小夥伴分享的一些ts型別使用技巧,在此總結並分享給更多的人,感謝閱讀~

Typescript型別常用使用技巧1