为什么自定义Hook中useState存储的items在searchItems函数内为null
问题根因与解决方案
核心问题1:事件绑定写法错误
你在DataList组件中给按钮绑定点击事件的写法存在逻辑错误:
return <button onClick={runSearch('searchTerm')}
以上写法会在组件渲染阶段直接执行runSearch函数,而非点击按钮时触发。此时useEffect中发起的API请求还未返回结果,items自然还是初始的null值。
修复方案
修改为点击时才执行的函数引用形式:
return <button onClick={() => runSearch('searchTerm')}>搜索</button>
核心问题2:Hooks闭包捕获陷阱
就算修复了事件绑定问题,你还是会碰到items为null的情况,本质是React Hooks的闭包捕获特性导致的:
- 首次渲染
DataList时,useStore执行生成初始的store对象,其中的searchItems函数捕获了当前作用域下的初始值items = null - API请求返回后调用
setItems,触发useStore重执行,生成新的store对象和新的searchItems函数,新函数捕获了更新后的items值 - 但你首次渲染时创建的
runSearch函数,引用的始终是首次生成的旧store里的旧searchItems函数,该函数永远只能拿到null的items值
这也是你给searchItems加useCallback也没用的原因:虽然items更新后会生成新的searchItems,但外层始终没用到这个新函数。
最优修复方案
在useStore中用useRef存储items的最新值,避开闭包的影响:
export const useStore = () => { const [items, setItems] = useState(null) // 用ref存储items最新值,不受闭包影响 const itemsRef = useRef(items) // 每次items更新同步到ref useEffect(() => { itemsRef.current = items }, [items]) const setItemsFromApi = async () => { // 调用API拿数据 const data = await fetchApi() setItems(data) } const searchItems = (query) => { // 直接从ref拿最新值,加可选链避免空值报错 return itemsRef.current?.filter(item => item.includes(query)) ?? [] } console.log(items) // 此处能拿到更新后的值是因为每次重渲染都会执行打印,拿到的是当前作用域的最新items return {setItemsFromApi, searchItems} }
其他可选修复方案
如果你不想用useRef,也可以把store加入runSearch的依赖,确保runSearch始终引用最新的searchItems:
const runSearch = useCallback((query) => { store.searchItems(query) }, [store])
该方案虽然能解决问题,但每次items更新都会生成新的runSearch函数,性能不如useRef方案。
内容的提问来源于stack exchange,提问作者D. Messier
相关产品推荐
相关产品推荐

