# 1. React 介绍
React 是一个用于构建用户界面的 JavaScript 库(而非框架),由 Facebook 开发并开源。
# 一、什么是 JSX?
JSX (JavaScript XML) 是 JavaScript 的语法扩展,它允许开发者在 JavaScript 代码中编写类似 HTML 的结构。
class App extends React.Component {
render() {
return(
<div>
<h1>{'Welcome to React world!'}</h1>
</div>
)
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
以上示例 render 方法中的 JSX 将会被转换为以下内容:
React.createElement("div", null, React.createElement(
"h1", null, 'Welcome to React world!'));
1
2
2
# 二、React开发是否必须使用 JSX?
答案是不必须,但强烈推荐使用。以下是详细分析
# 不使用 JSX 的替代方案
使用纯 JavaScript 调用 React.createElement:
// 用 JSX 编写的组件
function HelloJSX() {
return <div className="greeting">Hello</div>;
}
// 等效的不使用 JSX 写法
function HelloNoJSX() {
return React.createElement(
'div',
{ className: 'greeting' },
'Hello'
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 2.React 创建组件的方式
# 一、函数组件(Function Components)
- 简介:通过 JavaScript 函数定义的组件,是 React 推荐的主流方式(尤其是配合 Hooks 后)。
- 特点:
- 简洁,适合无状态或使用 Hooks 管理状态的组件。
- 性能较好(无类组件的实例化开销)。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 或使用箭头函数
const Welcome = (props) => <h1>Hello, {props.name}</h1>;
1
2
3
4
5
2
3
4
5
# 二、类组件(Class Components)
- 简介:通过 ES6 类定义的组件,是 React 早期的主要方式。
- 特点:
- 可以维护自身状态(this.state)和生命周期方法。
- 需要显式绑定事件处理函数的 this。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
1
2
3
4
5
2
3
4
5
# 三. React Hooks(函数组件的增强)
- 简介:Hooks 允许函数组件使用状态和其他 React 特性(如生命周期)。
- 常用 Hooks:
- useState:管理组件状态。
- useEffect:处理副作用(替代生命周期)。
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 四. 高阶组件(HOC, Higher-Order Component)
- 简介:一种复用组件逻辑的模式(接收组件,返回增强后的组件)。
- 用途:例如权限控制、日志记录等跨组件逻辑。
- 核心特点
- 不修改原组件,而是通过组合的方式增强功能
- 透传 props(需要手动处理 props 传递)
- 通常以 with 开头命名(如 withRouter)
function withLoading(WrappedComponent) {
return function(props) {
const [loading, setLoading] = useState(true);
useEffect(() => {
// 模拟数据加载
setTimeout(() => setLoading(false), 1000);
}, []);
return loading ? <div>Loading...</div> : <WrappedComponent {...props} />;
};
}
// 使用
const EnhancedComponent = withLoading(MyComponent);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 五. Render Props
- 简介:组件通过一个名为 render(或其他名称)的 prop 接收渲染逻辑函数。
- 注意事项
- 可以使用任意名称,不一定是 render(如 children)
- 注意性能优化,避免不必要的重新渲染
- 与 React.memo 结合使用时需小心
<DataProvider render={data => (
<ChildComponent data={data} />
)}/>
1
2
3
2
3
实现示例
function MouseTracker(props) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove}>
{props.render(position)}
</div>
);
}
// 使用
<MouseTracker render={({x, y}) => (
<div>
Mouse position: {x}, {y}
</div>
)}/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 如何选择哪种方式创建组件?
- 优先使用函数组件 + Hooks:现代 React 开发的主流方式,代码更简洁。
- 类组件:仅在维护旧项目或需要 Error Boundaries 时使用。
- 高阶组件/Render Props:复杂逻辑复用时考虑。
- 随着 React Hooks 的普及,许多 HOC 和 Render Props 的场景可以用自定义 Hook 更简洁地实现:
// 自定义 Hook 替代 MouseTracker 的 Render Props
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return position;
}
// 使用
function App() {
const { x, y } = useMousePosition();
return <div>Mouse position: {x}, {y}</div>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.条件渲染
# 一、if/else 语句
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <UserGreeting />;
} else {
return <GuestGreeting />;
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 特点:
- 适合较大的条件分支
- 不能在 JSX 中直接使用
# 二、三元运算符
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <UserGreeting /> : <GuestGreeting />}
<p>You are {isLoggedIn ? 'logged in' : 'a guest'}.</p>
</div>
);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 特点:
- 可以直接嵌入 JSX
- 嵌套过多会降低可读性
# 三、逻辑与运算符 (&&)
function Mailbox({ unreadMessages }) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && (
<h2>You have {unreadMessages.length} unread messages.</h2>
)}
</div>
);
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 特点:
- 注意:左侧表达式必须为布尔值(避免 0 &&
<Component />的问题) - 嵌套过多会降低可读性
- 注意:左侧表达式必须为布尔值(避免 0 &&
# 四. 立即执行函数 (IIFE)
function Greeting({ user }) {
return (
<div>
{(() => {
if (user.firstName) {
return <p>Hello, {user.firstName}!</p>;
} else if (user.isGuest) {
return <p>Welcome, Guest!</p>;
} else {
return <p>Please sign in.</p>;
}
})()}
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 特点:
- 可读性较差
- 现代 React 中较少使用
# 4.在哪个生命周期发起Ajax请求
# 一、类组件
# 1. componentDidMount (最常用)
- 组件挂载完成后立即调用
- 适合初始数据加载
class MyComponent extends React.Component {
componentDidMount() {
fetch('/api/data')
.then(res => res.json())
.then(data => this.setState({ data }));
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2. componentDidUpdate (用于props/state变化时)
- 在更新发生后立即调用
- 适合根据props变化重新获取数据
class MyComponent extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.userId !== prevProps.userId) {
fetch(`/api/users/${this.props.userId}`)
.then(res => res.json())
.then(user => this.setState({ user }));
}
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 二、函数组件,使用 useEffect Hook
# 1. 初始数据加载 (相当于componentDidMount)
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []); // 空依赖数组表示只在挂载时运行一次
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 2. props变化时重新获取数据 (相当于componentDidUpdate)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // userId变化时重新运行
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 5.最外层可以不包裹 div 标签
# 一、React Fragments(推荐方案)
React 16.2+ 提供了专门的 Fragment 语法:
// 简写语法(最常用)
const Component = () => (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
// 完整写法
const Component = () => (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 二、数组返回(React 16+)
const Component = () => [
<ChildA key="a" />,
<ChildB key="b" />,
<ChildC key="c" />
];
1
2
3
4
5
2
3
4
5
- 注意:
- 必须为每个元素添加 key
- 某些情况下样式处理可能更复杂
# 6.setState 传参的方式
# 一. 类组件中的 setState
# 1. 基本用法 - 直接传入新状态对象
this.setState({ count: 1 });
1
# 2. 基于前一个状态的更新 - 传入函数
this.setState((prevState, props) => {
// prevState 是之前的状态
// props 是当前的 props
return { count: prevState.count + 1 };
});
1
2
3
4
5
2
3
4
5
- 使用场景:
- 当新状态依赖于前一个状态时
- 避免状态更新合并导致的错误
# 3. 第二个参数 - 回调函数
this.setState(
{ count: 1 },
() => {
// 状态更新完成后的回调
console.log('状态已更新', this.state.count);
}
);
1
2
3
4
5
6
7
2
3
4
5
6
7
# 二. 函数组件中的 useState
# 1. 基本用法 - 直接传入新值
const [count, setCount] = useState(0);
// 直接设置新值
setCount(1);
1
2
3
2
3
# 2. 基于前一个状态的更新 - 传入函数
setCount(prevCount => prevCount + 1);
1
- 使用场景:
- 当新状态依赖于前一个状态时
- 在闭包中获取最新状态值
# 三. 对象状态更新的注意事项
# 1. 合并更新(类组件)
state = { name: 'John', age: 25 };
// 只更新 age,name 保持不变
this.setState({ age: 26 });s
1
2
3
2
3
# 2. 展开运算符更新对象(函数组件)
const [user, setUser] = useState({ name: 'John', age: 25 });
// 更新 age,保留其他属性
setUser(prevUser => ({ ...prevUser, age: 26 }));
1
2
3
2
3
# 五. 常见问题解决方案
# 1:连续多次 setState 不生效
// 函数组件中
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1); // 不会累加
};
1
2
3
4
5
6
2
3
4
5
6
正确解决方案:
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 现在会正确累加
};
1
2
3
4
2
3
4
# 7.setState同步还是异步
# 正常情况下:setState 是异步的
在 React 的生命周期或合成事件(如 onClick、onChange) 中调用 setState 时,React 会批量处理更新,表现为异步行为:
class Example extends React.Component {
state = { count: 0 };
handleClick = () => {
console.log("Before setState:", this.state.count); // 0
this.setState({ count: this.state.count + 1 });
console.log("After setState:", this.state.count); // 0(仍未更新)
};
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- 原因:React 会合并多个 setState 调用,并在稍后的某个时间点统一更新状态(批量更新)。
- React 为什么设计为异步?
- 性能优化:避免频繁渲染,合并多个 setState 减少不必要的 re-render。
- 保证一致性:在事件处理中,确保所有 setState 调用完成后才触发渲染,避免中间状态不一致。
# 特殊情况:setState 是同步的
在某些情况下,setState 会立即执行,表现为同步行为:
- 在React 16+的Fiber架构中:
- 即使同步更新也不会立即刷新DOM
- 但state的值会立即更新(可同步获取)
- DOM更新仍然会被合理调度
# (1) 在 setTimeout、Promise、原生DOM事件中调用
class Example extends React.Component {
state = { count: 0 };
componentDidMount() {
// 原生事件 - 同步更新
document.getElementById('btn').addEventListener('click', () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 输出新值
});
}
handleClick = () => {
setTimeout(() => {
console.log("Before setState:", this.state.count); // 0
this.setState({ count: this.state.count + 1 });
console.log("After setState:", this.state.count); // 1(同步更新)
}, 0);
};
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 原因:
setTimeout脱离了 React 的批处理机制,导致 setState 立即执行。
# (2) 使用 ReactDOM.flushSync 强制同步更新
import { flushSync } from "react-dom";
class Example extends React.Component {
state = { count: 0 };
handleClick = () => {
flushSync(() => {
this.setState({ count: 1 });
});
console.log("After flushSync:", this.state.count); // 1(同步)
};
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 用途:在需要立即获取最新状态时使用(如动画、测量布局)。
# 8.获取 setState 更新后的值
# 一、class组件
# 1. setState 回调函数(推荐)
this.setState(
{ count: this.state.count + 1 },
() => {
// 在这里可以获取更新后的state
console.log('更新后的值:', this.state.count);
// 可以在这里执行依赖于新state的操作
}
);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2. 使用componentDidUpdate生命周期
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('count已更新:', this.state.count);
// 可以在这里执行副作用操作
}
}
1
2
3
4
5
6
2
3
4
5
6
# 3. 使用函数式setState(确保基于最新状态)
this.setState(prevState => {
const newCount = prevState.count + 1;
// 这里可以立即使用新值进行计算
console.log('计算中的新值:', newCount);
return { count: newCount };
});
1
2
3
4
5
6
2
3
4
5
6
# 二、函数组件
# 1. 使用useEffect Hook
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count已更新:', count);
// 这里可以执行副作用操作
}, [count]); // 依赖count变化
return (
<button onClick={() => setCount(c => c + 1)}>
点击我: {count}
</button>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 2. 使用useState的函数式更新
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => {
const newCount = prevCount + 1;
console.log('新值:', newCount); // 立即获取新值
return newCount;
});
};
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 三、强制同步获取更新后的值(不推荐)
# 1. 使用ReactDOM.flushSync(React 18+)
import { flushSync } from 'react-dom';
// 强制同步更新
flushSync(() => {
this.setState({ count: 42 });
});
console.log(this.state.count); // 立即获取新值
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2. 在setTimeout/Promise/原生事件中
setTimeout(() => {
this.setState({ count: 100 });
console.log(this.state.count); // 同步获取
}, 0);
1
2
3
4
2
3
4
# 9.React Router(V6)
# 一、核心概念
# 1. 路由类型
| 生命周期阶段 | 选项式 API |
|---|---|
| BrowserRouter | 基于 HTML5 History API(推荐) |
| HashRouter | 基于 URL Hash(兼容旧浏览器) |
| MemoryRouter | 测试或非浏览器环境(如 React Native) |
# 2. 基础组件
| 组件 | 作用 |
|---|---|
<Routes> | 路由容器(v6 新增,替代 <Switch>) |
<Route> | 定义路由规则 |
<Link> / <NavLink> | 导航链接(NavLink 支持高亮) |
<Outlet> | 嵌套路由的占位符 |
useNavigate | 编程式导航(替代 useHistory) |
useParams | 获取动态路由参数 |
# 二、基本示例
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* 404 页面 */}
</Routes>
</BrowserRouter>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 三、嵌套路由
# 1. 动态路由参数
<Route path="/users/:id" element={<UserDetail />} />
// 在组件中获取参数
function UserDetail() {
const { id } = useParams();
return <div>User ID: {id}</div>;
}
1
2
3
4
5
6
2
3
4
5
6
# 2. 配置嵌套路由
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
// Dashboard.jsx
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to="profile">Profile</Link>
<Link to="settings">Settings</Link>
</nav>
<Outlet /> {/* 子路由将渲染在这里 */}
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3. 默认嵌套路由
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<DashboardHome />} /> {/* 默认子路由 */}
<Route path="profile" element={<Profile />} />
</Route>
1
2
3
4
2
3
4
# 四、编程式导航
# 1. 使用 useNavigate
import { useNavigate } from "react-router-dom";
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
navigate("/dashboard", { replace: true }); // replace 替换当前历史记录
};
return <button onClick={handleLogin}>Login</button>;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2. 导航传参
// 传递 state
navigate("/user", { state: { from: "/login" } });
// 接收 state
const location = useLocation();
console.log(location.state?.from); // "/login"
1
2
3
4
5
2
3
4
5
# 五、路由守卫(权限控制)
# 1. 封装 ProtectedRoute
function ProtectedRoute({ children }) {
const { user } = useAuth(); // 假设有权限钩子
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Route
path="/admin"
element={
<ProtectedRoute>
<AdminPanel />
</ProtectedRoute>
}
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. 全局路由拦截
function RouterWrapper() {
const location = useLocation();
const { isAuthenticated } = useAuth();
if (!isAuthenticated && location.pathname !== "/login") {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <Outlet />;
}
// 在根路由中使用
<Route element={<RouterWrapper />}>
<Route path="/" element={<Home />} />
<Route path="/admin" element={<AdminPanel />} />
</Route>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 常见问题
# 1. 如何实现路由懒加载?
const LazyComponent = React.lazy(() => import("./Component"));
<Route
path="/lazy"
element={
<React.Suspense fallback={<Spinner />}>
<LazyComponent />
</React.Suspense>
}
/>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 2. 如何监听路由变化?
useEffect(() => {
const unlisten = navigation.listen((location) => {
console.log("Route changed to:", location.pathname);
});
return () => unlisten(); // 清理监听
}, [navigation]);
1
2
3
4
5
6
2
3
4
5
6
# 10.高阶组件(HOC)
高阶组件(Higher-Order Component,HOC)是 React 中用于复用组件逻辑的高级技术,它本质上是一个函数,接收一个组件并返回一个新的增强组件。
# 一、核心概念
# 1. 基本结构
const EnhancedComponent = higherOrderComponent(WrappedComponent);
1
# 2. 类比高阶函数
就像高阶函数接收/返回函数一样,HOC 接收/返回组件:
// 高阶函数示例
const twice = f => x => f(f(x));
// 高阶组件示例
const withLogging = Component => props => {
console.log('Rendered:', Component.name);
return <Component {...props} />;
};
1
2
3
4
5
6
7
2
3
4
5
6
7
# 二、实现方式
# 1. 属性代理(Props Proxy)
// 最常见的 HOC 形式:
function withExtraProps(WrappedComponent) {
return function EnhancedComponent(props) {
const extraProps = { user: currentUser };
return <WrappedComponent {...props} {...extraProps} />;
};
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2. 继承反转(Inheritance Inversion)
// 通过继承操纵渲染:
function withTheme(WrappedComponent) {
return class extends WrappedComponent {
render() {
return (
<div style={{ color: this.props.themeColor }}>
{super.render()}
</div>
);
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 三、常见应用场景
# 1. 属性增强
const withUser = Component => props => (
<Component {...props} user={getCurrentUser()} />
);
1
2
3
2
3
# 2. 逻辑复用
const withLoading = Component => props =>
props.isLoading ? <Spinner /> : <Component {...props} />;
1
2
2
# 3. 状态抽象
const withToggle = Component => {
return class extends React.Component {
state = { isOn: false };
toggle = () => this.setState({ isOn: !this.state.isOn });
render() {
return (
<Component
{...this.props}
isOn={this.state.isOn}
toggle={this.toggle}
/>
);
}
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16