Published on

alibaba/hooks代码解读之useBoolean和useToggle

Authors
  • avatar
    Name
    祝你好运
    Twitter

说来惭愧,在上家公司里面hooks用的不多,现在的新公司新项目,全是函数式组件,没有类组件了。我也开始大量使用各种hooks,然后找到了这个第三方库hooks,自定义的hooks。这篇文章就是来分析一下各个hooks的源代码。

useToggle

本来第一个是想先分析useBoolean的,但是那里面用到了这个useToggle,所以就先分析这个了。useToggle是用来反转值的hooks,这个值可以是boolean,直觉上我们是这么觉得的,但它也可以是别的值,值的类型定义是这样的:string | number | boolean | undefined。它的前后两个状态甚至可以是两个不同的类型,比如初始值是0,反转值是true(只是举个例子,要看应用场景)。代码里面的泛型会比较难懂,我们一步一步看。

IStateActions

type IState = string | number | boolean | undefined;

这个比较好懂,就是IState可能是字符串、数字、布尔或者为undefined(也就是不传值)。

export interface Actions<T = IState> {
  setLeft: () => void;
  setRight: () => void;
  toggle: (value?: T) => void;
}

然后Actions是一个泛型接口,里面包含三个函数,泛型我就不解释了,可以参考官方文档。还有一个要注意的点就是这里的T是有默认值的,当外界传进来值的时候就用外界传进来的值,没有的话TIState类型。

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

  1. 第一个类型是无参数的,返回的状态是布尔类型的,返回的同样是数组,数组第一个元素是布尔类型,第二个元素是修改状态的函数,类型就是上面Actions定义的,但是它的T只能是boolean | undefined
  2. 第二个类型是接受一个参数TT的类型就是IState定义的类型,返回值类似上面的。
  3. 第三个类型是接受两个参数TU,他们的类型都是IState。注意这里要定义TU,不能只定义一个T,然后defaultValuereverseValue都是T类型,因为这样就限定了defaultValuereverseValue是同一类型,我们想要的结果是他们可以是不同类型。函数的返回值的话类似上面的。这里不太明白的话可以这么思考,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来做记忆化,defaultValuereverseValue的值是不会变的,所以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就带有缓存,值得参考。

参考:

  1. hooks
  2. use-boolean
  3. Functional updates