对比 React 与 Vue3 生命周期的区别

一个问题:组件从生到死,中间发生了什么?

你住酒店,流程是这样的:

  1. 入住:前台核验身份 → 给你房卡 → 你进房间放行李
  2. 住着:叫客房服务、调空调温度、每天打扫换毛巾
  3. 退房:收拾行李 → 退还房卡 → 结账走人

组件的生命周期,就是这三步。只不过名字叫 挂载(Mount)、更新(Update)、卸载(Unmount)。

但 React 和 Vue3 对这三步的处理方式完全不同——不是语法不同,是思路不同。


React:把所有事塞进一个 useEffect

React 16.8 之后,类组件的 componentDidMount、componentDidUpdate、componentWillUnmount 三件套被一个 useEffect 统一收编了。

一个函数干三件事:

import { useEffect, useState } from 'react';

function Countdown({ duration }) {
  const [remaining, setRemaining] = useState(duration);

  useEffect(() => {
    // ---- 入住(+ duration 变化时重新计时)----
    const timer = setInterval(() => {
      setRemaining((s) => (s > 0 ? s - 1 : 0));
    }, 1000);

    // ---- 退房(+ 重新计时前清掉旧定时器)----
    return () => clearInterval(timer);
  }, [duration]); // ← 依赖数组:duration 变了就重新计时

  return <div>剩余 {remaining} 秒</div>;
}

这里发生了什么?

  • 首次渲染(入住):useEffect 的回调执行,启动定时器,每秒减 1。
  • duration 变化(换个更短的倒计时):先执行上一次的清理函数(清掉旧定时器),再执行新的回调(重新开始计时)。
  • 组件卸载(退房):执行最后一次清理函数,清掉定时器。

一个函数,一个依赖数组,覆盖了挂载、更新、卸载三个阶段。React 的思路是:我不关心你在哪个阶段,我只关心你的"副作用"依赖了什么。

依赖数组是核心

useEffect(() => { /* ... */ }, []);          // 只在挂载时执行一次
useEffect(() => { /* ... */ }, [duration]);  // duration 变化时也执行
useEffect(() => { /* ... */ });              // 每次渲染都执行(慎用)

React 把"什么时候执行"这件事,完全交给了依赖数组。你告诉它依赖了什么,它替你决定什么时候跑、什么时候清理。

useLayoutEffect:DOM 测量的专用通道

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

function Tooltip({ children }) {
  const triggerRef = useRef(null);
  const [position, setPosition] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    // 在浏览器绘制前同步执行,避免"先闪到错误位置再跳正"的闪烁
    const rect = triggerRef.current.getBoundingClientRect();
    setPosition({
      top: rect.bottom + 8,
      left: rect.left + rect.width / 2,
    });
  }, [children]);

  return (
    <div ref={triggerRef} className="tooltip-trigger">
      {children}
      <div className="tooltip" style={{ position: 'absolute', ...position }}>
        {children}
      </div>
    </div>
  );
}

useLayoutEffect 和 useEffect 用法一模一样,唯一区别是执行时机:它在 DOM 更新后、浏览器绘制前同步执行。适合做 DOM 测量然后立即调整样式,避免视觉闪烁。


Vue3:每个阶段一个函数,各管各的

Vue3 的 Composition API 走了完全相反的路——每个生命周期阶段都有自己专属的函数。

<script setup>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue';

const props = defineProps({ duration: Number });
const remaining = ref(props.duration);
let timer = null;

// ---- 入住 ----
onMounted(() => {
  timer = setInterval(() => {
    if (remaining.value > 0) remaining.value--;
  }, 1000);
});

// ---- 住着(数据变化导致 DOM 更新后)----
onUpdated(() => {
  console.log('DOM 更新完成,倒计时画面已刷新');
});

// ---- 退房 ----
onUnmounted(() => {
  clearInterval(timer);
});
</script>

<template>
  <div>剩余 {{ remaining }} 秒</div>
</template>

Vue3 的思路是:你在哪个阶段做事,就去哪个函数里写。 语义明确,一看就知道这段代码什么时候跑。

Vue3 完整生命周期清单

钩子函数 触发时机 典型用途
onBeforeMount 挂载前(模板编译完成,还没渲染到 DOM) 最后一刻修改数据
onMounted 挂载后(DOM 已就绪) 发请求、启动定时器、操作 DOM
onBeforeUpdate 更新前(数据变了,DOM 还没重绘) 获取更新前的 DOM 状态
onUpdated 更新后(DOM 已重绘) 依赖 DOM 更新的操作
onBeforeUnmount 卸载前(组件还活着) 做最后的清理、发统计打点
onUnmounted 卸载后(组件已销毁) 清除定时器、移除事件监听

对比 React 的 useEffect 一把梭,Vue3 给了六个明确的钩子。多了吗?不多。你想想看,酒店前台如果只有一个按钮,既管入住又管退房又管换房,你会觉得方便还是混乱?

watch 和 watchEffect:Vue 独有的响应式监听

Vue3 还有一套 React 没有直接对应的东西——显式监听响应式数据的变化:

