Appearance
第13章:React 进阶特性(提升开发效率)
React 提供了许多进阶特性,这些特性可以帮助我们编写更高效、更可维护的代码。本章将介绍一些常用的 React 进阶特性,包括组件复用技巧、性能优化、错误边界、懒加载和 Portals 等。
13.1 组件复用技巧(自定义Hooks、高阶组件HOC,了解即可)
13.1.1 自定义 Hooks
自定义 Hooks 是 React 16.8+ 引入的特性,它允许我们将组件逻辑提取到可重用的函数中。自定义 Hooks 的命名必须以 use 开头,这样 React 才能识别它们。
示例:创建一个 useCounter 自定义 Hook
jsx
// src/hooks/useCounter.js
import { useState, useCallback } from 'react';
export function useCounter(initialValue = 0, step = 1) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => {
setCount(prevCount => prevCount + step);
}, [step]);
const decrement = useCallback(() => {
setCount(prevCount => prevCount - step);
}, [step]);
const reset = useCallback(() => {
setCount(initialValue);
}, [initialValue]);
return { count, increment, decrement, reset };
}使用自定义 Hook
jsx
import { useCounter } from '../hooks/useCounter';
function Counter() {
const { count, increment, decrement, reset } = useCounter(0, 1);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}
export default Counter;13.1.2 高阶组件(HOC)
高阶组件是一种设计模式,它接收一个组件并返回一个新的组件。HOC 可以用于添加额外的功能,如状态管理、认证、日志记录等。
示例:创建一个 withAuth 高阶组件
jsx
// src/hocs/withAuth.js
import React from 'react';
function withAuth(Component) {
return function WithAuthComponent(props) {
const isAuthenticated = localStorage.getItem('token') !== null;
if (!isAuthenticated) {
return <div>请先登录</div>;
}
return <Component {...props} />;
};
}
export default withAuth;使用高阶组件
jsx
import withAuth from '../hocs/withAuth';
function ProtectedPage() {
return <div>这是受保护的页面</div>;
}
// 使用 withAuth 包装组件
export default withAuth(ProtectedPage);13.2 memo、useMemo、useCallback(性能优化基础)
React 提供了几种性能优化的方法,包括 memo、useMemo 和 useCallback。这些方法可以帮助我们减少不必要的渲染,提高应用性能。
13.2.1 memo:组件缓存
memo 是一个高阶组件,它可以缓存组件的渲染结果,只有当组件的 props 发生变化时才重新渲染。
示例:使用 memo
jsx
import React, { memo } from 'react';
// 当 props 不变时,这个组件不会重新渲染
const ExpensiveComponent = memo(({ data }) => {
console.log('ExpensiveComponent 渲染');
// 模拟昂贵的计算
const expensiveResult = data.reduce((acc, item) => acc + item, 0);
return <div>结果: {expensiveResult}</div>;
});
function App() {
const [count, setCount] = useState(0);
const [data] = useState([1, 2, 3, 4, 5]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveComponent data={data} />
</div>
);
}
export default App;13.2.2 useMemo:计算结果缓存
useMemo 是一个 Hook,它可以缓存计算结果,只有当依赖项发生变化时才重新计算。
示例:使用 useMemo
jsx
import React, { useState, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0);
const [data] = useState([1, 2, 3, 4, 5]);
// 只有当 data 变化时才重新计算
const expensiveResult = useMemo(() => {
console.log('计算 expensiveResult');
return data.reduce((acc, item) => acc + item, 0);
}, [data]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<div>结果: {expensiveResult}</div>
</div>
);
}
export default App;13.2.3 useCallback:函数缓存
useCallback 是一个 Hook,它可以缓存函数,只有当依赖项发生变化时才重新创建函数。
示例:使用 useCallback
jsx
import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 只有当 count 变化时才重新创建 handleClick 函数
const handleClick = useCallback(() => {
console.log('点击了按钮,count:', count);
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="输入文本" />
<ChildComponent onClick={handleClick} />
</div>
);
}
export default App;
// ChildComponent.js
import React, { memo } from 'react';
const ChildComponent = memo(({ onClick }) => {
console.log('ChildComponent 渲染');
return <button onClick={onClick}>点击我</button>;
});
export default ChildComponent;13.3 错误边界(Error Boundary,捕获组件错误)
错误边界是一种 React 组件,它可以捕获其子组件树中的 JavaScript 错误,记录错误,并显示备用 UI,而不是让整个应用崩溃。
创建错误边界组件
jsx
// src/components/ErrorBoundary.js
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// 更新状态,下次渲染时显示备用 UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 可以在这里记录错误信息
console.error('错误边界捕获到错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
// 自定义备用 UI
return (
<div style={{ padding: '20px', backgroundColor: '#ffebee' }}>
<h2>出错了</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
重试
</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;使用错误边界
jsx
import React, { useState } from 'react';
import ErrorBoundary from './components/ErrorBoundary';
function BuggyComponent() {
const [count, setCount] = useState(0);
if (count === 5) {
// 模拟错误
throw new Error('故意触发的错误');
}
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
function App() {
return (
<div>
<h1>错误边界示例</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
export default App;13.4 懒加载(React.lazy、Suspense,优化页面加载速度)
React 提供了 React.lazy 和 Suspense 来实现组件的懒加载,这可以减小初始 bundle 的大小,提高页面加载速度。
13.4.1 基本使用
jsx
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route, Link } 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>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/contact">联系我们</Link>
</nav>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
export default App;13.4.2 代码分割策略
- 按路由分割:每个路由对应一个代码块
- 按功能分割:将大型功能模块分割成单独的代码块
- 按组件分割:将不常用的组件懒加载
13.5 Portals(传送门,解决DOM层级问题,如弹窗)
Portals 提供了一种将子组件渲染到父组件 DOM 层次结构之外的 DOM 节点的方法。这对于弹窗、模态框等需要突破父组件样式限制的场景非常有用。
示例:创建一个 Modal 组件
jsx
// src/components/Modal.js
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose, children }) {
useEffect(() => {
// 当模态框打开时,禁止背景滚动
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="close-button" onClick={onClose}>
×
</button>
{children}
</div>
</div>,
document.getElementById('modal-root') // 目标 DOM 节点
);
}
export default Modal;使用 Modal 组件
jsx
import React, { useState } from 'react';
import Modal from './components/Modal';
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<button onClick={() => setIsModalOpen(true)}>打开模态框</button>
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
<h2>模态框标题</h2>
<p>这是模态框的内容</p>
</Modal>
</div>
);
}
export default App;HTML 结构
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!-- 用于 Portal 的目标节点 -->
<div id="modal-root"></div>
</body>
</html>小结
本章介绍了 React 的一些进阶特性,包括:
- 组件复用技巧:自定义 Hooks 和高阶组件
- 性能优化:memo、useMemo 和 useCallback
- 错误边界:捕获和处理组件错误
- 懒加载:使用 React.lazy 和 Suspense 优化页面加载速度
- Portals:解决 DOM 层级问题,适用于弹窗等场景
这些特性可以帮助我们编写更高效、更可维护的 React 应用。在实际项目中,我们应该根据具体需求选择合适的特性来使用,以达到最佳的开发效率和用户体验。
