JWT 4:Zustand 到底解决了什么?为什么登录状态要放进 Store?

前面我们已经解决了:

  • Token 怎么生成
  • Token 怎么发送
  • Token 怎么验证

但 React 还有一个自己的问题:

页面怎么知道用户现在处于登录状态?

例如导航栏要决定显示:

text 复制代码
Login

还是:

text 复制代码
Logout

Pay 页面也需要知道:

当前用户有没有登录。

这时候,单靠 localStorage 并不是最舒服的解决方式。


一、浏览器存储和 React 状态不是一回事

登录成功以后:

js 复制代码
localStorage.setItem('token', token);

浏览器当然记住了 Token。

但是 React 组件要不要重新渲染?

这不是 localStorage 自动帮你解决的。

React 更关心的是:

text 复制代码
当前状态是什么?

所以我们需要一个专门管理:

text 复制代码
token
user

的地方。

这就是 Store。


二、为什么不直接用 Props?

假设 App 有:

text 复制代码
Nav
Login
Home
Pay

登录后:

text 复制代码
Login

拿到了:

text 复制代码
token
user

但是:

text 复制代码
Nav
Pay
RequireAuth

也都需要这个信息。

如果使用 Props:

text 复制代码
App
 ↓
Nav
 ↓
...

可能需要一层一层往下传。

登录状态明显属于:

整个应用共享的数据。

所以很适合放到全局 Store。


三、创建 Zustand Store

先创建:

text 复制代码
src/store/

然后:

text 复制代码
src/store/user.js

因为这个 Store 保存的是:

当前用户的身份和登录状态。

代码:

js 复制代码
import { create } from 'zustand';

然后:

js 复制代码
export const useAuthStore = create(set => ({
  token: '',
  user: null,

  setAuth: ({ token, user }) => {
    // ...
  },

  logout: () => {
    // ...
  }
}));

四、为什么 create(set => ({})) 看起来很奇怪?

这也是 Zustand 最容易让初学者懵住的地方。

js 复制代码
create(set => ({
  token: '',
  user: null
}))

可以先把它看成:

create() 需要你告诉它"这个 Store 里面有哪些状态和修改状态的方法"。

而:

js 复制代码
set

是 Zustand 提供的:

修改 Store 状态的方法。

所以:

js 复制代码
set({
  token,
  user
});

就是:

把 Store 里的 token 和 user 改掉。


五、Store 里面保存什么?

当前代码:

js 复制代码
export const useAuthStore = create(set => ({
  token: localStorage.getItem('token') || '',
  user: initialUser,

这里有两个最核心的状态:

text 复制代码
token
user

token 表示:

当前有没有登录凭证。

user 表示:

当前登录的是谁。


六、为什么 Store 初始化时还要读取 localStorage?

因为 Zustand 的状态本质上还是内存里的。

如果你刷新页面:

text 复制代码
刷新
 ↓
React 重新启动
 ↓
原来的内存状态消失

但:

text 复制代码
localStorage

不会因为刷新就消失。

因此我们在创建 Store 时:

js 复制代码
token: localStorage.getItem('token') || ''

就可以把之前保存的 Token 恢复回来。

用户信息也类似。


七、为什么 user 要 JSON.parse?

localStorage 只能保存字符串。

保存对象时:

js 复制代码
localStorage.setItem(
  'user',
  JSON.stringify(user)
);

读取的时候:

js 复制代码
const savedUser = localStorage.getItem('user');

得到的是字符串。

所以需要:

js 复制代码
JSON.parse(savedUser)

把字符串重新变成 JavaScript 对象。


八、登录成功后修改 Store

Login 页面:

js 复制代码
const setAuth = useAuthStore(
  state => state.setAuth
);

登录:

js 复制代码
const res = await login(formData);

成功以后:

js 复制代码
setAuth({
  token: res.token,
  user: res.user
});

Store 里的:

text 复制代码
token
user

就更新了。

同时 setAuth() 里还会:

js 复制代码
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));

所以它同时完成两件事:

text 复制代码
更新 React 当前状态
+
保存登录状态

九、为什么还需要 logout?

登录状态不是只能增加,当然也需要删除。

所以:

js 复制代码
logout: () => {
  localStorage.removeItem('token');
  localStorage.removeItem('user');

  set({
    token: '',
    user: null
  });
}

这里同样做了两件事情:

text 复制代码
localStorage 清掉
+
Zustand 清掉

这样整个应用就重新变成:

text 复制代码
未登录状态

十、Nav 为什么可以直接知道有没有登录?

因为 Nav 不需要自己找 localStorage。

它直接:

js 复制代码
const token = useAuthStore(
  state => state.token
);

有 Token:

jsx 复制代码
{token && <button>Logout</button>}

没有 Token:

jsx 复制代码
{!token && <Link to="/login">Login</Link>}

所以你会发现:

Zustand 让不同组件共享"当前登录状态"。

这就是它在这个项目里的核心价值。


十一、现在这个项目有两个"地方"保存登录状态

可以把它们分成两个角色:

text 复制代码
localStorage
负责:
刷新页面后还能找到 Token

Zustand
负责:
React 运行过程中共享登录状态

所以它们并不是重复的。

而是:

一个负责持久化,一个负责运行时状态。

这句话非常值得记住。

相关推荐
IT_陈寒1 小时前
Vite动态导入差点让我秃头,原来问题出在这
前端·人工智能·后端
白泽_hunter2 小时前
项目里最常见的 5 个 this 指向坑:从规则到实战彻底讲透
前端
宿6742 小时前
vue3-pinia
前端·vue.js
白泽_hunter2 小时前
搞懂 JS 数据类型:从 typeof 到手写完整类型判断,一张图理清
前端
唐青枫2 小时前
别只把大括号当作用域:Zig Block、标签块与控制流实战
后端
日光倾2 小时前
TypeScript 随手记 —— 1
前端·javascript·typescript
自学it的攻城狮3 小时前
elpis-core ,koa实现的系统底座, 沉淀80% 的通用能力,剩下20% 用于定制化开发
前端
CappuccinoRose3 小时前
模块化体系
前端·import·export·es modules
AIDANHANG4 小时前
放开靠时间戳对日志前先核请求ID跨服务传播与采样关联
前端·人工智能