React State已有值但调用deleteTag时显示为空数组问题
问题场景
组件通过useEffect成功获取标签数据并更新tags状态,但子组件调用deleteTag时,打印的tags却是初始的空数组。
代码复现
const CourseTags = () => { const [tags, setTags] = useState([]) useEffect(() => { const jwt = sessionStorage.getItem("jwt") // GET tags fetch(`${process.env.NEXT_PUBLIC_HOST}/api/tags`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${jwt}` } }) .then(res => res.json()) .then(data => { setTags(data) console.log(data) // 正常显示标签数组 [ {}, {}, ... ] }) .catch(err => { console.log(err) }) }, []) const deleteTag = (tagId) => { if (confirm("Delete Tag?")) { console.log(tags, tagId) // tags 为空数组 [] } } return ( <Fragment> <TagsList tagsList={tags} handleDeleteTag={(tagId) => deleteTag(tagId)} /> </Fragment> ) }
问题原因
这是闭包陷阱导致的:
deleteTag函数在组件首次渲染时创建,它捕获了当时的tags状态(空数组)。- 后续
useEffect获取数据更新tags后,组件会重新渲染并生成新的deleteTag函数,但如果子组件TagsList没有同步更新接收的handleDeleteTag回调(比如用了React.memo但未将该回调加入依赖项),或者因为confirm是异步操作,最终调用的还是首次渲染时的旧deleteTag,所以里面的tags始终是空数组。
解决方案
方案1:使用useRef保存最新的tags状态
用useRef跟踪最新的tags值,不管什么时候访问tagsRef.current,拿到的都是最新状态:
const CourseTags = () => { const [tags, setTags] = useState([]) const tagsRef = useRef(tags) // 每次tags更新时同步更新ref useEffect(() => { tagsRef.current = tags }, [tags]) useEffect(() => { const jwt = sessionStorage.getItem("jwt") fetch(`${process.env.NEXT_PUBLIC_HOST}/api/tags`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${jwt}` } }) .then(res => res.json()) .then(data => { setTags(data) }) .catch(err => { console.log(err) }) }, []) const deleteTag = (tagId) => { if (confirm("Delete Tag?")) { // 访问ref.current获取最新tags console.log(tagsRef.current, tagId) } } return ( <Fragment> <TagsList tagsList={tags} handleDeleteTag={(tagId) => deleteTag(tagId)} /> </Fragment> ) }
方案2:用useCallback包裹deleteTag并添加依赖
通过useCallback确保deleteTag函数在tags变化时重新生成,同时如果子组件用了React.memo,要把handleDeleteTag加入依赖数组:
const CourseTags = () => { const [tags, setTags] = useState([]) useEffect(() => { const jwt = sessionStorage.getItem("jwt") fetch(`${process.env.NEXT_PUBLIC_HOST}/api/tags`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${jwt}` } }) .then(res => res.json()) .then(data => { setTags(data) }) .catch(err => { console.log(err) }) }, []) // 用useCallback包裹,依赖tags const deleteTag = useCallback((tagId) => { if (confirm("Delete Tag?")) { console.log(tags, tagId) } }, [tags]) return ( <Fragment> <TagsList tagsList={tags} handleDeleteTag={deleteTag} /> </Fragment> ) }
如果TagsList是用React.memo包裹的子组件,需要确保它的props依赖正确:
const TagsList = React.memo(({ tagsList, handleDeleteTag }) => { // 子组件逻辑 }, (prevProps, nextProps) => { // 浅比较props,确保handleDeleteTag变化时重新渲染 return prevProps.tagsList === nextProps.tagsList && prevProps.handleDeleteTag === nextProps.handleDeleteTag })
方案3:直接在deleteTag中通过setTags的函数式更新获取最新状态(如果只是要更新tags)
如果你的目的是删除标签后更新状态,可以直接用函数式更新获取最新的tags,不需要在deleteTag里直接访问tags:
const deleteTag = (tagId) => { if (confirm("Delete Tag?")) { // 函数式更新,获取最新tags状态 setTags(prevTags => prevTags.filter(tag => tag._id !== tagId)) } }
内容的提问来源于stack exchange,提问作者osomaher
相关产品推荐
相关产品推荐

