大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Checkpoint: 外部包集成
你将完成什么
在 Todo 应用里集成第三方包:
- 用
axios替代原生fetch - 用
react-query管理服务端状态 - 用
date-fns格式化日期 - 用
react-hot-toast做消息提示
验收标准
- 用 npm 安装并配置第三方包
- 理解包的文档,正确使用 API
- 处理包的加载状态和错误
核心代码
1. Axios 替代 fetch
npm install axios
// api.js
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3000/api',
timeout: 10000
});
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
response => response.data,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export const login = (username, password) =>
api.post('/login', { username, password });
export const getTodos = () =>
api.get('/todos');
export const createTodo = (text) =>
api.post('/todos', { text });
export const updateTodo = (id, data) =>
api.put(`/todos/${id}`, data);
export const deleteTodo = (id) =>
api.delete(`/todos/${id}`);
2. React Query 管理状态
npm install @tanstack/react-query
// main.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
// TodoPage.jsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { getTodos, createTodo, deleteTodo } from './api';
function TodoPage() {
const queryClient = useQueryClient();
const [text, setText] = useState('');
// 查询
const { data, isLoading } = useQuery({
queryKey: ['todos'],
queryFn: getTodos
});
// 创建
const createMutation = useMutation({
mutationFn: createTodo,
onSuccess: () => {
queryClient.invalidateQueries(['todos']);
setText('');
}
});
// 删除
const deleteMutation = useMutation({
mutationFn: deleteTodo,
onSuccess: () => {
queryClient.invalidateQueries(['todos']);
}
});
if (isLoading) return <p>加载中...</p>;
return (
<div>
<h1>我的 Todo</h1>
<form onSubmit={(e) => {
e.preventDefault();
createMutation.mutate(text);
}}>
<input value={text} onChange={e => setText(e.target.value)} />
<button type="submit" disabled={createMutation.isLoading}>
{createMutation.isLoading ? '提交中...' : '添加'}
</button>
</form>
<ul>
{data?.data?.map(todo => (
<li key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => deleteMutation.mutate(todo.id)}>
删除
</button>
</li>
))}
</ul>
</div>
);
}
3. date-fns 格式化日期
npm install date-fns
import { format, formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale';
// 格式化日期
format(new Date(), 'yyyy-MM-dd HH:mm');
// "2025-01-15 14:30"
// 相对时间
formatDistanceToNow(new Date('2025-01-15T10:00:00'), {
addSuffix: true,
locale: zhCN
});
// "4 小时前"
4. react-hot-toast 消息提示
npm install react-hot-toast
import { Toaster, toast } from 'react-hot-toast';
function TodoPage() {
const createMutation = useMutation({
mutationFn: createTodo,
onSuccess: () => {
toast.success('添加成功');
queryClient.invalidateQueries(['todos']);
},
onError: (err) => {
toast.error(err.response?.data?.msg || '添加失败');
}
});
return (
<div>
<Toaster position="top-right" />
{/* ... */}
</div>
);
}
常见卡点
- 版本不兼容:查看包的
peerDependencies,确保 React 版本匹配。 - 文档 API 变了:看最新版本文档,不要看旧版教程。
- 包体积太大:用
import { format } from 'date-fns'而非import * as dateFns。
下一步
完成本课后,进入「基础设施」阶段,学习云服务器和域名。