大家好,我是若川。我持续组织了近一年的源码共读活动,感兴趣的可以 点此扫码加我微信 lxchuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北|河南
籍前端群,可加我微信进群。公众号回复【相亲】关键词可以获取男生、女生的菜单~
在了解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
库再使用第二种, 可以在https://microsoft.github.io/TypeSearch/中查找一个包是否存在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文件。具体用法可查看官方文档。
介绍完前菜,现在开始进入本文正题。 一起来看下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
的功能。