目錄
- node_modules中的@types是什么?
- types查找規則
- 模塊types
- *.d.ts是什么
- useContext
- useState
- useRef
- useEffect
- useLayoutEffect
- useReducer
- useCallback
- useMemo
- useImperativeHandle
- 總結
在了解react hooks的類型之前,有必要先了解一下@types、.d.ts文件的概念及作用。
node_modules中的@types是什么?
當我們使用第三方npm包的時候,如果這個包不是ts
編寫,則沒有導出類型,這時候如果在ts中導入會報錯。比如jquery
這時會報錯
無法找到模塊“jquery”的聲明文件嘗試使用
npm i --save-dev @types/jquery
(如果存在),或者添加一個包含declare module 'jquery';
的新聲明(.d.ts
????這里提示找不到jquery的類型定義 可以安裝@types/jquery
或者在d.ts
中自定義類型,大多數情況我們應該使用第一種辦法,如果這個庫沒有@types
庫再使用第二種。
types查找規則
當我們使用import xx from
時ts將會默認從./node_modules/@types
中獲取類型聲明,具體查找規則是ts編譯器先在當前編譯上下文找jquery
的定義,找不到則再去./node_modules/@types
中查找。 在本地模塊查找的類型聲明作用域是在模塊,在@types
中的類型聲明是全局的。在tsconfig.json
中也可以使用 typeRoots
設置默認路徑 。
模塊types
當然在`tsconfig.json`中也可以使用`types`單獨控制`@types`。`types`指定的包會被單獨引入。這樣全局引入就失效了。
*.d.ts是什么
@types下存放的文件都是.d.ts開頭的文件 對應的npm包js的類型聲明。 在.d.ts文件中聲明的類型或者模塊,在其他文件中不需要使用import導入,可以直接使用,d.ts的類型聲明可以自行編寫也可以使用工具聲明。有2個工具
可以使用微軟的dts-gen,生成單個文件的聲明dtsmake。值得注意的是如果你使用JSDOC
語法 在ts3.7以后是可以通過命令為js生成.ds文件。具體用法可查看TypeScript: Documentation - Creating .d.ts Files from .js files (typescriptlang.org)
介紹完前菜 現在開始進入本文正題。 一起來看下react hooks相關的類型聲明吧。在@types/react/index.d.ts
文件中。
useContext
`useContext和createContext`是結合一起使用的??
useContext定義: function useContext<T>(context:Context<T>):T
createContext定義: function createContext<T>(defaultValue:T,):Context<T>
createContext
的返回Context類型的值提供給useContext的參數。這里泛型T
在2個方法中是一致的,如果不指定 ts會類型推導出正確的類型。而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
的方法。默認情況下useState
會根據傳入類型自動推導出S
類型。 SetStateAction<S>
定義了傳入setState
的參數類型。是S
類型或者返回S
類型值的函數的聯合類型。SetStateAction
的定義為: type SetStateAction<S> = S|((prevState:S) =>S)
,prevState
為上一次的state
,聯合類型暫可以理解成或的關系。而 Dispatch
表示setState的類型,是一個沒有返回值的方法。定義也很簡單Dispatch :type Dispatch<A> = (value:A) =>void
。 還有useState
參數個數為0的情況。上面的類型無法滿足,所以后面個函數重載約束沒有傳入初始值的實現。 function useState<S=undefined>(): [S|undefined, Dispatch<SetStateAction<S|undefined>>];
useRef
定義比較簡單:function useRef<T>(initialValue:T):MutableRefObject<T>
, useRef
返回一個可變 ref 對象,其 .current
屬性初始化為傳遞的參數。MutableRefObject
就是一個包含current:T
的接口。值得注意的是 這里同樣用了函數重載,包括了initialValue
沒有傳或者為null的情況。ref
在props
中大部分的初始值都為null
。 類型聲明中注釋明確指定了如果要使用可變的useRef
則需要在范型參數中包含| 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
修飾。所以是不可變的。當在范型中指定了| null
?則根據函數重載命中第一種類型,返回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
的函數類型 用來處理副作用 。 void
表示沒有返回值 ,但這里并不意味著你賦值一個有返回值的函數會報錯,在一個返回值為void
的函數你明確返回類型 并不會報錯。而void真正表示無論你返回什么?編譯器都不會使用檢查它。 Destructor
表示析構函數,看下它的定義
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
類型是那些總是會拋出異常或根本就不會有返回值的函數表達式或箭頭函數表達式的返回值類型。這里使用void
和{ [UNDEFINED_VOID_ONLY]: never }
作為聯合類型, 明確約束了effect
是不能有返回類型的, 如果明確聲明 則會報錯。 如果有async
修飾函數默認返回promise
類型, 所以在useEffect
中的effect
也同樣不能使用async
。deps
是可選參數,作為依賴是一個只讀數組。ReadonlyArray
是一個真正的只讀數組類型,根據范型來約束數組元素類型。它沒有改變數組的方法push
shift
等。
useLayoutEffect
useLayoutEffect
類型聲明與useEffect
一致。但useLayoutEffect
的callback
會在DOM
更新后同步觸發 在瀏覽器同步刷新之前執行完成 可能會阻塞瀏覽器渲染。
useReducer
官方介紹useReducer
為 An alternative to
useState.
是useState
的替代解決方案。一般我們都這樣使用。當state
結構或邏輯比較復雜時,用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個重載函數類型。
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
函數沒有傳action
的情況。R表示reducer函數類型
, 其中參數state
類型和返回類型必須一致。initializerArg
表示初始參數,類型為泛型的第二個參數。initializer
定義稍微復雜,但是其實約束了此類型必須是一個參數為initializerArg
類型 返回值也同initializerArg
類型一致的參數類型。而這個initializerArg
就是reducer
的參數state
類型。ReducerStateWithoutAction
就是為了約束這三個參數的類型。舉個例子更清晰. 下述代碼reducer
中state
initializerArg
已經initializer
的參數和返回參數類型都應該保持一致。
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
條件類型是一種條件表達式進行類型的關系檢測,類似于三元表達式。意思為左側類型可分配給右側類型則返回?后面的類型 否則返回:后的類型。 而infer
關鍵字只能出現在條件類型extends
判斷為true
的分支,表示一個待推斷的類型,infer S
表示將推斷的類型保存在S
中。
- 第二個重載與第一個類似 只是在
initializer
為undefined
的情況。如果在useReducer
的泛型中指定了第二個參數,則命中第一個重載 此時會報錯。具體實現類似下述代碼。
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
函數傳入action
的情況,不同于redux
action
是any
類型。initializerArg初始參數為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>>];
舉個例子 初始參數initializer
的state
類型 在初始函數的參數類型也應該一致。
// 代碼實現 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個重載 和第三個類似 在初始參數不包括state的情況, 初始參數
initializer
的state
類型 在初始函數的參數類型也應該一致。
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;
就是一個沒有返回值的函數 用來觸發action
改變reducerState
。
useCallback
定義比較簡單: function useCallback<T extends (...args:any[]) =>any>(callback:T, deps:DependencyList):T;
范型T
為function
類型為第一個參數callback
的類型,第二個參數DependencyList
與useEffect
的依賴數組一致,都是一個只讀的數組。主要作用是用來緩存callback
實例,當傳遞給子組件方法時與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
的聯合類型,這里會有提示允許deps
為undefined
,但不能是可選的 否則可能是個錯誤。
useImperativeHandle
useImperativeHandle
主要用來配合forwardRef
自定義暴露給父組件數據的。一般用來父組件調用子組件方法或獲取子組件數據時使用。
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
是第二個參數init
方法的返回值,DependencyList
同上述依賴數組一樣 不可變數組。可以這樣使用
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}/> </> }
總結
本文根據閱讀@types/react下hook
相關源碼入手,意在幫助大家熟悉常用hook以及類型聲明 在開發時能得心應手 明白hooks的約束條件 更深入理解hook
的功能。如上述內容有錯誤,請不吝指出
以上就是一文帶你搞懂react hooks的類型聲明的詳細內容,更多關于react hooks的類型聲明的資料請關注技圈網其它相關文章!