组件生命周期 #

一、生命周期概述 #

组件生命周期是指组件从创建到销毁的整个过程。理解生命周期对于正确处理副作用至关重要。

1.1 生命周期阶段 #

text
┌─────────────────────────────────────────────────────┐
│                 组件生命周期                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  挂载阶段(Mounting)                                │
│  ├── constructor                                    │
│  ├── getDerivedStateFromProps                      │
│  ├── render                                        │
│  └── componentDidMount                             │
│                                                     │
│  更新阶段(Updating)                                │
│  ├── getDerivedStateFromProps                      │
│  ├── shouldComponentUpdate                         │
│  ├── render                                        │
│  ├── getSnapshotBeforeUpdate                       │
│  └── componentDidUpdate                            │
│                                                     │
│  卸载阶段(Unmounting)                              │
│  └── componentWillUnmount                          │
│                                                     │
│  错误处理(Error Handling)                          │
│  ├── getDerivedStateFromError                      │
│  └── componentDidCatch                             │
│                                                     │
└─────────────────────────────────────────────────────┘

二、类组件生命周期 #

2.1 挂载阶段 #

javascript
import { Component } from 'react';

class MountingDemo extends Component {
  constructor(props) {
    super(props);
    console.log('1. constructor');
    this.state = { count: 0 };
  }

  static getDerivedStateFromProps(props, state) {
    console.log('2. getDerivedStateFromProps');
    return null;
  }

  render() {
    console.log('3. render');
    return <div>{this.state.count}</div>;
  }

  componentDidMount() {
    console.log('4. componentDidMount');
    // 适合进行:API请求、订阅事件、DOM操作
  }
}

执行顺序

text
constructor → getDerivedStateFromProps → render → componentDidMount

2.2 更新阶段 #

javascript
class UpdatingDemo extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  static getDerivedStateFromProps(props, state) {
    console.log('1. getDerivedStateFromProps');
    return null;
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('2. shouldComponentUpdate');
    return true;
  }

  render() {
    console.log('3. render');
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          增加
        </button>
      </div>
    );
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('4. getSnapshotBeforeUpdate');
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('5. componentDidUpdate');
  }
}

触发更新的情况

  • 调用 setState()
  • 父组件传递新的 Props
  • 调用 forceUpdate()

2.3 卸载阶段 #

javascript
class UnmountingDemo extends Component {
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('tick');
    }, 1000);
  }

  componentWillUnmount() {
    console.log('componentWillUnmount');
    clearInterval(this.timer);
  }

  render() {
    return <div>Unmounting Demo</div>;
  }
}

2.4 错误处理 #

javascript
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error:', error);
    console.error('Error Info:', errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>出错了!</h1>;
    }
    return this.props.children;
  }
}

三、函数组件生命周期 #

3.1 useEffect基础 #

函数组件使用 useEffect 处理副作用:

javascript
import { useState, useEffect } from 'react';

function LifecycleDemo() {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount
  useEffect(() => {
    console.log('组件挂载');
  }, []);

  // 相当于 componentDidMount + componentDidUpdate
  useEffect(() => {
    console.log('count变化:', count);
  }, [count]);

  // 相当于 componentWillUnmount
  useEffect(() => {
    return () => {
      console.log('组件卸载');
    };
  }, []);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

3.2 useEffect完整形式 #

javascript
useEffect(() => {
  // 副作用逻辑
  console.log('副作用执行');

  // 清理函数(可选)
  return () => {
    console.log('清理函数执行');
  };
}, [dependencies]);

依赖数组说明

依赖数组 执行时机
每次渲染后都执行
[] 仅挂载时执行一次
[a, b] a 或 b 变化时执行

3.3 类组件 vs 函数组件对照 #

javascript
// 类组件
class Example extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }

  componentDidUpdate() {
    document.title = `Count: ${this.state.count}`;
  }

  componentWillUnmount() {
    console.log('清理');
  }

  render() {
    return (
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        {this.state.count}
      </button>
    );
  }
}

