一、核心定義(官方準(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
- 直接寫在組件函數(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ù)
- 寫在 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í)行
四、兩個易混淆說法的澄清
- useEffect 渲染后執(zhí)行
→ 描述執(zhí)行時機(jī),永遠(yuǎn)正確,底層規(guī)則 - useEffect 數(shù)據(jù)變化才執(zhí)行
→ 描述觸發(fā)條件,是補(bǔ)充說明,不是時機(jī)定義
? 完整總結(jié):useEffect 一定在渲染后執(zhí)行,依賴數(shù)組決定本次渲染后是否執(zhí)行。
五、核心鐵律(必背)
- 組件函數(shù)體 = 只負(fù)責(zé)計(jì)算 UI,禁止操作 DOM、視頻、定時器等外部系統(tǒng)
- 所有外部系統(tǒng)同步、副作用操作,必須放入 useEffect
- 嚴(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é)論
- 操作 DOM / 視頻 / 外部系統(tǒng) = 必須用 useEffect
- 區(qū)別核心是執(zhí)行時機(jī),不是執(zhí)行次數(shù)
- 函數(shù)體渲染期間 → 只算 UI;useEffect 渲染后 → 操作外部系統(tǒng)
- 不要用 “沒報錯” 證明代碼正確,要遵循 React 設(shè)計(jì)規(guī)則