You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 14:15:53