React 速查手册:日常开发实用参考
React 速查手册:日常开发实用参考
写了这么多年 React 之后,我发现日常开发其实就是在反复使用那么一小撮模式。这份速查手册整理的不是理论,而是我每天敲代码时真正会用到的语法和写法——把它当成手边的字典就好。
组件:React 的原子
现代 React 已经全面拥抱函数式。除非你在维护遗留代码或者写 Error Boundary,否则就别再写类组件了。
// 函数组件——默认写法
function Welcome({ name }) {
return <h1>Hello, {name}</h1>;
}
// 箭头函数写法——同样常见
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;
export default Welcome;组件就是一个接收 props、返回 JSX 的函数。整个心智模型就这么简单。
Hooks:逻辑的归宿
useState —— 局部状态
const [count, setCount] = useState(0);
// 直接更新
setCount(count + 1);
// 函数式更新——当新状态依赖旧状态时使用
setCount((prev) => prev + 1);经验法则:如果新状态依赖旧状态,就用函数式更新,可以避免闭包陷阱带来的 bug。
useEffect —— 副作用
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // 清理函数
}, [tick]);依赖数组决定了 effect 何时重新执行:
[]—— 仅在挂载时执行一次[a, b]—— 当a或b变化时执行- 不传 —— 每次渲染都执行(几乎一定是 bug)
useRef —— 不触发重渲染的可变值
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;useRef 不仅能引用 DOM,也很适合存放任何不应触发重渲染的可变值——定时器 ID、上一次的 props 等等。
useMemo 和 useCallback —— 性能优化
// 缓存昂贵的计算
const sorted = useMemo(() => items.sort(compare), [items]);
// 缓存传给已 memo 化子组件的回调
const handleClick = useCallback((id) => deleteItem(id), [deleteItem]);不要无脑滥用这两个 Hook。只有在你测出了真实的性能问题,或者要把函数/对象传给 React.memo 包装过的组件时,才需要它们。
useContext —— 摆脱 Props 透传
const ThemeContext = createContext("light");
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>...</div>;
}useReducer —— 复杂状态
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "reset":
return { count: 0 };
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "increment" });当状态转换变得复杂、或者多个相关值会一起变化时,就该考虑 useReducer。
每天都会用到的 JSX 模式
条件渲染
{
isLoggedIn && <Dashboard />;
}
{
isLoggedIn ? <Dashboard /> : <Login />;
}
{
user?.name ?? "游客";
}列表渲染
{
todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
}永远要用稳定的 key。除非列表永不重新排序,否则不要拿数组下标当 key。
Props 展开
<Input {...commonProps} value={value} onChange={onChange} />Fragment
<>
<Header />
<Main />
</>受控表单
function LoginForm() {
const [email, setEmail] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
submit(email);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">登录</button>
</form>
);
}三条铁律:value 绑定到状态、onChange 更新状态、onSubmit 里调 preventDefault。
自定义 Hook:复用利器
任何以 use 开头、并且内部调用了其他 Hook 的函数,都是自定义 Hook。要抽取的是逻辑,不是 JSX。
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
fetch(url)
.then((r) => r.json())
.then((d) => {
if (!cancelled) setData(d);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [url]);
return { data, loading };
}那个 cancelled 标志能避免组件卸载后还去调用 setState——这是一个常见但隐蔽的 bug。
性能工具
// 当 props 浅相等时跳过重渲染
const Card = React.memo(function Card({ title }) {
return <div>{title}</div>;
});
// 代码分割:按需加载体积较大的组件
const Editor = React.lazy(() => import("./Editor"));
<Suspense fallback={<Spinner />}>
<Editor />
</Suspense>;React Router v6 路由
import {
BrowserRouter,
Routes,
Route,
Link,
useNavigate,
useParams,
} from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<User />} />
</Routes>
</BrowserRouter>
);
}
function User() {
const { id } = useParams();
const navigate = useNavigate();
return <button onClick={() => navigate("/")}>返回</button>;
}常见坑
闭包陷阱(Stale Closure)。 函数捕获 state 时拿到的是渲染那一刻的值,之后再读还是老值。用函数式更新或者从 ref 里读,就能绕开。
开发模式下 effect 执行两次。 React 18+ 的 Strict Mode 会故意把 effect 跑两遍,用来暴露漏掉的清理逻辑。这是有意为之——把你的 effect 写成幂等的就好。
直接修改 state。 一定要创建新的对象/数组。arr.push(x) 不会触发重渲染,只有 setArr([...arr, x]) 才会。
列表忘了写 key。 React 靠 key 判断哪些元素变了。没有 key,输入框、动画、组件内部状态都会出现各种诡异的问题。
我习惯的项目结构
src/
components/ # 通用可复用 UI
pages/ # 路由级页面组件
hooks/ # 自定义 Hook
contexts/ # Context Provider
api/ # 网络请求层
utils/ # 纯函数工具
具体起什么名字其实不重要,关键是选一种结构、然后坚持下去。
写在最后
React 的核心很小:组件、props、state、effect。一个代码库写起来是顺畅还是痛苦,往往跟 React 本身关系不大,更多取决于你有多自律——组件够不够小、effect 够不够窄、state 是不是放在了它该在的位置。拿不准的时候记住一条:state 上提到刚刚好够用就行,多上提一层都嫌多。