Vue 和 React 项目中怎么正确集成 js-cookie

作者:山岚

时间:2026-11-02

浏览:248

深入解析在 Vue 3 与 React 18+ 项目中规范集成 js-cookie 的工程化方案,涵盖抽象服务层设计、响应式状态与自定义 Hook 桥接、路由守卫、安全配置(SameSite/Secure)及 SSR 边界防护。

文章目录
Vue 和 React 项目中怎么正确集成 js-cookie
在现代前端工程开发中,集成 js-cookie 的核心不是直接在每个业务组件或页面中零散调用 Cookies.set 与 Cookies.get,而是建立统一的 Cookie 抽象服务层,集中管理键名常量与安全策略(如 path: '/'、SameSite、Secure),再分别与 Vue(Pinia 状态管理、路由守卫)及 React(自定义 Hook useCookie、Context 上下文)的状态机制无缝对接。通过服务层解耦,既能规避硬编码键名、作用域错位及类型缺失的问题,也能在 SSR 服务端渲染或不同部署环境下保持清晰的运行边界与异常兜底。
Vue 和 React 项目中怎么正确集成 js-cookie
图 1:js-cookie 规范集成与工程落地四步流程(准备、抽象、桥接与加固)

一、为什么必须对 js-cookie 进行服务层封装?

原生浏览器的 document.cookie 接口设计于早期的 Web 标准,其读写方式极其原始:读取时返回的是以分号拼接的键值对长字符串,写入时则采用赋值追加语法的副作用机制。为了解析一个特定的 Cookie,开发者往往不得不手写正则或字符串切片逻辑;而在处理过期时间、路径和跨站标志时,复杂的字符格式又极易引发人为疏漏。

js-cookie 是一个轻量级(未压缩仅约 3KB,Gzip 后不足 1.8KB)的 JavaScript 库,它用简洁优雅的 Cookies.get()、Cookies.set() 和 Cookies.remove() API 抹平了跨浏览器的底层细节。然而,在实际团队协作和大型单页应用(SPA)中,直接在视图组件中“裸调”该库仍存在四大结构性缺陷:

  • 键名散落与拼写风险:如果业务组件各自手写 Cookies.get('auth_token'),一旦某处拼写为 auth-token 或 authtoken,将产生难以察觉的排查成本。
  • 默认路径陷阱:原生 Cookie 若未显式传入 path: '/',默认会绑定到当前 URL 的路径下。当用户在 /admin/dashboard 设置 Cookie 时,跳转至 /portal 页面将直接读取为空,且在其他路由下执行 remove 会由于路径不一致而删除失败。
  • 与框架响应式机制脱节:Cookie 是挂载在浏览器底座的物理存储,Cookie 的改变不会自动触发 Vue 的响应式追踪,也不会触发 React 的组件重新渲染。直接裸调往往导致 UI 显示滞后或逻辑脱节。
  • JSON 序列化与异常隐患:当存储复杂对象或数组时,若没有做格式化容错包装,一旦数据被意外篡改或格式损坏,JSON.parse 将直接抛出致命未捕获异常。

二、依赖准备与通用服务抽象层实现

在 Vue 3 与 React 18+ 项目中,首要步骤是引入依赖包并配齐静态类型定义。无论采用 Vite、Webpack 还是 Rsbuild,均在项目终端中执行以下安装命令:

# 安装核心库依赖
npm install js-cookie

# 为 TypeScript 项目安装类型声明包(开发依赖)
npm install @types/js-cookie -D

接下来,我们在工程的工具目录(例如 src/utils/cookie.ts)建立统一的抽象层。该抽象层实现三项关键能力:枚举统管 Key 键名、固化安全默认配置(全局根路径与 SameSite 策略)、以及泛型友好的 JSON 安全存取方法。

// src/utils/cookie.ts
import Cookies from 'js-cookie';

/**
 * 集中管理 Cookie 的键名枚举,杜绝业务中出现魔法字符串
 */
export enum CookieKeys {
  TOKEN = 'app_token',
  USER_INFO = 'app_user_info',
  THEME_MODE = 'app_theme_mode',
  SIDEBAR_COLLAPSED = 'app_sidebar_collapsed',
}

/**
 * 生产环境默认安全选项
 * 1. path: '/' 确保全站路由共享
 * 2. sameSite: 'Lax' 防御常见的跨站请求伪造 (CSRF)
 * 3. secure: 仅在 HTTPS 协议下生效(开发环境 localhost 自动兼容)
 */
