ahooks生命周期相关hook 源码解读
上期我们主要介绍了useMount源码和一些前置的知识点,在ahooks中生命周期相关hook还剩下useUnmount,useUnmountedRef
上期留下的问题
上期传送门,useMount的实现为什么不用useLayoutEffect,我个人总结还是同步和异步的区别,如果使用useLayoutEffect实现的话,如果useMount的fn中,如果存在重度计算等代码就会存在阻塞首屏渲染的风险。其次的话,如果在SSR的场景,React也是不支持useLayoutEffect的
为什么上期useUnmount的实现是有问题的
首先我们看下
const useUnmount = (fn: () => void) => {
useEffect(
() => () => {
fn?.();// 就这?
},
[],
);
};
可能暂时一眼还看不出问题所在,不如我们自己在demo中跑一下看看
import { useEffect, useState } from "react";
const useUnmount = (fn) => {
useEffect(
() => () => {
fn?.(); // 就这?
},
[],
);
};
const Child = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
useUnmount(() => {
console.log("count:", count);
});
return <div>{count}</div>;
};
export default function Test() {
const [flag, setFlag] = useState(false);
return (
<div>
<button
onClick={() => {
setFlag(!flag);
}}
>
点我
</button>
{!!flag ? <Child /> : null}
</div>
);
}
大家脑海里面可以想下组件卸载的时候log的count值是多少,界面上的count值是多少?
如果觉得log中count值是0的同学,恭喜你们get到了,这里就存在一个闭包的问题,如何解决呢?我们看ahooks的源码是如何解决的
useUnmount
import { useEffect } from 'react';
import useLatest from '../useLatest';
import { isFunction } from '../utils';
import isDev from '../utils/isDev';
const useUnmount = (fn: () => void) => {
if (isDev) {
if (!isFunction(fn)) {
console.error(`useUnmount expected parameter is a function, got ${typeof fn}`);
}
}
const fnRef = useLatest(fn);
useEffect(
() => () => {
fnRef.current();
},
[],
);
};
export default useUnmount;
这里使用了useLatest来保证得到最新值,至于useLatest又是怎么实现的,我们到对应的章节来讲,现在大家只要知道这样能解决刚才说的闭包问题就好了,我们改下代码,再试一下就能发现
Wow!?变成1了。但是,你是不是发现界面上的值也一直是1,为什么没有累加上去呢?
思考一下。嗯。
对咯,这个Effect里面也是有闭包问题的,不妨你在定时器内部也log下count,是不是一直是0
具体原因就牵扯到useEffct的源码实现了,我们后面在讲,这里不展开了。
解决办法呢也是可以用下刚才提到的useLatest
const ref = useLatest(count);
useEffect(() => {
const interval = setInterval(() => {
console.log("count:", count);
console.log("ref:", ref);
setCount(ref.current + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
useUnmountRef
本篇最后我们再聊最后一个ahooks中跟生命周期有关的一个hook,useUnmountRef
作用就是给你一个标记,让你在使用的时候知道当前组件是否已经被卸载
import { useEffect, useRef } from 'react';
const useUnmountedRef = () => {
const unmountedRef = useRef(false);
useEffect(() => {
unmountedRef.current = false;
return () => {
unmountedRef.current = true;
};
}, []);
return unmountedRef;
};
export default useUnmountedRef;
这里牵扯到原生hook,useRef,不在赘述,简单理解就是通过useRef,分别在挂载和卸载阶段修改对应的值
demo如下
/**
* title: Default usage
* desc: unmountedRef.current means whether the component is unmounted
*
* title.zh-CN: 基础用法
* desc.zh-CN: unmountedRef.current 代表组件是否已经卸载
*/
import { useBoolean, useUnmountedRef } from 'ahooks';
import { message } from 'antd';
import React, { useEffect } from 'react';
const MyComponent = () => {
const unmountedRef = useUnmountedRef();
useEffect(() => {
setTimeout(() => {
if (!unmountedRef.current) {
message.info('component is alive');
}
}, 3000);
}, []);
return <p>Hello World!</p>;
};
export default () => {
const [state, { toggle }] = useBoolean(true);
return (
<>
<button type="button" onClick={toggle}>
{state ? 'unmount' : 'mount'}
</button>
{state && <MyComponent />}
</>
);
};
好了,本期内容就到这里。
个人感觉一开始这3个hook的实现是相对比较容易的,但也有些细微的点值得大家去注意的。后面我们会讲下ahooks中State相关的hook及源码实现。
下期预告
我们将在下期,clone下ahooks的源码项目,进行源码目录,工程化,测试等分析和使用,敬请期待,更多的交流关注喵爸的小作坊