【react】 useEffect

useEffect

useEffect 依赖为空时 (无依赖),无论组件首次挂载还是重渲染,useEffect 都会执行

js 复制代码
function Children(){
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('Children rendered')
    console.log(count) // 组件挂载时执行
  })
  
  return(
    <div onClick={() => setCount(count + 1)}>children{count}</div>
  )
}

useEffect 依赖依赖为空数组时 函数只会在组件首次挂载时执行,之后组件在渲染(在重新执行),useEffect中的函数不会再执行

useEffect 依赖为空数组时,只会在组件首次挂载时执行

js 复制代码
function Children(){
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('Children mounted') // 组件挂载时执行
  },[])
  
  return(
    <div onClick={() => setCount(count + 1)}>children{count}</div>
  )
}

useEffect 指定依赖项时 只有首次渲染依赖项发生变化时才会执行

js 复制代码
function Children(){
  const [count, setCount] = useState(0)
  const [count2, setCount2] = useState(0)
  useEffect(() => {
    console.log('Children rendered')
    console.log(count) // 组件挂载时执行
  },[count])
  
  return(
    <div>
      <div onClick={() => setCount(count + 1)}>children{count}</div>
      <div onClick={() => setCount2(count2 + 1)}>children{count2}</div>
    </div>
  )
}

每种useEffect 都 可以返回一个return函数return函数在组件卸载时执行

js 复制代码
function Children(){
  const [count, setCount] = useState(0)
  const [count2, setCount2] = useState(0)
  useEffect(() => {
    console.log('Children rendered')
    console.log(count) // 组件挂载时执行
    
    // 返回清理函数,组件卸载时执行
    return () => {
      console.log('Children unmounted')
    }
  })
  
  return(
    <div>
      <div onClick={() => setCount(count + 1)}>children{count}</div>
      <div onClick={() => setCount2(count2 + 1)}>children{count2}</div>
    </div>
  )
}

useEffect 用来干什么?

纯函数

纯函数,相同的输入,必然得到相同的结果。函数不依赖外部,也不影响外部。

🔹 useEffect 用来执行那些"与渲染无关,但需要响应渲染结果的副作用操作"。


🧠 一、什么是"副作用(Side Effect)"

在 React 里,"副作用"指的是那些:

  • 不会直接参与视图计算
  • 可能影响外部系统或环境 的操作。

简单说:

👉 渲染是"纯的"逻辑计算

👉 副作用是"脏的"真实世界交互

比如:

副作用类型 示例
网络请求 请求后端接口、上传文件
DOM 操作 手动设置样式、聚焦 input
计时器 setTimeoutsetInterval
订阅 WebSocket、事件监听
本地存储 localStoragesessionStorage
日志 console.log、打点统计
清理资源 组件卸载时取消订阅或清理计时器

⚙️ 二、为什么一定要放在 useEffect 里执行

React 希望渲染阶段是纯函数

ini 复制代码
UI = render(state, props)

意思是:

  • 同样的 state、props → 一定返回相同的 UI;
  • 不能有请求、修改全局变量、DOM 操作等副作用;
  • 否则 React 在"并发渲染""重新渲染""中断渲染"时就会出现混乱。

useEffect 的回调是在:

✅ 真实 DOM 已经更新、浏览器完成绘制后 执行。

这就保证了:

  • 副作用不会干扰渲染;
  • 可以安全地访问真实 DOM;
  • 能正确对应最新的渲染结果。

🌈 三、典型使用场景示例

1️⃣ 页面加载后发请求
scss 复制代码
useEffect(() => {
  fetch('/api/user')
    .then(res => res.json())
    .then(setUser);
}, []); // 依赖空数组 -> 只在初次渲染后执行一次
2️⃣ 订阅事件(并在卸载时清理)
javascript 复制代码
useEffect(() => {
  const onResize = () => console.log(window.innerWidth);
  window.addEventListener('resize', onResize);

  return () => {
    window.removeEventListener('resize', onResize); // cleanup
  };
}, []);
3️⃣ 操作 DOM
scss 复制代码
const inputRef = useRef();

useEffect(() => {
  inputRef.current.focus(); // 页面渲染后自动聚焦
}, []);
4️⃣ 设置计时器
javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => console.log('tick'), 1000);
  return () => clearInterval(timer); // 卸载时清理
}, []);

🔁 四、依赖数组的作用(再强调一下)

useEffect(() => {...}, [deps])

  • 每次组件更新时,React 会检查依赖是否变化;
  • 如果变化,才会重新执行副作用。
ini 复制代码
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 只有 count 改变时执行

✅ 总结一句话

阶段 能做的事 工具
渲染阶段 计算 UI(不能有副作用) 函数组件本身
提交阶段 真实 DOM 已更新,可以执行副作用 useEffect

useEffect用来获取数据

React 渲染函数组件的顺序大概是: 执行函数组件本身(App() 函数体) 计算 JSX → 返回虚拟 DOM(React elements) 在这个阶段,useEffect 只会被注册,不会立即执行回调 所以 console.log 不会立刻打印 React 根据虚拟 DOM 更新真实 DOM 浏览器渲染页面 渲染完成后(commit 阶段),React 执行 useEffect 回调 这时候 console.log('App mounted') 才会输出 如果它用来获取页面需要的数据,那岂不是一开始页面渲染的时候,没有数据

🧩 一、是的------第一次渲染时,确实没有数据!

用effect请求数据,组件第一次渲染时,是没有数据的。所以可以设置loading,当数据返回时再使用数据。如果数据返回错误,可以设置错误页面。

