React useEffect 核心知識點(diǎn)

一、核心定義(官方準(zhǔn)確定義)

  • 1.執(zhí)行時機(jī):組件渲染完成、DOM 已更新、ref 已綁定之后 執(zhí)行(固定時序,不可改變)
  • 2.觸發(fā)條件:由依賴數(shù)組控制是否執(zhí)行(不是改變時機(jī),是控制是否運(yùn)行)
    • useEffect中執(zhí)行的邏輯會根據(jù)useEffect函數(shù)的第二個參數(shù)數(shù)組中的內(nèi)容變化控制是否執(zhí)行第一個參數(shù)函數(shù)中邏輯執(zhí)行
  • 3.核心用途:專門處理React 之外的系統(tǒng)同步(DOM 操作、視頻 / 音頻、定時器、接口請求、服務(wù)器連接、日志上報等)

二、React 渲染生命周期時序圖(必記)

1. 組件函數(shù)執(zhí)行 → 計(jì)算JSX(渲染階段)
   ↓ (此時 DOM 未生成,ref 為 null,禁止操作外部系統(tǒng))
2. React 更新 DOM → 綁定 ref
   ↓
3. 【渲染完成】
   ↓
4. 執(zhí)行 useEffect 回調(diào)
   (此時 DOM 就緒,操作安全)

三、關(guān)鍵對比:函數(shù)體 vs useEffect

  1. 直接寫在組件函數(shù)體里(錯誤寫法)
import { useState, useRef, useEffect } from "react";
function VideoPlayer({ src, isPlaying }) {
  console.log("xiaomai VideoPlayer......");
  const ref = useRef(null);

  /** 錯誤寫法!!!!????
   * 當(dāng)前這個示例中不使用useEffect好像也能跑?因?yàn)閕f (ref.current) { ... }跳過了第一次渲染,只在后續(xù)更新時執(zhí)行,看起來'能用',但是這只是巧合,是錯誤的寫法,不是正確的機(jī)制
   * 錯誤1: 在組件渲染的過程中直接操作DOM(React 組件函數(shù)是用來計(jì)算 UI 的,不是執(zhí)行副作用的) 違背react設(shè)計(jì)機(jī)制,執(zhí)行操作dom的時機(jī)不對
   * 錯誤2: 函數(shù)體會無限次數(shù)執(zhí)行,React 組件函數(shù)任何時候都可能重新執(zhí)行 eg:父組件刷新/狀態(tài)變化/父組件傳參變化/甚至react未來的并發(fā)渲染
   * 錯誤3: ref 賦值時機(jī)不確定
   */
  if (ref.current) {
    console.log("xiaomai ref 11111===:", ref, isPlaying);
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }
  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? "暫停" : "播放"}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
  • 執(zhí)行時機(jī):渲染期間(React 計(jì)算 UI 時)
  • 問題:違反 React 規(guī)則,依賴ref.current運(yùn)氣賦值,多余執(zhí)行,不可控
  • 表象:簡單場景不報錯,但本質(zhì)錯誤、生產(chǎn)環(huán)境易出 BUG
  • 在開發(fā)環(huán)境嚴(yán)格嚴(yán)重StrictMode模式情況下,每一次更新isPlaying時候if (ref.current) {中的邏輯} 都會執(zhí)行兩次,不報錯是因?yàn)闉g覽器兼容重復(fù)調(diào)用操作vedio的控制播放函數(shù)
  1. 寫在 useEffect 里(正確寫法)
import { useState, useRef, useEffect } from "react";
function VideoPlayer({ src, isPlaying }) {
  console.log("xiaomai VideoPlayer......");
  const ref = useRef(null);

  /* 正確寫法????
   * useEffect:渲染后執(zhí)行,專門用來和外部系統(tǒng)(視頻、DOM、服務(wù)器)同步
   * 注意: useEffect = 渲染后執(zhí)行 + 依賴變化才觸發(fā)  這里依賴的數(shù)組[isPlaying]是控制觸發(fā)條件,只有isPlaying變化時候才會執(zhí)行里面的邏輯
   * 終極正確理解:1.時機(jī):所有 useEffect 都是渲染后執(zhí)行(DOM 已更新) 2.依賴數(shù)組決定這次渲染后要不要執(zhí)行
   * 為什么 useEffect 才是正確的?useEffect 的核心規(guī)則:在渲染完成、DOM 真正生成后,才會執(zhí)行里面的代碼
   */
  useEffect(() => {
    console.log("xiaomai VideoPlayer useEffect......", ref, isPlaying);
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? "暫停" : "播放"}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
  • 執(zhí)行時機(jī):渲染完成后
  • 優(yōu)勢:時機(jī)安全、觸發(fā)可控、符合 React 設(shè)計(jì)、無隱形風(fēng)險
  • 在react嚴(yán)格校驗(yàn)?zāi)J较?,useEffect中的邏輯執(zhí)行觸發(fā)條件是數(shù)組中的元素是否發(fā)生變化,不會產(chǎn)生重復(fù)的邏輯執(zhí)行

四、兩個易混淆說法的澄清

  1. useEffect 渲染后執(zhí)行
    → 描述執(zhí)行時機(jī),永遠(yuǎn)正確,底層規(guī)則
  2. useEffect 數(shù)據(jù)變化才執(zhí)行
    → 描述觸發(fā)條件,是補(bǔ)充說明,不是時機(jī)定義

? 完整總結(jié):useEffect 一定在渲染后執(zhí)行,依賴數(shù)組決定本次渲染后是否執(zhí)行。

五、核心鐵律(必背)

  1. 組件函數(shù)體 = 只負(fù)責(zé)計(jì)算 UI,禁止操作 DOM、視頻、定時器等外部系統(tǒng)
  2. 所有外部系統(tǒng)同步、副作用操作,必須放入 useEffect
  3. 嚴(yán)格模式下執(zhí)行兩次不報錯 ≠ 代碼正確(是瀏覽器 API 寬容)

六、完整示例代碼(標(biāo)準(zhǔn)用法)

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

// 子組件:使用Effect同步視頻狀態(tài)
function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  // 渲染后,根據(jù)isPlaying同步視頻播放/暫停
  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

// 父組件
export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? '暫停' : '播放'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

七、最終結(jié)論

  1. 操作 DOM / 視頻 / 外部系統(tǒng) = 必須用 useEffect
  2. 區(qū)別核心是執(zhí)行時機(jī),不是執(zhí)行次數(shù)
  3. 函數(shù)體渲染期間 → 只算 UI;useEffect 渲染后 → 操作外部系統(tǒng)
  4. 不要用 “沒報錯” 證明代碼正確,要遵循 React 設(shè)計(jì)規(guī)則
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容