不清楚React Hooks的類型聲明?來看就對了
在了解react hooks的類型之前,有必要先了解一下@types、.d.ts文件的概念及作用。
node_modules中的@types是什么?
當(dāng)我們使用第三方npm包的時候,如果這個包不是ts編寫,則沒有導(dǎo)出類型,這時候如果在ts中導(dǎo)入會報錯。比如jquery 這時會報錯
無法找到模塊“jquery”的聲明文件嘗試使用 npm i --save-dev @types/jquery (如果存在),或者添加一個包含 declare module 'jquery'; 的新聲明(.d.ts
這里提示找不到j(luò)query的類型定義 可以安裝@types/jquery或者在d.ts中自定義類型,大多數(shù)情況我們應(yīng)該使用第一種辦法,如果這個庫沒有@types庫再使用第二種, 可以在https://microsoft.github.io/TypeSearch/中查找一個包是否存在types。
types查找規(guī)則
當(dāng)我們使用import xx from時ts將會默認(rèn)從./node_modules/@types中獲取類型聲明,具體查找規(guī)則是ts編譯器先在當(dāng)前編譯上下文找jquery的定義,找不到則再去./node_modules/@types中查找。 在本地模塊查找的類型聲明作用域是在模塊,在@types中的類型聲明是全局的。在tsconfig.json中也可以使用typeRoots設(shè)置默認(rèn)路徑 。
模塊types
當(dāng)然在`tsconfig.json`中也可以使用`types`單獨控制`@types`。`types`指定的包會被單獨引入。這樣全局引入就失效了。
*.d.ts是什么
@types下存放的文件都是.d.ts開頭的文件 對應(yīng)的npm包js的類型聲明。 在.d.ts文件中聲明的類型或者模塊,在其他文件中不需要使用import導(dǎo)入,可以直接使用,d.ts的類型聲明可以自行編寫也可以使用工具聲明。有2個工具
可以使用微軟的dts-gen,生成單個文件的聲明dtsmake。值得注意的是如果你使用JSDOC語法 在ts3.7以后是可以通過命令為js生成.ds文件。具體用法可查看官方文檔。
介紹完前菜,現(xiàn)在開始進入本文正題。 一起來看下react hooks相關(guān)的類型聲明吧。在@types/react/index.d.ts文件中。
useContext
`useContext和createContext`是結(jié)合一起使用的
useContext定義: function useContext<T>(context:Context<T>):TcreateContext定義: function createContext<T>(defaultValue:T,):Context<T>createContext的返回Context類型的值提供給useContext的參數(shù)。這里泛型T在2個方法中是一致的,如果不指定 ts會類型推導(dǎo)出正確的類型。而Context 類型 則是一個interface
interface Context<T> {
Provider: Provider<T>;
Consumer: Consumer<T>;
displayName?: string | undefined;
}
`Provider` 擁有`value`和`children` `Consumer`擁有 `children` 類型都是`ReactNode|undefined`。想想我們這react中使用`Context`傳值 是不是感覺很熟悉?看懂類型定義 再也不怕忘記api了。
useState
定義:function useState<S>(initialState:S| (() =>S)): [S, Dispatch<SetStateAction<S>>]泛型S表示state 是用來約束initialState類型,也可以傳入返回值是S的方法。useState返回值為2個元素的元組類型,返回state和更新state的方法。默認(rèn)情況下useState會根據(jù)傳入類型自動推導(dǎo)出S類型。SetStateAction<S>定義了傳入setState的參數(shù)類型。是S類型或者返回S類型值的函數(shù)的聯(lián)合類型。SetStateAction 的定義為: type SetStateAction<S> = S|((prevState:S) =>S),prevState為上一次的state,聯(lián)合類型暫可以理解成或的關(guān)系。而 Dispatch 表示setState的類型,是一個沒有返回值的方法。定義也很簡單Dispatch :type Dispatch<A> = (value:A) =>void。 還有useState參數(shù)個數(shù)為0的情況。上面的類型無法滿足,所以后面?zhèn)€函數(shù)重載約束沒有傳入初始值的實現(xiàn)。function useState<S=undefined>(): [S|undefined, Dispatch<SetStateAction<S|undefined>>];
useRef
定義比較簡單:function useRef<T>(initialValue:T):MutableRefObject<T>, useRef 返回一個可變 ref 對象,其 .current 屬性初始化為傳遞的參數(shù)。MutableRefObject就是一個包含current:T的接口。值得注意的是 這里同樣用了函數(shù)重載,包括了initialValue沒有傳或者為null的情況。ref在props中大部分的初始值都為null。 類型聲明中注釋明確指定了如果要使用可變的useRef 則需要在范型參數(shù)中包含| null.
* Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
* of the generic argument.
如果我們這樣寫,此時ref為RefObject類型 RefObject的current被readonly修飾。所以是不可變的。當(dāng)在范型中指定了| null 則根據(jù)函數(shù)重載命中第一種類型,返回MutableRefObject是可變的。
const ref = useRef<number>(null)
ref.current = 2 // 無法分配到 "current" ,因為它是只讀屬性。
// 此時命中的這個重載的useRef
function useRef<T>(initialValue: T|null): RefObject<T>;
useEffect
定義: function useEffect(effect:EffectCallback, deps?:DependencyList):void, EffectCallback是一個只能返回void|Destructor的函數(shù)類型 用來處理副作用 。 void表示沒有返回值 ,但這里并不意味著你賦值一個有返回值的函數(shù)會報錯,在一個返回值為void的函數(shù)你明確返回類型 并不會報錯。而void真正表示無論你返回什么?編譯器都不會使用檢查它。 Destructor 表示析構(gòu)函數(shù),看下它的定義
declare const UNDEFINED_VOID_ONLY: unique symbol;
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never }
這里UNDEFINED_VOID_ONLY表示一個常量類型 unique symbol是symbol的子類型 , 使用unique symbol的變量必須為const,而值為never表示的是那些永不存在的值的類型。 never 類型是那些總是會拋出異?;蚋揪筒粫蟹祷刂档暮瘮?shù)表達式或箭頭函數(shù)表達式的返回值類型。這里使用void和{ [UNDEFINED_VOID_ONLY]: never }作為聯(lián)合類型, 明確約束了effect是不能有返回類型的, 如果明確聲明 則會報錯。 如果有async修飾函數(shù)默認(rèn)返回promise類型, 所以在useEffect中的effect也同樣不能使用async。deps是可選參數(shù),作為依賴是一個只讀數(shù)組。ReadonlyArray是一個真正的只讀數(shù)組類型,根據(jù)范型來約束數(shù)組元素類型。它沒有改變數(shù)組的方法push shift等。
useLayoutEffect
useLayoutEffect類型聲明與useEffect一致。但useLayoutEffect的callback會在DOM更新后同步觸發(fā) 在瀏覽器同步刷新之前執(zhí)行完成 可能會阻塞瀏覽器渲染。
useReducer
官方介紹useReducer 為 An alternative to useState.是useState的替代解決方案。一般我們都這樣使用。當(dāng)state結(jié)構(gòu)或邏輯比較復(fù)雜時,用useReducer管理更方便容易。
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, {count: 0});
state.count
dispatch({type: 'decrement'})
在類型聲明文件中useReducer寫了5個重載函數(shù)類型。
type ReducerWithoutAction<S> = (prevState: S) => S;
type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> =
R extends ReducerWithoutAction<infer S> ? S : never;
function useReducer<R extends ReducerWithoutAction<any>, I>(
reducer: R,
initializerArg: I,
initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
- 第一種是reducer函數(shù)沒有傳action的情況。R表示reducer函數(shù)類型, 其中參數(shù)state類型和返回類型必須一致。initializerArg表示初始參數(shù),類型為泛型的第二個參數(shù)。initializer定義稍微復(fù)雜,但是其實約束了此類型必須是一個參數(shù)為initializerArg類型 返回值也同initializerArg類型一致的參數(shù)類型。而這個initializerArg就是reducer的參數(shù)state類型。ReducerStateWithoutAction就是為了約束這三個參數(shù)的類型。舉個例子更清晰. 下述代碼reducer中state initializerArg 已經(jīng) initializer的參數(shù)和返回參數(shù)類型都應(yīng)該保持一致。
type stateType = {num: number}
function reducer(state: stateType) {
return state
}
const [state,dispatch]=useReducer<typeof reducer,stateType>(
reducer, {num: 0},state=>{
return {num: state.num+1}
})
這里的extends 條件類型是一種條件表達式進行類型的關(guān)系檢測,類似于三元表達式。意思為左側(cè)類型可分配給右側(cè)類型則返回?后面的類型 否則返回:后的類型。 而infer關(guān)鍵字只能出現(xiàn)在條件類型extends 判斷為true的分支,表示一個待推斷的類型,infer S表示將推斷的類型保存在S中。
- 第二個重載與第一個類似 只是在initializer為undefined的情況。如果在useReducer的泛型中指定了第二個參數(shù),則命中第一個重載 此時會報錯。具體實現(xiàn)類似下述代碼。
function useReducer<R extends ReducerWithoutAction<any>>(
reducer: R,
initializerArg: ReducerStateWithoutAction<R>,
initializer?: undefined
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
type stateType = {num: number}
function reducer(state: stateType) {
return state
}
const [state,dispatch]=useReducer<typeof reducer>(
reducer, {num: 0})
- 第三個重載約束了reducer函數(shù)傳入action的情況,不同于redux action是any類型。initializerArg初始參數(shù)為 state與泛型I的交叉類型。I可能是state的子集的情況。ReducerState同樣是為了取出reducer中state類型。initializer同上述第一種重載類似。要約束arg initializerArg 一致。而初始initializer的返回值要與reducer中state一致。
// Unlike redux, the actions _can_ be anything
type Reducer<S, A> = (prevState: S, action: A) => S;
// types used to try and prevent the compiler from reducing S
// to a supertype common with the second argument to useReducer()
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
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>>];
舉個例子 初始參數(shù)initializer的state類型 在初始函數(shù)的參數(shù)類型也應(yīng)該一致。
// 代碼實現(xiàn)
type stateType = {num: number}
type actionType = { type: string, payload: number}
function reducer(state: stateType,action: actionType) {
if(action.type=='add'){
return {num: state.num+1}
}else {
return {num: state.num-1}
}
}
const [state,dispatch]=useReducer<typeof reducer,actionType>(
reducer, { type: 'add', payload: 1,num: 2},state=>{
return {num:state.num+state.payload}
})
- 第4個重載 和第三個類似 在初始參數(shù)不包括state的情況, 初始參數(shù)initializer的state類型 在初始函數(shù)的參數(shù)類型也應(yīng)該一致。
function useReducer<R extends Reducer<any, any>, I>(
reducer: R,
initializerArg: I,
initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
第5個重載 和上述類似 約束了initializer為undefined,reducer存在actions的情況
function useReducer<R extends Reducer<any, any>>(
reducer: R,
initialState: ReducerState<R>,
initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
useReducer的返回值都是一致。返回reducerState和Dispatch,而type Dispatch<A> = (value:A) =>void;就是一個沒有返回值的函數(shù) 用來觸發(fā)action 改變reducerState。
useCallback
定義比較簡單:function useCallback<T extends (...args:any[]) =>any>(callback:T, deps:DependencyList):T;范型T為function類型為第一個參數(shù)callback的類型,第二個參數(shù)DependencyList與useEffect的依賴數(shù)組一致,都是一個只讀的數(shù)組。主要作用是用來緩存callback實例,當(dāng)傳遞給子組件方法時與React.memo 或者shouldComponentUpdate一起使用。
useMemo
定義也比較簡單:
// allow undefined, but don't make it optional as that is very likely a mistake
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
范型T為factory的返回值類型。deps依賴為DependencyList和undefined的聯(lián)合類型,這里會有提示允許deps為undefined,但不能是可選的 否則可能是個錯誤。
useImperativeHandle
useImperativeHandle主要用來配合forwardRef自定義暴露給父組件數(shù)據(jù)的。一般用來父組件調(diào)用子組件方法或獲取子組件數(shù)據(jù)時使用。
function useImperativeHandle<T, R extends T>(ref: Ref<T>|undefined, init: () => R, deps?: DependencyList): void;
interface RefObject<T> {
readonly current: T | null;
}
// Bivariance hack for consistent unsoundness with RefObject
type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];
type Ref<T> = RefCallback<T> | RefObject<T> | null;
泛型T為ref的current的類型,R是第二個參數(shù)init方法的返回值,DependencyList同上述依賴數(shù)組一樣 不可變數(shù)組。可以這樣使用
const Child = React.forwardRef<{num: number}>((prop,ref)=>{
useImperativeHandle<{num: number}, {num: number}>(ref,()=>({
'num': 1
}))
return (<div>123</div>)
})
const Foo = ()=>{
const childRef = useRef<{num: number}|null>(null)
useLayoutEffect(() => {
console.log(childRef.current?.num) // 1
}, [])
return <>
<Child ref={childRef}/>
</>
}
總結(jié)
本文根據(jù)閱讀@types/react下hook相關(guān)源碼入手,意在幫助大家熟悉常用hook以及類型聲明,在開發(fā)時能得心應(yīng)手,明白hooks的約束條件,更深入理解hook的功能。