<script setup>
import { ref, watch, watchEffect } from 'vue';

const duration = ref(60);

// watch:明确指定监听谁,变化时执行(首次不执行)
watch(duration, (newVal, oldVal) => {
  console.log(`时长从 ${oldVal} 秒改成 ${newVal} 秒`);
});

// watchEffect:自动追踪内部用到的响应式数据
watchEffect(() => {
  // duration.value 变了就会自动重新执行
  console.log(`当前时长:${duration.value} 秒`);
});
</script>

watch 和 React 的 useEffect 有相似之处,但有个关键区别:watch 只在监听的值变化时执行,首次不执行(除非加 { immediate: true })。而 useEffect 在挂载时一定会执行一次。

watchEffect 更像 useEffect——首次会执行一次,然后自动追踪依赖。但它的依赖是自动收集的,不需要你手写依赖数组。


同一个功能,两种写法

现在用同一个简单场景——一个倒计时组件,挂载时开始计时,时长(duration)变化时重新计时,卸载时清除定时器——看看两种框架的完整写法。

React 版

import { useState, useEffect } from 'react';

function Countdown({ duration }) {
  const [remaining, setRemaining] = useState(duration);

  useEffect(() => {
    // 时长变化时,重置倒计时并重新开始
    setRemaining(duration);
    const timer = setInterval(() => {
      setRemaining((s) => (s > 0 ? s - 1 : 0));
    }, 1000);

    // 清理函数:duration 变化 / 卸载时都会执行
    return () => clearInterval(timer);
  }, [duration]);

  return <div>倒计时:{remaining} 秒</div>;
}

Vue3 版

<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';

const props = defineProps({ duration: Number });

const remaining = ref(props.duration);
let timer = null;

function start() {
  clearInterval(timer);
  remaining.value = props.duration;
  timer = setInterval(() => {
    if (remaining.value > 0) remaining.value--;
  }, 1000);
}

// 挂载时开始计时
onMounted(start);

// 卸载时清除定时器
onUnmounted(() => clearInterval(timer));

// duration 变了:重新开始计时
watch(() => props.duration, start);
</script>

<template>
  <div>倒计时:{{ remaining }} 秒</div>
</template>

看出区别了吗?

  • React 用一个 useEffect + 依赖数组 [duration] 就搞定了挂载、换时长、卸载三件事。创建逻辑和清理逻辑写在同一个函数里——它们是配对的。
  • Vue3 把挂载逻辑放在 onMounted,卸载放在 onUnmounted,换时长放在 watch。三个阶段分散在三个位置——每个阶段各司其职。

React 的写法更紧凑,但你要读懂"依赖数组 + 返回清理函数"的约定。Vue3 的写法更直白,函数名就是时间线,但你得在文件里跳来跳去看完整逻辑。


正面对比表

维度 React(Hooks) Vue3(Composition API)
挂载阶段 useEffect(() => {}, []) onMounted(() => {})
更新阶段 useEffect(() => {}, [dep]) onUpdated / watch / watchEffect
卸载阶段 useEffect 返回的清理函数 onUnmounted / onBeforeUnmount
DOM 更新前 useLayoutEffect onBeforeUpdate
DOM 更新后 useEffect(依赖变化时触发) onUpdated
响应式监听 useEffect 依赖数组 watch / watchEffect
依赖收集 手动写依赖数组 自动追踪(watchEffect)或显式声明(watch)
首次执行 useEffect 挂载时必执行一次 watch 默认不执行,watchEffect 执行一次
清理方式 在 effect 内部 return 一个函数 独立的 onUnmounted 钩子
设计哲学 副作用是数据依赖的函数 生命周期是时间线上的事件点
学习成本 依赖数组的心智负担较重 函数名即语义,直觉清晰
灵活性 极高,一个 effect 可覆盖多阶段 各阶段解耦,但需要手动串联

两种思路背后的设计差异

React 的 useEffect 之所以看起来"什么都能干",是因为它的设计起点是一个判断:副作用是数据依赖的函数——你依赖了什么,effect 就在那些东西变化时重新执行。挂载是"依赖第一次发生变化",卸载是"最后一次清理"。三个阶段只是这个模型的自然产物,不需要单独的钩子。

Vue3 的做法更传统:生命周期就是时间线上的事件点。你在哪个点做事,就去注册哪个点。这更接近浏览器原生的事件模型——addEventListener('DOMContentLoaded', ...) 的思路。

哪种更好?

取决于你脑子里装的模型是什么。如果你习惯"数据驱动一切"的思路——数据变了,副作用自动跟着跑——React 的统一模型更顺。如果你习惯"在合适的时间做合适的事"——先挂载再操作 DOM,先卸载再清除定时器——Vue3 的分离模型更直觉。

不要因为一个写法看起来短就觉得它好。useEffect 一个函数干三件事,也意味着 bug 可能藏在一个函数的三个分支里。Vue3 三个函数各管各的,也意味着逻辑分散在文件不同位置,你需要跳来跳去看全貌。

理解了这两种设计,你才能在面试或实际项目中说出"为什么选这个"——而不是"因为大家都在用"。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容