在上一期的《「React实战面试题」useEffect依赖数组的常见陷阱 》讨论中,我们探讨了useEffect依赖数组的陷阱问题。感谢大家在评论区的积极参与!正确答案确实是选项A、C和D的组合:
详细解析我放在了文末,现在让我们进入今天的新挑战!
你正在为电商平台开发商品详情页,产品经理要求添加一个"点赞"功能。用户可以通过点击按钮为商品点赞,实时看到点赞数的变化。
这听起来是个很简单的需求,你信心满满地开始编码。
function ProductDetail({ product }) {
const [likes, setLikes] = useState(product.initialLikes);
const handleLike = () => {
setLikes(likes + 1);
};
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p className="price">¥{product.price}</p>
<button
className="like-button"
onClick={handleLike}
>
👍 点赞 ({likes})
</button>
</div>
);
}
功能开发完成,你进行了基本测试:
一切看起来完美,代码顺利上线。
上线几天后,用户开始反馈问题:
你开始怀疑是不是网络问题,但仔细观察发现这是一个纯前端的状态管理问题。
你可以用以下方式重现这个问题:
// 在控制台快速执行以下代码来模拟快速点击
const button = document.querySelector('.like-button');
for(let i = 0; i < 5; i++) {
setTimeout(() => button.click(), i * 10); // 每10ms点击一次
}
结果让人困惑:执行了5次点击,但计数器可能只增加了1-3次。
在深入分析之前,让我们思考几个关键问题:
当你调用setLikes(likes + 1)
时,likes
的值是什么时候确定的?是调用时的值,还是更新后的值?
如果在很短时间内多次调用setLikes
,React会如何处理这些更新?
每次渲染时,handleLike
函数中的likes
变量捕获的是哪个时刻的值?
让我们通过一个简化的实验来理解问题:
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log('点击时的count值:', count);
setCount(count + 1);
};
// 如果快速点击多次会发生什么?
return<button onClick={handleClick}>点击: {count}</button>;
}
基于以上场景和分析,请回答以下问题:
快速点击时点赞数不准确的根本原因是什么?
A. React的状态更新是异步的,存在延迟 B. 多个状态更新基于了相同的旧状态值 C. JavaScript事件处理存在防抖机制 D. 浏览器的渲染性能限制
哪种方式可以正确解决这个问题?
A. 使用函数式更新:setLikes(prevLikes => prevLikes + 1)
B. 添加防抖,限制点击频率
C. 使用useRef存储最新的likes值
D. 将状态更新包装在setTimeout中
在实际项目中,还需要考虑哪些因素?
A. 乐观更新与服务器同步 B. 用户体验的即时反馈 C. 网络请求失败的回滚机制 D. 以上都需要考虑
除了技术实现,这个问题还引发了一些值得思考的设计问题:
请在评论区分享你的答案和思考:
这类问题在以下场景中也很常见:
通过这个问题,我们可以学到:
问题核心:fetchUserProfile
函数在每次渲染时都是新的引用,导致useEffect无限执行。
标准解决方案:
// 方案1:使用useCallback缓存函数
const fetchUserProfile = useCallback(() => {
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(setProfile);
}, [userId]);
useEffect(() => {
fetchUserProfile();
}, [fetchUserProfile]);
// 方案2:函数内联(推荐简单场景)
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(setProfile);
}, [userId]);
为什么选项B错误:移除userId依赖会导致effect无法响应userId变化,违反了exhaustive-deps规则。
🎯 下期预告:我们将探讨React中的内存泄漏问题,特别是useEffect清理函数的正确使用。