从根上理解ReactHooks的闭包陷阱(续集)

​​上篇文章​​我们知道了什么是 hooks 的闭包陷阱,它的产生原因和解决方式,并通过一个案例做了演示。

我们提供的服务有:成都网站建设、成都做网站、微信公众号开发、网站优化、网站认证、武江ssl等。为上千多家企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的武江网站制作公司

其实那个案例的闭包陷阱的解决方式不够完善,这篇文章我们再完善一下。

首先我们先来回顾下什么是闭包陷阱:

hooks 的闭包陷阱是指 useEffect 等 hook 中用到了某个 state,但是没有把它加到 deps 数组里,导致 state 变了,但是执行的函数依然引用着之前的 state。

它的解决方式就是正确设置 deps 数组,把用到的 state 放到 deps 数组里,这样每次 state 变了就能执行最新的函数,引用新的 state。同时要清理上次的定时器、事件监听器等。

我们举了这样一个例子:

import { useEffect, useState } from 'react';
function Dong() {
const [count,setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 500);
}, []);
useEffect(() => {
setInterval(() => {
console.log(count);
}, 500);
}, []);
return
guang
;
}
export default Dong;

每次打印都是 0 :

解决方式就是把 count 设置到 deps 里,并添加清理函数:

import { useEffect, useState } from 'react';
function Dong() {
const [count,setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 500);
return () => clearInterval(timer);
}, [count]);
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 500);
return () => clearInterval(timer);
}, [count]);

return
guang
;
}
export default Dong;

这样就能解决闭包陷阱:

但是这种解决闭包陷阱的方式用在定时器上不是很合适。

为什么呢?

因为现在每次 count 变了就会重置定时器,那之前的计时就重新计算,这样就会导致计时不准。

所以,这种把依赖的 state 添加到 deps 里的方式是能解决闭包陷阱,但是定时器不能这样做。

那还有什么方式能解决闭包陷阱呢?

useRef。

闭包陷阱产生的原因就是 useEffect 的函数里引用了某个 state,形成了闭包,那不直接引用不就行了?

useRef 是在 memorizedState 链表中放一个对象,current 保存某个值。

它的源码是这样的:

初始化的时候创建了一个对象放在 memorizedState 上,后面始终返回这个对象。

这样通过 useRef 保存回调函数,然后在 useEffect 里从 ref.current 来取函数再调用,避免了直接调用,也就没有闭包陷阱的问题了。

也就是这样:

const fn = () => {
console.log(count);
};
const ref = useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
useEffect(() => {
setInterval(() => ref.current(), 500);
}, []);

useEffect 里执行定时器,deps 设置为了 [],所以只会执行一次,回调函数用的是 ref.current,没有直接依赖某个 state,所以不会有闭包陷阱。

用 useRef 创建个 ref 对象,初始值为打印 count 的回调函数,每次 render 都修改下其中的函数为新创建的函数,这个函数里引用的 count 就是最新的。

这里用了 useLayoutEffect 而不是 useEffect 是因为 useLayoutEffect 是在 render 前同步执行的,useEffect 是在 render 后异步执行的,所以用 useLayoutEffect 能保证在 useEffect 之前被调用。

这种方式避免了 useEffect 里直接对 state 的引用,从而避免了闭包问题。

另外,修改 count 的地方,可以用 setCount(count => count + 1) 代替 setCount(count + 1),这样也就避免了闭包问题:

useEffect(() => {
setInterval(() => {
setCount(count => count + 1);
}, 500);
}, []);

现在组件的代码是这样的:

import { useEffect, useLayoutEffect, useState, useRef } from 'react';
function Dong() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count => count + 1);
}, 500);
}, []);
const fn = () => {
console.log(count);
};
const ref = useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
useEffect(() => {
setInterval(() => ref.current(), 500);
}, []);
return
guang
;
}
export default Dong;

测试下:

确实,打印也是正常的,这就是解决闭包陷阱的第二种方式,通过 useRef 避免直接对 state 的引用,从而避免闭包问题。

这段逻辑用到了多个 hook,可以封装成个自定义 hook:

function useInterval(fn, time) {
const ref = useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
useEffect(() => {
setInterval(() => ref.current(), time);
}, []);
}

然后组件代码就可以简化了:

function Dong() {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(count + 1);
}, 500);
useInterval(() => {
console.log(count);
}, 500);
return
guang
;
}

这样我们就用 useRef 的方式解决了闭包陷阱问题。

总结

上篇文章我们通过把依赖的 state 添加到 deps 数组中的方式,使得每次 state 变了就执行新的函数,引用新的 state,从而解决了闭包陷阱问题。

这种方式用在定时器上是不合适的,因为定时器一旦被重置和重新计时,那计时就不准确了。

所以我们才用了避免闭包陷阱的第二种方式:使用 useRef。

useRef 能解决闭包陷阱的原因是 useEffect 等 hook 里不直接引用 state,而是引用 ref.current,这样后面只要修改了 ref 中的值,这里取出来的就是最新的。

然后我们把这段逻辑封装成了个自定义 hook,这样可以方便复用。

解决 hooks 的闭包陷阱有两种方式:

  • 设置依赖的 state 到 deps 数组中并添加清理函数。
  • 不直接引用 state,把 state 放到 useRef 创建的 ref 对象中再引用。

处理定时器的时候,为保证计时的准确,最好使用 useRef 的方式,其余情况两种都可以。

网站栏目:从根上理解ReactHooks的闭包陷阱(续集)
链接URL:http://www.stwzsj.com/qtweb/news42/5442.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联