1. React 介绍
React是UI库,专注于视图层。它用JSX将HTML和JavaScript结合,提供最大的灵活性。React的Hooks和函数组件让逻辑复用更方便,但需要自己选择路由、状态管理等生态。
一、什么是 JSX?
JSX (JavaScript XML) 是 JavaScript 的语法扩展,它允许开发者在 JavaScript 代码中编写类似 HTML 的结构。
class App extends React.Component {
render() {
return(
<div>
<h1>{'Welcome to React world!'}</h1>
</div>
)
}
}
以上示例 render 方法中的 JSX 将会被转换为以下内容:
React.createElement("div", null, React.createElement(
"h1", null, 'Welcome to React world!'));
二、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'
);
}
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>;
二、类组件(Class Components)
- 简介:通过 ES6 类定义的组件,是 React 早期的主要方式。
- 特点:
- 可以维护自身状态(this.state)和生命周期方法。
- 需要显式绑定事件处理函数的 this。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
三. 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>
);
}
四. 高阶组件(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);
五. Render Props
- 简介:组件通过一个名为 render(或其他名称)的 prop 接收渲染逻辑函数。
- 注意事项
- 可以使用任意名称,不一定是 render(如 children)
- 注意性能优化,避免不必要的重新渲染
- 与 React.memo 结合使用时需小心
<DataProvider render={data => (
<ChildComponent data={data} />
)}/>
实现示例
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>
)}/>
如何选择哪种方式创建组件?
- 优先使用函数组件 + Hooks:现代 React 开发的主流方式,代码更简洁。
- 类组件:仅在维护旧项目或需要 Error Boundaries(React 的错误捕获组件,生周周期的
componentDidCatch) 时使用。 - 高阶组件/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>;
}
3.条件渲染⭐️
一、if/else 语句
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <UserGreeting />;
} else {
return <GuestGreeting />;
}
}
- 特点:
- 适合较大的条件分支
- 不能在 JSX 中直接使用
二、三元运算符
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <UserGreeting /> : <GuestGreeting />}
<p>You are {isLoggedIn ? 'logged in' : 'a guest'}.</p>
</div>
);
}
- 特点:
- 可以直接嵌入 JSX
- 嵌套过多会降低可读性
三、逻辑与运算符 (&&)
function Mailbox({ unreadMessages }) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && (
<h2>You have {unreadMessages.length} unread messages.</h2>
)}
</div>
);
}
- 特点:
- 注意:左侧表达式必须为布尔值(避免 0 &&
<Component />的问题) - 嵌套过多会降低可读性
- 注意:左侧表达式必须为布尔值(避免 0 &&
4.在哪个生命周期发起Ajax请求⭐️
一、类组件
1. componentDidMount (最常用)
- 组件挂载完成后立即调用
- 适合初始数据加载
class MyComponent extends React.Component {
componentDidMount() {
fetch('/api/data')
.then(res => res.json())
.then(data => this.setState({ data }));
}
}
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 }));
}
}
}
二、函数组件,使用 useEffect Hook
1. 初始数据加载 (相当于componentDidMount)
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []); // 空依赖数组表示只在挂载时运行一次
}
2. props变化时重新获取数据 (相当于componentDidUpdate)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // userId变化时重新运行
}
5.最外层可以不包裹 div 标签
一、React Fragments(推荐方案)
React 16.2+ 提供了专门的 Fragment 语法:
// 简写语法(最常用)
const Component = () => (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
// 完整写法
const Component = () => (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
二、数组返回(React 16+)
const Component = () => [
<ChildA key="a" />,
<ChildB key="b" />,
<ChildC key="c" />
];
- 注意:
- 必须为每个元素添加 key
- 某些情况下样式处理可能更复杂
6.setState 传参的方式⭐️
一. 类组件中的 setState
1. 基本用法 - 直接传入新状态对象
this.setState({ count: 1 });
2. 基于前一个状态的更新 - 传入函数
this.setState((prevState, props) => {
// prevState 是之前的状态
// props 是当前的 props
return { count: prevState.count + 1 };
});
- 使用场景:
- 当新状态依赖于前一个状态时
- 避免状态更新合并导致的错误
3. 第二个参数 - 回调函数
this.setState(
{ count: 1 },
() => {
// 状态更新完成后的回调
console.log('状态已更新', this.state.count);
}
);
二. 函数组件中的 useState
1. 基本用法 - 直接传入新值
const [count, setCount] = useState(0);
// 直接设置新值
setCount(1);
2. 基于前一个状态的更新 - 传入函数
setCount(prevCount => prevCount + 1);
- 使用场景:
- 当新状态依赖于前一个状态时
- 在闭包中获取最新状态值
三. 对象状态更新的注意事项
1. 合并更新(类组件)
state = { name: 'John', age: 25 };
// 只更新 age,name 保持不变
this.setState({ age: 26 });
2. 展开运算符更新对象(函数组件)
const [user, setUser] = useState({ name: 'John', age: 25 });
// 更新 age,保留其他属性
setUser(prevUser => ({ ...prevUser, age: 26 }));
五. 常见问题解决方案
1:连续多次 setState 不生效
// 函数组件中
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1); // 不会累加
};
正确解决方案:
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 现在会正确累加
};
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>;
}
}
- 原因: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>;
}
}
- 原因:
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>;
}
}
- 用途:在需要立即获取最新状态时使用(如动画、测量布局)。
8.获取 setState 更新后的值
一、class组件
1. setState 回调函数(推荐)
this.setState(
{ count: this.state.count + 1 },
() => {
// 在这里可以获取更新后的state
console.log('更新后的值:', this.state.count);
// 可以在这里执行依赖于新state的操作
}
);
2. 使用componentDidUpdate生命周期
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('count已更新:', this.state.count);
// 可以在这里执行副作用操作
}
}
3. 使用函数式setState(确保基于最新状态)
this.setState(prevState => {
const newCount = prevState.count + 1;
// 这里可以立即使用新值进行计算
console.log('计算中的新值:', newCount);
return { count: newCount };
});
二、函数组件
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>
);
}
2. 使用useState的函数式更新
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => {
const newCount = prevCount + 1;
console.log('新值:', newCount); // 立即获取新值
return newCount;
});
};
三、强制同步获取更新后的值(不推荐)
1. 使用ReactDOM.flushSync(React 18+)
import { flushSync } from 'react-dom';
// 强制同步更新
flushSync(() => {
this.setState({ count: 42 });
});
console.log(this.state.count); // 立即获取新值
2. 在setTimeout/Promise/原生事件中
setTimeout(() => {
this.setState({ count: 100 });
console.log(this.state.count); // 同步获取
}, 0);
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. 动态路由参数
<Route path="/users/:id" element={<UserDetail />} />
// 在组件中获取参数
function UserDetail() {
const { id } = useParams();
return <div>User ID: {id}</div>;
}
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>
);
}
3. 默认嵌套路由
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<DashboardHome />} /> {/* 默认子路由 */}
<Route path="profile" element={<Profile />} />
</Route>
四、编程式导航
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>;
}
2. 导航传参
// 传递 state
navigate("/user", { state: { from: "/login" } });
// 接收 state
const location = useLocation();
console.log(location.state?.from); // "/login"
五、路由守卫(权限控制)
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>
}
/>
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. 如何实现路由懒加载?
const LazyComponent = React.lazy(() => import("./Component"));
<Route
path="/lazy"
element={
<React.Suspense fallback={<Spinner />}>
<LazyComponent />
</React.Suspense>
}
/>
2. 如何监听路由变化?
useEffect(() => {
const unlisten = navigation.listen((location) => {
console.log("Route changed to:", location.pathname);
});
return () => unlisten(); // 清理监听
}, [navigation]);
10.高阶组件(HOC)
高阶组件(Higher-Order Component,HOC)是 React 中用于复用组件逻辑的高级技术,它本质上是一个函数,接收一个组件并返回一个新的增强组件。
一、核心概念
1. 基本结构
const EnhancedComponent = higherOrderComponent(WrappedComponent);
2. 类比高阶函数
就像高阶函数接收/返回函数一样,HOC 接收/返回组件:
// 高阶函数示例
const twice = f => x => f(f(x));
// 高阶组件示例
const withLogging = Component => props => {
console.log('Rendered:', Component.name);
return <Component {...props} />;
};
二、实现方式
1. 属性代理(Props Proxy)
// 最常见的 HOC 形式:
function withExtraProps(WrappedComponent) {
return function EnhancedComponent(props) {
const extraProps = { user: currentUser };
return <WrappedComponent {...props} {...extraProps} />;
};
}
2. 继承反转(Inheritance Inversion)
// 通过继承操纵渲染:
function withTheme(WrappedComponent) {
return class extends WrappedComponent {
render() {
return (
<div style={{ color: this.props.themeColor }}>
{super.render()}
</div>
);
}
};
}
三、常见应用场景
1. 属性增强
const withUser = Component => props => (
<Component {...props} user={getCurrentUser()} />
);
2. 逻辑复用
const withLoading = Component => props =>
props.isLoading ? <Spinner /> : <Component {...props} />;
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}
/>
);
}
};
};
11.React.memo
React.memo 是一个用于优化功能组件性能的高阶组件。如果一个组件被包裹在 React.memo 中,当该组件的 props 不变时,React 会跳过对该组件及其子组件的渲染。
memo(Component, arePropsEqual?)
Component:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件。
可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true。否则返回 false。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop。(浅比较)
简单的使用例子
import React, { useState } from 'react';
// 定义一个计算开销比较大的组件
const ExpensiveComponent = React.memo(({ value }) => {
console.log('ExpensiveComponent rendered');
// 模拟一些昂贵的计算
const result = value * 2; // 这是一个简单的计算,实际上可以是更复杂的渲染逻辑
return <div>Result: {result}</div>;
});
const App = () => {
const [count, setCount] = useState(0);
const [value, setValue] = useState(1);
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment Counter</button>
<h2>Value: {value}</h2>
<button onClick={() => setValue(value + 1)}>Increment Value</button>
{/* 只有当 value 改变时,ExpensiveComponent 才会重新渲染 */}
<ExpensiveComponent value={value} />
</div>
);
};
export default App;
当点击 “Increment Counter” 按钮时,count 发生变化,组件 App 将重新渲染,但 ExpensiveComponent 因为 value 没有变化而不会重新渲染。这归功于 React.memo 的性能优化。
只有当点击 “Increment Value” 按钮时,value 改变,ExpensiveComponent 才会重新渲染。
memo可能无效?
import React, { memo, useState } from 'react';
// 子组件 - 使用 memo
const Child = memo(({ onClick }) => {
console.log('🔵 Child 重新渲染了!');
});
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
// ❌ 不使用 useCallback
const handleClick = () => {
console.log('按钮被点击');
};
console.log('🟡 Parent 重新渲染了!');
return (
<div>
<button onClick={() => setCount(count + 1)}>
增加计数: {count}
</button>
<Child onClick={handleClick} />
</div>
);
};
结果:每次点击"增加计数"按钮,控制台都会输出:
🟡 Parent 重新渲染了!
🔵 Child 重新渲染了!
⚡ 根本原因:函数引用每次都是新的
// 每次 Parent 重新渲染时
const Parent = () => {
// 这行代码会执行
const handleClick = () => { // <- 每次都是全新的函数!
console.log('按钮被点击');
};
// 然后传递给 Child
return <Child onClick={handleClick} />;
};
- 关键理解:
- 函数是引用类型(对象)
- 即使函数体完全一样,每次重新创建的函数都是不同的引用
- React.memo 进行的是浅比较(===比较)
🔧 解决方案
- 方案1:将函数移到组件外部
// ✅ 将函数移出组件
const handleClick = () => {
console.log('点击');
};
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>计数: {count}</button>
<Child onClick={handleClick} /> {/* 引用稳定 */}
</div>
);
};
- 方案2:使用 useCallback(推荐)
// ✅ 使用 useCallback
import React, { memo, useState, useCallback } from 'react';
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('点击');
}, []); // 依赖为空,函数引用稳定
return (
<div>
<button onClick={() => setCount(count + 1)}>计数: {count}</button>
<Child onClick={handleClick} /> {/* 不会重新渲染 */}
</div>
);
};
使用 React.memo 的场景
- 场景1:昂贵的渲染组件,如图表、长列表等
// ✅ 计算密集型组件
const ExpensiveChart = memo(({ data }) => {
console.log('📊 渲染复杂图表...');
// 模拟复杂计算
const result = useMemo(() => {
return calculateChartData(data); // 耗时计算
}, [data]);
return <ComplexChart data={result} />;
});
// 父组件
const Dashboard = () => {
const [filters, setFilters] = useState({});
const [refreshKey, setRefreshKey] = useState(0);
return (
<div>
<Filters onChange={setFilters} />
<button onClick={() => setRefreshKey(k => k + 1)}>
刷新界面
</button>
{/* 只有 data 变化时才重新渲染图表 */}
<ExpensiveChart data={filters.chartData} />
</div>
);
};
- 场景2:纯展示组件
// ✅ 纯展示组件,无状态
const UserProfile = memo(({ user, theme }) => {
console.log('👤 渲染用户资料');
return (
<div className={`profile ${theme}`}>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
<Stats stats={user.stats} />
</div>
);
});
// 使用
const App = () => {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(initialUser);
return (
<>
<ThemeToggle onChange={setTheme} />
{/* 切换主题不会导致 UserProfile 重新渲染 */}
<UserProfile user={user} theme={theme} />
</>
);
};
React.memo 是性能优化的工具,不是默认行为。只有在确实存在性能问题,且 memo 能解决时才使用。过早优化可能会导致代码复杂度增加,而收益甚微。
