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

21 阅读3分钟

前面我们已经解决了:

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

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

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

例如导航栏要决定显示:

Login

还是:

Logout

Pay 页面也需要知道:

当前用户有没有登录。

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


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

登录成功以后:

localStorage.setItem('token', token);

浏览器当然记住了 Token。

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

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

React 更关心的是:

当前状态是什么?

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

token
user

的地方。

这就是 Store。


二、为什么不直接用 Props?

假设 App 有:

Nav
Login
Home
Pay

登录后:

Login

拿到了:

token
user

但是:

Nav
Pay
RequireAuth

也都需要这个信息。

如果使用 Props:

App
 ↓
Nav
 ↓
...

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

登录状态明显属于:

整个应用共享的数据。

所以很适合放到全局 Store。


三、创建 Zustand Store

先创建:

src/store/

然后:

src/store/user.js

因为这个 Store 保存的是:

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

代码:

import { create } from 'zustand';

然后:

export const useAuthStore = create(set => ({
  token: '',
  user: null,

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

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

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

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

create(set => ({
  token: '',
  user: null
}))

可以先把它看成:

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

而:

set

是 Zustand 提供的:

修改 Store 状态的方法。

所以:

set({
  token,
  user
});

就是:

把 Store 里的 token 和 user 改掉。


五、Store 里面保存什么?

当前代码:

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

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

token
user

token 表示:

当前有没有登录凭证。

user 表示:

当前登录的是谁。


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

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

如果你刷新页面:

刷新
 ↓
React 重新启动
 ↓
原来的内存状态消失

但:

localStorage

不会因为刷新就消失。

因此我们在创建 Store 时:

token: localStorage.getItem('token') || ''

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

用户信息也类似。


七、为什么 user 要 JSON.parse?

localStorage 只能保存字符串。

保存对象时:

localStorage.setItem(
  'user',
  JSON.stringify(user)
);

读取的时候:

const savedUser = localStorage.getItem('user');

得到的是字符串。

所以需要:

JSON.parse(savedUser)

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


八、登录成功后修改 Store

Login 页面:

const setAuth = useAuthStore(
  state => state.setAuth
);

登录:

const res = await login(formData);

成功以后:

setAuth({
  token: res.token,
  user: res.user
});

Store 里的:

token
user

就更新了。

同时 setAuth() 里还会:

localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));

所以它同时完成两件事:

更新 React 当前状态
+
保存登录状态

九、为什么还需要 logout?

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

所以:

logout: () => {
  localStorage.removeItem('token');
  localStorage.removeItem('user');

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

这里同样做了两件事情:

localStorage 清掉
+
Zustand 清掉

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

未登录状态

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

因为 Nav 不需要自己找 localStorage。

它直接:

const token = useAuthStore(
  state => state.token
);

有 Token:

{token && <button>Logout</button>}

没有 Token:

{!token && <Link to="/login">Login</Link>}

所以你会发现:

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

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


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

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

localStorage
负责:
刷新页面后还能找到 Token

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

所以它们并不是重复的。

而是:

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

这句话非常值得记住。