React中不传第二参数的useEffect与不使用useEffect的区别
无依赖useEffect与组件顶层直接执行逻辑的核心差异
React useEffect官方文档中给出过通过useEffect钩子更新document.title的代码示例:
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default App;
有开发者提出疑问:如果不用useEffect包裹更新title的逻辑,直接把代码写在组件函数顶层,像下面这样,和无依赖的useEffect写法有什么本质区别:
import { useState, useEffect } from 'react'; function App() { const [count, setCount] = useState(0); document.title = `You clicked ${count} times`; // useEffect(() => { // document.title = `You clicked ${count} times`; // }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default App;
二者核心差异可以归纳为以下4点:
- 执行时机完全不同
组件顶层的代码属于渲染阶段逻辑,在React计算虚拟DOM、对比差异的过程中就会同步执行,此时浏览器还没有收到最新的DOM更新,页面还没发生重绘。开发环境严格模式下React会主动执行两次渲染流程排查不纯净的渲染逻辑,顶层代码会被重复触发。
不传依赖数组的useEffect属于副作用逻辑,会在React完成DOM提交、浏览器完成页面绘制之后异步执行,不会卡在渲染流程中间。 - 对渲染性能的影响不同
顶层执行的逻辑如果存在耗时计算、DOM操作,会直接阻塞整个渲染流程,导致页面卡顿、交互响应延迟。即使在React并发模式下,渲染阶段的逻辑也可能因为高优先级任务插队被反复中断、重跑,带来无意义的性能开销。
useEffect内的逻辑在渲染完成后才执行,不会阻塞首屏内容呈现,且只会对应最终成功提交到页面的那次渲染执行,不会因为渲染被中断产生无效运行。 - 跨场景兼容性不同
组件顶层直接操作浏览器API(比如访问document、window对象)在服务端渲染(SSR)场景下会直接报错:服务端运行组件代码时不存在浏览器环境的全局对象,渲染阶段就会抛出异常导致页面崩溃。
useEffect内的逻辑只会在客户端hydrate完成后执行,天然兼容SSR、静态导出等非纯客户端渲染场景。 - 语义合规性不同
React明确要求组件渲染阶段的逻辑必须是「纯函数」:相同的输入(props/state/context)必须返回相同的JSX结果,不能产生任何外部可观测的副作用。直接在顶层写修改title、绑定事件、发起请求这类副作用,属于违反React组件设计约定的写法,在版本迭代中很容易出现隐性bug。
注:两个写法在简单的纯客户端、无并发更新、逻辑极轻量的场景下运行效果看起来几乎一致,只是特殊场景下的巧合,不代表两种写法可以等价替换。
内容的提问来源于stack exchange,提问作者ZedBee
相关产品推荐
相关产品推荐

