React Cheat Sheet
title: React date: 2023-11-17 10:12:25 background: bg-[#1289D8] tags: - react - web - 前端开发 - 框架 categories: - Programming intro: | React 速查表 - 包含 React 18+ 最重要的概念、Hooks、组件模式和最佳实践。适合初学者和开发者快速参考。 plugins: - copyCode - runCode
入门指南 {.cols-2}
简介
React 是一个用于构建用户界面的 JavaScript 库,采用组件化开发模式。
- React 官方文档 (react.dev)
- React 中文文档 (zh-hans.react.dev)
- Create React App (create-react-app.dev)
- Next.js (nextjs.org)
# 创建新项目
npx create-react-app my-app
npx create-react-app my-app --template typescript
# 使用 Vite (推荐)
npm create vite@latest my-app -- --template react
npm create vite@latest my-app -- --template react-ts
JSX 基础
JSX 是 JavaScript 的语法扩展,让你可以在 JS 中编写类 HTML 的标记。
// 基本元素
const element = <h1>Hello, World!</h1>;
// 嵌套元素 (需要单个根元素)
const component = (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
// 使用 Fragment 避免额外 DOM 节点
const fragment = (
<>
<h1>标题</h1>
<p>段落</p>
</>
);
// 自闭合标签
const image = <img src="url" alt="描述" />;
const input = <input type="text" />;
JSX 表达式
const name = '张三';
const age = 25;
// 变量插值
const greeting = <h1>你好,{name}!</h1>;
// 表达式
const info = <p>明年 {age + 1} 岁</p>;
// 函数调用
const upper = <p>{name.toUpperCase()}</p>;
// 条件表达式
const status = <p>{age >= 18 ? '成年' : '未成年'}</p>;
// 对象属性
const user = { name: '张三', avatar: '/avatar.jpg' };
const profile = <img src={user.avatar} alt={user.name} />;
// 注释
const withComment = (
<div>
{/* 这是 JSX 注释 */}
<p>内容</p>
</div>
);
JSX 属性
// className 代替 class
<div className="container">内容</div>
// htmlFor 代替 for
<label htmlFor="email">邮箱</label>
<input id="email" type="email" />
// style 使用对象
<div style={{ color: 'red', fontSize: '16px' }}>文本</div>
// 布尔属性
<input disabled />
<input disabled={true} />
<input disabled={isDisabled} />
// 展开属性
const props = { id: 'btn', className: 'primary', onClick: handleClick };
<button {...props}>点击</button>
// data 属性
<div data-testid="container" data-id={123}>内容</div>
// 动态属性名
const attrName = 'title';
<div {...{ [attrName]: '标题' }}>内容</div>
组件 {.cols-2}
函数组件
// 基本函数组件
function Welcome() {
return <h1>Welcome!</h1>;
}
// 箭头函数组件
const Welcome = () => {
return <h1>Welcome!</h1>;
};
// 简写 (单行返回)
const Welcome = () => <h1>Welcome!</h1>;
// 带 Props
function Greeting({ name, age = 18 }) {
return (
<div>
<h1>你好,{name}!</h1>
<p>年龄:{age}</p>
</div>
);
}
// 使用组件
function App() {
return (
<div>
<Welcome />
<Greeting name="张三" age={25} />
</div>
);
}
Props (属性)
// 接收 Props
function UserCard({ name, email, avatar, onClick }) {
return (
<div className="user-card" onClick={onClick}>
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
// 使用组件并传递 Props
<UserCard
name="张三"
email="zhangsan@example.com"
avatar="/avatar.jpg"
onClick={() => console.log('clicked')}
/>;
// 默认 Props
function Button({ text = '点击', type = 'primary' }) {
return <button className={type}>{text}</button>;
}
// children Props
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
<Card title="卡片标题">
<p>这是卡片内容</p>
<button>按钮</button>
</Card>;
条件渲染
function Greeting({ isLoggedIn, user }) {
// if-else
if (isLoggedIn) {
return <h1>欢迎回来,{user.name}!</h1>;
}
return <h1>请登录</h1>;
}
// 三元运算符
function Message({ unread }) {
return (
<div>{unread > 0 ? <span>你有 {unread} 条未读消息</span> : <span>没有新消息</span>}</div>
);
}
// && 短路运算
function Notification({ show, message }) {
return <div>{show && <div className="notification">{message}</div>}</div>;
}
// 多条件渲染
function Status({ status }) {
const statusMap = {
pending: <span className="pending">待处理</span>,
success: <span className="success">成功</span>,
error: <span className="error">失败</span>
};
return statusMap[status] || <span>未知状态</span>;
}
列表渲染
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
// 带索引的列表
function NumberList({ numbers }) {
return (
<ul>
{numbers.map((number, index) => (
<li key={index}>{number}</li>
))}
</ul>
);
}
// 对象列表
function UserList({ users }) {
return (
<div>
{users.map(user => (
<UserCard key={user.id} name={user.name} email={user.email} avatar={user.avatar} />
))}
</div>
);
}
// 过滤后渲染
function ActiveUsers({ users }) {
return (
<ul>
{users
.filter(user => user.active)
.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Hooks 基础 {.cols-2}
useState
import { useState } from 'react';
function Counter() {
// 基本用法
const [count, setCount] = useState(0);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
}
// 函数式更新 (基于前一个状态)
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 正确累加
};
return <button onClick={increment}>{count}</button>;
}
// 惰性初始化 (复杂计算)
const [state, setState] = useState(() => {
const initialValue = expensiveComputation();
return initialValue;
});
// 对象状态
const [user, setUser] = useState({ name: '', age: 0 });
setUser(prev => ({ ...prev, name: '张三' }));
useEffect
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 每次渲染后执行
useEffect(() => {
document.title = `点击了 ${count} 次`;
});
// 仅在挂载时执行
useEffect(() => {
console.log('组件已挂载');
return () => console.log('组件将卸载');
}, []);
// 依赖项变化时执行
useEffect(() => {
console.log(`count 变为 ${count}`);
}, [count]);
return <button onClick={() => setCount(count + 1)}>点击 {count} 次</button>;
}
// 数据获取
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <div>加载中...</div>;
return <div>{user.name}</div>;
}
useRef
import { useRef, useEffect } from 'react';
// DOM 引用
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
}
// 保存可变值 (不触发重新渲染)
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
const stop = () => clearInterval(intervalRef.current);
return (
<div>
<p>计数: {count}</p>
<button onClick={stop}>停止</button>
</div>
);
}
// 保存前一个值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
useMemo 和 useCallback
import { useMemo, useCallback, useState } from 'react';
function ExpensiveComponent({ items, filter }) {
// useMemo - 缓存计算结果
const filteredItems = useMemo(() => {
console.log('过滤计算...');
return items.filter(item => item.includes(filter));
}, [items, filter]);
// useCallback - 缓存函数
const handleClick = useCallback(id => {
console.log('点击了', id);
}, []);
return (
<ul>
{filteredItems.map(item => (
<li key={item} onClick={() => handleClick(item)}>
{item}
</li>
))}
</ul>
);
}
// 避免子组件不必要的重新渲染
function Parent() {
const [count, setCount] = useState(0);
const handleSubmit = useCallback(data => {
// 提交逻辑
}, []);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<ChildComponent onSubmit={handleSubmit} />
</div>
);
}
Hooks 进阶 {.cols-2}
useContext
import { createContext, useContext, useState } from 'react';
// 创建 Context
const ThemeContext = createContext('light');
// Provider 组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
};
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;
}
// 消费 Context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
>
当前主题: {theme}
</button>
);
}
// 使用
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}
useReducer
import { useReducer } from 'react';
// 定义 reducer
function todoReducer(state, action) {
switch (action.type) {
case 'ADD':
return [
...state,
{
id: Date.now(),
text: action.text,
completed: false
}
];
case 'TOGGLE':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case 'DELETE':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const addTodo = text => {
dispatch({ type: 'ADD', text });
};
const toggleTodo = id => {
dispatch({ type: 'TOGGLE', id });
};
const deleteTodo = id => {
dispatch({ type: 'DELETE', id });
};
return (
<div>
<AddTodoForm onAdd={addTodo} />
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
自定义 Hook
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 controller = new AbortController();
setLoading(true);
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
if (err.name !== 'AbortError') {
setError(err);
setLoading(false);
}
});
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// 使用自定义 Hook
function UserList() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 本地存储 Hook
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
useId 和其他 Hooks
import { useId, useTransition, useDeferredValue } from 'react';
// useId - 生成唯一 ID
function FormField({ label }) {
const id = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} type="text" />
</div>
);
}
// useTransition - 标记非紧急更新
function SearchResults({ query }) {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
const handleSearch = value => {
startTransition(() => {
// 非紧急更新
setResults(filterResults(value));
});
};
return (
<div>
<input onChange={e => handleSearch(e.target.value)} />
{isPending ? <div>搜索中...</div> : <ResultList items={results} />}
</div>
);
}
// useDeferredValue - 延迟更新值
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<SearchResults query={deferredQuery} />
</div>
);
}
表单处理 {.cols-2}
受控组件
import { useState } from 'react';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = e => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = e => {
e.preventDefault();
console.log('提交数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">姓名:</label>
<input id="name" name="name" value={formData.name} onChange={handleChange} />
</div>
<div>
<label htmlFor="email">邮箱:</label>
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="message">留言:</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
/>
</div>
<button type="submit">提交</button>
</form>
);
}
表单验证
import { useState } from 'react';
function LoginForm() {
const [values, setValues] = useState({ email: '', password: '' });
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validate = (name, value) => {
switch (name) {
case 'email':
if (!value) return '邮箱不能为空';
if (!/\S+@\S+\.\S+/.test(value)) return '邮箱格式不正确';
return '';
case 'password':
if (!value) return '密码不能为空';
if (value.length < 6) return '密码至少6位';
return '';
default:
return '';
}
};
const handleChange = e => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
setErrors(prev => ({ ...prev, [name]: validate(name, value) }));
};
const handleBlur = e => {
const { name } = e.target;
setTouched(prev => ({ ...prev, [name]: true }));
};
const handleSubmit = e => {
e.preventDefault();
const newErrors = {
email: validate('email', values.email),
password: validate('password', values.password)
};
setErrors(newErrors);
setTouched({ email: true, password: true });
if (!newErrors.email && !newErrors.password) {
console.log('提交:', values);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
placeholder="邮箱"
/>
{touched.email && errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input
name="password"
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
placeholder="密码"
/>
{touched.password && errors.password && (
<span className="error">{errors.password}</span>
)}
</div>
<button type="submit">登录</button>
</form>
);
}
自定义表单 Hook
import { useState } from 'react';
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = e => {
const { name, value, type, checked } = e.target;
setValues(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = async callback => {
return async e => {
e.preventDefault();
const validationErrors = validate ? validate(values) : {};
setErrors(validationErrors);
if (Object.keys(validationErrors).length === 0) {
setIsSubmitting(true);
try {
await callback(values);
} finally {
setIsSubmitting(false);
}
}
};
};
const reset = () => {
setValues(initialValues);
setErrors({});
};
return {
values,
errors,
isSubmitting,
handleChange,
handleSubmit,
reset,
setValues
};
}
// 使用
function SignupForm() {
const { values, errors, handleChange, handleSubmit, isSubmitting } = useForm(
{ name: '', email: '', password: '' },
values => {
const errors = {};
if (!values.name) errors.name = '姓名必填';
if (!values.email) errors.email = '邮箱必填';
if (!values.password) errors.password = '密码必填';
return errors;
}
);
const onSubmit = async data => {
await api.signup(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 表单字段 */}
<button disabled={isSubmitting}>{isSubmitting ? '提交中...' : '注册'}</button>
</form>
);
}
样式处理 {.cols-2}
CSS 类和内联样式
// CSS 类名
import './Button.css';
function Button({ variant = 'primary', children }) {
return <button className={`btn btn-${variant}`}>{children}</button>;
}
// 条件类名
function NavLink({ isActive, children }) {
return <a className={`nav-link ${isActive ? 'active' : ''}`}>{children}</a>;
}
// 多个条件类名
function Card({ featured, disabled }) {
const classNames = ['card', featured && 'card-featured', disabled && 'card-disabled']
.filter(Boolean)
.join(' ');
return <div className={classNames}>...</div>;
}
// 内联样式
function Box({ width, height, color }) {
const style = {
width: `${width}px`,
height: `${height}px`,
backgroundColor: color,
borderRadius: '8px'
};
return <div style={style}>内容</div>;
}
CSS Modules
// Button.module.css
/*
.button {
padding: 10px 20px;
border-radius: 4px;
}
.primary {
background: blue;
color: white;
}
.secondary {
background: gray;
color: white;
}
*/
import styles from './Button.module.css';
function Button({ variant = 'primary', children }) {
return <button className={`${styles.button} ${styles[variant]}`}>{children}</button>;
}
// 动态类名
function Card({ isActive }) {
return <div className={isActive ? styles.active : styles.inactive}>内容</div>;
}
Styled Components
import styled from 'styled-components';
// 基本样式组件
const Button = styled.button`
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
background: ${props => (props.primary ? 'blue' : 'gray')};
color: white;
&:hover {
opacity: 0.8;
}
`;
// 继承样式
const PrimaryButton = styled(Button)`
background: #007bff;
`;
// 根据 props 动态样式
const Input = styled.input`
padding: 8px 12px;
border: 2px solid ${props => (props.error ? 'red' : '#ddd')};
border-radius: 4px;
&:focus {
border-color: ${props => (props.error ? 'red' : 'blue')};
outline: none;
}
`;
// 使用
function Form() {
return (
<div>
<Input placeholder="姓名" />
<Input placeholder="邮箱" error />
<Button>普通按钮</Button>
<Button primary>主要按钮</Button>
</div>
);
}
性能优化 {.cols-2}
React.memo
import { memo, useState } from 'react';
// 使用 memo 包裹组件
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent 渲染');
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});
// 自定义比较函数
const MemoizedComponent = memo(
function MyComponent({ user, onClick }) {
return <div onClick={onClick}>{user.name}</div>;
},
(prevProps, nextProps) => {
// 返回 true 表示不重新渲染
return prevProps.user.id === nextProps.user.id;
}
);
// 父组件
function Parent() {
const [count, setCount] = useState(0);
const [data] = useState([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>计数: {count}</button>
{/* data 不变时,ExpensiveComponent 不会重新渲染 */}
<ExpensiveComponent data={data} />
</div>
);
}
代码分割
import { lazy, Suspense } from 'react';
// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
<div>
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
// 路由级别的代码分割
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 带错误边界
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
</ErrorBoundary>;
虚拟列表
import { useRef, useState, useCallback } from 'react';
// 简单的虚拟列表实现
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef(null);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
const visibleItems = items.slice(startIndex, endIndex);
const totalHeight = items.length * itemHeight;
const offsetY = startIndex * itemHeight;
const handleScroll = useCallback(e => {
setScrollTop(e.target.scrollTop);
}, []);
return (
<div
ref={containerRef}
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div key={startIndex + index} style={{ height: itemHeight }}>
{item.content}
</div>
))}
</div>
</div>
</div>
);
}
// 推荐使用专业库
// npm install react-window
import { FixedSizeList } from 'react-window';
function List({ items }) {
const Row = ({ index, style }) => <div style={style}>{items[index].name}</div>;
return (
<FixedSizeList height={400} itemCount={items.length} itemSize={35} width="100%">
{Row}
</FixedSizeList>
);
}
常用模式 {.cols-2}
组合模式
// 复合组件模式
function Tabs({ children, defaultValue }) {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }) {
return <div className="tabs-list">{children}</div>;
};
Tabs.Tab = function Tab({ value, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button className={activeTab === value ? 'active' : ''} onClick={() => setActiveTab(value)}>
{children}
</button>
);
};
Tabs.Panel = function TabsPanel({ value, children }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== value) return null;
return <div className="tabs-panel">{children}</div>;
};
// 使用
<Tabs defaultValue="tab1">
<Tabs.List>
<Tabs.Tab value="tab1">标签1</Tabs.Tab>
<Tabs.Tab value="tab2">标签2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="tab1">内容1</Tabs.Panel>
<Tabs.Panel value="tab2">内容2</Tabs.Panel>
</Tabs>;
渲染属性模式
// Render Props 模式
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = e => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
{render(position)}
</div>
);
}
// 使用
<MouseTracker
render={({ x, y }) => (
<p>
鼠标位置: ({x}, {y})
</p>
)}
/>;
// children 作为函数
function DataFetcher({ url, children }) {
const { data, loading, error } = useFetch(url);
return children({ data, loading, error });
}
<DataFetcher url="/api/users">
{({ data, loading, error }) => {
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <UserList users={data} />;
}}
</DataFetcher>;
高阶组件 (HOC)
// 高阶组件
function withLoading(WrappedComponent) {
return function WithLoading({ isLoading, ...props }) {
if (isLoading) {
return <div>加载中...</div>;
}
return <WrappedComponent {...props} />;
};
}
// 使用
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading isLoading={loading} users={users} />;
// 带配置的 HOC
function withAuth(WrappedComponent, { redirectTo = '/login' } = {}) {
return function WithAuth(props) {
const { user } = useAuth();
if (!user) {
return <Navigate to={redirectTo} />;
}
return <WrappedComponent {...props} user={user} />;
};
}
const ProtectedDashboard = withAuth(Dashboard);
const ProtectedSettings = withAuth(Settings, { redirectTo: '/signin' });
错误边界
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('错误边界捕获:', error, errorInfo);
// 发送错误到日志服务
}
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<div className="error-fallback">
<h2>出错了</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>重试</button>
</div>
)
);
}
return this.props.children;
}
}
// 使用
<ErrorBoundary fallback={<div>加载失败</div>}>
<MyComponent />
</ErrorBoundary>;
// 推荐使用 react-error-boundary 库
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div>
<p>出错了: {error.message}</p>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// 重置应用状态
}}
>
<MyComponent />
</ErrorBoundary>;