主题
React Router 与状态管理
一、React Router v6:页面导航
路由配置
React Router 用 <Routes> 树声明路由:
tsx
// src/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import DefaultLayout from '@/layouts/DefaultLayout'
import Home from '@/views/Home'
import ProductDetail from '@/views/ProductDetail'
import Cart from '@/views/Cart'
import OrderList from '@/views/OrderList'
import Login from '@/views/Login'
import Register from '@/views/Register'
export default function App() {
return (
<BrowserRouter>
<Routes>
{/* 带公共布局的路由 */}
<Route element={<DefaultLayout />}>
<Route path="/" element={<Navigate to="/home" replace />} />
<Route path="/home" element={<Home />} />
<Route path="/product/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/order/list" element={<OrderList />} />
</Route>
{/* 不带布局的路由 */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</BrowserRouter>
)
}布局路由(
DefaultLayout通过<Outlet />渲染子路由)对应 Vue 端的嵌套路由。
路由跳转与参数
tsx
import { Link, useNavigate, useParams } from 'react-router-dom'
// 声明式跳转
<Link to="/cart">购物车</Link>
// 编程式跳转
const navigate = useNavigate()
navigate('/cart')
// 读动态参数
const { id } = useParams()路由守卫:登录校验
tsx
// src/components/RequireAuth.tsx
import { Navigate, Outlet } from 'react-router-dom'
import { useUserStore } from '@/stores/user'
export default function RequireAuth() {
const isLogin = useUserStore((s) => s.isLogin)
if (!isLogin) return <Navigate to="/login" replace />
return <Outlet />
}tsx
// App.tsx 里包一层
<Route element={<RequireAuth />}>
<Route path="/cart" element={<Cart />} />
<Route path="/order/list" element={<OrderList />} />
</Route>二、Zustand:全局状态管理
Zustand 以 Hooks 风格管理全局状态,代码量比 Pinia 更少:
tsx
// src/stores/user.ts
import { create } from 'zustand'
interface UserState {
token: string
nickname: string
isLogin: boolean
setLogin: (token: string, nickname: string) => void
logout: () => void
}
export const useUserStore = create<UserState>((set) => ({
token: localStorage.getItem('token') || '',
nickname: localStorage.getItem('nickname') || '',
isLogin: !!localStorage.getItem('token'),
setLogin: (token, nickname) => {
localStorage.setItem('token', token)
localStorage.setItem('nickname', nickname)
set({ token, nickname, isLogin: true })
},
logout: () => {
localStorage.removeItem('token')
localStorage.removeItem('nickname')
set({ token: '', nickname: '', isLogin: false })
},
}))在组件中使用:
tsx
import { useUserStore } from '@/stores/user'
function Header() {
const isLogin = useUserStore((s) => s.isLogin)
const nickname = useUserStore((s) => s.nickname)
const logout = useUserStore((s) => s.logout)
return isLogin
? <span>{nickname}<button onClick={logout}>退出</button></span>
: <Link to="/login">登录</Link>
}购物车数量 Store
tsx
// src/stores/cart.ts
import { create } from 'zustand'
export const useCartStore = create<{ count: number; setCount: (n: number) => void }>(
(set) => ({
count: 0,
setCount: (n) => set({ count: n }),
}),
)三、登录态与路由配合(与 Vue 端逻辑一致)
1. 登录页提交 → POST /api/auth/login → 后端返回 token
2. useUserStore.getState().setLogin(token, nickname)
3. navigate(redirect || '/home')
4. axios 拦截器给请求加 Authorization 头
5. RequireAuth 守卫拦截未登录访问四、本章验收
- [ ] 顶栏跳转各页面正常
- [ ] 未登录访问
/cart被<RequireAuth>拦截 - [ ] 登录后 Zustand 状态更新,Header 显示昵称
- [ ] 购物车 Badge 显示数量