- Published on
alibaba/hooks代码解读之useBoolean和useToggle
- Authors
- Name
- 祝你好运
说来惭愧,在上家公司里面hooks用的不多,现在的新公司新项目,全是函数式组件,没有类组件了。我也开始大量使用各种hooks,然后找到了这个第三方库hooks,自定义的hooks。这篇文章就是来分析一下各个hooks的源代码。
useToggle
本来第一个是想先分析useBoolean
的,但是那里面用到了这个useToggle
,所以就先分析这个了。useToggle
是用来反转值的hooks,这个值可以是boolean
,直觉上我们是这么觉得的,但它也可以是别的值,值的类型定义是这样的:string | number | boolean | undefined
。它的前后两个状态甚至可以是两个不同的类型,比如初始值是0,反转值是true
(只是举个例子,要看应用场景)。代码里面的泛型会比较难懂,我们一步一步看。
IState
和Actions
type IState = string | number | boolean | undefined;
这个比较好懂,就是IState
可能是字符串、数字、布尔或者为undefined
(也就是不传值)。
export interface Actions<T = IState> {
setLeft: () => void;
setRight: () => void;
toggle: (value?: T) => void;
}
然后Actions
是一个泛型接口,里面包含三个函数,泛型我就不解释了,可以参考官方文档。还有一个要注意的点就是这里的T
是有默认值的,当外界传进来值的时候就用外界传进来的值,没有的话T
是IState
类型。
useToggle
useToggle
声明
函数声明就是为了描述函数名,函数有几个参数,每个参数以及返回值的类型。
function useToggle<T = boolean | undefined>(): [boolean, Actions<T>];
function useToggle<T = IState>(defaultValue: T): [T, Actions<T>];
function useToggle<T = IState, U = IState>(
defaultValue: T,
reverseValue: U,
): [T | U, Actions<T | U>];
这里就是三个useToggle
函数的类型定义,也就是Function Overloads。
- 第一个类型是无参数的,返回的状态是布尔类型的,返回的同样是数组,数组第一个元素是布尔类型,第二个元素是修改状态的函数,类型就是上面
Actions
定义的,但是它的T
只能是boolean | undefined
。 - 第二个类型是接受一个参数
T
,T
的类型就是IState
定义的类型,返回值类似上面的。 - 第三个类型是接受两个参数
T
和U
,他们的类型都是IState
。注意这里要定义T
和U
,不能只定义一个T
,然后defaultValue
和reverseValue
都是T
类型,因为这样就限定了defaultValue
和reverseValue
是同一类型,我们想要的结果是他们可以是不同类型。函数的返回值的话类似上面的。这里不太明白的话可以这么思考,T
有多少种类型,U
有多少种类型,他们组合的话有多少种类型?(答案是他俩的类型种类相乘,还挺多的)。
useToggle
定义
function useToggle<D extends IState = IState, R extends IState = IState>(
defaultValue: D = false as D,
reverseValue?: R,
)
这个定义是比较难懂的,extends
表示类型的约束,这个是泛型中类型约束的固定语法,就像我们定义函数参数的时候,用:
来定义参数的类型约束一样。D extends IState = IState
,这里我也没看懂。
useToggle
函数体
function useToggle<D extends IState = IState, R extends IState = IState>(
defaultValue: D = false as D,
reverseValue?: R,
) {
const [state, setState] = useState<D | R>(defaultValue);
const actions = useMemo(() => {
const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;
// 切换返回值
const toggle = (value?: D | R) => {
// 强制返回状态值,适用于点击操作
if (value !== undefined) {
setState(value);
return;
}
setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
};
// 设置默认值
const setLeft = () => setState(defaultValue);
// 设置取反值
const setRight = () => setState(reverseValueOrigin);
return {
toggle,
setLeft,
setRight,
};
}, [defaultValue, reverseValue]);
return [state, actions];
}
我们可以看出来useToggle
也是通过useState
来实现的。这里面的D | R
是因为被实例化的时候,D
是一个类型而R
可能是另外一种类型,那他们的useState<xxx>
里面的xxx
就应该是D
的类型或上R
的类型。然后再来看核心结构actions
,这里用了useMemo
来做记忆化,defaultValue
和reverseValue
的值是不会变的,所以actions
也是不会变的,它也不应该会变,这如果外界依赖actions
的话,就不会有问题。
这里面最难得就是那个setState
,这里是设置了一个函数?不应该都是设置一个值吗?原来这是我不知道的一个特性,参考这里。设置一个函数之后,这里就真的是非常巧妙。然后setLeft
就对应设置为原始值,setRight
就对应设置为反向值(就是那个reverseValue
)。
用法
其实用到useToggle
的类型还是挺多的,比如我们即将讲到的useBoolean
。
useBoolean
import { useMemo } from 'react';
import useToggle from '../useToggle';
export interface Actions {
setTrue: () => void;
setFalse: () => void;
toggle: (value?: boolean | undefined) => void;
}
export default function useBoolean(defaultValue = false): [boolean, Actions] {
const [state, { toggle }] = useToggle(defaultValue);
const actions: Actions = useMemo(() => {
const setTrue = () => toggle(true);
const setFalse = () => toggle(false);
return { toggle, setTrue, setFalse };
}, [toggle]);
return [state, actions];
}
这个代码相对来说比较简单,就两个点,一个就是同样用useMemo
做了记忆化,另一个就是是封装了一下useToggle
。但是使用的时候会很方便,比如很多地方的onCancel
就对应setFalse
,这样就不用再定义一个箭头函数。
后记
当然这里并不是一个十分通用的hooks,只能说满足了我们这个业务需求,如果是别的需求,可以考虑用成熟的第三方库,比如阿里的React Hooks Library,里面的useRequest就带有缓存,值得参考。