useEffect 为什么在组件首次渲染时就执行了?

码农翌耀 阅读 8

我刚学 React,看到 useEffect 默认会在组件挂载后执行一次,但我不太理解为什么它不等依赖变化才运行。比如我在 Vue 里用 watch 是不会一进来就触发的,但在 React 里写了个空依赖数组,结果还是执行了,这正常吗?

下面是我写的 Vue 对比代码,想搞清楚两者的逻辑差异:

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

const count = ref(0)

// 这个 watch 不会立即执行
watch(count, (newVal) => {
  console.log('count changed:', newVal)
})
</script>
我来解答 赞 1 收藏
二维码
手机扫码查看
1 条解答
IT人克培
这完全正常,你遇到的是 React 和 Vue 在设计哲学上的根本差异。我以前刚从 Vue 转 React 时也在这儿绕过弯子,咱们把这事儿掰扯清楚。

先说结论,useEffect 的设计初衷就是"渲染后执行副作用",它的语义是"当组件渲染完成之后,我要做点什么",而不是"当数据变化时我要做点什么"。这两者看起来很像,但出发点完全不同。

React 的 useEffect 执行逻辑是这样的:组件第一次渲染完成后执行一次,之后每次依赖项变化导致重新渲染后,也会执行。你传了空数组 [] 作为依赖,意思是"我不依赖任何 props 或 state",所以后续渲染不会再触发这个 effect,但第一次挂载后的那次执行是雷打不动的。

Vue 的 watch 默认是惰性的,它只关注"变化"这个动作,没有变化就不触发。而 useEffect 关注的是"渲染完成"这个时间点,渲染完成了就要执行。

来看一段代码对比两者的差异:

// React useEffect
useEffect(() => {
console.log('我执行了');
}, []); // 空依赖数组 = 只在挂载后执行一次,之后不再执行

// Vue watch(默认行为)
watch(count, (newVal) => {
console.log('count changed:', newVal);
}); // 初始不执行,只有 count 变化时才执行

// Vue watch(immediate 模式)
watch(count, (newVal) => {
console.log('count changed:', newVal);
}, { immediate: true }); // 这就类似 useEffect 了,会立即执行一次


你会发现 Vue 也有 immediate: true 选项,开启后行为就跟 React 的 useEffect 一样了。这说明两边都意识到了"立即执行"这个场景的必要性,只是默认值选得不一样。

为什么 React 要这样设计?因为 React 团队认为,很多副作用的逻辑结构是这样的:执行某些操作,然后在组件卸载或下次执行前清理。比如订阅事件、开启定时器、发起网络请求。这些操作在首次渲染后就需要执行,而不是等到数据变化。

一个典型的例子:

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

return () => clearInterval(timer); // 清理函数
}, []);


如果 useEffect 不在首次渲染后执行,这个定时器就不会启动,你还得另外找个地方去初始化它,逻辑就散了。

那如果你真的想要"只在数据变化时执行,首次不触发"的行为呢?可以用 useRef 配合一个标志位来实现:

const isFirstRender = useRef(true);

useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return; // 首次渲染直接返回,不执行后续逻辑
}

console.log('count 真的变化了:', count);
}, [count]);


或者封装成一个自定义 hook,网上有现成的库比如 useUpdateEffect,专门干这事儿。

需要注意一点,不要把 useEffect 理解成 Vue 的 watch。useEffect 是声明式的,你告诉 React"渲染后我要做这些事",React 会保证在正确的时机调用。而 watch 是响应式的,你告诉 Vue"这个数据变了你要做什么"。思维模式不一样,用 useEffect 去模拟 watch 的行为,写出来的代码往往很别扭。

总结一下:useEffect 首次执行是设计如此,空数组只是控制"后续是否因依赖变化而再次执行",控制不了首次。习惯了就好,React 这套设计用多了你会发现它在处理副作用时其实挺优雅的,只是跟 Vue 的脑回路不太一样。
点赞 1
2026-03-02 22:20