前面我们已经解决了:
- 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 运行过程中共享登录状态
所以它们并不是重复的。
而是:
一个负责持久化,一个负责运行时状态。
这句话非常值得记住。