React单次点击更新useState提交Firestore时取空值或旧值问题
React动漫收藏功能Firestore写入异常修复
问题原因
React函数组件的useState状态更新是异步批处理机制:调用setFavani、setImg这类setter方法时,不会立刻修改当前函数执行上下文里的状态变量,所有状态更新会等当前同步代码执行完毕、组件重新渲染后才会生效。
原代码的问题就出在时序上:刚调用完setter就立刻读取favani、img等变量传给addDoc,此时闭包里拿到的还是上一轮渲染留存的旧状态值:
- 第一次点击add按钮时,这些状态的初始值为空,所以Firestore写入空值
- 后续点击时,状态里存的是上一次点击触发更新后的值,所以会写入上一次选中的动漫数据
页面展示能正常更新,是因为重渲染后页面读取的是最新状态值,和addDoc执行时所在的闭包上下文不是同一个环境,所以表现不一致。
修复代码
不需要等状态更新完成再传值,直接用事件回调拿到的anime入参字段写入Firestore即可,原有的状态更新逻辑保留,不影响页面展示:
const favAnime = async (anime) => { // 更新页面展示用的状态 setFavani(anime.title) setImg(anime.image_url) setScore(anime.score) setEpisodes(anime.episodes) setSynopsis(anime.synopsis) setUrl(anime.url) // 直接从入参取最新值写入,避开闭包旧值问题 await addDoc(collection(db, "Users", uid, "fav"), { favani: anime.title, img: anime.image_url, score: anime.score, episodes: anime.episodes, synopsis: anime.synopsis, url: anime.url }) }
避坑提示
- 不要在调用状态setter的同一段同步逻辑里立刻读取状态值,此时拿到的永远是闭包缓存的旧值
- 事件回调中如果能直接拿到最新数据源(比如本次的
anime入参),优先直接使用数据源传值,不要绕一层状态中转,能避免绝大多数闭包时序问题 - 只有后续逻辑确实依赖状态更新后的派生结果时,再使用
useEffect监听状态变化执行对应逻辑,不要滥用effect增加不必要的渲染开销
内容的提问来源于stack exchange,提问作者Deividas
相关产品推荐
相关产品推荐

