React函数组件中的闭包陷阱
在React中有 Class & Function 两种类型的组件,在使用上会有一些区别,典型的就是函数组件是有闭包状态,没有上下文的this,而Class 类型的组件具有指向上下文的this。
1、问题场景
看一个场景。在一个页面中,有两个按钮,一个按钮点击会自增count变量的值,另一个alert按钮点击之后会在3s之后弹出count的值。
正常的话使用函数组件来实现这个功能,先点击若干次自增count的按钮,再点击alert按钮(此时此刻count的有个值val1),然后接着继续点击自增按钮,3s之后alert函数起作用了(这时候count的值为val2)出来的count的值是val还是val2呢?如果相同的功能使用Class组件会有什么不一样么。?
我们直接看下不同实现下的相关代码和效果。
2、使用函数组件
import React, { useState } from "react";
const Demo1 = () => {
let [count, setCount] = useState(0);
const addCount = () => {
setCount(count + 1);
};
const alertCount = () => {
setTimeout(() => {
alert(count);
}, 3000);
};
return (
<div>
<button onClick={addCount}>add Count</button>
<button onClick={alertCount}>alertCount</button>
<div>count: {count}</div>
</div>
);
};
export default Demo1;
很简答的一个小功能,看下操作之后的效果 可以看到,点击alert 按钮的那一瞬间count的值为5,然后接着点击add count 按钮使count自增,3s之后count的值为16,弹出来的为5。再来看一下使用Class组件实现相同的功能:
3、class实现
import React from "react";
class Demo3 extends React.Component {
state = {
count: 0
};
addCount = () => {
this.setState({
count: this.state.count + 1
});
};
alertCount = () => {
// 记录点击alert按钮那一时刻count的值
const recordCount = this.state.count;
setTimeout(() => {
// 返回的始终是 this对象上的状态
alert(
`count: ${this.state.count}`
);
}, 3000);
};
render() {
return (
<div>
<button onClick={this.addCount}>add Count</button>
<button onClick={this.alertCount}>alertCount</button>
<div>count: {this.state.count}</div>
</div>
);
}
}
export default Demo3;
看一下效果如何
可以看到相同的操作,但在Class组件中alert出来的是15 而不是5。
4、原因
这是因为在函数组件中,每次点击add count按钮都会使得count自增一次=>导致Demo1函数重新执行一遍,形成一个新的闭包。每个闭包中的count值是相互独立的。点击alert按钮的这一瞬间形成的闭包中count的值为5,3s之后弹出来的这个值自然也为5。后面再次自增,形成的是新的闭包。互不干扰。
而在Class组件中由于都是在this的上下文中改变count的值,而不会形成隔离的闭包,因此,虽然点击alert按钮那一时刻count的值为5,但是随着后面点击add count按钮,count值不断自增。修改的都是this上面的值,3s后弹出的也就是count的最新值了。
在业务开发中,可能常常需要的是count的最新值,但其实在编程中,函数组件的行为才是正常的,类组件的行为反而有些’迷惑‘。正常情况下,本应该alert出我点击弹出按钮那一刻count的值,而不是之后的值。
额外说一下,我们在使用Function组件开发的时候,使用useState中的 setXXX如果期望使用当前XXX最新的值使用的是回调函数,会将当前XXX的值传入,否则返回的也是之前时刻闭包中的值而非最新的值,也是相同的道理。
为了加深对函数组件的闭包的理解,这里辅助再配置一个例子,如果不使用useState来改变count的值,而只是在函数中正常设置一个count的值得话,那又该如何呢? 代码如下
import React from "react";
const Demo4 = () => {
let count = 0;
const addCount = () => {
count = count + 1;
console.log(count);
};
const alertCount = () => {
setTimeout(() => {
alert(count);
}, 3000);
};
return (
<div>
<p>函数组件中的普通变量</p>
<button onClick={addCount}>add Count</button>
<button onClick={alertCount}>alertCount</button>
<div>count: 打开console查看count的值</div>
</div>
);
};
export default Demo4;
答案是3s之后alert出的是count的最新值,因为不使用usestate,该函数组件始终不会去更新,始终都是一个闭包。因此返回的值是最新的count的值
5、补充
有的时候在不同的业务场景下,需要即获得点击alert按钮那一时刻下的count值也需要获取count的最新值,该怎么办呢?
Fcuntion组件中可以使用useRef这个hook实现。代码如下:
import React, { useState, useRef } from "react";
const Demo2 = () => {
let [count, setCount] = useState(0);
// useRef 返回的是同一个对象,类似 {current:''}
const countRef = useRef();
countRef.current = count;
const addCount = () => {
setCount(count + 1);
};
const alertCount = () => {
setTimeout(() => {
alert(countRef.current);
}, 3000);
};
return (
<div>
<button onClick={addCount}>add Count</button>
<button onClick={alertCount}>alertCount</button>
<div>count: {count}</div>
</div>
);
};
export default Demo2;
效果如下: 原因是使用了useRef这个hook每次返回的都是类似{current:""}这么一个相同的对象。在3s之后自然就会获取最新count的值了 在Class组件中只需要在alertCount函数中稍作修改,用一个变量保存点击alert按钮时刻 count的值即可了
alertCount = () => {
// 记录点击alert按钮那一时刻count的值
const recordCount = this.state.count;
setTimeout(() => {
// 返回的始终是 this对象上的状态
alert(
`点击alert按钮那一时刻的count: ${recordCount}, 最新count: ${this.state.count}`
);
}, 3000);
};
效果如下:
也可以直接去codesandbox中查看代码,调试一下:
转载自:https://juejin.cn/post/7255592202241196087