React 早已不只是一个 UI 库,围绕它构建的“全家桶”(React + React Router + Redux/ Zustand + 常用 Hooks 组合)已成为现代前端工程化的标准范式。本文不重复基础语法,而是从工程实践角度,带你实现一个具备认证、列表展示、全局状态和路由守卫的迷你应用,完整呈现全家桶的协同用法。
使用 Vite 快速启动 TypeScript 项目:
bash
npm create vite@latest react-family -- --template react-ts
cd react-family
npm install react-router-dom zustand axios @tanstack/react-query
npm install -D @types/react @types/react-dom我们选择:
react-router-dom v6zustand(轻量,替代 Redux)@tanstack/react-query(缓存 + 异步状态管理)axios创建 store/authStore.ts:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuthState {
token: string | null;
user: { id: string; name: string } | null;
isAuthenticated: boolean;
login: (token: string, user: { id: string; name: string }) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
user: null,
isAuthenticated: false,
login: (token, user) => set({ token, user, isAuthenticated: true }),
logout: () => set({ token: null, user: null, isAuthenticated: false }),
}),
{ name: 'auth-storage' } // 持久化到 localStorage
)
);使用 persist 中间件自动同步到本地存储,刷新页面不丢失登录态。
创建 router/index.tsx:
import { createBrowserRouter, Navigate } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { useAuthStore } from '@/store/authStore';
// 懒加载页面
const Login = lazy(() => import('@/pages/Login'));
const Dashboard = lazy(() => import('@/pages/Dashboard'));
const Profile = lazy(() => import('@/pages/Profile'));
// 路由守卫组件(高阶组件)
const PrivateRoute = ({ children }: { children: JSX.Element }) => {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
return isAuthenticated ? children : <Navigate to="/login" replace />;
};
export const router = createBrowserRouter([
{
path: '/login',
element: (
<Suspense fallback={<div>Loading...</div>}>
<Login />
</Suspense>
),
},
{
path: '/',
element: (
<PrivateRoute>
<Suspense fallback={<div>Loading...</div>}>
<Dashboard />
</Suspense>
</PrivateRoute>
),
children: [
{ path: 'profile', element: <Profile /> },
],
},
{ path: '*', element: <Navigate to="/" /> },
]);注意 PrivateRoute 通过 Zustand 的 isAuthenticated 决定是否重定向,完全响应式。
创建 api/client.ts:
import axios from 'axios';
import { useAuthStore } from '@/store/authStore';
export const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE,
timeout: 10000,
});
// 请求拦截器自动注入 token
apiClient.interceptors.request.use((config) => {
const token = useAuthStore.getState().token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器:401 自动登出
apiClient.interceptors.response.use(
(res) => res,
(error) => {
if (error.response?.status === 401) {
useAuthStore.getState().logout();
window.location.href = '/login';
}
return Promise.reject(error);
}
);定义 api/posts.ts:
import { apiClient } from './client';
export interface Post {
id: string;
title: string;
content: string;
}
export const fetchPosts = async (): Promise<Post[]> => {
const { data } = await apiClient.get('/posts');
return data;
};在组件中使用 useQuery 获取数据:
// pages/Dashboard.tsx
import { useQuery } from '@tanstack/react-query';
import { fetchPosts } from '@/api/posts';
export default function Dashboard() {
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 5 * 60 * 1000, // 5分钟新鲜
});
if (isLoading) return <div>Loading posts...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.map((post) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</li>
))}
</ul>
);
}React Query 自动处理缓存、重试、后台刷新,替代了传统的 useEffect + useState 模式。
创建 hooks/useAuth.ts:
import { useAuthStore } from '@/store/authStore';
import { useNavigate } from 'react-router-dom';
import { apiClient } from '@/api/client';
export const useAuth = () => {
const { login, logout, isAuthenticated, user } = useAuthStore();
const navigate = useNavigate();
const signIn = async (email: string, password: string) => {
const { data } = await apiClient.post('/auth/login', { email, password });
login(data.token, data.user);
navigate('/');
};
const signOut = () => {
logout();
navigate('/login');
};
return { signIn, signOut, isAuthenticated, user };
};这个 Hook 封装了登录/登出的业务逻辑,并集成了 Zustand 和路由导航,组件只需调用 signIn 即可。
// pages/Login.tsx
import { useState } from 'react';
import { useAuth } from '@/hooks/useAuth';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { signIn } = useAuth();
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
await signIn(email, password);
} catch (err) {
alert('Login failed');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
</form>
);
}const token = useAuthStore(state => state.token) 只会订阅 token 变化。staleTime 和 cacheTime,避免重复请求。react-error-boundary 包裹路由,捕获渲染错误。React 全家桶并非简单堆砌库,而是通过分层协作构建稳健的应用架构:
以上代码可直接运行(需配合后端 API),完整体现了现代 React 工程化的核心范式。在实际项目中,你还可以加入 Zod 进行运行时校验、i18next 国际化、Vite 环境变量管理不同环境。全家桶的意义在于“组合”,而组合的关键在于清晰的边界设计——让每个工具做它最擅长的事,你的应用自然变得可维护、可扩展。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。