NextJS中ContextAPI+useReducer+localStorage状态持久化问题修复
问题根源
持久化逻辑失效核心出在jobReducer.js里的initializer初始化函数,存在两个致命问题:
- 没有对从localStorage读取到的值做结构校验,只要是truthy值就直接返回。之前的代码bug会往localStorage写入空数组
[],而空数组在JS里属于truthy值,会直接被当做初始state返回,完全不符合你预期的{jobArray: []}对象结构。 - 初始化函数的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
相关产品推荐
相关产品推荐

