下面是一个简单的函数组件,有两个按钮:“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 值了。
正文结束
Ctrl + Enter