Skip to content

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 显示数量

基于 MIT 协议发布,可自由学习与修改