從 @types/react 源碼中挖掘一些 Typescript 使用技巧吧。前端
泛型能夠指代可能的參數類型,但指代任意類型範圍太模糊,當咱們須要對參數類型加以限制,或者肯定只處理某種類型參數時,就能夠對泛型進行 extends 修飾。react
問題:React.lazy
須要限制返回值是一個 Promise<T>
類型,且 T
必須是 React 組件類型。git
方案:github
function lazy<T extends ComponentType<any>>(
factory: () => Promise<{ default: T }>
): LazyExoticComponent<T>;
複製代碼
T extends ComponentType
確保了 T 這個類型必定符合 ComponentType
這個 React 組件類型定義,咱們再將 T 用到 Promise<{ default: T }>
位置便可。typescript
若是有一種場景,須要拿到一個類型,這個類型是當某個參數符合某種結構時,這個結構內的一種子類型,就須要結合 泛型 extends + infer 了。編程
問題:React.useReducer
第一個參數是 Reducer,第二個參數是初始化參數,其實第二個參數的類型是第一個參數中回調函數第一個參數的類型,那咱們怎麼將這兩個參數的關係聯繫到一塊兒呢?微信
方案:函數
function useReducer<R extends Reducer<any, any>, I>(
reducer: R,
initializerArg: I & ReducerState<R>,
initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
? S
: never;
複製代碼
R extends Reducer<any, any>
的意思在上面已經提過了,也就是 R 必須符合 Reducer
結構,也就是 reducer
必須符合這個結構,以後重點來了:initializerArg
利用 ReducerState
這個類型直接從 reducer
的類型 R
中將第一個回調參數挖了出來並返回。ui
ReducerState
定義中 R extends Reducer<infer S, any> ? S : never
的含義是:若是 R 符合 Reducer<infer S, any>
類型,則返回類型 S
,這個 S
是 Reducer<infer S>
也就是 State 位置的類型,不然返回 never
類型。spa
因此 infer 表示待推斷類型,是很是強大的功能,能夠指定在任意位置代指其類型,並配合 extends 判斷是否符合結構,可使類型推斷具有必定編程能力。
要用 extends 的另外一個緣由是,只有 extends 才能將結構描述出來,咱們才能精肯定義 infer 指代類型的位置。
當一個類型擁有多種使用可能性時,能夠採用類型重載定義複數類型,Typescript 做用時會逐個匹配並找到第一個知足條件的。
問題:createElement
第一個參數支持 FunctionComponent 與 ClassComponent,並且傳入參數不一樣,返回值的類型也不一樣。
方案:
function createElement<P extends {}>(
type: FunctionComponent<P>,
props?: (Attributes & P) | null,
...children: ReactNode[]
): FunctionComponentElement<P>;
function createElement<P extends {}>(
type: ClassType<
P,
ClassicComponent<P, ComponentState>,
ClassicComponentClass<P>
>,
props?: (ClassAttributes<ClassicComponent<P, ComponentState>> & P) | null,
...children: ReactNode[]
): CElement<P, ClassicComponent<P, ComponentState>>;
複製代碼
將 createElement
寫兩遍佈以上,並配合不一樣的參數類型與返回值類型便可。
咱們能夠經過 typeof
或 instanceof
作一些類型收窄工做,但有些類型甚至自定義類型的收窄判斷函數須要自定義,咱們能夠經過 is
關鍵字定義自定義類型收窄判斷函數。
問題:isValidElement
判斷對象是不是合法的 React 元素,咱們但願這個函數具有類型收窄的功能。
方案:
function isValidElement<P>( object: {} | null | undefined ): object is ReactElement<P>;
const element: string | ReactElement = "";
if (isValidElement(element)) {
element; // 自動推導類型爲 ReactElement
} else {
element; // 自動推導類型爲 string
}
複製代碼
基於這個方案,咱們能夠建立一些頗有用的函數,好比 isArray
,isMap
,isSet
等等,經過 is
關鍵字時其被調用時具有類型收窄的功能。
通常定義函數類型咱們用 type
,但有些狀況下定義的函數既可被調用,也有一些默認屬性值須要定義,咱們能夠繼續用 Interface 定義。
問題:FunctionComponent
既能夠看成函數調用,同時又能定義 defaultProps
displayName
等固定屬性。
方案:
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
複製代碼
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null
表示這種類型的變量能夠做爲函數執行:
const App: FunctionComponent = () => <div />;
App.displayName = "App";
複製代碼
看完文章內容,相信你已經能夠獨立讀懂 @types/react 這個包的全部類型定義!
更多基礎內容能夠閱讀 精讀《Typescript2.0 - 2.9》 與 精讀《Typescript 3.2 新特性》,因爲 TS 更新頻繁,後續 TS 技巧可能繼續以閱讀源碼方式進行,但願此次選用的 React 類型源碼可讓你印象深入。
討論地址是:精讀《@types/react 值得注意的 TS 技巧》 · Issue #245 · dt-fe/weekly
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)