Next.js中useCity自定义Hook跨页面状态不更新问题排查与解决
问题原因与解决方案
问题根源
你写的useCity自定义Hook本质是状态逻辑复用,而非状态共享。每个组件(包括不同页面的组件)调用useCity时,都会独立执行一次Hook内部的useState,生成完全隔离的状态实例。也就是说,/changeCity页面修改的是该页面自己的city状态,/city页面的city状态是另一套独立的变量,两者毫无关联,自然不会同步更新。
正确解决方案
下面是几种适用于Next.js项目的可行方案:
方案1:React Context API(原生方案,无额外依赖)
通过Context实现全局状态共享,所有页面共享同一状态实例:
- 创建Context文件:
contexts/CityContext.ts
import { createContext, useContext, useState, ReactNode } from 'react'; type CityContextType = { city: string | null; setCity: (city: string | null) => void; }; const CityContext = createContext<CityContextType | undefined>(undefined); export function CityProvider({ children }: { children: ReactNode }) { const [city, setCity] = useState<string | null>(null); return ( <CityContext.Provider value={{ city, setCity }}> {children} </CityContext.Provider> ); } export function useCity() { const context = useContext(CityContext); if (!context) { throw new Error('useCity must be used within a CityProvider'); } return context; }
- 在
_app.tsx中包裹所有页面:
import { AppProps } from 'next/app'; import { CityProvider } from '../contexts/CityContext'; export default function App({ Component, pageProps }: AppProps) { return ( <CityProvider> <Component {...pageProps} /> </CityProvider> ); }
- 页面中直接调用
useCity即可共享状态:
// /pages/changeCity.tsx import { useCity } from '../contexts/CityContext'; import { useEffect } from 'react'; export default function ChangeCity() { const { setCity } = useCity(); useEffect(() => { setCity('Beijing'); }, [setCity]); return <div>Changed city</div>; }
// /pages/city.tsx import { useCity } from '../contexts/CityContext'; import { useEffect } from 'react'; export default function City() { const { city } = useCity(); useEffect(() => { console.log('Current city:', city); }, [city]); return <div>Current city: {city}</div>; }
方案2:状态管理库(比如Zustand,轻量易用)
如果项目需要更复杂的状态管理,Zustand是个不错的选择:
- 安装Zustand:
npm install zustand
- 创建store文件:
stores/cityStore.ts
import { create } from 'zustand'; type CityStore = { city: string | null; setCity: (city: string | null) => void; }; export const useCityStore = create<CityStore>((set) => ({ city: null, setCity: (newCity) => set({ city: newCity }), }));
- 页面中使用:
// /pages/changeCity.tsx import { useCityStore } from '../stores/cityStore'; import { useEffect } from 'react'; export default function ChangeCity() { const setCity = useCityStore((state) => state.setCity); useEffect(() => { setCity('Shanghai'); }, [setCity]); return <div>Changed city</div>; }
// /pages/city.tsx import { useCityStore } from '../stores/cityStore'; import { useEffect } from 'react'; export default function City() { const city = useCityStore((state) => state.city); useEffect(() => { console.log('Current city:', city); }, [city]); return <div>Current city: {city}</div>; }
方案3:本地存储(结合useState,适合需要持久化的场景)
如果需要状态在页面刷新后依然保留,可以结合localStorage:
修改useCity.ts:
import { useState, useEffect } from 'react'; export function useCity() { // 初始化时从localStorage读取 const [city, setCity] = useState<string | null>(() => { if (typeof window !== 'undefined') { return localStorage.getItem('city') || null; } return null; }); // 修改状态时同步到localStorage useEffect(() => { if (city !== null) { localStorage.setItem('city', city); } else { localStorage.removeItem('city'); } }, [city]); // 监听其他页面修改localStorage的事件(跨标签页同步) useEffect(() => { function handleStorageChange(e: StorageEvent) { if (e.key === 'city') { setCity(e.newValue || null); } } window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, []); return { city, setCity }; }
这种方案下,页面跳转后会读取localStorage的最新值,跨标签页也能同步状态,但注意Next.js服务端渲染时要判断window是否存在。
内容的提问来源于stack exchange,提问作者Sergey Kamalian
相关产品推荐
相关产品推荐

