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

NextJS中ContextAPI+useReducer+localStorage状态持久化问题修复

问题根源

持久化逻辑失效核心出在jobReducer.js里的initializer初始化函数,存在两个致命问题:

  1. 没有对从localStorage读取到的值做结构校验,只要是truthy值就直接返回。之前的代码bug会往localStorage写入空数组[],而空数组在JS里属于truthy值,会直接被当做初始state返回,完全不符合你预期的{jobArray: []}对象结构。
  2. 初始化函数的fallback逻辑错误,一旦读取到错误结构的缓存值,永远不会回退到正确的初始state,还会在组件首次挂载触发的useEffect里把错误的空数组重新写回localStorage,直接覆盖之前正确的收藏数据,造成刷新就清空的现象。

另外还有几个潜在逻辑隐患一并修复:

  • reducer外提前计算新数组再传参的写法冗余,状态更新逻辑应该统一收敛在reducer内部
  • Favorites列表用index做key容易引发渲染错误,应该用职位唯一id作为key
  • 缺少localStorage读取异常的容错,一旦localStorage里存了非法JSON字符串,JSON.parse会直接报错导致页面崩溃

修复步骤

1. 修正jobReducer.js的初始化和reducer逻辑

替换原有代码为:

export const initialState = {
  jobArray: []
}

// 初始化函数,保证永远返回符合结构的state
export const initializer = (initialValue) => {
  try {
    const cachedData = localStorage.getItem("favorited-jobs");
    // 无缓存直接返回初始值
    if (!cachedData) return initialValue;
    const parsedData = JSON.parse(cachedData);
    // 校验缓存结构是否合法,必须是对象且包含jobArray数组
    if (parsedData && Array.isArray(parsedData.jobArray)) {
      return parsedData;
    }
    // 结构不合法返回初始值
    return initialValue;
  } catch (err) {
    // JSON解析出错直接返回初始值
    return initialValue;
  }
}

const jobReducer = (state,action) => {
  const {type,payload} = action;
  switch(type) {
    case "ADD_TO_FAVORITE":
      // 去重判断,避免重复添加
      const isExisted = state.jobArray.some(job => job.id === payload.job.id);
      if (isExisted) return state;
      return {
        ...state,
        jobArray: [...state.jobArray, payload.job]
      }
    case "REMOVE_FROM_FAVORITE":
      return {
        ...state,
        jobArray: state.jobArray.filter(job => job.id !== payload.job.id)
      }
    default:
      return state;
  }
}

export default jobReducer

2. 简化JobContext.js里的状态更新逻辑

不需要在dispatch前提前拼接数组,把更新逻辑全交给reducer处理,替换对应代码:

import { createContext, useReducer, useContext, useEffect } from "react";
import jobReducer, { initialState, initializer } from "./jobReducer";

const JobContext = createContext(initialState)

export const JobProvider = ({children}) => {
  const [state,dispatch] = useReducer(jobReducer, initialState, initializer);

  useEffect(() => {
    localStorage.setItem("favorited-jobs", JSON.stringify(state));
  }, [state]);

  const addToFavorite = (job) => {
    dispatch({
      type: "ADD_TO_FAVORITE",
      payload: { job }
    })
  }

  const removeFavorite = (job) => {
    dispatch({
      type: "REMOVE_FROM_FAVORITE",
      payload: { job }
    })
  }

  const value = {
    jobArray: state.jobArray,
    addToFavorite, 
    removeFavorite,
  }

  return <JobContext.Provider value={value}>{children}</JobContext.Provider>
}

// 自定义hook
const useJob = () => {
  const context = useContext(JobContext)
  if(context === undefined){
    throw new Error("useJob must be used within JobProvider")
  }
  return context
}

export default useJob 

3. 修复Favorites.js的key问题

不要用数组index作为key,替换为职位id:

import JobCard from "../components/JobCard";
import useJob from "../context/JobContext";

const Favorites = () => {
  const {jobArray} = useJob();
  return(
      <div className="favorite_list">
        {jobArray.map((job) => (
          <JobCard key={job.id} {...job} />
        ))}
    </div>
  )
}

export default Favorites

验证操作

修复代码后,打开浏览器开发者工具,手动清除LocalStorage下的favorited-jobs缓存,再重新测试功能即可。
修复后效果:

  • 新增收藏时会自动去重,不会重复添加同一条职位
  • 刷新页面后收藏数据会正常从localStorage读取,不会被重置
  • 就算localStorage被写入非法值,也会自动回退到初始空状态,不会报错或覆盖正确数据
  • 列表渲染不会出现因为index作为key导致的状态错乱问题

内容的提问来源于stack exchange,提问作者Chris Wu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:45:40