组件生命周期详解
生命周期定义:组件生命周期是指组件从创建到销毁的整个过程。React提供了多个钩子方法,让我们可以在组件的不同阶段执行特定的操作。
class LifecycleExample extends React.Component {
constructor(props) {
super(props);
console.log('1. constructor - 组件构造函数');
this.state = { count: 0 };
}
componentDidMount() {
console.log('3. componentDidMount - 组件已挂载');
// 在这里执行副作用操作
document.title = '组件已挂载';
// 发起网络请求
this.fetchData();
}
fetchData() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
this.setState({ data });
});
}
render() {
console.log('2. render - 渲染组件');
return <div>Count: {this.state.count}</div>;
}
}
class UpdatingExample extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate - 是否应该更新');
// 返回false可以阻止组件更新
return nextState.count !== this.state.count;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate - 更新前获取快照');
// 返回一个值,将传递给componentDidUpdate
return { scrollPosition: window.scrollY };
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate - 组件已更新');
if (snapshot) {
console.log('更新前的滚动位置:', snapshot.scrollPosition);
}
// 在这里执行更新后的副作用操作
if (this.props.user !== prevProps.user) {
this.fetchUserData(this.props.user);
}
}
render() {
return <div>Count: {this.state.count}</div>;
}
}
class UnmountingExample extends React.Component {
state = { timer: null };
componentDidMount() {
// 设置定时器
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
this.setState({ timer });
}
componentWillUnmount() {
console.log('componentWillUnmount - 组件即将卸载');
// 清理副作用
if (this.state.timer) {
clearInterval(this.state.timer);
}
// 取消网络请求
if (this.abortController) {
this.abortController.abort();
}
// 清理事件监听器
window.removeEventListener('resize', this.handleResize);
}
render() {
return <div>组件内容</div>;
}
}
useEffect Hook:useEffect是React Hooks中用于处理副作用的Hook,它结合了componentDidMount、componentDidUpdate和componentWillUnmount的功能。
import { useState, useEffect } from 'react';
function EffectExample() {
const [count, setCount] = useState(0);
// 每次渲染后都执行
useEffect(() => {
console.log('useEffect执行 - 每次渲染后');
});
// 只在挂载时执行一次
useEffect(() => {
console.log('useEffect执行 - 仅挂载时');
}, []);
// 当count变化时执行
useEffect(() => {
console.log('useEffect执行 - count变化:', count);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}
function CleanupExample() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('设置定时器');
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 返回清理函数
return () => {
console.log('清理定时器');
clearInterval(timer);
};
}, []);
return <div>Count: {count}</div>;
}
function SideEffectsExample() {
const [data, setData] = useState(null);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
// 场景1: 数据获取
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
} catch (error) {
console.error('获取数据失败:', error);
}
};
fetchData();
}, []);
// 场景2: 订阅事件
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// 场景3: 手动DOM操作
useEffect(() => {
document.title = '页面标题已更新';
return () => {
document.title = 'React App';
};
}, []);
// 场景4: 定时器
useEffect(() => {
const timer = setTimeout(() => {
console.log('延迟执行的操作');
}, 1000);
return () => {
clearTimeout(timer);
};
}, []);
return (
<div>
<p>窗口宽度: {windowWidth}px</p>
{data && <p>数据: {JSON.stringify(data)}</p>}
</div>
);
}
依赖数组的重要性:依赖数组决定了useEffect何时执行。正确设置依赖数组可以避免无限循环和不必要的重复执行。
function DependencyExample() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 场景1: 每次渲染都执行
useEffect(() => {
console.log('每次渲染都执行');
});
// 场景2: 只在挂载时执行一次
useEffect(() => {
console.log('只在挂载时执行一次');
}, []);
// 场景3: 当count变化时执行
useEffect(() => {
console.log('count变化:', count);
}, [count]);
// 场景4: 当count或name变化时执行
useEffect(() => {
console.log('count或name变化:', count, name);
}, [count, name]);
return (
<div>
<button onClick={() => setCount(count + 1)}>
增加
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="输入姓名"
/>
</div>
);
}
常见错误:遗漏依赖项会导致闭包陷阱,而过多的依赖会导致不必要的重新执行。
// ❌ 错误:遗漏依赖项
function BadExample() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
// 这里永远使用的是初始值0
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, []); // count被遗漏了
}
// ✅ 正确:使用函数式更新
function GoodExample() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
// 使用函数式更新,避免闭包陷阱
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
}
class ClassComponent extends React.Component {
state = { count: 0 };
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Count: ${this.state.count}`;
}
componentWillUnmount() {
// 清理操作
}
render() {
return <div>{this.state.count}</div>;
}
}
function HookComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
return () => {
// 清理操作
};
}, [count]);
return <div>{count}</div>;
}
class OptimizedComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只有当props或state真正改变时才更新
return (
this.props.value !== nextProps.value ||
this.state.count !== nextState.count
);
}
render() {
return <div>{this.props.value}</div>;
}
}
import { memo } from 'react';
const MemoComponent = memo(function MemoComponent({ value }) {
console.log('MemoComponent渲染');
return <div>{value}</div>;
});
// 自定义比较函数
const CustomMemoComponent = memo(
function CustomMemoComponent({ value }) {
return <div>{value}</div>;
},
(prevProps, nextProps) => {
// 返回true表示props相等,不需要重新渲染
return prevProps.value === nextProps.value;
}
);
错误边界:错误边界是React组件,可以捕获其子组件树中任何位置的JavaScript错误,记录错误,并显示备用UI。
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('错误边界捕获到错误:', error, errorInfo);
// 可以将错误日志发送到服务器
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>出错了!</h2>
<p>{this.state.error.message}</p>
<button onClick={() => window.location.reload()}>
刷新页面
</button>
</div>
);
}
return this.props.children;
}
}
// 使用错误边界
function App() {
return (
<ErrorBoundary>
<SomeComponent />
</ErrorBoundary>
);
}
任务:创建一个实时时钟组件
要求:
// 参考答案
function Clock() {
const [time, setTime] = useState(new Date());
const [isRunning, setIsRunning] = useState(true);
useEffect(() => {
if (!isRunning) return;
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, [isRunning]);
const formatTime = (date) => {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
};
return (
<div>
<h2>当前时间</h2>
<p style={{ fontSize: '48px', fontFamily: 'monospace' }}>
{formatTime(time)}
</p>
<button onClick={() => setIsRunning(!isRunning)}>
{isRunning ? '暂停' : '开始'}
</button>
</div>
);
}