使用记录
大约 3 分钟
React 使用记录
项目实战经验
自定义 Hook 封装
// useLocalStorage.js
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用
function App() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return <button onClick={() => setTheme('dark')}>Switch Theme</button>;
}请求封装 Hook
// useFetch.js
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
const json = await response.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}踩坑记录
闭包陷阱
// ❌ 错误:获取的是旧的 count 值
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 永远是 0
setCount(count + 1); // 永远变成 1
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖,闭包陷阱
return <div>{count}</div>;
}
// ✅ 正确:使用函数式更新
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 获取最新值
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{count}</div>;
}依赖数组遗漏
// ❌ 错误:未包含所有依赖
function User({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // 缺少 userId
return <div>{user?.name}</div>;
}
// ✅ 正确:包含所有依赖
function User({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}事件监听器清理
// ❌ 错误:未清理事件监听
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
// ✅ 正确:清理事件监听
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);无限循环
// ❌ 错误:setState 导致重新渲染,形成循环
useEffect(() => {
setState(newState);
});
// ✅ 正确:添加依赖或空数组
useEffect(() => {
setState(newState);
}, [someValue]);路由配置
React Router v6
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="users/:id" element={<UserDetail />} />
<Route path="*" element={<NotFound />} />
</Route>
{/* 受保护路由 */}
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
</Routes>
</BrowserRouter>
);
}
// 受保护路由组件
function RequireAuth({ children }) {
const { isLoggedIn } = useAuth();
return isLoggedIn ? children : <Navigate to="/login" replace />;
}路由参数获取
import { useParams, useNavigate, useLocation } from 'react-router-dom';
function UserDetail() {
const { id } = useParams(); // URL 参数
const navigate = useNavigate(); // 编程式导航
const location = useLocation(); // 当前位置信息
const goBack = () => navigate(-1);
const goHome = () => navigate('/');
return (
<div>
<p>User ID: {id}</p>
<button onClick={goBack}>Back</button>
</div>
);
}状态管理实践
Zustand 使用
// store.js
import { create } from 'zustand';
const useStore = create((set, get) => ({
count: 0,
user: null,
// 简单更新
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
// 异步操作
fetchUser: async (id) => {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user });
},
// 使用 get 访问当前状态
doubleCount: () => get().count * 2,
}));
// 组件中使用
function Counter() {
const count = useStore(state => state.count);
const increment = useStore(state => state.increment);
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}样式方案
CSS Modules
// Button.module.css
.primary {
background: #007bff;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ children }) {
return <button className={styles.primary}>{children}</button>;
}Tailwind CSS
function Card({ title, content }) {
return (
<div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold mb-2 text-gray-800">{title}</h2>
<p className="text-gray-600">{content}</p>
</div>
);
}部署相关
构建生产版本
# Vite 项目
npm run build
# 预览生产构建
npm run preview环境变量
# .env.development
VITE_API_URL=http://localhost:3000
# .env.production
VITE_API_URL=https://api.example.com// 在代码中使用
const apiUrl = import.meta.env.VITE_API_URL;调试技巧
React DevTools
- 安装浏览器扩展 React Developer Tools
- 查看组件树结构
- 检查 Props 和 State
- 使用 Profiler 分析性能
常见错误排查
| 错误信息 | 原因 | 解决 |
|---|---|---|
Objects are not valid as React child | 直接渲染对象 | 使用 JSON.stringify() 或提取属性 |
Cannot read property of undefined | 数据未加载完成 | 添加 loading 状态或可选链 ?. |
Too many re-renders | 无限循环 | 检查 useEffect 依赖和 setState 调用 |
Invalid hook call | Hook 规则违规 | 确保在组件顶层调用,不在循环/条件中 |