Next.js中如何基于localStorage初始化购物车状态?
Next.js购物车状态与localStorage同步的正确姿势
问题根源
你遇到的刷新后localStorage被清空的问题,本质是Next.js的服务端渲染特性导致的:_app.js(或根组件)会在服务端先执行一次,而服务端没有localStorage,所以useState初始化为空数组;客户端hydrate后,你的同步useEffect会先把空数组写入localStorage,覆盖了之前存储的数据,后续再用useEffect读取时已经晚了。
方案一:在useState初始化时直接读取localStorage(适配Pages Router)
修改_app.js里的useState初始化逻辑,先判断是否在客户端环境,再读取localStorage:
import { useState, useEffect, createContext, useContext } from 'react'; const CartContext = createContext(); function MyApp({ Component, pageProps }) { // 初始化时优先读取localStorage(仅客户端执行) const [cart, setCart] = useState(() => { if (typeof window !== 'undefined') { const savedCart = localStorage.getItem('cart'); return savedCart ? JSON.parse(savedCart) : []; } // 服务端返回空数组,客户端hydrate时会替换为localStorage的值 return []; }); // 仅当cart变化时同步到localStorage useEffect(() => { if (typeof window !== 'undefined') { localStorage.setItem('cart', JSON.stringify(cart)); } }, [cart]); return ( <CartContext.Provider value={{ cart, setCart }}> <Component {...pageProps} /> </CartContext.Provider> ); } export default MyApp;
这个方法的核心是把读取localStorage的逻辑放到useState的初始化函数里,客户端初始化时直接拿到存储的数据,避免了先写空数组再读的顺序问题。
方案二:将购物车状态移到客户端组件(推荐Next.js 13+ App Router)
Next.js 13+的App Router中组件默认是服务端组件,我们可以创建一个带'use client'指令的客户端组件来托管购物车状态,彻底避开服务端执行的问题:
// components/CartProvider.jsx 'use client'; import { createContext, useContext, useState, useEffect } from 'react'; const CartContext = createContext(); export function CartProvider({ children }) { // 客户端环境下直接读取localStorage初始化 const [cart, setCart] = useState(() => { const savedCart = localStorage.getItem('cart'); return savedCart ? JSON.parse(savedCart) : []; }); // 同步cart变化到localStorage useEffect(() => { localStorage.setItem('cart', JSON.stringify(cart)); }, [cart]); return ( <CartContext.Provider value={{ cart, setCart }}> {children} </CartContext.Provider> ); } // 自定义Hook方便组件调用 export function useCart() { return useContext(CartContext); }
然后在根布局layout.js里引入并包裹子组件:
// app/layout.js import { CartProvider } from '@/components/CartProvider'; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body> <CartProvider>{children}</CartProvider> </body> </html> ); }
这种方式更符合Next.js新架构的设计,把客户端状态逻辑完全隔离在客户端组件中,不会有服务端与客户端的状态冲突问题。
为什么之前的useEffect恢复无效?
你之前尝试用useEffect恢复状态,但因为执行顺序问题:客户端hydrate后,先执行useState初始化为空数组,此时触发useEffect同步空数组到localStorage(把原有数据覆盖),之后再执行恢复的useEffect时,localStorage已经是空的了,自然无效。
内容的提问来源于stack exchange,提问作者Mason Randall
相关产品推荐
相关产品推荐

