精讀《@types react 值得注意的 TS 技巧》

1 引言

@types/react 源碼中挖掘一些 Typescript 使用技巧吧。前端

2 精讀

泛型 extends

泛型能夠指代可能的參數類型,但指代任意類型範圍太模糊,當咱們須要對參數類型加以限制,或者肯定只處理某種類型參數時,就能夠對泛型進行 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

若是有一種場景,須要拿到一個類型,這個類型是當某個參數符合某種結構時,這個結構內的一種子類型,就須要結合 泛型 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,這個 SReducer<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 寫兩遍佈以上,並配合不一樣的參數類型與返回值類型便可。

自定義類型收窄

咱們能夠經過 typeofinstanceof 作一些類型收窄工做,但有些類型甚至自定義類型的收窄判斷函數須要自定義,咱們能夠經過 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
}
複製代碼

基於這個方案,咱們能夠建立一些頗有用的函數,好比 isArrayisMapisSet 等等,經過 is 關鍵字時其被調用時具有類型收窄的功能。

用 Interface 定義函數

通常定義函數類型咱們用 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";
複製代碼

3 總結

看完文章內容,相信你已經能夠獨立讀懂 @types/react 這個包的全部類型定義!

更多基礎內容能夠閱讀 精讀《Typescript2.0 - 2.9》精讀《Typescript 3.2 新特性》,因爲 TS 更新頻繁,後續 TS 技巧可能繼續以閱讀源碼方式進行,但願此次選用的 React 類型源碼可讓你印象深入。

討論地址是:精讀《@types/react 值得注意的 TS 技巧》 · Issue #245 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章
相關標籤/搜索