外观
React 笔记
约 3311 字大约 11 分钟
2025-08-16
一、React 基础
1.1 什么是 React?
React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发并维护。它专注于构建可重用的 UI 组件,采用声明式编程范式,使开发复杂交互式 UI 变得更加简单。
React 的核心优势:
- 组件化开发:将 UI 拆分为独立、可复用的组件
- 虚拟 DOM:高效更新真实 DOM,提升性能
- 声明式设计:只需描述 UI 在任意给定时刻应该长什么样
- 单向数据流:数据流动清晰,易于理解和调试
- 生态系统丰富:庞大的社区和配套工具(如 React Router、Redux)
1.2 核心概念
虚拟 DOM
- React 在内存中维护一个虚拟表示的 DOM
- 当状态变化时,React 会创建一个新的虚拟 DOM
- 通过 diff 算法比较新旧虚拟 DOM
- 只更新需要变更的真实 DOM 部分
组件化
- UI 被拆分为独立、可复用的组件
- 每个组件管理自己的状态
- 组件可以组合嵌套,构建复杂 UI
1.3 创建 React 项目
使用 Create React App(推荐)
npx create-react-app my-app
cd my-app
npm start项目结构概览
my-app/
├── node_modules/
├── public/
│ ├── index.html
│ └── favicon.ico
├── src/
│ ├── App.js
│ ├── index.js
│ ├── components/ # 组件目录
│ ├── assets/ # 静态资源
│ └── ...
├── package.json
└── ...二、JSX 语法
2.1 JSX 基础
JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的代码。
const element = <h1>Hello, world!</h1>;为什么使用 JSX:
- 更直观地描述 UI 结构
- 充分利用 JavaScript 的能力
- 编译后转换为常规 JavaScript 函数调用
2.2 在 JSX 中使用 JavaScript
嵌入表达式
const name = 'React';
const element = <h1>Hello, {name}</h1>;条件渲染
// 三元表达式
const isLoggedIn = true;
const element = (
<div>
{isLoggedIn ? <p>Welcome back!</p> : <p>Please sign up.</p>}
</div>
);
// 逻辑与操作符
const unreadMessages = ['Hello', 'React'];
const element = (
<div>
{unreadMessages.length > 0 &&
<h2>You have {unreadMessages.length} unread messages.</h2>
}
</div>
);2.3 JSX 与 HTML 的区别
属性命名差异
// HTML
<div class="container" tabindex="0"></div>
// JSX
<div className="container" tabIndex="0"></div>特殊属性
className代替classhtmlFor代替for- 样式使用对象:
style={{ color: 'red', fontSize: '16px' }}
JSX 中的注释
<div>
{/* 这是一个 JSX 注释 */}
<h1>Hello</h1>
</div>三、组件开发
3.1 函数组件
基本结构
import React from 'react';
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 使用箭头函数
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};组件命名规则:
- 组件名必须以大写字母开头
- 组件名应具有描述性(如
UserProfile而不是Component1)
3.2 类组件
基本结构
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}组件生命周期(常用)
class Example extends Component {
// 挂载阶段
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('Constructor');
}
componentDidMount() {
console.log('Component did mount');
// 常用于数据获取、订阅事件
}
// 更新阶段
componentDidUpdate(prevProps, prevState) {
console.log('Component did update');
// 常用于对更新做出响应
}
// 卸载阶段
componentWillUnmount() {
console.log('Component will unmount');
// 常用于清理工作(如取消订阅)
}
render() {
return <div>Count: {this.state.count}</div>;
}
}3.3 组件通信
父组件 → 子组件:Props
// ParentComponent.js
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const [message, setMessage] = useState('Hello from parent');
return (
<div>
<ChildComponent
message={message}
count={5}
isActive={true}
/>
</div>
);
}// ChildComponent.js
import React from 'react';
function ChildComponent(props) {
return (
<div>
<p>Message: {props.message}</p>
<p>Count: {props.count}</p>
<p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
</div>
);
}
// 可以添加默认值
ChildComponent.defaultProps = {
count: 0,
isActive: false
};
// 可以添加类型检查(使用PropTypes)
ChildComponent.propTypes = {
message: PropTypes.string.isRequired,
count: PropTypes.number,
isActive: PropTypes.bool
};子组件 → 父组件:回调函数
// ParentComponent.js
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const [childMessage, setChildMessage] = useState('');
const handleChildMessage = (message) => {
setChildMessage(message);
};
return (
<div>
<ChildComponent onMessage={handleChildMessage} />
<p>Message from child: {childMessage}</p>
</div>
);
}// ChildComponent.js
import React, { useState } from 'react';
function ChildComponent({ onMessage }) {
const [input, setInput] = useState('');
const handleSubmit = () => {
onMessage(input);
setInput('');
};
return (
<div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={handleSubmit}>Send to Parent</button>
</div>
);
}3.4 组件状态(State)
函数组件中使用状态(useState)
import React, { useState } from 'react';
function Counter() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
{/* 使用函数更新状态(当新状态需要基于旧状态时) */}
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Safe Increment
</button>
</div>
);
}类组件中使用状态
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
// 错误:this.state.count 可能不是最新值
// this.setState({ count: this.state.count + 1 });
// 正确:使用函数形式确保获取最新状态
this.setState(prevState => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.increment}>
Click me
</button>
</div>
);
}
}四、React Hooks
4.1 useState
基本用法
import React, { useState } from 'react';
function Example() {
// 声明多个 state 变量
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Name: {name}</p>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
);
}状态更新注意事项
- 状态更新是异步的
- 当新状态依赖于先前状态时,使用函数式更新
- 对象和数组状态需要创建新引用
// 错误:直接修改状态
function updateName() {
const newName = { ...name };
newName.first = 'New';
setName(newName);
}
// 正确:返回新对象
function updateName() {
setName(prev => ({
...prev,
first: 'New'
}));
}4.2 useEffect
副作用处理
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
// 相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
document.title = `You clicked ${count} times`;
});
// 相当于 componentDidMount
useEffect(() => {
console.log('Component mounted');
// 相当于 componentWillUnmount
return () => {
console.log('Component will unmount');
};
}, []);
// 仅当 count 变化时执行
useEffect(() => {
console.log('Count changed');
}, [count]);
// 获取数据
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
};
fetchData();
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
{data ? <div>Data: {JSON.stringify(data)}</div> : <div>Loading...</div>}
</div>
);
}useEffect 使用规则:
- 不要在条件语句或循环中调用 Hook
- 只在 React 函数组件或自定义 Hook 中调用 Hook
- 确保正确设置依赖数组
4.3 useContext
跨组件传递数据
import React, { useState, useContext, createContext } from 'react';
// 创建 Context
const ThemeContext = createContext();
// 提供者组件
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 消费者组件
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#333' : '#fff' }}
onClick={toggleTheme}
>
Toggle Theme
</button>
);
}4.4 其他常用 Hooks
useReducer
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}useCallback 和 useMemo
import React, { useState, useCallback, useMemo } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 记忆化函数,避免不必要的重新创建
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
// 记忆化计算值
const expensiveValue = useMemo(() => {
return calculateExpensiveValue(count);
}, [count]);
return (
<div>
<ChildComponent
count={count}
increment={increment}
expensiveValue={expensiveValue}
/>
<input value={text} onChange={e => setText(e.target.value)} />
</div>
);
}
// 使用 React.memo 避免不必要的渲染
const ChildComponent = React.memo(({ count, increment, expensiveValue }) => {
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={increment}>Increment</button>
</div>
);
});五、列表与条件渲染
5.1 渲染列表
基本用法
import React from 'react';
function NumberList(props) {
const numbers = props.numbers;
// 使用 map 创建元素列表
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
// 直接在 JSX 中使用
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) => (
<li key={number.toString()}>
{number}
</li>
))}
</ul>
);
}5.2 key 属性
key 的作用:
- 帮助 React 识别哪些元素改变了、添加了或移除了
- 应该使用稳定且唯一的标识符(如 ID)
- 不要使用数组索引作为 key(除非列表是静态且不变的)
// 推荐:使用唯一ID作为key
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
// 不推荐:使用索引作为key(可能导致性能问题和状态问题)
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);5.3 条件渲染
if 语句
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}三元运算符
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<UserGreeting />
) : (
<GuestGreeting />
)}
</div>
);
}逻辑与操作符
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}六、表单处理
6.1 受控组件
基本表单
import React, { useState } from 'react';
function NameForm() {
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
alert('Submitted name: ' + name);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}处理多个输入
function Reservation() {
const [state, setState] = useState({
isGoing: true,
numberOfGuests: 2
});
const handleInputChange = (e) => {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
setState(prevState => ({
...prevState,
[name]: value
}));
};
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={state.isGoing}
onChange={handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={state.numberOfGuests}
onChange={handleInputChange} />
</label>
</form>
);
}6.2 表单验证
简单验证示例
function SignupForm() {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Email is invalid';
}
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
} else {
// 提交表单
console.log('Form submitted:', formData);
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
// 实时验证
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<label>Password:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <span className="error">{errors.password}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}七、路由管理
7.1 React Router 基础
安装 React Router
npm install react-router-dom7.2 路由配置
基本路由设置
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
</nav>
{/* 路由出口 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
</Routes>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
export default App;嵌套路由
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="users" element={<Users />}>
<Route path=":userId" element={<User />} />
<Route path="admin" element={<Admin />} />
</Route>
</Route>
</Routes>
</Router>
);
}
function Layout() {
return (
<div>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/users">Users</Link></li>
</ul>
</nav>
<hr />
{/* 嵌套路由的出口 */}
<Outlet />
</div>
);
}7.3 导航与参数
编程式导航
import { useNavigate } from 'react-router-dom';
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
// 模拟登录
const isLoggedIn = true;
if (isLoggedIn) {
navigate('/dashboard', { replace: true });
}
};
return (
<div>
<button onClick={handleLogin}>Login</button>
</div>
);
}获取路由参数
import { useParams } from 'react-router-dom';
function User() {
const { userId } = useParams();
return <h2>User ID: {userId}</h2>;
}
function Product() {
const { productId } = useParams();
const { category } = useParams();
return (
<div>
<h2>Product ID: {productId}</h2>
<p>Category: {category}</p>
</div>
);
}获取查询参数
import { useSearchParams } from 'react-router-dom';
function SearchResults() {
const [searchParams] = useSearchParams();
const query = searchParams.get('q');
const page = searchParams.get('page') || '1';
return (
<div>
<h2>Search Results for "{query}"</h2>
<p>Page: {page}</p>
</div>
);
}八、状态管理
8.1 Context API
创建和使用 Context
// 创建 Context
const ThemeContext = React.createContext();
// 创建 Provider 组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
const value = {
theme,
toggleTheme
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 使用 Context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
onClick={toggleTheme}
>
Toggle Theme
</button>
);
}
// 在应用中使用
function App() {
return (
<ThemeProvider>
<ThemedButton />
<ThemedComponent />
</ThemeProvider>
);
}组合多个 Context
function App() {
return (
<AuthProvider>
<ThemeContextProvider>
<LanguageContextProvider>
<Main />
</LanguageContextProvider>
</ThemeContextProvider>
</AuthProvider>
);
}
function Main() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}8.2 Redux 基础
安装 Redux
npm install @reduxjs/toolkit react-redux创建 Redux Store
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer
}
});连接 React 组件
// app.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './features/counter/counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
</div>
<input
type="number"
onChange={e => dispatch(incrementByAmount(Number(e.target.value)))}
/>
</div>
);
}九、实用技巧与最佳实践
9.1 组件设计原则
单一职责原则
- 一个组件只做一件事
- 将大组件拆分为更小的组件
// 不推荐:一个组件做太多事情
function UserProfile() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
useEffect(() => {
// 获取用户数据
// 获取帖子数据
// 获取评论数据
}, []);
// 渲染用户信息、帖子、评论等
}
// 推荐:拆分为多个专注的组件
function UserProfile() {
return (
<div>
<UserHeader />
<UserPosts />
<UserComments />
</div>
);
}组件组织结构
src/
├── components/
│ ├── common/
│ │ ├── Button.js
│ │ ├── Input.js
│ │ └── Modal.js
│ ├── layout/
│ │ ├── Header.js
│ │ ├── Footer.js
│ │ └── Sidebar.js
│ ├── features/
│ │ ├── UserProfile/
│ │ │ ├── UserProfile.js
│ │ │ ├── UserHeader.js
│ │ │ └── UserPosts.js
│ │ └── Dashboard/
│ │ ├── Dashboard.js
│ │ └── StatsCard.js
│ └── App.js
├── store/ # Redux 相关
├── hooks/ # 自定义 Hooks
├── utils/ # 工具函数
└── ...9.2 性能优化
避免不必要的渲染
// 使用 React.memo 包装组件
const UserItem = React.memo(({ user }) => {
return (
<li>{user.name}</li>
);
});
// 使用 useCallback 记忆化函数
const handleUserClick = useCallback((userId) => {
console.log(`User ${userId} clicked`);
}, []);
// 使用 useMemo 记忆化计算值
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => a.name.localeCompare(b.name));
}, [users]);代码分割
// 使用 React.lazy 和 Suspense 进行代码分割
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}列表优化
// 使用 key 优化列表渲染
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
// 使用 windowing 技术处理长列表
import { FixedSizeList as List } from 'react-window';
function Row({ index, style }) {
return (
<div style={style}>
Row {index}
</div>
);
}
function UserList({ users }) {
return (
<List
height={600}
itemCount={users.length}
itemSize={35}
width="100%"
>
{Row}
</List>
);
}9.3 错误处理
错误边界
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error caught by boundary:", error, errorInfo);
// 你可以在这里记录错误到错误报告服务
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<button onClick={() => window.location.reload()}>
Reload
</button>
</div>
);
}
return this.props.children;
}
}
// 使用错误边界
function App() {
return (
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
);
}