【TS】1109- React + TypeScript 實(shí)踐經(jīng)驗(yàn)總結(jié)
?? 準(zhǔn)備知識(shí)
熟悉 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,簡(jiǎn)寫形式: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 對(duì)靜態(tài)屬性:displayName、propTypes、defaultProps 提供了類型檢查和自動(dòng)補(bǔ)全
React.FC 為 children 提供了隱式的類型(ReactElement | null),但是目前,提供的類型存在一些 issue[6](問題)
比如以下用法 React.FC 會(huì)報(bào)類型錯(cuò)誤:
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 的方式聲明最簡(jiǎn)單有效,推薦使用;如果出現(xiàn)類型不兼容問題,建議使用以下兩種方式:
第二種:使用 PropsWithChildren,這種方式可以為你省去頻繁定義 children 的類型,自動(dòng)設(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 會(huì)自動(dòng)為你推導(dǎo) state 的類型:
//?`val`會(huì)推導(dǎo)為boolean類型,?toggle接收boolean類型參數(shù)
const?[val,?toggle]?=?React.useState(false)
//?obj會(huì)自動(dòng)推導(dǎo)為類型:?{name:?string}
const?[obj]?=?React.useState({?name:?'sj'?})
//?arr會(huì)自動(dòng)推導(dǎo)為類型:?string[]
const?[arr]?=?React.useState(['One',?'Two'])
使用推導(dǎo)類型作為接口/類型:
export?default?function?App()?{
??//?user會(huì)自動(dòng)推導(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)初始值為空時(shí)(null),需要顯示地聲明類型:
type?User?=?{
??name:?string
??age:?number
}const?[user,?setUser]?=?React.useStatenull>(null)
useRef
當(dāng)初始值為 null 時(shí),有兩種創(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
},?[])
這兩種方式在使用時(shí),都需要對(duì)類型進(jìn)行檢查:
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...
??},?[])
??//?返回值是一個(gè)函數(shù)
??React.useEffect(()?=>?{
????//?do?something...
????return?()?=>?{}
??},?[])
}
useMemo / useCallback
useMemo 和 useCallback 都可以直接從它們返回的值中推斷出它們的類型
useCallback 的參數(shù)必須制定類型,否則 ts 不會(huì)報(bào)錯(cuò),默認(rèn)指定為 any
const?value?=?10
//?自動(dòng)推斷返回值為?number
const?result?=?React.useMemo(()?=>?value?*?2,?[value])
//?自動(dòng)推斷?(value:?number)?=>?number
const?multiply?=?React.useCallback((value:?number)?=>?value?*?multiplier,?[
??multiplier,
])
同時(shí)也支持傳入泛型, useMemo 的泛型指定了返回值類型,useCallback 的泛型指定了參數(shù)類型
//?也可以顯式的指定返回值類型,返回值不一致會(huì)報(bào)錯(cuò)
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 會(huì)自動(dòng)推導(dǎo)為 Union 類型,而我們實(shí)際需要的是數(shù)組里里每一項(xiàng)的具體類型,需要手動(dòng)添加 const 斷言 進(jìn)行處理:
function?useLoading()?{
??const?[isLoading,?setState]?=?React.useState(false)
??const?load?=?(aPromise:?Promise )?=>?{
????setState(true)
????return?aPromise.then(()?=>?setState(false))
??}
??//?實(shí)際需要:?[boolean,?typeof?load]?類型
??//?而不是自動(dòng)推導(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 需要處理,這里有一個(gè)方便的工具方法可以處理 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)討論可以點(diǎn)擊參考鏈接[8]
推薦方式:使用默認(rèn)參數(shù)值來代替默認(rèn)屬性:
type?GreetProps?=?{?age?:?number?}
const?Greet?=?({?age?=?21?}:?GreetProps)?=>?{
??/*?...?*/
}
defaultProps 類型
TypeScript3.0+[9] 在默認(rèn)屬性 的類型推導(dǎo)上有了極大的改進(jìn),雖然尚且存在一些邊界 case 仍然存在問題[10],不推薦使用,如果有需要使用的場(chǎng)景,可參照如下方式:
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 的使用場(chǎng)景十分類似
implements 與 extends 靜態(tài)操作,不允許存在一種或另一種實(shí)現(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?{
??//?類只能實(shí)現(xiàn)具有靜態(tài)已知成員的對(duì)象類型或?qū)ο箢愋偷慕患?/span>
??x?=?2
??y?=?3
??area()?{
????return?this.x?+?this.y
??}
}
interface?ShapeOrPerimeter?extends?RectangleShape?{}
//?接口只能擴(kuò)展使用靜態(tài)已知成員的對(duì)象類型或?qū)ο箢愋偷慕患?/span>
使用 Type 還是 Interface?
有幾種常用規(guī)則:
在定義公共 API 時(shí)(比如編輯一個(gè)庫(kù))使用 interface,這樣可以方便使用者繼承接口
在定義組件屬性(Props)和狀態(tài)(State)時(shí),建議使用 type,因?yàn)?type的約束性更強(qiáng)
interface 和 type 在 ts 中是兩個(gè)不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以達(dá)到相同的功能效果,type 和 interface 最大的區(qū)別是:
type 類型不能二次編輯,而 interface 可以隨時(shí)擴(kuò)展
interface?Animal?{
??name:?string
}
//?可以繼續(xù)在原有屬性基礎(chǔ)上,添加新屬性:color
interface?Animal?{
??color:?string
}
/********************************/
type?Animal?=?{
??name:?string
}
//?type類型不支持屬性擴(kuò)展
//?Error:?Duplicate?identifier?'Animal'
type?Animal?=?{
??color:?string
}
獲取未導(dǎo)出的 Type
某些場(chǎng)景下我們?cè)谝氲谌降膸?kù)時(shí)會(huì)發(fā)現(xiàn)想要使用的組件并沒有導(dǎo)出我們需要的組件參數(shù)類型或者返回值類型,這時(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,為了提高可維護(hù)性和代碼可讀性,在日常的開發(fā)過程中我們希望可以添加清晰的注釋。
現(xiàn)在有這樣一個(gè) type
type?OtherProps?=?{
??name:?string
??color:?string
}
在使用的過程中,hover 對(duì)應(yīng)類型會(huì)有如下展示
//?type?OtherProps?=?{
//???name:?string;
//???color:?string;
//?}
const?OtherHeading:?React.FC?=?({?name,?color?})?=>?(
??<h1>My?Website?Headingh1>
)
增加相對(duì)詳細(xì)的注釋,使用時(shí)會(huì)更清晰,需要注意,注釋需要使用 /**/ , // 無法被 vscode 識(shí)別
//?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'
??/**?任意需要使用其屬性的對(duì)象(不推薦使用,但是作為占位很有用)?*/
??obj:?object
??/**?作用和`object`幾乎一樣,和?`Object`完全一樣?*/
??obj2:?{}
??/**?列出對(duì)象全部數(shù)量的屬性?(推薦使用)?*/
??obj3:?{
????id:?string
????title:?string
??}
??/**?array?of?objects!?(common)?*/
??objArr:?{
????id:?string
????title:?string
??}[]
??/**?任意數(shù)量屬性的字典,具有相同類型*/
??dict1:?{
????[key:?string]:?MyTypeHere
??}
??/**?作用和dict1完全相同?*/
??dict2:?Record
??/**?任意完全不會(huì)調(diào)用的函數(shù)?*/
??onSomething:?Function
??/**?沒有參數(shù)&返回值的函數(shù)?*/
??onClick:?()?=>?void
??/**?攜帶參數(shù)的函數(shù)?*/
??onChange:?(id:?number)?=>?void
??/**?攜帶點(diǎn)擊事件的函數(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對(duì)象
??onChange?:?React.FormEventHandler
}
export?declare?interface?AppProps?{
??children1:?JSX.Element?//?差,?不支持?jǐn)?shù)組
??children2:?JSX.Element?|?JSX.Element[]?//?一般,?不支持字符串
??children3:?React.ReactChildren?//?忽略命名,不是一個(gè)合適的類型,工具類類型
??children4:?React.ReactChild[]?//?很好
??children:?React.ReactNode?//?最佳,支持所有類型?推薦使用
??functionChildren:?(name:?string)?=>?React.ReactNode?//?recommended?function?as?a?child?render?prop?type
??style?:?React.CSSProperties?//?傳遞style對(duì)象
??onChange?:?React.FormEventHandler?//?表單事件,?泛型參數(shù)是event.target的類型
}
Forms and Events
onChange
change 事件,有兩個(gè)定義參數(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>
??)
}
第二種方法強(qiáng)制使用 @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)表單有想要訪問的自定義命名輸入,可以使用類型擴(kuò)展
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?}
????}?//?類型擴(kuò)展
????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ù)返回類型的類型保護(hù)
Tips
使用查找類型訪問組件屬性類型
通過查找類型減少 type 的非必要導(dǎo)出,如果需要提供復(fù)雜的 type,應(yīng)當(dāng)提取到作為公共 API 導(dǎo)出的文件中。
現(xiàn)在我們有一個(gè) Counter 組件,需要 name 這個(gè)必傳參數(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
第二種是通過在原組件進(jìn)行導(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ù)類型時(shí)強(qiáng)制執(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
事件處理
我們?cè)谶M(jìn)行事件注冊(cè)時(shí)經(jīng)常會(huì)在事件處理函數(shù)中使用 event 事件對(duì)象,例如當(dāng)使用鼠標(biāo)事件時(shí)我們通過 clientX、clientY 去獲取指針的坐標(biāo)。
大家可能會(huì)想到直接把 event 設(shè)置為 any 類型,但是這樣就失去了我們對(duì)代碼進(jìn)行靜態(tài)檢查的意義。
function?handleEvent(event:?any)?{、
??console.log(event.clientY)
}
試想下當(dāng)我們注冊(cè)一個(gè) Touch 事件,然后錯(cuò)誤的通過事件處理函數(shù)中的 event 對(duì)象去獲取其 clientY 屬性的值,在這里我們已經(jīng)將 event 設(shè)置為 any 類型,導(dǎo)致 TypeScript 在編譯時(shí)并不會(huì)提示我們錯(cuò)誤, 當(dāng)我們通過 event.clientY 訪問時(shí)就有問題了,因?yàn)?Touch 事件的 event 對(duì)象并沒有 clientY 這個(gè)屬性。
通過 interface 對(duì) event 對(duì)象進(jìn)行類型聲明編寫的話又十分浪費(fèi)時(shí)間,幸運(yùn)的是 React 的聲明文件提供了 Event 對(duì)象的類型聲明。
Event 事件對(duì)象類型
ClipboardEvent 剪切板事件對(duì)象
DragEvent
拖拽事件對(duì)象 ChangeEvent
Change 事件對(duì)象 KeyboardEvent
鍵盤事件對(duì)象 MouseEvent
鼠標(biāo)事件對(duì)象 TouchEvent
觸摸事件對(duì)象 WheelEvent
滾輪時(shí)間對(duì)象 AnimationEvent
動(dòng)畫事件對(duì)象 TransitionEvent
過渡事件對(duì)象
事件處理函數(shù)類型
當(dāng)我們定義事件處理函數(shù)時(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ù)接收一個(gè) event 對(duì)象,并且其類型為接收到的泛型變量 E 的類型, 返回值為 void
關(guān)于為何是用 bivarianceHack 而不是(event: E): void,這與 strictfunctionTypes 選項(xiàng)下的功能兼容性有關(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 類型
在做異步操作時(shí)我們經(jīng)常使用 async 函數(shù),函數(shù)調(diào)用時(shí)會(huì) return 一個(gè) Promise 對(duì)象,可以使用 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 的類型。然后聲明了一個(gè) 異步函數(shù) getResponse 并且將函數(shù)返回值的類型定義為 Promise
泛型參數(shù)的組件
下面這個(gè)組件的 name 屬性都是指定了傳參格式,如果想不指定,而是想通過傳入?yún)?shù)的類型去推導(dǎo)實(shí)際類型,這就要用到泛型。
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"?/>
????
??)
}
什么時(shí)候使用泛型
當(dāng)你的函數(shù),接口或者類:
需要作用到很多類型的時(shí)候,舉個(gè) ??
當(dāng)我們需要一個(gè) id 函數(shù),函數(shù)的參數(shù)可以是任何值,返回值就是將參數(shù)原樣返回,并且其只能接受一個(gè)參數(shù),在 js 時(shí)代我們會(huì)很輕易地甩出一行
const?id?=?arg?=>?arg
由于其可以接受任意值,也就是說我們的函數(shù)的入?yún)⒑头祷刂刀紤?yīng)該可以是任意類型,如果不使用泛型,我們只能重復(fù)的進(jìn)行定義
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
}
需要被用到很多地方的時(shí)候,比如常用的工具泛型 Partial。
功能是將類型的屬性變成可選, 注意這是淺 Partial。
type?Partial?=?{?[P?in?keyof?T]?:?T[P]?}
如果需要深 Partial 我們可以通過泛型遞歸來實(shí)現(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
