组件生命周期 #

一、生命周期概述 #

1.1 什么是生命周期 #

组件从创建到销毁的过程,分为三个阶段:

text
挂载 → 更新 → 卸载

1.2 生命周期图示 #

text
┌─────────────────────────────────────────────────┐
│                    挂载阶段                      │
├─────────────────────────────────────────────────┤
│  constructor → getDerivedStateFromProps         │
│       → render → componentDidMount              │
└─────────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────┐
│                    更新阶段                      │
├─────────────────────────────────────────────────┤
│  getDerivedStateFromProps → shouldComponentUpdate│
│       → render → getSnapshotBeforeUpdate        │
│       → componentDidUpdate                      │
└─────────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────┐
│                    卸载阶段                      │
├─────────────────────────────────────────────────┤
│  componentWillUnmount                           │
└─────────────────────────────────────────────────┘

二、函数组件生命周期 #

2.1 使用 useEffect #

jsx
import { useEffect, useState } from 'preact/hooks';

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

  // 挂载和更新时执行
  useEffect(() => {
    console.log('Component mounted or updated');
  });

  // 仅挂载时执行
  useEffect(() => {
    console.log('Component mounted');
  }, []);

  // count 变化时执行
  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]);

  return <div>{count}</div>;
}

2.2 清理副作用 #

jsx
function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    // 清理函数(卸载时执行)
    return () => {
      clearInterval(interval);
      console.log('Timer cleaned up');
    };
  }, []);

  return <div>Seconds: {seconds}</div>;
}

2.3 常见使用场景 #

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

  // 数据获取
  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      setLoading(true);
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      
      if (!cancelled) {
        setUser(data);
        setLoading(false);
      }
    }

    fetchUser();

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

  // 订阅事件
  useEffect(() => {
    const handleResize = () => {
      console.log('Window resized');
    };

    window.addEventListener('resize', handleResize);

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

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>User not found</p>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

2.4 useLayoutEffect #

jsx
import { useLayoutEffect, useRef } from 'preact/hooks';

function Tooltip({ content }) {
  const tooltipRef = useRef(null);

  // 在 DOM 更新后同步执行
  useLayoutEffect(() => {
    const tooltip = tooltipRef.current;
    const { width } = tooltip.getBoundingClientRect();
    tooltip.style.left = `-${width / 2}px`;
  }, [content]);

  return (
    <div ref={tooltipRef} class="tooltip">
      {content}
    </div>
  );
}

三、类组件生命周期 #

3.1 挂载阶段 #

jsx
import { Component } from 'preact';

class Example extends Component {
  // 初始化 state
  state = { count: 0 };

  // 组件挂载前(已废弃,不推荐使用)
  componentWillMount() {
    console.log('Component will mount');
  }

  // 组件挂载后
  componentDidMount() {
    console.log('Component mounted');
    // 适合:数据获取、订阅事件、DOM 操作
    this.fetchData();
  }

  async fetchData() {
    const response = await fetch('/api/data');
    const data = await response.json();
    this.setState({ data });
  }

  render() {
    return <div>{this.state.count}</div>;
  }
}

3.2 更新阶段 #

jsx
class Example extends Component {
  // 是否应该更新
  shouldComponentUpdate(nextProps, nextState) {
    // 返回 false 阻止更新
    return this.props.id !== nextProps.id;
  }

  // 组件更新前
  componentWillUpdate(nextProps, nextState) {
    console.log('Component will update');
  }

  // 获取更新前的快照
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.items.length < this.props.items.length) {
      return this.listRef.current.scrollHeight;
    }
    return null;
  }

  // 组件更新后
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('Component updated');
    
    if (snapshot !== null) {
      this.listRef.current.scrollTop += snapshot;
    }
  }

  render() {
    return <div ref={this.listRef}>{/* ... */}</div>;
  }
}

3.3 卸载阶段 #

jsx
class Example extends Component {
  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
    this.startPolling();
  }

  componentWillUnmount() {
    // 清理工作
    window.removeEventListener('resize', this.handleResize);
    this.stopPolling();
    console.log('Component will unmount');
  }

  handleResize = () => {
    console.log('Window resized');
  };

  startPolling() {
    this.pollInterval = setInterval(() => {
      this.fetchData();
    }, 5000);
  }

  stopPolling() {
    clearInterval(this.pollInterval);
  }

  render() {
    return <div>Example</div>;
  }
}

四、生命周期对比 #

4.1 类组件 vs 函数组件 #

类组件 函数组件 (Hooks)
componentDidMount useEffect(() => {}, [])
componentDidUpdate useEffect(() => {}, [deps])
componentWillUnmount useEffect(() => return () => {}, [])
shouldComponentUpdate React.memo / useMemo
getSnapshotBeforeUpdate useLayoutEffect

4.2 转换示例 #

jsx
// 类组件
class UserProfile extends Component {
  state = { user: null };

  componentDidMount() {
    this.fetchUser();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser();
    }
  }

  async fetchUser() {
    const response = await fetch(`/api/users/${this.props.userId}`);
    const user = await response.json();
    this.setState({ user });
  }

  render() {
    if (!this.state.user) return <p>Loading...</p>;
    return <div>{this.state.user.name}</div>;
  }
}

// 函数组件
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    }
    fetchUser();
  }, [userId]);

  if (!user) return <p>Loading...</p>;
  return <div>{user.name}</div>;
}

五、常见模式 #

5.1 数据获取 #

jsx
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url);
        
        if (!response.ok) {
          throw new Error('Network error');
        }
        
        const json = await response.json();
        
        if (!cancelled) {
          setData(json);
          setError(null);
        }
      } catch (e) {
        if (!cancelled) {
          setError(e.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchData();

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

  return { data, loading, error };
}

5.2 事件订阅 #

jsx
function useWindowSize() {
  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 size;
}

5.3 定时器 #

jsx
function useInterval(callback, delay) {
  const savedCallback = useRef(callback);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay === null) return;

    const id = setInterval(() => savedCallback.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}

// 使用
function Counter() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    setCount(c => c + 1);
  }, 1000);

  return <h1>{count}</h1>;
}

5.4 之前的值 #

jsx
function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

// 使用
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

六、错误处理 #

6.1 错误边界(类组件) #

jsx
class ErrorBoundary extends Component {
  state = { hasError: false, error: null };

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

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

  render() {
    if (this.state.hasError) {
      return (
        <div class="error">
          <h1>Something went wrong.</h1>
          <p>{this.state.error.message}</p>
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

七、最佳实践 #

7.1 避免内存泄漏 #

jsx
function DataFetcher({ url }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let cancelled = false;

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setData(data);
        }
      });

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

  return <div>{/* ... */}</div>;
}

7.2 正确设置依赖 #

jsx
// 错误:缺少依赖
useEffect(() => {
  fetchData(userId);
}, []); // userId 变化时不会重新获取

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

7.3 分离关注点 #

jsx
function Example({ userId }) {
  const [user, setUser] = useState(null);
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });

  // 用户数据获取
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);

  // 窗口大小监听
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

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

  return <div>{/* ... */}</div>;
}

八、总结 #

阶段 类组件方法 函数组件 Hook
挂载 componentDidMount useEffect(…, [])
更新 componentDidUpdate useEffect(…, [deps])
卸载 componentWillUnmount useEffect return
渲染前 getSnapshotBeforeUpdate useLayoutEffect

核心要点:

  • 函数组件使用 useEffect 处理副作用
  • 记得清理副作用避免内存泄漏
  • 正确设置依赖数组
  • 分离不同关注点到独立的 useEffect
最后更新:2026-03-28