例如:

javascript 复制代码
function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(setUser);
  }, []);

  return (
    <div>
      {user ? <p>{user.name}</p> : <p>Loading...</p>}
    </div>
  );
}

执行顺序:

1️⃣ 第一次渲染(Render 阶段)

  • 执行 App()
  • 此时 user = null
  • 返回虚拟 DOM <p>Loading...</p>

2️⃣ Commit 阶段 → 页面渲染到屏幕

  • 用户立刻看到"Loading..."
  • 页面已经有内容(不会空白)

3️⃣ React 执行 useEffect 回调

  • 发起网络请求 /api/user

4️⃣ 请求返回 → setUser() 触发 re-render

  • 再次执行 App()
  • 此时 user 不为 null
  • 渲染 <p>{user.name}</p>

💡 二、为什么 React 要"先渲染再请求"

React 这么设计是刻意的,理由有三个:

✅ 1. 让页面尽快有内容(提升感知速度)

如果等待数据加载后再渲染,用户在慢网环境下就看到空白页面。

而现在:

即使数据没回来,也先渲染一个骨架屏 / loading 状态。


✅ 2. 避免阻塞渲染

假设在组件函数里直接请求:

csharp 复制代码
const data = await fetch(...)

那整个函数必须等待网络完成后才继续执行,React 就没法继续渲染 UI

这会严重影响性能。


✅ 3. 副作用必须在渲染完成后执行

React 希望渲染是纯函数式的,也就是说:

"同样的输入(props/state)一定返回同样的 UI。"

如果你在渲染阶段发请求、改 DOM、操作浏览器,就打破了这种"纯粹性"。

所以所有副作用都被安排在 useEffect(渲染后执行)。


🧱 三、所以怎么办?

正确做法是:第一次渲染显示 Loading,拿到数据后再重新渲染。

javascript 复制代码
function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('/api/user')
      .then(r => r.json())
      .then(setUser);
  }, []);

  if (!user) {
    return <p>加载中...</p>;
  }

  return <p>你好,{user.name}</p>;
}

这就是 React 的常规数据加载模式。

React 认为:"即使没数据,也应该先渲染一个稳定的 UI 状态。"

useEffect是异步的

1️⃣ 核心原因

useEffect 是异步的,最核心的原因是:

保证渲染过程是纯函数、可中断、不阻塞浏览器绘制。

也就是说,React 希望 函数组件的渲染阶段(render)只做两件事:

  1. 接收 props 和 state
  2. 根据这些数据生成虚拟 DOM(VDOM)

不允许在渲染阶段做副作用(如 DOM 操作、网络请求、定时器等) ,因为这样会:

  • 阻塞渲染,导致页面卡顿
  • 破坏 React 可中断、可恢复的渲染能力
  • 让函数组件变得不纯,难以预测输出

所以,React 选择把副作用延后到浏览器 绘制之后 执行,也就是异步执行。


2️⃣ 执行顺序示意

scss 复制代码
render()                // 函数组件执行,生成 VDOM
↓
DOM 更新               // React 把更新应用到真实 DOM
↓
浏览器绘制            // 用户看到页面
↓
useEffect 执行         // 执行副作用

这里的 "异步" 指的是 useEffect 的回调不会阻塞浏览器绘制


3️⃣ 为什么不在 render 阶段执行?

假设我们在 render 阶段做副作用:

javascript 复制代码
function App() {
  const [count, setCount] = useState(0);
  fetch("/api/data")   // ❌ 副作用出现在 render
    .then(res => console.log(res));
  return <div>{count}</div>;
}
  • 这个 fetch 会阻塞渲染吗?
    如果 React 遇到多个状态更新,或者并发模式(Concurrent Mode),就可能多次渲染,副作用就会执行多次,产生 不可预测行为
  • React 渲染希望是 纯函数:相同输入一定产生相同输出,副作用会破坏这个原则。

4️⃣ useLayoutEffect 的"例外"

有些场景必须在 浏览器绘制前 执行副作用(如测量 DOM 尺寸、滚动条定位),React 提供了 useLayoutEffect

  • 同步执行
  • 阻塞浏览器绘制
  • 可以安全地读取或修改 DOM 布局

💡 总结

  • useEffect 异步 → 先渲染再执行副作用,保证渲染纯净、流畅
  • useLayoutEffect 同步 → 绘制前执行副作用,保证布局正确

一句话记忆:

"useEffect:用户看见之后再做事;useLayoutEffect:用户看见之前先调整好一切。"

相关推荐
前端小咸鱼一条6 小时前
16.React性能优化SCU
前端·react.js·性能优化
起风了___6 小时前
Flutter 全局音频播放单例实现(附完整源码)——基于 just_audio 的零依赖方案
前端·flutter
im_AMBER6 小时前
React 09
前端·javascript·笔记·学习·react.js·前端框架
用户4099322502126 小时前
快速入门Vue模板里的JS表达式有啥不能碰?计算属性为啥比方法更能打?
前端·ai编程·trae
非专业程序员6 小时前
HarfBuzz 实战:五大核心API 实例详解【附iOS/Swift实战示例】
前端·程序员
DreamMachine7 小时前
Flutter 开发的极简风格音乐播放器
前端·flutter
前端老宋Running7 小时前
前端防抖与节流一篇讲清楚
前端·面试
ejinxian7 小时前
Rust UI 框架GPUI 与 Electron 的对比
前端·javascript·electron
小马哥learn7 小时前
Vue3 + Electron + Node.js 桌面项目完整开发指南
前端·javascript·electron