logo

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 库,采用组件化开发模式。

# 创建新项目
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>;
🎨 前端开发

React

React Cheat Sheet - 快速参考指南,收录常用语法、命令与实践。

📂 分类 · 前端开发🧭 Markdown 速查🏷️ 2 个标签
#react#hooks
向下滚动查看内容
返回全部 Cheat Sheets

入门指南

简介

React 是一个用于构建用户界面的 JavaScript 库,采用组件化开发模式。

BASH
滚动查看更多
# 创建新项目
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 的标记。

JSX
滚动查看更多
// 基本元素
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 表达式
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 属性
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>

组件

函数组件
JSX
滚动查看更多
// 基本函数组件
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 (属性)
JSX
滚动查看更多
// 接收 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>;
条件渲染
JSX
滚动查看更多
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>;
}
列表渲染
JSX
滚动查看更多
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 基础

useState
JSX
滚动查看更多
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
JSX
滚动查看更多
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
JSX
滚动查看更多
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
JSX
滚动查看更多
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 进阶

useContext
JSX
滚动查看更多
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
JSX
滚动查看更多
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
JSX
滚动查看更多
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
JSX
滚动查看更多
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>
	);
}

表单处理

受控组件
JSX
滚动查看更多
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>
	);
}
表单验证
JSX
滚动查看更多
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
JSX
滚动查看更多
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>
	);
}

样式处理

CSS 类和内联样式
JSX
滚动查看更多
// 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
JSX
滚动查看更多
// 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
JSX
滚动查看更多
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>
	);
}

性能优化

React.memo
JSX
滚动查看更多
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>
	);
}
代码分割
JSX
滚动查看更多
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>;
虚拟列表
JSX
滚动查看更多
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>
	);
}

常用模式

组合模式
JSX
滚动查看更多
// 复合组件模式
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>;
渲染属性模式
JSX
滚动查看更多
// 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)
JSX
滚动查看更多
// 高阶组件
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' });
错误边界
JSX
滚动查看更多
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>;

相关 Cheat Sheets