const DEFAULT_OPTIONS: Cookies.CookieAttributes = {
  path: '/',
  sameSite: 'Lax',
  secure: typeof window !== 'undefined' && window.location.protocol === 'https:',
};

/**
 * 读取字符串值
 */
export function getCookie(key: CookieKeys | string): string | undefined {
  if (typeof document === 'undefined') return undefined; // SSR 边界保护
  return Cookies.get(key);
}

/**
 * 写入字符串值,合并默认配置与自定义选项
 */
export function setCookie(
  key: CookieKeys | string,
  value: string,
  customOptions?: Cookies.CookieAttributes
): void {
  if (typeof document === 'undefined') return;
  Cookies.set(key, value, { ...DEFAULT_OPTIONS, ...customOptions });
}

/**
 * 写入 JSON 结构化数据
 */
export function setJsonCookie<T>(
  key: CookieKeys | string,
  data: T,
  customOptions?: Cookies.CookieAttributes
): void {
  try {
    const serialized = JSON.stringify(data);
    setCookie(key, serialized, customOptions);
  } catch (err) {
    console.error(`[Cookie] JSON 序列化失败,key: ${key}`, err);
  }
}

/**
 * 安全读取 JSON 结构化数据,附带泛型推断和异常回退
 */
export function getJsonCookie<T>(key: CookieKeys | string, fallback: T): T {
  const raw = getCookie(key);
  if (!raw) return fallback;
  try {
    return JSON.parse(raw) as T;
  } catch (err) {
    console.warn(`[Cookie] JSON 解析失败,已返回默认兜底值,key: ${key}`, err);
    return fallback;
  }
}

/**
 * 清除指定 Cookie(必须与写入时的 path/domain 保持一致)
 */
export function removeCookie(
  key: CookieKeys | string,
  customOptions?: Cookies.CookieAttributes
): void {
  if (typeof document === 'undefined') return;
  Cookies.remove(key, { ...DEFAULT_OPTIONS, ...customOptions });
}
Vue 和 React 项目中怎么正确集成 js-cookie
图 2:双框架集成架构与数据流转分层模型(底层驱动、服务抽象、状态同步与 UI 交互)

三、在 Vue 3 项目中的标准落地实践

在 Vue 3 工程中,集成 Cookie 的核心在于与 Pinia 全局状态和 Vue Router 导航守卫形成闭环联动。因为 Cookie 本身无响应式通知能力,若直接在模板中调用 getCookie(),当 Cookie 更新或失效时,视图将不会触发重绘。

1. 结合 Pinia Store 进行鉴权凭证受控同步

在用户状态 Store 中,将 token 初始化为从 Cookie 读取;在用户完成登录或退出时,同时操作 Pinia 响应式状态与底层 Cookie 存储,从而保证全应用依赖该状态的组件均能即时响应。

// src/stores/auth.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { CookieKeys, getCookie, setCookie, removeCookie } from '@/utils/cookie';

export const useAuthStore = defineStore('auth', () => {
  // 初始化时从 Cookie 恢复 Token 状态
  const token = ref<string | undefined>(getCookie(CookieKeys.TOKEN));
  const isLogin = computed(() => !!token.value);

  /**
   * 登录成功动作:同步写入 Pinia 响应式 state 与持久化 Cookie
   */
  function handleLoginSuccess(newToken: string, expiresDays = 7) {
    token.value = newToken;
    setCookie(CookieKeys.TOKEN, newToken, { expires: expiresDays });
  }

  /**
   * 注销登出动作:清除内存状态与本地 Cookie
   */
  function handleLogout() {
    token.value = undefined;
    removeCookie(CookieKeys.TOKEN);
  }

  return { token, isLogin, handleLoginSuccess, handleLogout };
});

2. 在 Vue Router 全局前置守卫中实现路由鉴权

当用户在地址栏手动输入受保护页面的 URL 或点击跳转时,路由守卫需要在渲染页面前核验身份凭证。直接在 router.beforeEach 中调用封装好的 getCookie 判定,可以实现零闪烁的前置跳转拦截。

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { CookieKeys, getCookie } from '@/utils/cookie';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/login', component: () => import('@/views/Login.vue') },
    {
      path: '/dashboard',
      component: () => import('@/views/Dashboard.vue'),
      meta: { requiresAuth: true },
    },
  ],
});

