【ahooks源码学习】—— useToggle

491 阅读5分钟

useToggle

介绍

在React开发中,状态管理是一个重要的问题。ahooks是一个由Alibaba开发的React Hooks库,其中的useToggle提供了一种便捷的方式来管理布尔值类型的状态。

useToggle可以用于在两个状态之间进行切换,例如开关状态。除此之外,它还可以被用于管理任何数据类型的状态。

本文将分析ahooks3.7.5版本下useToggle的实现源代码,讨论它的使用方法和参数,以及它是如何管理状态的。

源码学习

源代码中的useToggle函数提供了三个重载函数,分别处理不同的情况。

import { useMemo, useState } from 'react';

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

function useToggle<T = boolean>(): [boolean, Actions<T>];

function useToggle<T>(defaultValue: T): [T, Actions<T>];

function useToggle<T, U>(defaultValue: T, reverseValue: U): [T | U, Actions<T | U>];

function useToggle<D, R>(defaultValue: D = false as unknown 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 = () => setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
    const set = (value: D | R) => setState(value);
    const setLeft = () => setState(defaultValue);
    const setRight = () => setState(reverseValueOrigin);

    return {
      toggle,
      set,
      setLeft,
      setRight,
    };
    // useToggle ignore value change
    // }, [defaultValue, reverseValue]);
  }, []);

  return [state, actions];
}

export default useToggle;

其中,第一个重载函数不接受任何参数,返回一个元组。该元组的第一个值是布尔类型的状态值,第二个值是包含setLeftsetRightsettoggle四个函数的对象。

第二个重载函数接受一个参数,返回一个元组。该元组的第一个值是该参数类型的状态值,第二个值是包含setLeftsetRightsettoggle四个函数的对象。

第三个重载函数接受两个参数,分别是初始状态值和一个可选的反转值。返回一个元组,元组的第一个值可以是初始状态值或者反转值,第二个值是包含setLeftsetRightsettoggle四个函数的对象。

useToggle函数内部,使用useState Hook来管理状态。同时,使用useMemo Hook来缓存包含setLeftsetRightsettoggle四个函数的对象,以便于性能优化。

actions对象中,toggle函数用于切换状态,set函数用于设置状态值,setLeft函数用于将状态值设置为初始状态值,setRight函数用于将状态值设置为反转值。

使用方法和场景

使用useToggle函数时,可以选择不同的参数类型。在第一个重载函数中,不接受任何参数,因此默认状态值为布尔类型的false。在第二个和第三个重载函数中,可以指定状态值的类型,例如字符串或数字类型。

如果在第三个重载函数中指定了反转值,那么在切换状态时,状态值将在初始状态值和反转值之间切换。如果没有指定反转值,则状态值将在truefalse之间切换。

使用方法

使用useToggle函数时,只需要调用它并将其返回值解构为需要的变量即可。例如,在使用第一个重载函数时,可以这样做:

import useToggle from 'ahooks';

const [state, { toggle }] = useToggle();

<button onClick={toggle}>{state ? 'On' : 'Off'}</button>

当用户点击按钮时,toggle函数将在truefalse之间切换状态,并且按钮上的文本将随之改变。

如果需要使用第二个或第三个重载函数,只需要传递相应的参数即可。例如,如果需要使用字符串类型的状态值,可以这样做:

const [text, { toggle }] = useToggle('Hello, world!'); <button onClick={toggle}>{text}</button>

在这个例子中,text变量的初始值为Hello, world!,当用户点击按钮时,它的值将在Hello, world!false之间切换。

状态管理

useToggle函数通过useState Hook来管理状态。在初始状态下,它返回给定的默认值或反转值。当用户调用toggle函数时,它将根据当前状态值来选择返回默认值或反转值。在这个过程中,setState函数将被调用来更新状态。

setLeftsetRight函数分别用于将状态值设置为默认值或反转值,而set函数则用于将状态值设置为任何其他值。

结论

useToggle是一个方便的React Hooks函数,可以用于管理布尔类型和其他数据类型的状态。它可以用于在两个状态之间切换,例如开关状态。此外,它还提供了一些辅助函数,例如setLeftsetRight,可以用于在状态之间进行切换。通过使用useStateuseMemo Hook,useToggle可以高效地管理状态,避免了手动管理状态带来的复杂性。

测试源码学习

import { renderHook, act } from '@testing-library/react';
import useToggle from '../index'; 

const callToggle = (hook: any) => {
  act(() => {
    hook.result.current[1].toggle();
  });
};

describe('useToggle', () => {
  // 第一个测试用例,检查useToggle函数的初始状态默认值为false
  it('test on init', async () => {
    const hook = renderHook(() => useToggle()); 
    expect(hook.result.current[0]).toBeFalsy(); 
  });


  // 第二个测试用例,测试useToggle函数中的toggle、setLeft、setRight等方法
  it('test on methods', async () => {
    const hook = renderHook(() => useToggle('Hello')); 
    expect(hook.result.current[0]).toBe('Hello'); 
    act(() => {
      hook.result.current[1].toggle(); 
    });
    expect(hook.result.current[0]).toBeFalsy(); 
    act(() => {
      hook.result.current[1].setLeft(); 
    });
    expect(hook.result.current[0]).toBe('Hello'); 
    act(() => {
      hook.result.current[1].setRight(); 
    });
    expect(hook.result.current[0]).toBeFalsy(); 
  });

  // 第三个测试用例,测试useToggle函数中的可选参数
  it('test on optional', () => {
    const hook = renderHook(() => useToggle('Hello', 'World'));
    callToggle(hook);
    expect(hook.result.current[0]).toBe('World');
    act(() => {
      hook.result.current[1].set('World');
    });
    expect(hook.result.current[0]).toBe('World');
    callToggle(hook);
    expect(hook.result.current[0]).toBe('Hello');
  });
});

renderHookact是React测试库@testing-library/react提供的两个工具函数。

renderHook用于在测试中渲染React Hooks,它提供了一个类似于React组件的环境来测试Hooks的行为,可以轻松模拟出Hook的使用场景,并且可以获取Hook的返回值和状态。

act则用于模拟React组件中的行为,比如用户的交互行为、异步操作等,通过使用act可以确保React在处理这些行为时会进行必要的更新,从而保证测试的正确性。

在使用renderHook时,通常需要在act中包裹需要测试的操作,比如调用Hook函数、触发事件等等,以确保在测试过程中React能够正确地更新组件的状态。

本文正在参加「金石计划」