ahooks 源码解读系列 - 8

1,722 阅读3分钟

这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。

为了和代码原始注释区分,个人理解部分使用 ///开头,此处和 三斜线指令没有关系,只是为了做区分。

往期回顾

系列文章已经是第 8 篇了,看来是我的实力太强了,前面 7 篇都没人留言指出错漏,那我就继续保持吧。 今天进入 LifeCycle 部分的 hooks。

LifeCycle

useMount

模拟 componentDidMount() ,只在初次渲染时运行。

import { useEffect } from 'react';

const useMount = (fn: () => void) => {
  /// 生命周期相关 hook 基本都是利用 useEffect 实现
  useEffect(() => {
    fn(); /// 注意,没有return,所以不会执行清理工作
  }, []);
};

export default useMount;

useUnmount

模拟componentWillUnmount() ,只在组件卸载时运行。

import { useEffect } from 'react';
import usePersistFn from '../usePersistFn';
import { isFunction } from '../utils';

const useUnmount = (fn: any) => {
  const fnPersist = usePersistFn(fn);

  useEffect(
    () => () => {
      if (isFunction(fnPersist)) {
        fnPersist();
      }
    },
    [],
  );
};

export default useUnmount;

useUnmountedRef

“你走了嘛?”

返回组件是否卸载的标志位。

import { useRef, useEffect } from 'react';

const useUnmountedRef = () => {
  const unmountedRef = useRef(false);
  useEffect(() => {
    return () => {
      /// 依赖数组为空,所以只有组件卸载时才会赋值为真
      unmountedRef.current = true;
    };
  }, []);
  return unmountedRef;
};

export default useUnmountedRef;

useTrackedEffect

“您看就是这样一个情况”

提供具体变化依赖项的 useEffect

import { useEffect, useRef, DependencyList } from 'react';

const useTrackedEffect = (effect, deps?: DependencyList) => {
  const previousDepsRef = useRef<DependencyList>();
  /// 对比依赖的变动,使用 !== 做判断是否有变动,返回的是变动项在依赖列表中的索引
  const diffTwoDeps = (deps1, deps2) => {
    //Let's do a reference equality check on 2 dependency list.
    //If deps1 is defined, we iterate over deps1 and do comparison on each element with equivalent element from deps2
    //As this func is used only in this hook, we assume 2 deps always have same length.
    return deps1
      ? deps1.map((_ele, idx) => (deps1[idx] !== deps2[idx] ? idx : -1)).filter((ele) => ele >= 0)
      : deps2
      ? deps2.map((_ele, idx) => idx)
      : [];
  };
  useEffect(() => {
    let changes = diffTwoDeps(previousDepsRef.current, deps);
    const previousDeps = previousDepsRef.current;
    previousDepsRef.current = deps;
    return effect(changes, previousDeps, deps); /// 提供了变动值索引,前一份依赖数组,当前依赖数组给到 cb
  }, deps);
};

export default useTrackedEffect;

useDebounceEffect

带防抖功能的 useEffect

import { useEffect, EffectCallback, DependencyList, useState } from 'react';
import { DebounceOptions } from '../useDebounce/debounceOptions';
import useDebounceFn from '../useDebounceFn';
import useUpdateEffect from '../useUpdateEffect';
import useUnmount from '../useUnmount';

function useDebounceEffect(
  effect: EffectCallback,
  deps?: DependencyList,
  options?: DebounceOptions,
) {
  const [flag, setFlag] = useState({});

  const { run, cancel } = useDebounceFn(() => {
    setFlag({}); /// 更新 flag 触发 useUpdateEffect 从而触发 effect 回调
  }, options);
  
  /// 第一次渲染时触发一次 effect
  useEffect(() => {
    return run();
  }, deps);
  /// 清理
  useUnmount(cancel);

  useUpdateEffect(effect, [flag]);
}

export default useDebounceEffect;

useThrottleEffect

带节流功能的 useEffect

import { useEffect, EffectCallback, DependencyList, useState } from 'react';
import { ThrottleOptions } from '../useThrottle/throttleOptions';
import useThrottleFn from '../useThrottleFn';
import useUpdateEffect from '../useUpdateEffect';
import useUnmount from '../useUnmount';

function useThrottleEffect(
  effect: EffectCallback,
  deps?: DependencyList,
  options?: ThrottleOptions,
) {
  const [flag, setFlag] = useState({});

  const { run, cancel } = useThrottleFn(() => {
    setFlag({});
  }, options);

  useEffect(() => {
    return run();
  }, deps);

  useUnmount(cancel);

  useUpdateEffect(effect, [flag]);
}

export default useThrottleEffect;

useUpdate

模拟 forceUpdate()

import { useCallback, useState } from 'react';

const useUpdate = () => {
  const [, setState] = useState({});

  return useCallback(() => setState({}), []); /// 调用时通过更新state来触发一次组件更新
};

export default useUpdate;

useUpdateEffect

模拟 componentDidUpdate ,忽略首次渲染版本的 useEffect。和 useUpdate 没有关系。

import { useEffect, useRef } from 'react';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isMounted = useRef(false);

  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return effect(); /// 忽略了首次渲染,只在依赖更新时才会运行
    }
  }, deps);
};

export default useUpdateEffect;

useUpdateLayoutEffect

等价 componentDidUpdate ,忽略首次渲染版本的 useLayoutEffect

深入理解 React useLayoutEffect 和 useEffect 的执行时机

/* eslint consistent-return: 0 */

import { useLayoutEffect, useRef } from 'react';

const useUpdateLayoutEffect: typeof useLayoutEffect = (effect, deps) => {
  const isMounted = useRef(false);

  useLayoutEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return effect();
    }
  }, deps);
};

export default useUpdateLayoutEffect;

以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。