NextJS 13:如何从LocalStorage等获取数据构建Context API初始状态
针对LocalStorage/SessionStorage的解决方案
直接在"use client"组件中调用localStorage会返回undefined,因为Next.js 13的客户端组件仍会经过服务端渲染初始化阶段,此时window对象不存在;用useEffect延迟更新状态又会导致页面加载时的闪烁问题。可以通过以下两种方式解决:
方式1:使用useSyncExternalStore(推荐)
这个API可以直接在客户端初始化时读取LocalStorage值,同时订阅存储变化,避免闪烁且支持多标签页同步:
'use client'; import { createContext, useContext, useSyncExternalStore, useState } from 'react'; const AppContext = createContext<{ user: string | null; setUser: (user: string | null) => void; }>({ user: null, setUser: () => {} }); // 订阅LocalStorage变化 function subscribeStorage(key: string, callback: () => void) { window.addEventListener('storage', callback); return () => window.removeEventListener('storage', callback); } // 获取LocalStorage值(服务端返回默认值) function getStorageValue(key: string) { if (typeof window === 'undefined') return null; return localStorage.getItem(key); } export function AppProvider({ children }: { children: React.ReactNode }) { // 同步LocalStorage值到组件状态 const storedValue = useSyncExternalStore( (cb) => subscribeStorage('user', cb), () => getStorageValue('user'), () => null ); const [user, setUser] = useState<string | null>(storedValue); const updateUser = (newUser: string | null) => { setUser(newUser); newUser ? localStorage.setItem('user', newUser) : localStorage.removeItem('user'); }; return ( <AppContext.Provider value={{ user, setUser: updateUser }}> {children} </AppContext.Provider> ); } export function useAppContext() { return useContext(AppContext); }
方式2:懒初始化useState
如果不需要多标签页同步,也可以用懒初始化函数直接在客户端读取LocalStorage,避免服务端报错:
'use client'; import { createContext, useContext, useState } from 'react'; const AppContext = createContext<{ user: string | null; setUser: (user: string | null) => void; }>({ user: null, setUser: () => {} }); export function AppProvider({ children }: { children: React.ReactNode }) { // 懒初始化,仅在客户端执行 const [user, setUser] = useState<string | null>(() => { if (typeof window === 'undefined') return null; return localStorage.getItem('user'); }); const updateUser = (newUser: string | null) => { setUser(newUser); newUser ? localStorage.setItem('user', newUser) : localStorage.removeItem('user'); }; return ( <AppContext.Provider value={{ user, setUser: updateUser }}> {children} </AppContext.Provider> ); } export function useAppContext() { return useContext(AppContext); }
针对Cookies的解决方案
Cookies可以在服务端直接读取,因此可以从服务端组件传递初始值到客户端Context Provider,彻底避免闪烁和undefined问题:
步骤1:服务端读取Cookies(比如layout.tsx)
// app/layout.tsx(服务端组件,无需"use client") import { cookies } from 'next/headers'; import { AppProvider } from './app-provider'; export default function RootLayout({ children }: { children: React.ReactNode }) { const cookieStore = cookies(); const initialUser = cookieStore.get('user')?.value || null; return ( <html lang="zh-CN"> <body> <AppProvider initialUser={initialUser}>{children}</AppProvider> </body> </html> ); }
步骤2:客户端Context接收初始值
'use client'; import { createContext, useContext, useState } from 'react'; const AppContext = createContext<{ user: string | null; setUser: (user: string | null) => void; }>({ user: null, setUser: () => {} }); export function AppProvider({ children, initialUser }: { children: React.ReactNode; initialUser: string | null; }) { const [user, setUser] = useState<string | null>(initialUser); const updateUser = (newUser: string | null) => { setUser(newUser); // 更新Cookies if (newUser) { document.cookie = `user=${newUser}; path=/; max-age=3600`; } else { document.cookie = `user=; path=/; max-age=0`; } }; return ( <AppContext.Provider value={{ user, setUser: updateUser }}> {children} </AppContext.Provider> ); } export function useAppContext() { return useContext(AppContext); }
内容的提问来源于stack exchange,提问作者Crodic
相关产品推荐
相关产品推荐