const WHITE_LIST = ['/login', '/404'];

router.beforeEach((to, from, next) => {
  const hasToken = !!getCookie(CookieKeys.TOKEN);

  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/dashboard' });
    } else {
      next();
    }
  } else {
    if (to.meta.requiresAuth && !WHITE_LIST.includes(to.path)) {
      next({ path: '/login', query: { redirect: to.fullPath } });
    } else {
      next();
    }
  }
});

export default router;

四、在 React 18+ 项目中的标准落地实践

在 React 体系中,组件渲染受控于 State 和 Props。若仅仅在组件内使用 getCookie(),该值在 React 的心智模型中属于“外部不可控的可变变量”,Cookie 变动绝不会引发组件重新运行。解决该问题的标准做法是封装自定义 Hook(如 useCookie),将底层 Cookie 与内部 useState 双向绑定。

1. 封装通用 useCookie 自定义 Hook

下面的自定义 Hook 提供了类型安全的状态值、更新方法与删除方法,确保调用 updateValue 时既持久化到底层 Cookie,又通过 setValue 触发 React 组件的 UI 更新。

// src/hooks/useCookie.ts
import { useState, useCallback } from 'react';
import Cookies from 'js-cookie';
import { CookieKeys, getCookie, setCookie, removeCookie } from '@/utils/cookie';

export function useCookie(
  key: CookieKeys | string,
  initialValue?: string
): [
  string | undefined,
  (val: string, options?: Cookies.CookieAttributes) => void,
  (options?: Cookies.CookieAttributes) => void
] {
  // 以 Cookie 当前值优先,无值时回退为 initialValue
  const [storedValue, setStoredValue] = useState<string | undefined>(() => {
    const cookieVal = getCookie(key);
    return cookieVal !== undefined ? cookieVal : initialValue;
  });

  // 更新方法:同步设置 Cookie 与触发 React 状态重绘
  const updateCookie = useCallback(
    (newValue: string, options?: Cookies.CookieAttributes) => {
      setCookie(key, newValue, options);
      setStoredValue(newValue);
    },
    [key]
  );

  // 删除方法:清理物理存储并重置本地 state
  const deleteCookie = useCallback(
    (options?: Cookies.CookieAttributes) => {
      removeCookie(key, options);
      setStoredValue(undefined);
    },
    [key]
  );

  return [storedValue, updateCookie, deleteCookie];
}

2. 结合 React Context 构建全局鉴权提供者

在大型 React 应用中,通常需要将登录状态注入整棵组件树。结合上述 Hook 与 React.createContext,即可构建开箱即用的 AuthProvider:

// src/contexts/AuthContext.tsx
import React, { createContext, useContext, useMemo } from 'react';
import { CookieKeys } from '@/utils/cookie';
import { useCookie } from '@/hooks/useCookie';

interface AuthContextType {
  token: string | undefined;
  isAuthenticated: boolean;
  login: (token: string, days?: number) => void;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [token, setTokenCookie, removeTokenCookie] = useCookie(CookieKeys.TOKEN);

  const contextValue = useMemo(
    () => ({
      token,
      isAuthenticated: !!token,
      login: (newToken: string, days = 7) => setTokenCookie(newToken, { expires: days }),
      logout: () => removeTokenCookie(),
    }),
    [token, setTokenCookie, removeTokenCookie]
  );

  return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
};

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth 必须在 AuthProvider 作用域内使用');
  return ctx;
}
Vue 和 React 项目中怎么正确集成 js-cookie
图 3:客户端存储方案特性对比及 js-cookie 生产核心参数配置矩阵

五、存储选型对比与关键安全配置准则

虽然 js-cookie 操作非常方便,但在系统架构选型时,团队必须清楚不同存储方案的边界。不能盲目将所有持久化需求都压在 Cookie 上。

技术指标 js-cookie (客户端) HttpOnly Cookie (服务端) localStorage sessionStorage
容量限制 约 4 KB(单域名约 50 个) 约 4 KB 约 5 ~ 10 MB 约 5 MB
脚本可访问性 完全开放(JS 可读写) 严格禁止(防 XSS 窃取) 完全开放(JS 可读写) 完全开放(JS 可读写)
同源请求自动携带 是(按 path 与同源规则附带) 是(浏览器后台静默携带) 否(需前端在拦截器手动装配) 否(需前端在拦截器手动装配)
生命周期管理 可自由指定天数、具体时刻 由服务端响应头明确指定 永久持久化(除非显式清空) 当前浏览器标签页关闭即销毁
典型应用场景 UI 主题、轻量跨页标识、非敏感 Token 银行/金融级鉴权 Session、敏感 Token 离线数据草稿、大体积静态配置 单次多步表单临时进度暂存

