下面是一个简单的函数组件,有两个按钮:“alert”、“add”。
如果先点击“alert”按钮,再点击一次“add”按钮,那么弹窗框中的值和页面中展示value
分别是什么?
const FunctionComponent = () => {
const [value, setValue] = useState(1);
const log = () => {
setTimeout(() => {
alert(value);
}, 3000);
};
return (
<div>
<p>FunctionComponent</p>
<div>value: {value}</div>
<button onClick={log}>alert</button>
<button onClick={() => setValue(value + 1)}>add</button>
</div>
);
};
弹出的值是 1,页面显示的值是 2
我们发现弹出的值和当前页面显示的值不相同。
换句话说:log 方法内的 value 和点击动作触发那一刻的 value 相同,value 的后续变化不会对 log 方法内的 value 造成影响。
这种现象被称为“闭包陷阱”或者被叫做“Capture Value” :函数式组件每次render 都会生产一个新的 log 函数,这个新的 log 函数会产生一个在当前这个阶段 value 值的闭包。
上面例子 “闭包陷阱” 的分析:
- 初始次渲染,生成一个 log 函数(value = 1)
- value 为 1 时,点击 alert 按钮执行 log 函数(value = 1)
- 点击按钮增加 value,比如 value 增加到 6,组件 render ,生成一个新的 log 函数(value = 6)
- 计时器触发,log 函数(value = 1)弹出闭包内的 value 为 1
如何让弹窗中展示最新的value值呢?
使用 useRef 解决闭包陷阱的问题
const FunctionComponent = () => {
const [value, setValue] = useState(1);
const countRef = useRef(value);
const log = () => {
setTimeout(() => {
alert(countRef.current);
}, 3000);
};
useEffect(() => {
countRef.current = value;
}, [value]);
return (
<div>
<p>FunctionComponent</p>
<div>value: {value}</div>
<button onClick={log}>alert</button>
<button onClick={() => setValue(value + 1)}>add</button>
</div>
);
};
useRef 每次 render 时都会返回同一个引用类型的对象,我们设置值和读取值都在这个对象上处理,这样就能获取到最新的 value 值了。