React同一函数内如何基于一个状态变更更新另一个关联状态
React 点赞按钮状态同步问题
问题场景
需要实现点击即可切换点赞/取消点赞状态的按钮,初始实现代码如下:
import React, {useState, useEffect} from 'react'; const Test = ( {numar}) => { const [likeStatus, setLikeStatus] = useState(true); const [likeNumber, setLikeNumber] = useState(100); const onLikeHandler = () => { setLikeStatus(prevState => !prevState); if(likeStatus){ setLikeNumber(prevState=> prevState +1) } else { setLikeNumber(prevState=>prevState-1); } } console.log(likeStatus); console.log(likeNumber); return <button className={`like ${likeStatus ? 'liked' : ""}`} onClick={onLikeHandler}>{`Like | ${ likeNumber}`}</button> } export default Test;
遇到的问题:无法让点赞数更新逻辑拿到点赞状态更新后的最新值,尝试用useEffect监听likeStatus变化触发计数更新时,页面刷新会出现取消点赞逻辑意外触发两次的问题。
问题根因
- React 的状态更新是异步批量执行的,在同一个同步点击回调的执行上下文里,调用
setLikeStatus后读取到的likeStatus还是当前渲染周期的闭包旧值,不是更新后的新值,会导致计数判断和实际状态错位。 - 直接用
useEffect监听likeStatus的方案,会在组件首次挂载完成后就触发一次执行,加上React 18严格模式下开发环境会双调用挂载阶段的副作用逻辑,就会出现首次加载时逻辑误触发两次的问题。
解决方案
方案1:事件回调内直接计算目标状态(最推荐)
不需要等待第一个状态更新完成,在点击事件里直接算出本次操作后的目标点赞状态,基于这个计算值同步更新两个状态即可,完全避开闭包陈旧值问题,没有多余的副作用触发:
import React, {useState} from 'react'; const Test = ( {numar}) => { // 初始值根据实际业务调整:已点赞为true,未点赞为false const [likeStatus, setLikeStatus] = useState(true); const [likeNumber, setLikeNumber] = useState(100); const onLikeHandler = () => { // 直接计算本次点击后的最终状态,无需等待state更新 const nextLikeStatus = !likeStatus; setLikeStatus(nextLikeStatus); // 基于计算好的目标状态更新点赞数 setLikeNumber(prev => nextLikeStatus ? prev + 1 : prev - 1); } return ( <button className={`like ${likeStatus ? 'liked' : ""}`} onClick={onLikeHandler} > {`Like | ${likeNumber}`} </button> ) } export default Test;
小提示:两个同上下文触发的setState会被React自动批量合并,只会触发一次重渲染,没有性能问题。
方案2:useEffect 监听场景适配
如果必须基于likeStatus的变化执行额外逻辑(比如状态变更后调用后端上报接口),可以通过ref标记挂载状态,跳过首次渲染的无效触发:
import React, {useState, useEffect, useRef} from 'react'; const Test = ( {numar}) => { const [likeStatus, setLikeStatus] = useState(true); const [likeNumber, setLikeNumber] = useState(100); // 标记组件是否完成首次挂载 const isMountedRef = useRef(false); const onLikeHandler = () => { setLikeStatus(prevState => !prevState); } useEffect(() => { // 首次挂载时跳过逻辑执行 if (!isMountedRef.current) { isMountedRef.current = true; return; } // 此处可拿到最新的likeStatus,执行计数更新、接口调用等逻辑 setLikeNumber(prev => likeStatus ? prev + 1 : prev -1); }, [likeStatus]) return ( <button className={`like ${likeStatus ? 'liked' : ""}`} onClick={onLikeHandler} > {`Like | ${likeNumber}`} </button> ) } export default Test;
注意:React 18 开发环境严格模式的双调用机制是为了检测不规范的副作用逻辑,仅在开发环境生效,生产环境不会出现重复触发问题,不需要为了规避该现象关闭严格模式。
内容的提问来源于stack exchange,提问作者FlorinC
相关产品推荐
相关产品推荐