核心安全原则:同源保护与跨站防线

由于客户端通过 js-cookie 写入的 Cookie 能被任意执行的 JavaScript 代码读取,因此严禁在客户端 Cookie 中以明文方式存储用户密码、支付密钥或未脱敏的个人隐私数据。对于防止恶意脚本窃取,应配合 Content Security Policy (CSP) 限制脚本注入;而在预防跨站请求伪造(CSRF)时,必须配置 sameSite: 'Lax' 或 sameSite: 'Strict'。

六、生产环境常见避坑与 SSR 注意事项

1. 规避 SSR 服务端渲染中的 document is not defined 错误

在 Nuxt 3 或 Next.js 等服务端渲染架构下,代码首先在 Node.js 环境中执行。Node.js 并不存在浏览器的全局 document 对象,若在模块顶层作用域或服务端生命周期中调用 Cookies.get(),服务端会直接崩溃中断:

  • Nuxt 3 项目:推荐优先使用 Nuxt 官方内置的 useCookie() 组合式函数,它会在 SSR 期间自动提取入站请求头中的 Cookie,并在客户端自动桥接浏览器上下文。若使用 js-cookie,必须确保只在 onMounted 钩子或经由 process.client 判断后执行。
  • Next.js App Router:在服务端组件(Server Components)中,应使用 import { cookies } from 'next/headers' 读取;在客户端组件(带有 'use client' 指令)内使用 js-cookie 时,也必须包裹环境检查或只在 useEffect 内执行。

2. 务必锁定 path: '/' 避免子路由作用域隔离

当未指定 path 选项时,浏览器会将当前页面的路径目录作为 Cookie 的默认作用域。例如用户在 https://example.com/system/settings 页面写入了名为 active_tab 的 Cookie,其默认路径即为 /system。当用户后退到 https://example.com/home 时,调用 getCookie('active_tab') 将得到 undefined;如果在 /home 页面执行 removeCookie('active_tab'),也无法删除 /system 下的 Cookie,造成“明明删了却依然在”的假死幽灵现象。因此在封装层中强制默认 path: '/' 是最重要的一道防线。

3. 正确处理 401 响应的清理闭环

当后端鉴权 Token 过期时,除了清理客户端 Cookie,还需同步重置框架内部的响应式状态,避免状态错位。在 Axios 拦截器中,应统一监听 401 状态码:

// src/api/request.ts
import axios from 'axios';
import { CookieKeys, getCookie, removeCookie } from '@/utils/cookie';

const apiClient = axios.create({
  baseURL: '/api',
  timeout: 10000,
});

// 请求拦截器:动态注入最新 Token
apiClient.interceptors.request.use((config) => {
  const token = getCookie(CookieKeys.TOKEN);
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截器:处理 401 鉴权失效
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response && error.response.status === 401) {
      // 清理 Cookie,并重定向至登录页
      removeCookie(CookieKeys.TOKEN);
      if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
        window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname)}`;
      }
    }
    return Promise.reject(error);
  }
);

export default apiClient;
Vue 和 React 项目中怎么正确集成 js-cookie
图 4:Vue / React 集成 js-cookie 全景自检清单与四大避坑维度

七、总结

在 Vue 和 React 工程中正确集成 js-cookie 的本质是一次清晰的分层解耦与职责划分:

  1. 在服务层:通过 src/utils/cookie.ts 封装唯一的存取口径,收敛 CookieKeys 枚举,锁定 path: '/' 和 SameSite,杜绝零散裸调。
  2. 在框架层:在 Vue 中以 Pinia 为核心状态中枢并结合路由守卫核验;在 React 中以 useCookie 自定义 Hook 保证 UI 响应式重绘并结合 Context 全局下发。
  3. 在通信与安全层:通过 Axios 请求与响应拦截器完成 Token 的附带与失效清理,严格遵守环境判定,构筑清晰的生产级客户端存储架构。
本文由前端架构工程实践指南整理编撰,适用于现代 Vue 3、React 18 及主流前端构建体系。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章