【TS】1109- React + TypeScript 實踐經(jīng)驗總結(jié)
?? 準(zhǔn)備知識
熟悉 React
熟悉 TypeScript (參考書籍:2ality's guide[1], 初學(xué)者建議閱讀:chibicode's tutorial[2])
熟讀 React 官方文檔 TS 部分[3]
熟讀 TypeScript playground React 部分[4]
本文檔參考 TypeScript 最新版本
如何引入 React
import?*?as?React?from?'react'
import?*?as?ReactDOM?from?'react-dom'
這種引用方式被證明[5]是最可靠的一種方式, 推薦使用。
而另外一種引用方式:
import?React?from?'react'
import?ReactDOM?from?'react-dom'
需要添加額外的配置:"allowSyntheticDefaultImports": true
函數(shù)式組件的聲明方式
聲明的幾種方式
第一種:也是比較推薦的一種,使用 React.FunctionComponent,簡寫形式:React.FC:
//?Great
type?AppProps?=?{
??message:?string
}
const?App:?React.FC?=?({?message,?children?})?=>?(
??<div>
????{message}
????{children}
??div>
)
使用用 React.FC 聲明函數(shù)組件和普通聲明以及 PropsWithChildren 的區(qū)別是:
React.FC 顯式地定義了返回類型,其他方式是隱式推導(dǎo)的
React.FC 對靜態(tài)屬性:displayName、propTypes、defaultProps 提供了類型檢查和自動補全
React.FC 為 children 提供了隱式的類型(ReactElement | null),但是目前,提供的類型存在一些 issue[6](問題)
比如以下用法 React.FC 會報類型錯誤:
const?App:?React.FC?=?props?=>?props.children
const?App:?React.FC?=?()?=>?[1,?2,?3]
const?App:?React.FC?=?()?=>?'hello'
解決方法:
const?App:?React.FC<{}>?=?props?=>?props.children?as?any
const?App:?React.FC<{}>?=?()?=>?[1,?2,?3]?as?any
const?App:?React.FC<{}>?=?()?=>?'hello'?as?any
//?或者
const?App:?React.FC<{}>?=?props?=>?(props.children?as?unknown)?as?JSX.Element
const?App:?React.FC<{}>?=?()?=>?([1,?2,?3]?as?unknown)?as?JSX.Element
const?App:?React.FC<{}>?=?()?=>?('hello'?as?unknown)?as?JSX.Element
在通常情況下,使用 React.FC 的方式聲明最簡單有效,推薦使用;如果出現(xiàn)類型不兼容問題,建議使用以下兩種方式:
第二種:使用 PropsWithChildren,這種方式可以為你省去頻繁定義 children 的類型,自動設(shè)置 children 類型為 ReactNode:
type?AppProps?=?React.PropsWithChildren<{?message:?string?}>
const?App?=?({?message,?children?}:?AppProps)?=>?(
??<div>
????{message}
????{children}
??div>
)
第三種:直接聲明:
type?AppProps?=?{
??message:?string
??children?:?React.ReactNode
}
const?App?=?({?message,?children?}:?AppProps)?=>?(
??<div>
????{message}
????{children}
??div>
)
Hooks
useState
大部分情況下,TS 會自動為你推導(dǎo) state 的類型:
//?`val`會推導(dǎo)為boolean類型,?toggle接收boolean類型參數(shù)
const?[val,?toggle]?=?React.useState(false)
//?obj會自動推導(dǎo)為類型:?{name:?string}
const?[obj]?=?React.useState({?name:?'sj'?})
//?arr會自動推導(dǎo)為類型:?string[]
const?[arr]?=?React.useState(['One',?'Two'])
使用推導(dǎo)類型作為接口/類型:
export?default?function?App()?{
??//?user會自動推導(dǎo)為類型:?{name:?string}
??const?[user]?=?React.useState({?name:?'sj',?age:?32?})
??const?showUser?=?React.useCallback((obj:?typeof?user)?=>?{
????return?`My?name?is?${obj.name},?My?age?is?${obj.age}`
??},?[])??return?<div?className="App">用戶:?{showUser(user)}div>
}
但是,一些狀態(tài)初始值為空時(null),需要顯示地聲明類型:
type?User?=?{
??name:?string
??age:?number
}const?[user,?setUser]?=?React.useStatenull>(null)
useRef
當(dāng)初始值為 null 時,有兩種創(chuàng)建方式:
const?ref1?=?React.useRef(null)
const?ref2?=?React.useRefnull>(null)
這兩種的區(qū)別在于:
第一種方式的 ref1.current 是只讀的(read-only),并且可以傳遞給內(nèi)置的 ref 屬性,綁定 DOM 元素 ; 第二種方式的 ref2.current 是可變的(類似于聲明類的成員變量)
const?ref?=?React.useRef(0)
React.useEffect(()?=>?{
??ref.current?+=?1
},?[])
這兩種方式在使用時,都需要對類型進行檢查:
const?onButtonClick?=?()?=>?{
??ref1.current?.focus()
??ref2.current?.focus()
}
在某種情況下,可以省去類型檢查,通過添加 ! 斷言,不推薦:
//?Bad
function?MyComponent()?{
??const?ref1?=?React.useRef(null!)
??React.useEffect(()?=>?{
????//??不需要做類型檢查,需要人為保證ref1.current.focus一定存在
????doSomethingWith(ref1.current.focus())
??})
??return?<div?ref={ref1}>?etc?div>
}
useEffect
useEffect 需要注意回調(diào)函數(shù)的返回值只能是函數(shù)或者 undefined
function?App()?{
??//?undefined作為回調(diào)函數(shù)的返回值
??React.useEffect(()?=>?{
????//?do?something...
??},?[])
??//?返回值是一個函數(shù)
??React.useEffect(()?=>?{
????//?do?something...
????return?()?=>?{}
??},?[])
}
useMemo / useCallback
useMemo 和 useCallback 都可以直接從它們返回的值中推斷出它們的類型
useCallback 的參數(shù)必須制定類型,否則 ts 不會報錯,默認(rèn)指定為 any
const?value?=?10
//?自動推斷返回值為?number
const?result?=?React.useMemo(()?=>?value?*?2,?[value])
//?自動推斷?(value:?number)?=>?number
const?multiply?=?React.useCallback((value:?number)?=>?value?*?multiplier,?[
??multiplier,
])
同時也支持傳入泛型, useMemo 的泛型指定了返回值類型,useCallback 的泛型指定了參數(shù)類型
//?也可以顯式的指定返回值類型,返回值不一致會報錯
const?result?=?React.useMemo(()?=>?2,?[])
//?類型“()?=> number”的參數(shù)不能賦給類型“()?=> string”的參數(shù)。
const?handleChange?=?React.useCallback<
??React.ChangeEventHandler
>(evt?=>?{
??console.log(evt.target.value)
},?[])
自定義 Hooks
需要注意,自定義 Hook 的返回值如果是數(shù)組類型,TS 會自動推導(dǎo)為 Union 類型,而我們實際需要的是數(shù)組里里每一項的具體類型,需要手動添加 const 斷言 進行處理:
function?useLoading()?{
??const?[isLoading,?setState]?=?React.useState(false)
??const?load?=?(aPromise:?Promise )?=>?{
????setState(true)
????return?aPromise.then(()?=>?setState(false))
??}
??//?實際需要:?[boolean,?typeof?load]?類型
??//?而不是自動推導(dǎo)的:(boolean | typeof load)[]
??return?[isLoading,?load]?as?const
}
如果使用 const 斷言遇到問題[7],也可以直接定義返回類型:
export?function?useLoading():?[
??boolean,
??(aPromise:?Promise )?=>?Promise<any>
]?{
??const?[isLoading,?setState]?=?React.useState(false)
??const?load?=?(aPromise:?Promise )?=>?{
????setState(true)
????return?aPromise.then(()?=>?setState(false))
??}
??return?[isLoading,?load]
}
如果有大量的自定義 Hook 需要處理,這里有一個方便的工具方法可以處理 tuple 返回值:
function?tuplify<T?extends?any[]>(...elements:?T)?{
??return?elements
}
function?useLoading()?{
??const?[isLoading,?setState]?=?React.useState(false)
??const?load?=?(aPromise:?Promise )?=>?{
????setState(true)
????return?aPromise.then(()?=>?setState(false))
??}
??//?(boolean?|?typeof?load)[]
??return?[isLoading,?load]
}
function?useTupleLoading()?{
??const?[isLoading,?setState]?=?React.useState(false)
??const?load?=?(aPromise:?Promise )?=>?{
????setState(true)
????return?aPromise.then(()?=>?setState(false))
??}
??//?[boolean,?typeof?load]
??return?tuplify(isLoading,?load)
}
默認(rèn)屬性 defaultProps
大部分文章都不推薦使用 defaultProps , 相關(guān)討論可以點擊參考鏈接[8]
推薦方式:使用默認(rèn)參數(shù)值來代替默認(rèn)屬性:
type?GreetProps?=?{?age?:?number?}
const?Greet?=?({?age?=?21?}:?GreetProps)?=>?{
??/*?...?*/
}
defaultProps 類型
TypeScript3.0+[9] 在默認(rèn)屬性 的類型推導(dǎo)上有了極大的改進,雖然尚且存在一些邊界 case 仍然存在問題[10],不推薦使用,如果有需要使用的場景,可參照如下方式:
type?IProps?=?{
??name:?string
}
const?defaultProps?=?{
??age:?25,
}
//?類型定義
type?GreetProps?=?IProps?&?typeof?defaultProps
const?Greet?=?(props:?GreetProps)?=>?<div>div>
Greet.defaultProps?=?defaultProps
//?使用
const?TestComponent?=?(props:?React.ComponentProps<typeof?Greet>)?=>?{
??return?<h1?/>
}
const?el?=?<TestComponent?name="foo"?/>
Types or Interfaces
在日常的 react 開發(fā)中 interface 和 type 的使用場景十分類似
implements 與 extends 靜態(tài)操作,不允許存在一種或另一種實現(xiàn)的情況,所以不支持使用聯(lián)合類型:
class?Point?{
??x:?number?=?2
??y:?number?=?3
}
interface?IShape?{
??area():?number
}
type?Perimeter?=?{
??perimeter():?number
}
type?RectangleShape?=?(IShape?|?Perimeter)?&?Point
class?Rectangle?implements?RectangleShape?{
??//?類只能實現(xiàn)具有靜態(tài)已知成員的對象類型或?qū)ο箢愋偷慕患?/span>
??x?=?2
??y?=?3
??area()?{
????return?this.x?+?this.y
??}
}
interface?ShapeOrPerimeter?extends?RectangleShape?{}
//?接口只能擴展使用靜態(tài)已知成員的對象類型或?qū)ο箢愋偷慕患?/span>
使用 Type 還是 Interface?
有幾種常用規(guī)則:
在定義公共 API 時(比如編輯一個庫)使用 interface,這樣可以方便使用者繼承接口
在定義組件屬性(Props)和狀態(tài)(State)時,建議使用 type,因為 type的約束性更強
interface 和 type 在 ts 中是兩個不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以達到相同的功能效果,type 和 interface 最大的區(qū)別是:
type 類型不能二次編輯,而 interface 可以隨時擴展
interface?Animal?{
??name:?string
}
//?可以繼續(xù)在原有屬性基礎(chǔ)上,添加新屬性:color
interface?Animal?{
??color:?string
}
/********************************/
type?Animal?=?{
??name:?string
}
//?type類型不支持屬性擴展
//?Error:?Duplicate?identifier?'Animal'
type?Animal?=?{
??color:?string
}
獲取未導(dǎo)出的 Type
某些場景下我們在引入第三方的庫時會發(fā)現(xiàn)想要使用的組件并沒有導(dǎo)出我們需要的組件參數(shù)類型或者返回值類型,這時候我們可以通過 ComponentProps/ ReturnType 來獲取到想要的類型。
//?獲取參數(shù)類型
import?{?Button?}?from?'library'?//?但是未導(dǎo)出props?type
type?ButtonProps?=?React.ComponentProps<typeof?Button>?//?獲取props
type?AlertButtonProps?=?Omit'onClick'>?//?去除onClick
const?AlertButton:?React.FC?=?props?=>?(
??<Button?onClick={()?=>?alert('hello')}?{...props}?/>
) //?獲取返回值類型
function?foo()?{
??return?{?baz:?1?}
}
type?FooReturn?=?ReturnType<typeof?foo>?//?{?baz:?number?}
Props
通常我們使用 type 來定義 Props,為了提高可維護性和代碼可讀性,在日常的開發(fā)過程中我們希望可以添加清晰的注釋。
現(xiàn)在有這樣一個 type
type?OtherProps?=?{
??name:?string
??color:?string
}
在使用的過程中,hover 對應(yīng)類型會有如下展示
//?type?OtherProps?=?{
//???name:?string;
//???color:?string;
//?}
const?OtherHeading:?React.FC?=?({?name,?color?})?=>?(
??<h1>My?Website?Headingh1>
)
增加相對詳細的注釋,使用時會更清晰,需要注意,注釋需要使用 /**/ , // 無法被 vscode 識別
//?Great
/**
?*?@param?color?color
?*?@param?children?children
?*?@param?onClick?onClick
?*/
type?Props?=?{
??/**?color?*/
??color?:?string
??/**?children?*/
??children:?React.ReactNode
??/**?onClick?*/
??onClick:?()?=>?void
}
//?type?Props
//?@param?color?—?color
//?@param?children?—?children
//?@param?onClick?—?onClick
const?Button:?React.FC?=?({?children,?color?=?'tomato',?onClick?})?=>?{
??return?(
????<button?style={{?backgroundColor:?color?}}?onClick={onClick}>
??????{children}
????button>
??)
}
常用 Props ts 類型
基礎(chǔ)屬性類型
type?AppProps?=?{
??message:?string
??count:?number
??disabled:?boolean
??/**?array?of?a?type!?*/
??names:?string[]
??/**?string?literals?to?specify?exact?string?values,?with?a?union?type?to?join?them?together?*/
??status:?'waiting'?|?'success'
??/**?任意需要使用其屬性的對象(不推薦使用,但是作為占位很有用)?*/
??obj:?object
??/**?作用和`object`幾乎一樣,和?`Object`完全一樣?*/
??obj2:?{}
??/**?列出對象全部數(shù)量的屬性?(推薦使用)?*/
??obj3:?{
????id:?string
????title:?string
??}
??/**?array?of?objects!?(common)?*/
??objArr:?{
????id:?string
????title:?string
??}[]
??/**?任意數(shù)量屬性的字典,具有相同類型*/
??dict1:?{
????[key:?string]:?MyTypeHere
??}
??/**?作用和dict1完全相同?*/
??dict2:?Record
??/**?任意完全不會調(diào)用的函數(shù)?*/
??onSomething:?Function
??/**?沒有參數(shù)&返回值的函數(shù)?*/
??onClick:?()?=>?void
??/**?攜帶參數(shù)的函數(shù)?*/
??onChange:?(id:?number)?=>?void
??/**?攜帶點擊事件的函數(shù)?*/
??onClick(event:?React.MouseEvent):?void
??/**?可選的屬性?*/
??optional?:?OptionalType
}
常用 React 屬性類型
export?declare?interface?AppBetterProps?{
??children:?React.ReactNode?//?一般情況下推薦使用,支持所有類型?Great
??functionChildren:?(name:?string)?=>?React.ReactNode
??style?:?React.CSSProperties?//?傳遞style對象
??onChange?:?React.FormEventHandler
}
export?declare?interface?AppProps?{
??children1:?JSX.Element?//?差,?不支持?jǐn)?shù)組
??children2:?JSX.Element?|?JSX.Element[]?//?一般,?不支持字符串
??children3:?React.ReactChildren?//?忽略命名,不是一個合適的類型,工具類類型
??children4:?React.ReactChild[]?//?很好
??children:?React.ReactNode?//?最佳,支持所有類型?推薦使用
??functionChildren:?(name:?string)?=>?React.ReactNode?//?recommended?function?as?a?child?render?prop?type
??style?:?React.CSSProperties?//?傳遞style對象
??onChange?:?React.FormEventHandler?//?表單事件,?泛型參數(shù)是event.target的類型
}
Forms and Events
onChange
change 事件,有兩個定義參數(shù)類型的方法。
第一種方法使用推斷的方法簽名(例如:React.FormEvent
import?*?as?React?from?'react'
type?changeFn?=?(e:?React.FormEvent )?=>?void
const?App:?React.FC?=?()?=>?{
??const?[state,?setState]?=?React.useState('')
??const?onChange:?changeFn?=?e?=>?{
????setState(e.currentTarget.value)
??}
??return?(
????<div>
??????<input?type="text"?value={state}?onChange={onChange}?/>
????div>
??)
}
第二種方法強制使用 @types / react 提供的委托類型,兩種方法均可。
import?*?as?React?from?'react'const?App:?React.FC?=?()?=>?{
??const?[state,?setState]?=?React.useState('')
??const?onChange:?React.ChangeEventHandler?=? e?=>?{
????setState(e.currentTarget.value)
??}
??return?(
????<div>
??????<input?type="text"?value={state}?onChange={onChange}?/>
????div>
??)
}
onSubmit
如果不太關(guān)心事件的類型,可以直接使用 React.SyntheticEvent,如果目標(biāo)表單有想要訪問的自定義命名輸入,可以使用類型擴展
import?*?as?React?from?'react'
const?App:?React.FC?=?()?=>?{
??const?onSubmit?=?(e:?React.SyntheticEvent)?=>?{
????e.preventDefault()
????const?target?=?e.target?as?typeof?e.target?&?{
??????password:?{?value:?string?}
????}?//?類型擴展
????const?password?=?target.password.value
??}
??return?(
????<form?onSubmit={onSubmit}>
??????<div>
????????<label>
??????????Password:
??????????<input?type="password"?name="password"?/>
????????label>
??????div>
??????<div>
????????<input?type="submit"?value="Log?in"?/>
??????div>
????form>
??)
}
Operators
常用的操作符,常用于類型判斷
typeof and instanceof: 用于類型區(qū)分
keyof: 獲取 object 的 key
O[K]: 屬性查找
[K in O]: 映射類型
+ or - or readonly or ?: 加法、減法、只讀和可選修飾符
x ? Y : Z: 用于泛型類型、類型別名、函數(shù)參數(shù)類型的條件類型
!: 可空類型的空斷言
as: 類型斷言
is: 函數(shù)返回類型的類型保護
Tips
使用查找類型訪問組件屬性類型
通過查找類型減少 type 的非必要導(dǎo)出,如果需要提供復(fù)雜的 type,應(yīng)當(dāng)提取到作為公共 API 導(dǎo)出的文件中。
現(xiàn)在我們有一個 Counter 組件,需要 name 這個必傳參數(shù):
//?counter.tsx
import?*?as?React?from?'react'
export?type?Props?=?{
??name:?string
}
const?Counter:?React.FC?=?props?=>?{
??return?<>>
}
export?default?Counter
在其他引用它的組件中我們有兩種方式獲取到 Counter 的參數(shù)類型
第一種是通過 typeof 操作符(推薦)
//?Great
import?Counter?from?'./d-tips1'
type?PropsNew?=?React.ComponentProps<typeof?Counter>?&?{
??age:?number
}
const?App:?React.FC?=?props?=>?{
??return?<Counter?{...props}?/>
}
export?default?App
第二種是通過在原組件進行導(dǎo)出
import?Counter,?{?Props?}?from?'./d-tips1'
type?PropsNew?=?Props?&?{
??age:?number
}
const?App:?React.FC?=?props?=>?{
??return?(
????<>
??????<Counter?{...props}?/>
????>
??)
}
export?default?App
不要在 type 或 interface 中使用函數(shù)聲明
保持一致性,類型/接口的所有成員都通過相同的語法定義。
--strictFunctionTypes 在比較函數(shù)類型時強制執(zhí)行更嚴(yán)格的類型檢查,但第一種聲明方式下嚴(yán)格檢查不生效。
?
interface?ICounter?{
??start:?(value:?number)?=>?string
}
?
interface?ICounter1?{
??start(value:?number):?string
}
??
interface?Animal?{}
interface?Dog?extends?Animal?{
??wow:?()?=>?void
}
interface?Comparer?{
??compare:?(a:?T,?b:?T)?=>?number
}
declare?let?animalComparer:?Comparer
declare?let?dogComparer:?Comparer
animalComparer?=?dogComparer?//?Error
dogComparer?=?animalComparer?//?Ok
interface?Comparer1?{
??compare(a:?T,?b:?T):?number
}
declare?let?animalComparer1:?Comparer1
declare?let?dogComparer1:?Comparer1
animalComparer1?=?dogComparer?//?Ok
dogComparer1?=?animalComparer?//?Ok
事件處理
我們在進行事件注冊時經(jīng)常會在事件處理函數(shù)中使用 event 事件對象,例如當(dāng)使用鼠標(biāo)事件時我們通過 clientX、clientY 去獲取指針的坐標(biāo)。
大家可能會想到直接把 event 設(shè)置為 any 類型,但是這樣就失去了我們對代碼進行靜態(tài)檢查的意義。
function?handleEvent(event:?any)?{、
??console.log(event.clientY)
}
試想下當(dāng)我們注冊一個 Touch 事件,然后錯誤的通過事件處理函數(shù)中的 event 對象去獲取其 clientY 屬性的值,在這里我們已經(jīng)將 event 設(shè)置為 any 類型,導(dǎo)致 TypeScript 在編譯時并不會提示我們錯誤, 當(dāng)我們通過 event.clientY 訪問時就有問題了,因為 Touch 事件的 event 對象并沒有 clientY 這個屬性。
通過 interface 對 event 對象進行類型聲明編寫的話又十分浪費時間,幸運的是 React 的聲明文件提供了 Event 對象的類型聲明。
Event 事件對象類型
ClipboardEvent 剪切板事件對象
DragEvent
拖拽事件對象 ChangeEvent
Change 事件對象 KeyboardEvent
鍵盤事件對象 MouseEvent
鼠標(biāo)事件對象 TouchEvent
觸摸事件對象 WheelEvent
滾輪時間對象 AnimationEvent
動畫事件對象 TransitionEvent
過渡事件對象
事件處理函數(shù)類型
當(dāng)我們定義事件處理函數(shù)時有沒有更方便定義其函數(shù)類型的方式呢?答案是使用 React 聲明文件所提供的 EventHandler 類型別名,通過不同事件的 EventHandler 的類型別名來定義事件處理函數(shù)的類型
type?EventHandler>?=?{
??bivarianceHack(event:?E):?void
}['bivarianceHack']
type?ReactEventHandler?=?EventHandler>
type?ClipboardEventHandler?=?EventHandler>
type?DragEventHandler?=?EventHandler>
type?FocusEventHandler?=?EventHandler>
type?FormEventHandler?=?EventHandler>
type?ChangeEventHandler?=?EventHandler>
type?KeyboardEventHandler?=?EventHandler>
type?MouseEventHandler?=?EventHandler>
type?TouchEventHandler?=?EventHandler>
type?PointerEventHandler?=?EventHandler>
type?UIEventHandler?=?EventHandler>
type?WheelEventHandler?=?EventHandler>
type?AnimationEventHandler?=?EventHandler>
type?TransitionEventHandler?=?EventHandler<
??React.TransitionEvent
>
bivarianceHack 為事件處理函數(shù)的類型定義,函數(shù)接收一個 event 對象,并且其類型為接收到的泛型變量 E 的類型, 返回值為 void
關(guān)于為何是用 bivarianceHack 而不是(event: E): void,這與 strictfunctionTypes 選項下的功能兼容性有關(guān)。(event: E): void,如果該參數(shù)是派生類型,則不能將其傳遞給參數(shù)是基類的函數(shù)。
class?Animal?{
??private?x:?undefined
}
class?Dog?extends?Animal?{
??private?d:?undefined
}
type?EventHandler?=?(event:?E)?=>?void
let?z:?EventHandler?=?(o:?Dog)?=>?{}?//?fails?under?strictFunctionTyes
type?BivariantEventHandler?=?{
??bivarianceHack(event:?E):?void
}['bivarianceHack']
let?y:?BivariantEventHandler?=?(o:?Dog)?=>?{}
Promise 類型
在做異步操作時我們經(jīng)常使用 async 函數(shù),函數(shù)調(diào)用時會 return 一個 Promise 對象,可以使用 then 方法添加回調(diào)函數(shù)。Promise
type?IResponse?=?{
??message:?string
??result:?T
??success:?boolean
}
async?function?getResponse():?Promise<IResponse<number[]>>?{
??return?{
????message:?'獲取成功',
????result:?[1,?2,?3],
????success:?true,
??}
}
getResponse().then(response?=>?{
??console.log(response.result)
})
首先聲明 IResponse 的泛型接口用于定義 response 的類型,通過 T 泛型變量來確定 result 的類型。然后聲明了一個 異步函數(shù) getResponse 并且將函數(shù)返回值的類型定義為 Promise
泛型參數(shù)的組件
下面這個組件的 name 屬性都是指定了傳參格式,如果想不指定,而是想通過傳入?yún)?shù)的類型去推導(dǎo)實際類型,這就要用到泛型。
const?TestB?=?({?name,?name2?}:?{?name:?string;?name2?:?string?})?=>?{
??return?(
????<div?className="test-b">
??????TestB--{name}
??????{name2}
????div>
??)
}
如果需要外部傳入?yún)?shù)類型,只需 ->
type?Props?=?{
??name:?T
??name2?:?T
}
const?TestC:?(props:?Props)?=>?React.ReactElement?=?({?name,?name2?})?=>?{
??return?(
????
??????TestB--{name}
??????{name2}
????
??)
}
const?TestD?=?()?=>?{
??return?(
????
???????name="123"?/>
????
??)
}
什么時候使用泛型
當(dāng)你的函數(shù),接口或者類:
需要作用到很多類型的時候,舉個 ??
當(dāng)我們需要一個 id 函數(shù),函數(shù)的參數(shù)可以是任何值,返回值就是將參數(shù)原樣返回,并且其只能接受一個參數(shù),在 js 時代我們會很輕易地甩出一行
const?id?=?arg?=>?arg
由于其可以接受任意值,也就是說我們的函數(shù)的入?yún)⒑头祷刂刀紤?yīng)該可以是任意類型,如果不使用泛型,我們只能重復(fù)的進行定義
type?idBoolean?=?(arg:?boolean)?=>?boolean
type?idNumber?=?(arg:?number)?=>?number
type?idString?=?(arg:?string)?=>?string
//?...
如果使用泛型,我們只需要
function?id<T>(arg:?T):?T?{
??return?arg
}
//?或
const?id1:?<T>(arg:?T)?=>?T?=?arg?=>?{
??return?arg
}
需要被用到很多地方的時候,比如常用的工具泛型 Partial。
功能是將類型的屬性變成可選, 注意這是淺 Partial。
type?Partial?=?{?[P?in?keyof?T]?:?T[P]?}
如果需要深 Partial 我們可以通過泛型遞歸來實現(xiàn)
type?DeepPartial?=?T?extends?Function
????T
??:?T?extends?object
????{?[P?in?keyof?T]?:?DeepPartial?}
??:?T
type?PartialedWindow?=?DeepPartial
參考資料
2ality's guide: http://2ality.com/2018/04/type-notation-typescript.html
[2]chibicode's tutorial: https://ts.chibicode.com/todo/
[3]TS 部分: https://reactjs.org/docs/static-type-checking.html#typescript
[4]React 部分: http://www.typescriptlang.org/play/index.html?jsx=2&esModuleInterop=true&e=181#example/typescript-with-react
[5]被證明: https://www.reddit.com/r/reactjs/comments/iyehol/import_react_from_react_will_go_away_in_distant/
[6]一些 issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33006
[7]問題: https://github.com/babel/babel/issues/9800
[8]參考鏈接: https://twitter.com/hswolff/status/1133759319571345408
[9]TypeScript3.0+: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html
[10]存在一些邊界 case 仍然存在問題: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61
