hooks

138 阅读4分钟

1 useSetState

使用

可以在函数式组件中使用类组件的setState

仿写

import { useCallback, useState } from "react";

// 使用类组件的setState
const useClassState = (initialState) => {
  // 先把传入的state使用函数式的useState定义上
  const [state, setState] = useState(initialState);
  // 使用usecallback防止重复创建函数
  const mergeState = useCallback((patch) => {
    setState((prevState) => {
      // 如果传入的是函数的话,就调用传入的函数来生成新的state
      const newState = typeof patch === "function" ? patch(prevState) : patch;
      // 类的setState是合并
      return newState ? { ...prevState, ...patch } : prevState;
    });
  }, []);

  return [state, mergeState];
};

export default useClassState;

2 useToggle

使用

const [state, { toggle, set, setLeft, setRight }] = useToggle('Hello', 'World');

传入两个值,setLeft将值切换成第一个,setRight将值切换成第二个,set可以随意设置值,toggle则来回切换

仿写

import { useMemo, useState } from "react";

/**
 *
 * @param {*} firstState 第一个值
 * @param {*} secondState 第二个值
 * @returns [state, {
 *      toggle,   // 切换值
 *      set,      // 设置值
 *      setFirst, // 切换到第一个值
 *      setSecond // 切换到第二个值
 * }]
 */

const useToggle = (firstState, secondState) => {
  const [state, setState] = useState(firstState);

  // 之后再不需要重新生成对象了
  const actions = useMemo(() => {
    // 如果只传了第一个值,则第二个值取反
    const secondStateJudgeValue =
      secondState === undefined ? !firstState : secondState;

    const toggle = () =>
      setState((val) =>
        val === firstState ? secondStateJudgeValue : firstState
      );
    const set = (val) => setState(val);
    const setFirst = () => setState(firstState);
    const setSecond = () => setState(secondStateJudgeValue);
    return { toggle, set, setFirst, setSecond };
  }, []);

  return [state, actions];
};

export default useToggle;

3 useCookieState

使用

const [value, setValue] = useCookieState("useCookieStateOptions", {
  defaultValue: "0",
  path: "/",
  expires: (() => new Date(+new Date() + 10000))(),
});
setValue((v) => String(Number(v) + 1), {
  expires: (() => new Date(+new Date() + 10000))(),
});

内部借助了js-cookie

初始化不会加入cookie中

仿写

import { useState } from "react";
import Cookies from "js-cookie";

const useCookie = (key, value, options = {}) => {
  const [state, setState] = useState(() => {
    // 先检查本地是否有cookie
    const localCookie = Cookies.get(key);
    if (localCookie) {
      return localCookie;
    }

    // 如果本地无cookie,则保存value
    if (typeof value === "function") {
      return value();
    } else {
      return value;
    }
  });

  // 源码中设计的初始化不会加入cookie中,这里初始化会加入cookie
  Cookies.set(key, state, options);

  const updateState = (newValue, newOptions) => {
    // 合并options
    const nowOptions = { ...options, ...newOptions };

    // 拿到value
    const value = typeof newValue === "function" ? newValue(state) : newValue;

    // 如果value是undefined则删除
    if (value === undefined) {
      Cookies.remove(key);
    } else {
      Cookies.set(key, value, nowOptions);
    }

    setState(value);
  };

  return [state, updateState];
};

export default useCookie;

4 useLocalstorageState & useSessionStorageState

使用

const [message, setMessage] = useLocalStorageState(
  'use-local-storage-state-demo3',
  {
    defaultValue: 'Hello~',
    serializer: (v) => v ?? '',
    deserializer: (v) => v,
  },
);

serializerdeserializer是自定义序列化和反序列化的函数

仿写

本hook按照自己的想法写的

import { useState } from "react";
/**
 * 
 * @param {*} key storage的键
 * @param {*} value storage的值
 * @param {*} options 用户配置:
 *          serializer序列化函数,
 *          deserializer反序列化函数,
 *          storage为localStorage或sessionStorage
 * @returns [state, 
 *          updateState // 更新值,updateState(val),val为空则表示删除storage
 * ]
 */
