useEffect Hook 生命周期模拟

用 useEffect 模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount

问题

如何用 useEffect 模拟类组件的生命周期方法?

解答

模拟 componentDidMount

组件挂载时执行一次,依赖数组传空数组。

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // 组件挂载后执行,只执行一次
    console.log('componentDidMount');
    
    // 例如:获取数据、订阅事件、操作 DOM
    fetchData();
  }, []); // 空依赖数组

  return <div>Hello</div>;
}

模拟 componentDidUpdate

监听特定状态变化,依赖数组传入要监听的值。

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

function MyComponent() {
  const [count, setCount] = useState(0);
  const isFirstRender = useRef(true);

  // 方式一:包含首次渲染
  useEffect(() => {
    console.log('count 变化了', count);
  }, [count]);

  // 方式二:排除首次渲染(更接近 componentDidUpdate)
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }
    console.log('componentDidUpdate: count =', count);
  }, [count]);

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

模拟 componentWillUnmount

返回清理函数,组件卸载时执行。

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('tick');
    }, 1000);

    // 返回清理函数,组件卸载时执行
    return () => {
      console.log('componentWillUnmount');
      clearInterval(timer);
    };
  }, []);

  return <div>Timer Running</div>;
}

完整示例

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

function LifecycleDemo({ userId }) {
  const [user, setUser] = useState(null);
  const isFirstRender = useRef(true);

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

  // componentDidUpdate (监听 userId 变化)
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }
    console.log('userId 更新为:', userId);
  }, [userId]);

  // 数据获取(挂载 + userId 变化时)
  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      
      // 防止组件卸载后更新状态
      if (!cancelled) {
        setUser(data);
      }
    }

    fetchUser();

    return () => {
      cancelled = true;
    };
  }, [userId]);

  return <div>{user?.name}</div>;
}

关键点

  • 空依赖数组 []:effect 只在挂载时执行一次,模拟 componentDidMount
  • 有依赖数组 [dep]:依赖变化时执行,模拟 componentDidUpdate
  • 返回函数:组件卸载或依赖变化前执行,模拟 componentWillUnmount
  • 跳过首次渲染:用 useRef 标记是否首次渲染,实现纯粹的 componentDidUpdate
  • 清理副作用:定时器、订阅、请求等都需要在返回函数中清理