Next.js使用js-cookie存储数组刷新后数据异常解决方案
问题根因
出现数据错乱、重复的核心原因有三个:
- 序列化与反序列化逻辑不匹配:写入Cookie时使用
JSON.stringify()将数组转为JSON格式字符串,读取时却直接调用split(',')做字符串拆分。JSON序列化后的数组会携带方括号、元素双引号,例如["London","Kyiv","Los Angeles"],直接按逗号分割会把括号、引号混入数组元素,下次写入时又会对错误内容再次做JSON序列化,多次刷新后内容会不断叠加、格式彻底错乱。 - Next.js水合阶段逻辑冲突:
js-cookie是纯浏览器端API,服务端渲染阶段无法访问浏览器Cookie,组件首次在服务端渲染时会直接使用默认城市列表生成HTML,客户端水合完成后useEffect触发,会把当前渲染用的默认值写入Cookie,覆盖用户之前存储的自定义数据,和序列化错误叠加后,刷新次数越多异常越明显。 - Cookie配置不规范:写入Cookie时
path参数传空字符串不符合规范,会导致Cookie作用域异常,不同路由路径下可能读取到不同的Cookie副本,进一步造成数据混乱。
最优修复方案
不需要额外做重复写入判断,只要修正三个核心逻辑即可:
- 统一序列化/反序列化逻辑:读取Cookie时使用
JSON.parse()解析JSON字符串,增加解析失败的兜底逻辑 - 规避SSR水合问题:不在useState初始化阶段直接读取Cookie,改为客户端挂载后再读取Cookie初始化状态,避免服务端渲染用默认值覆盖用户存储的数据
- 修正Cookie配置:将
path设为/保证全站共享同一份Cookie,同时增加空值校验避免存入无效内容
修复后的完整代码如下:
import { LineChartProps } from "./LineChart.props"; import { Input } from "../Input/Input"; import { Button } from "../Button/Button"; import cookie from "js-cookie"; import React, { useState, useEffect } from "react"; export const LineChart = ({ }: LineChartProps): JSX.Element => { const [inputValue, setInputValue] = useState(""); // 初始化先设为空数组,避免SSR阶段读取Cookie报错 const [citiesList, setCitiesList] = useState<string[]>([]); const [isLoaded, setIsLoaded] = useState(false); // 客户端挂载后再读取Cookie初始化状态,解决水合冲突 useEffect(() => { const storedCities = cookie.get("citiesList"); if (storedCities) { try { // 统一用JSON.parse解析,和写入时的JSON.stringify对应 const parsedCities = JSON.parse(storedCities); // 校验解析结果是数组再赋值 if (Array.isArray(parsedCities)) { setCitiesList(parsedCities); } else { setCitiesList(["London", "Kyiv", "Los Angeles"]); } } catch (e) { // 解析失败时用默认值,同时清除错误Cookie setCitiesList(["London", "Kyiv", "Los Angeles"]); cookie.remove("citiesList", { path: "/" }); } } else { setCitiesList(["London", "Kyiv", "Los Angeles"]); } setIsLoaded(true); }, []); const onChange = (event: React.ChangeEvent<HTMLInputElement>) => { setInputValue(event.target.value); }; const onClick = () => { // 过滤空输入,避免存入无效值 if (!inputValue.trim()) return; setCitiesList((currentArray) => [...currentArray, inputValue.trim()]); setInputValue(""); }; // 仅在客户端初始化完成后再监听状态变化写入Cookie useEffect(() => { if (!isLoaded) return; // 修正path为根路径,统一作用域 cookie.set("citiesList", JSON.stringify(citiesList), { path: "/" }); }, [citiesList, isLoaded]); return ( <div> <Button appearance="primary" onClick={onClick}>click me</Button> <Input type="text" name="search" value={inputValue} onChange={onChange} /> <div> <p>{inputValue}</p> {/* 初始化完成后再渲染城市列表 */} {isLoaded && citiesList.map(city => <span key={city} style={{marginRight: '8px'}}>{city}</span>)} </div> </div> ) };
额外注意事项
- Cookie单条存储上限通常为4KB,如果后续城市列表数据量较大,建议替换为
localStorage存储,API使用逻辑和上述方案一致,且不需要额外配置path作用域。 - 如果需要在服务端渲染阶段就获取用户存储的城市列表,不能直接在组件中调用
js-cookie,需要在Next.js的getServerSideProps或App Router的服务端组件中从请求头Cookie字段解析获取后,通过props传递给客户端组件。
内容的提问来源于stack exchange,提问作者user17880509
相关产品推荐
相关产品推荐