const useLocalStorage = (key, value, options = {}) => {
    let storage = localStorage
    if (options.storage === localStorage || options.storage === sessionStorage) {
        storage = options.storage
    }
    // 如果提供了自定义序列化方法,则使用用户提供的方法
    const serializer = (value) => {
        if (options.serializer) {
            return options.serializer(value);
        } else {
            return JSON.stringify(value);
        }
    };
    const deserializer = (value) => {
        if (options.deserializer) {
            return options.deserializer(value);
        } else {
            return JSON.parse(value);
        }
    };

    // 取数据
    const getItem = () => {
        const item = storage.getItem(key);
        if (item) {
            return deserializer(item);
        } else {
            return typeof value === "function" ? value() : value;
        }
    };

    const [state, setState] = useState(getItem());

    const updateState = (newValue) => {
        const val = typeof newValue === "function" ? newValue() : newValue;
        if (val) {
            storage.setItem(key, serializer(val));
            setState(getItem());
        } else {
            storage.removeItem(key);
        }
    };

    return [state, updateState];
};

export default useLocalStorage;

5 useMap

使用

const [map, { set, setAll, remove, reset, get }] = useMap([
  ['msg', 'hello world'],
  [123, 'number type'],
]);

仿写

本hook按照自己的想法写的

import { useState } from "react";

/**
 * 
 * @param {*} itor 传入一个迭代器
 * @returns [map, { 
 *      set,        // 新加
 *      del,        // 删除
 *      clear,      // 清除
 *      reset,      // 重置
 *      setAll      // 重新设置一个新的map
 * }]
 */
const useMap = (itor) => {
    const [map, setMap] = useState(new Map(itor));

    const set = (key, val) => {
        setMap((oldMap) => {
            const newMap = new Map(oldMap);
            newMap.set(key, val);
            return newMap;
        });
    };

    const del = (key) => {
        setMap((oldMap) => {
            const newMap = new Map(oldMap);
            newMap.delete(key);
            return newMap;
        });
    };

    const clear = () => setMap(new Map());

    const reset = () => setMap(new Map(itor));

    const setAll = (newitor) => setMap(() => new Map(newitor));

    return [map, { set, del, clear, reset, setAll }]
};

export default useMap;

6 useSet

使用

const [set, { add, remove, reset }] = useSet(['Hello']);

仿写

本hook按照自己的想法写的

import { useState } from "react"

/**
 * 
 * @param {*} itor 传入一个迭代器
 * @returns [set, { 
 *      add,        // 新加
 *      del,        // 删除
 *      clear,      // 清除
 *      reset,      // 重置
 *      setAll      // 重新设置一个新的set
 * }]
 */
const useSet = (itor) => {
    const [set, setSet] = useState(new Set(itor))

    const add = (val) => {
        if (set.has(val)) return;
        setSet(() => {
            const newSet = new Set(set)
            newSet.add(val)
            return newSet
        })
    }

    const del = (val) => {
        if (!set.has(val)) return;
        setSet(() => {
            const newSet = new Set(set)
            newSet.delete(val)
            return newSet
        })
    }

    const clear = () => setSet(new Set())
    const reset = () => setSet(new Set(itor))
    const setAll = (newitor) => setSet(() => new Set(newitor));

    return [set, { add, del, clear, reset, setAll }]
}

export default useSet

7 useResetState

使用

const [state, setState, resetState] = useResetState({
  hello: '',
  count: 0,
});

仿写

import { useCallback, useState } from "react"

/**
 * 
 * @param {*} initialState 初始值
 * @returns [state, setState, reset] reset:重置为初始值
 */
const useResetState = (initialState) => {
    const [state, setState] = useState(initialState)

    const reset = useCallback(() => {
        setState(initialState)
    }, [])

    return [state, setState, reset]
}

export default useResetState

8 useLatest

使用

const ref = useLatest(value)

这个hook是为了能够拿到最新值,而不用产生闭包。

仿写

import { useRef } from "react";

/**
 * 可以保证每次拿到最新值,防止产生大量闭包
 * @param {*} value 传入的值
 * @returns Ref
 */
const useLatest = (value) => {
  // 创建ref,但是需要注意的是,每次重新调用useLatest这个hook,都不会再创建一个新的ref了
  const ref = useRef(value);

  ref.current = value;
  return ref;
};

export default useLatest;

9 useDebounce

使用

返回一个函数

仿写

/**
 * 实现简单的防抖
 * @param {function} fn 希望做防抖的函数
 * @param {*} time 防抖的时间
 * @param  {...any} args 其他参数,内部会被转成数组,方便apply传值
 * @returns
 */
const useDebounce = (fn, time = 0, ...args) => {
  let timer = null;
  let that = this;
​
  return () => {
    if (timer) {
      // 每次都清空上一次的计时器保证函数只会执行一次
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      // 保证this指向,args保证事件对象可以正常传入
      fn.apply(that, args);
    }, time);
  };
};
​
export default useDebounce;