面试题答案
一键面试闭包陷阱在React Hooks中的表现形式
在React函数组件中使用Hooks时,闭包陷阱通常表现为:当函数捕获了某个状态值,但在后续异步操作或回调执行时,该状态值并没有更新到最新,仍然是闭包创建时的值。例如,在setTimeout
回调函数或事件处理函数等异步场景中,访问到的状态值不是最新的渲染状态。
产生原因
React函数组件每次渲染都是一个全新的闭包环境。当在组件内部定义函数并捕获了某些状态(比如useState
定义的状态),这些函数会记住捕获时状态的值。如果在函数执行之前,组件重新渲染,状态值更新了,但之前捕获状态的函数依然使用的是旧值,因为它保存的是闭包创建时的值,而不是最新渲染的值。
利用Hooks特性解决闭包陷阱同时优化性能的方法及代码示例
-
使用
useCallback
和useRef
import React, { useState, useCallback, useRef } from'react'; const App = () => { const [count, setCount] = useState(0); const countRef = useRef(count); const handleClick = useCallback(() => { setTimeout(() => { // 使用ref来获取最新的count值 console.log('最新的count:', countRef.current); }, 1000); }, []); const increment = () => { setCount(count + 1); // 更新ref的值 countRef.current = count + 1; }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={handleClick}>Log Count After 1 Second</button> </div> ); }; export default App;
解释:
useRef
创建一个可变的引用,countRef.current
可以在组件重新渲染时保持不变。useCallback
返回一个记忆化的回调函数,依赖数组为空表示回调函数不会因为组件重新渲染而改变。在increment
函数中,更新count
状态的同时更新countRef.current
,这样在handleClick
的setTimeout
回调中通过countRef.current
就能获取到最新的count
值,避免了闭包陷阱。同时,useCallback
和useRef
的使用在一定程度上优化了性能,useCallback
防止了不必要的函数重新创建,useRef
避免了因获取最新状态而导致的额外渲染。
-
使用
useEffect
来更新回调函数的依赖import React, { useState, useEffect } from'react'; const App = () => { const [count, setCount] = useState(0); let handleClick; useEffect(() => { handleClick = () => { setTimeout(() => { console.log('最新的count:', count); }, 1000); }; }, [count]); const increment = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={handleClick}>Log Count After 1 Second</button> </div> ); }; export default App;
解释:
useEffect
依赖数组中包含count
,每当count
更新,useEffect
中的回调函数会重新执行,从而重新定义handleClick
函数。这样handleClick
中的count
始终是最新的,解决了闭包陷阱。通过依赖数组精准控制handleClick
函数的更新时机,优化了性能,避免了不必要的函数更新。