这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。
为了和代码原始注释区分,个人理解部分使用 ///开头,此处和 三斜线指令没有关系,只是为了做区分。
往期回顾
- ahooks 源码解读系列
- ahooks 源码解读系列 - 2
- ahooks 源码解读系列 - 3
- ahooks 源码解读系列 - 4
- ahooks 源码解读系列 - 5
- ahooks 源码解读系列 - 6
- ahooks 源码解读系列 - 7
系列文章已经是第 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
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。
/* 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;
以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。