// 函数组件
function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;
    
    return () => {
      console.log('清理');
    };
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

四、常见生命周期场景 #

4.1 数据请求 #

javascript
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        
        if (isMounted) {
          setUser(data);
          setError(null);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };

    fetchUser();

    return () => {
      isMounted = false;
    };
  }, [userId]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

4.2 事件订阅 #

javascript
function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return (
    <div>
      {size.width} x {size.height}
    </div>
  );
}

4.3 定时器 #

javascript
function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return;

    const timer = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, [isRunning]);

  return (
    <div>
      <p>{seconds}秒</p>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? '暂停' : '开始'}
      </button>
      <button onClick={() => setSeconds(0)}>重置</button>
    </div>
  );
}

4.4 DOM操作 #

javascript
function FocusInput() {
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

五、useLayoutEffect #

5.1 与useEffect的区别 #

javascript
// useEffect - 异步执行,不阻塞浏览器绘制
useEffect(() => {
  console.log('useEffect');
});

// useLayoutEffect - 同步执行,阻塞浏览器绘制
useLayoutEffect(() => {
  console.log('useLayoutEffect');
});

执行顺序

text
render → useLayoutEffect → 浏览器绘制 → useEffect

5.2 使用场景 #

javascript
function Tooltip({ children, targetRect }) {
  const tooltipRef = useRef();
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = tooltipRef.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, [children]);

  let tooltipX = 0;
  let tooltipY = 0;
  
  if (targetRect) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      tooltipY = targetRect.bottom;
    }
  }

  return (
    <div
      ref={tooltipRef}
      style={{
        position: 'absolute',
        left: tooltipX,
        top: tooltipY
      }}
    >
      {children}
    </div>
  );
}

六、生命周期最佳实践 #

6.1 避免在渲染中产生副作用 #

javascript
// ❌ 错误:在渲染中产生副作用
function Counter() {
  const [count, setCount] = useState(0);
  document.title = `Count: ${count}`; // 副作用
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ✅ 正确:使用useEffect
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

6.2 正确设置依赖 #

javascript
// ❌ 缺少依赖
useEffect(() => {
  fetchData(userId);
}, []); // userId变化时不会重新请求

// ✅ 包含所有依赖
useEffect(() => {
  fetchData(userId);
}, [userId]);

6.3 清理副作用 #

javascript
// ❌ 没有清理
useEffect(() => {
  const timer = setInterval(() => {
    console.log('tick');
  }, 1000);
}, []);

// ✅ 正确清理
useEffect(() => {
  const timer = setInterval(() => {
    console.log('tick');
  }, 1000);
  
  return () => {
    clearInterval(timer);
  };
}, []);

6.4 避免无限循环 #

javascript
// ❌ 无限循环
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    setCount(count + 1); // 每次渲染都更新状态
  }, [count]);
  
  return <div>{count}</div>;
}

// ✅ 避免循环
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    if (count < 10) {
      setCount(count + 1);
    }
  }, [count]);
  
  return <div>{count}</div>;
}

七、生命周期方法速查表 #

7.1 类组件 #

方法 时机 用途
constructor 创建组件时 初始化state
getDerivedStateFromProps 渲染前 根据props更新state
render 渲染时 返回JSX
componentDidMount 挂载后 API请求、订阅
shouldComponentUpdate 更新前 性能优化
getSnapshotBeforeUpdate DOM更新前 获取DOM信息
componentDidUpdate 更新后 DOM操作
componentWillUnmount 卸载前 清理工作

7.2 函数组件 #

Hook 用途
useEffect 副作用处理
useLayoutEffect 同步DOM操作
useInsertionEffect CSS-in-JS注入

八、总结 #

要点 说明
理解阶段 挂载、更新、卸载
正确使用 useEffect处理副作用
清理资源 返回清理函数
依赖数组 正确设置依赖项

核心原则:

  • 副作用放在 useEffect 中
  • 记得清理订阅和定时器
  • 正确设置依赖数组
  • 使用 useLayoutEffect 处理 DOM 测量
最后更新:2026-03-26