【React】useEffect、useLayoutEffect底層機制

目錄

  • useEffect
    • 不設置依賴
    • 設置空數組,無依賴
    • 設置多個依賴
    • 返回值是一個函數
    • 總結
    • useEffect的使用環境
    • useEffect 中發送請求
      • 錯誤示例
      • 用.then獲取數據
      • 在useEffect創建一個函數
    • 總結
  • useLayoutEffect
    • useLayoutEffect 和useEffect區別
      • 執行時機:瀏覽器渲染的關系
        • useEffect:
        • useLayoutEffect:
      • 對瀏覽器渲染的影響
        • useEffect 的影響:
        • useLayoutEffect 的影響:
      • 使用場景
        • useEffect 的常見場景:
        • useLayoutEffect 的常見場景:
      • 性能對比

在這里插入圖片描述

useEffect

useEffect 是 React 中的一個 Hook,允許你在函數組件中執行副作用操作。副作用(Side Effects)是指組件中不直接涉及渲染過程的行為,例如數據獲取、事件監聽、訂閱、設置定時器、手動修改 DOM 等。

基本用法:

useEffect(() => {// 執行副作用操作// 可以是數據獲取、訂閱等操作return () => {// 可選的清理操作,清理副作用};
}, [dependencies]);

不設置依賴

useEffect(()=>{//獲取最新的狀態值
})
  • 第一次渲染完成后,執行callback,等價于 componentDidMount
  • 在組件每一次更新完畢后,也會執行callback,等價于 componentDidUpdate
    下面的寫法可以獲取到最新的狀態值

const Demo = function Demo() {let [num, setNum] = useState(0),[x, setX] = useState(100);useEffect(() => {// 獲取最新的狀態值console.log('@1', num);});const handle = () => {setNum(num + 1);};return <div className="demo"><span className="num">{num}</span><Button type="primary"size="small"onClick={handle}>新增</Button></div>;
};

設置空數組,無依賴

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

只有第一次渲染完畢后,才會執行callback,每一次視圖更新完畢后,callback不再執行,「類似于componentDidMount

初次渲染,打印@1 @2 ,點擊按鈕之后,只打印出@1


const Demo = function Demo() {let [num, setNum] = useState(0),[x, setX] = useState(100);useEffect(() => {// 獲取最新的狀態值console.log('@1', num);});useEffect(() => {console.log('@2', num);}, []);const handle = () => {setNum(num + 1);};return <div className="demo"><span className="num">{num}</span><Button type="primary"size="small"onClick={handle}>新增</Button></div>;
};

設置多個依賴

useEffect(() => {}, [依賴項1,依賴項2,依賴項3]);
  • 第一次渲染完畢會執行callback
  • 依賴的狀態值(或者多個依賴狀態中的一個)發生變化,也會出發callback執行
  • 但是依賴的狀態如果沒有變化,在組件更新的時候,callback是不會執行

const Demo = function Demo() {let [num, setNum] = useState(0),[x, setX] = useState(100);useEffect(() => {console.log('@3', num);}, [num]);const handle = () => {setNum(num + 1);};return <div className="demo"><span className="num">{num}</span><Button type="primary"size="small"onClick={handle}>新增</Button></div>;
};

返回值是一個函數

useEffect(()=>{ return ()=>{ 
//獲取的是上一次狀態的值
//返回的函數,會在組件釋放的時候執行
} } )
  • 初始渲染之后返回一個小函數,放到鏈表當中
  • 如果組件更新,會通過updateEffect會把上一次返回的函數執行「可以“理解為”上一次渲染的組件釋放了」

const Demo = function Demo() {let [num, setNum] = useState(0),[x, setX] = useState(100);useEffect(() => {return () => {// 獲取的是上一次的狀態值console.log('@4', num);};});const handle = () => {setNum(num + 1);};return <div className="demo"><span className="num">{num}</span><Button type="primary"size="small"onClick={handle}>新增</Button></div>;
};

在這里插入圖片描述

總結

在這里插入圖片描述

useEffect的使用環境

useEffect必須是在函數的最外層上下文中調用,不能把其嵌入到條件判斷、循環等操作語句中。
下面是錯誤的寫法:


const Demo = function Demo() {let [num, setNum] = useState(0);if (num > 5) {useEffect(() => {console.log('OK');});}const handle = () => {setNum(num + 1);};return <div className="demo"><span className="num">{num}</span><Button type="primary"size="small"onClick={handle}>新增</Button></div>;
};

正確的應該是這樣,把邏輯寫在useEffect內部:

  useEffect(() => {if (num > 5) {console.log('OK');}}, [num]);

useEffect 中發送請求

首先模擬一個請求

// 模擬從服務器異步獲取數據
const queryData = () => {return new Promise(resolve => {setTimeout(() => {resolve([10, 20, 30]);}, 1000);});
};

錯誤示例

這樣寫會直接進行報錯。useEffect如果設置返回值,則返回值必須是一個函數「代表組件銷毀時觸發」;下面案例中,callback經過async的修飾,返回的是一個promise實例,不符合要求,所以報錯!

useEffect(async ()=>{let data = await queryData();console.log(”成功“,data)
},[])

在這里插入圖片描述

用.then獲取數據

直接調用queryData,通過.then獲取數據

useEffect(async ()=>{queryData().then(data=>{console.log(”成功“,data)})
},[])

在useEffect創建一個函數

useEffect返回值里創建一個函數并調用

useEffect( ()=>{const next = async()=>{let data = await queryData();console.log(”成功“,data)};next();
},[])

總結

在這里插入圖片描述

useLayoutEffect

useLayoutEffect useEffect 具有相似的 API 和用法,但它們的執行時機不同。useLayoutEffect 是 同步執行 的,它會在瀏覽器 繪制(paint)之前 執行副作用操作。

基本用法:

useLayoutEffect(() => {// 執行副作用操作,特別是需要與 DOM 布局相關的操作return () => {// 可選的清理操作};
}, [dependencies]);

useLayoutEffect 和useEffect區別

useLayoutEffect 會阻塞瀏覽器渲染真實DOM,優先執行Effect鏈表中的callback;
useEffect不會阻塞瀏覽器渲染真實DOM,在渲染真實DOM的同時,去執行Effect鏈表中的callback;

  • useLayoutEffect設置的callback要優先于useEffect去執行
  • 在兩者設置的callback中,依然可以獲取DOM元素「因為這是的DOM對象已經創建了,區別只是瀏覽器是否渲染」
  • 如果在callback函數中又修改了狀態值「視圖又要更新」
    • useEffect:瀏覽器肯定是把第一次的真實DOM已經繪制,再去渲染第二次的真實DOM
    • useLayoutEffect:瀏覽器是把兩次真實DOM的渲染,合并在一起渲染

視圖更新的步驟
1、基于babel-preset-react-app把JSX便衣乘createElement`格式

2、把createElement執行,創建virtualDOM

3、基于root.render方法把virtual變為真實DOM對象「DOM- DIFF」
useLayoutEffect 阻塞第4步操作,先去執行Effect鏈表中的方法「同步操作」
useEffect第4步操作和Effect鏈表中的方法執行,是同時進行的「異步操作」

4、瀏覽器渲染和繪制真實DOM對象

下面先打印出useLayoutEffect,再打印出useEffect

const Demo = function Demo() {// console.log('RENDER');let [num, setNum] = useState(0);useLayoutEffect(() => {console.log('useLayoutEffect'); //第一個輸出}, [num]);useEffect(() => {console.log('useEffect'); //第二個輸出}, [num]);return <div className="demo"style={{backgroundColor: num === 0 ? 'red' : 'green'}}><span className="num">{num}</span><Button type="primary" size="small"onClick={() => {setNum(0);}}>新增</Button></div>;
};

在這里插入圖片描述

在這里插入圖片描述

執行時機:瀏覽器渲染的關系

useEffect:

useEffect 是 異步 執行的,它是在 React 更新完 DOM 后(即瀏覽器繪制之后)執行的。瀏覽器渲染通常分為幾個階段:

瀏覽器渲染:更新 DOM、進行布局計算、繪制頁面等。

React 執行副作用(useEffect):在頁面渲染完成后,再去執行副作用。

這種順序意味著 useEffect 中的副作用操作不會阻塞瀏覽器渲染。換句話說,React 在觸發 useEffect 后,會立即開始瀏覽器的繪制過程,所以不會影響頁面的視覺展示。

舉個例子,如果你使用 useEffect 來發起 API 請求,React 會等到瀏覽器完成渲染后,再去發起請求,不會影響渲染速度。

useLayoutEffect:

useLayoutEffect 與 useEffect 的最大區別是它會 同步執行,并且會在 DOM 更新后但在瀏覽器渲染(繪制)之前執行。執行順序如下:

React 更新虛擬 DOM 和 DOM:這一步會根據組件的變化更新頁面結構。

useLayoutEffect 執行:同步執行副作用,這時 DOM 已經更新,但瀏覽器還沒進行繪制。

瀏覽器繪制:完成頁面渲染。

這意味著 useLayoutEffect 會 阻塞 渲染,直到它執行完成后,瀏覽器才會進行頁面渲染。因此,如果 useLayoutEffect 中執行的操作非常耗時,可能會導致頁面渲染延遲,影響用戶體驗。

對瀏覽器渲染的影響

useEffect 的影響:

異步執行:不會阻塞頁面渲染,可以在渲染完成后執行副作用操作。

不會影響頁面視覺:由于 useEffect 在瀏覽器渲染完成后才執行,它不會導致頁面布局變化,也不會造成視覺閃爍。

性能優化:因為是異步執行的,所以瀏覽器渲染不會被卡住,頁面的響應速度和流暢性得到保證。

useLayoutEffect 的影響:

同步執行:會在 DOM 更新后但在頁面渲染之前立即執行副作用,阻塞瀏覽器的繪制過程。

可能影響性能:由于同步執行,瀏覽器渲染必須等待 useLayoutEffect 完成,如果副作用中有復雜的操作,可能會導致頁面加載時間延遲或出現白屏現象。

影響布局計算:適合用于獲取 DOM 元素的大小、位置等布局信息,因為它在頁面渲染之前執行,你可以確保你拿到的是最新的、正確的布局信息。

使用場景

useEffect 的常見場景:

數據獲取:例如從 API 獲取數據,或者發起網絡請求。

事件監聽和取消訂閱:如為組件添加事件監聽器(例如 resize 或 scroll),并在組件卸載時移除它們。

定時器/計時器:設置定時任務(如 setInterval 或 setTimeout),并在組件卸載時清理。

更新狀態:例如當某個副作用觸發時更新組件狀態,通常與 DOM 操作無關。

例如: 通過 useEffect 實現獲取數據并更新狀態:

useEffect(() => {fetchData().then(data => {setData(data);});
}, []); // 依賴空數組,表示只在組件掛載時執行

在組件中監聽 resize 或 scroll 事件時,useEffect 是一個常見的選擇。你可以在 useEffect 中添加事件監聽器,并在組件卸載時清除這些監聽器。

import React, { useState, useEffect } from 'react';function WindowResize() {const [windowWidth, setWindowWidth] = useState(window.innerWidth);useEffect(() => {// 定義事件處理函數const handleResize = () => {setWindowWidth(window.innerWidth); // 更新寬度狀態};// 在組件掛載時添加事件監聽器window.addEventListener('resize', handleResize);// 返回清理函數,在組件卸載時移除事件監聽器return () => {window.removeEventListener('resize', handleResize);};}, []); // 空數組,表示只在組件掛載和卸載時執行return (<div><p>Window width: {windowWidth}px</p></div>);
}export default WindowResize;

使用定時器來執行某些定期操作,如每隔一定時間更新狀態。

import React, { useState, useEffect } from 'react';function Timer() {const [seconds, setSeconds] = useState(0);useEffect(() => {// 創建定時器,每秒增加一次秒數const intervalId = setInterval(() => {setSeconds((prevSeconds) => prevSeconds + 1);}, 1000);// 清理函數,組件卸載時清除定時器return () => clearInterval(intervalId);}, []); // 空數組,表示只在組件掛載時設置定時器,卸載時清理return (<div><p>Seconds: {seconds}</p></div>);
}export default Timer;

副作用可能會觸發狀態更新,特別是在某些條件發生變化時,比如從 API 獲取數據或處理輸入事件等。useEffect 在 inputValue 改變時觸發,設置一個 500 毫秒的延遲,用 setTimeout 更新 delayedValue。每次 inputValue 更新時,都會清理上一個定時器,避免舊的定時器執行。這樣,delayedValue 會延遲顯示輸入框的值,實現了一個防抖的效果。

import React, { useState, useEffect } from 'react';function InputWithDelay() {const [inputValue, setInputValue] = useState('');const [delayedValue, setDelayedValue] = useState('');useEffect(() => {// 設置延遲更新的效果const timeoutId = setTimeout(() => {setDelayedValue(inputValue);}, 500); // 輸入后 500ms 更新 delayedValue// 清理函數:在輸入值變化時清除上一個 timeoutreturn () => clearTimeout(timeoutId);}, [inputValue]); // 依賴于 inputValue,每次輸入值變化時都會觸發return (<div><inputtype="text"value={inputValue}onChange={(e) => setInputValue(e.target.value)}placeholder="Type something..."/><p>Delayed value: {delayedValue}</p></div>);
}export default InputWithDelay;
useLayoutEffect 的常見場景:

DOM 操作:需要在頁面渲染之前操作 DOM(比如滾動條位置、修改樣式、元素大小調整等)。

獲取布局信息:例如測量 DOM 元素的寬度、高度或位置,因為這些信息可能在瀏覽器繪制過程中發生變化,所以你必須在渲染之前獲取。

修復布局閃爍:如果你需要在頁面渲染之前進行 DOM 操作,否則會導致閃爍或視覺不一致。

例如: 使用 useLayoutEffect 獲取 DOM 元素尺寸:

import React, { useState, useLayoutEffect, useRef } from 'react';function Component() {const [size, setSize] = useState({ width: 0, height: 0 });const divRef = useRef(null);useLayoutEffect(() => {const div = divRef.current;if (div) {const { width, height } = div.getBoundingClientRect();setSize({ width, height });}}, []); // 只在掛載時執行return (<div ref={divRef}>Width: {size.width}, Height: {size.height}</div>);
}

使用 useLayoutEffect 來確保在瀏覽器繪制頁面之前,能獲取到最新的 DOM 元素的尺寸。

性能對比

  • useEffect 的性能優勢:由于是異步執行,它不會阻塞瀏覽器的渲染過程。即使副作用中有較重的操作(如網絡請求、設置定時器等),它們也會在瀏覽器渲染完成后執行,不會影響頁面渲染速度。

  • useLayoutEffect 的性能成本:由于它是同步執行,并且會阻塞瀏覽器繪制,可能會導致頁面渲染的延遲,特別是在副作用操作比較復雜時(比如大量的 DOM 計算)。如果在 useLayoutEffect 中執行了復雜的邏輯,它可能會影響頁面的響應速度,給用戶帶來不流暢的體驗。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/74058.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/74058.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/74058.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深度解析學術論文成果評估(Artifact Evaluation):從歷史到現狀

深度解析學術論文成果評估(Artifact Evaluation)&#xff1a;從歷史到現狀 引言 在計算機科學和工程領域的學術研究中&#xff0c;可重復性和可驗證性越來越受到重視。隨著實驗性研究的復雜性不斷增加&#xff0c;確保研究成果可以被其他研究者驗證和構建變得尤為重要。這一需…

VSCode創建VUE項目(四)增加用戶Session管理

將用戶信息存儲或者更新到Session sessionStorage.setItem("userID",loginform.value.username); sessionStorage.setItem(loginTime, Date.now()); 獲取Session信息 const storedUserInfo sessionStorage.getItem(userID); const loginTime sessionStorage.get…

威聯通 后臺可用命令查看Bash

一、查看所有可用命令的方法 列出所有外部命令&#xff08;二進制文件&#xff09; 外部命令通常存放在系統路徑&#xff08;如 /bin, /usr/bin, /sbin, /usr/sbin&#xff09;中&#xff1a; bash ls /bin /usr/bin /sbin /usr/sbin # 直接列出命令目錄&#xff08;結果較長&…

游戲MOD伴隨盜號風險,仿冒網站借“風靈月影”竊密【火絨企業版V2.0】

游戲MOD&#xff08;即游戲修改器&#xff09;是一種能夠對游戲進行修改或增強的程序&#xff0c;因其能夠提升游戲體驗&#xff0c;在玩家群體中擁有一定的市場。然而&#xff0c;這類程序大多由第三方開發者制作&#xff0c;容易缺乏完善的安全保障機制&#xff0c;這就為不法…

Kubernetes Init 容器:實現 Nginx 和 PHP 對 MySQL 的依賴檢查

在設計 Kubernetes Pod 時&#xff0c;如果需要在啟動 Nginx 和 PHP 之前等待 MySQL 啟動完成&#xff0c;可以通過 初始化容器&#xff08;initC&#xff09; 來實現。初始化容器可以用于檢查 MySQL 是否可用&#xff0c;只有在 MySQL 可用后&#xff0c;才會繼續啟動主容器&a…

SSL/TLS 和 SSH 介紹以及他們的區別

目錄 SSL/TLS SSL/TLS工作原理的核心步驟握手階段&#xff08;Handshake Protocol&#xff09;加密通信階段&#xff08;Encrypted Communication Phase&#xff09;會話恢復&#xff08;Session Resumption&#xff09; SSH SSH 加密機制的核心步驟 SSH 和 SSL 區別 SSL/TLS …

QT二 QT使用generate form 生成常用UI,各種UI控件

一 。沒有使用general form 和 使用 general form 后&#xff0c;file層面和代碼層面的不同比較 file層面的不同 代碼層面的不同&#xff0c; 在 使用了general form之后&#xff0c;在主界面的構造方法中&#xff0c;使用ui->setupUi(this),就完成了所有UI的處理。 而之…

Qt中多線程

在Qt中實現多線程主要有兩種常用方式&#xff1a;基于QThread的子類化和QObjectmoveToThread的Worker模式。以下是詳細說明和示例代碼&#xff1a; 1. 傳統方法&#xff1a;繼承 QThread&#xff08;適用于簡單任務&#xff09; #include <QThread> #include <QDebug…

從PGC到AIGC:海螺AI多模態內容生成系統架構一站式剖析

海螺AI&#xff1a;基于多模態架構的下一代認知智能引擎 海螺AI核心模型架構基礎模型&#xff1a;abab-6.5語音模型&#xff1a;speech-01 視頻生成管線關鍵子系統快速接入海螺AI 藍耘MaaS平臺什么是MaaS平臺&#xff1f;支持的大模型藍耘搭載海螺AI的優勢 實戰應用教程如何注冊…

二分查找上下界問題的思考

背景 最近在做力扣hot100中的二分查找題目時&#xff0c;發現很多題目都用到了二分查找的變種問題&#xff0c;即二分查找上下界問題&#xff0c;例如以下題目&#xff1a; 35. 搜索插入位置 74. 搜索二維矩陣 34. 在排序數組中查找元素的第一個和最后一個位置 它們不同于查找…

android adjust 卸載與重裝監測

想要洞察應用內用戶的留存率,可以通過Adjust 的卸載與重裝進行監測 名詞解釋: 卸載:集成完成后,卸載應用,安裝狀態為:卸載 重裝:如果應用已經卸載,但一段時間后又進行安裝,則會被視為重裝。 ??????:adjust 文件中說到24 小時后,可以再 adjust 控制臺看安裝…

算法系列——有監督學習——4.支持向量機

一、概述 支持向量機&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;是一種應用范圍非常廣泛的算法&#xff0c;既可以用于分類&#xff0c;也可以用于回歸。 本文將介紹如何將線性支持向量機應用于二元分類問題&#xff0c;以間隔&#xff08;margin&#x…

【Mani_skill】success判斷的核心調用邏輯

1. 可視化調用流程&#xff08;from Deepseek-r1-Cursor&#xff09; [RL算法調用 env.step()]↓ 調用 env.get_info()↓ 調用 env.evaluate() → 返回包含 success 的字典↓ 將 success 存入 info 字典↓ 在 step() 中處理終止條件&#xff1a; terminated success | fail

【圖像處理基石】什么是HDR圖片?

1. 什么是HDR圖片&#xff1f; HDR&#xff08;高動態范圍圖像&#xff0c;High Dynamic Range&#xff09;是一種通過技術手段擴展照片明暗細節的成像方式。以下是關于HDR的詳細說明&#xff1a; 核心原理 動態范圍&#xff1a;指圖像中最亮和最暗區域之間的亮度差。人眼能…

嵌入式筆記 | 正點原子STM32F103ZET6 4 | 中斷補充

1. 外設引腳重映射 1.1 定義 在STM32中&#xff0c;每個外設的引腳都有默認的GPIO端口&#xff0c;但有些引腳可以通過重映射寄存器將功能映射到其他端口。這種機制稱為引腳重映射&#xff0c;主要用于解決引腳復用沖突或優化PCB布線。 1.2 重映射的類型 部分重映射&#x…

如何選擇合適的 AI 模型?(開源 vs 商業 API,應用場景分析)

1. 引言 在 AI 迅猛發展的今天&#xff0c;各類 AI 模型層出不窮&#xff0c;從開源模型&#xff08;如 DeepSeek、Llama、Qwen&#xff09;到商業 API&#xff08;如 OpenAI 的 ChatGPT、Anthropic 的 Claude、Google Gemini&#xff09;&#xff0c;每種方案都有其優勢與適用…

攻克 3D 模型網站建設難題,看迪威系統優勢

在當今數字化時代&#xff0c;3D 模型廣泛應用于建筑設計、游戲開發、工業制造、文化創意等諸多領域。擁有一個功能強大的 3D 模型網站&#xff0c;對于企業展示產品、設計師分享作品、教育機構開展教學等都具有重要意義。然而&#xff0c;構建這樣一個網站卻并非易事&#xff…

使用uniapp的vite版本進行微信小程序開發,在項目中使用mqtt連接、訂閱、發布信息

1、保證在微信公眾平臺配置socket合法域名 2、項目中使用mqtt 建議在package.json中配置"mqtt": “4.1.0”&#xff0c;使用這個版本的依賴 頁面中引入mqtt并配置連接 // ts-ignoreimport * as mqtt from mqtt/dist/mqtt.js; //要使用這里面的const state reacti…

【FAQ】HarmonyOS SDK 閉源開放能力 —Map Kit(6)

1.問題描述&#xff1a; 使用華為內置的MapComponent&#xff0c; 發現顯示不出來。查看日志&#xff0c; MapRender底層有報錯。 解決方案&#xff1a; 麻煩按以下步驟檢查下地圖服務&#xff0c;特別是簽名證書指紋那部分。 1.一般沒有展示地圖&#xff0c;可能和沒有配置…

現代復古像素風品牌海報游戲排版設計裝飾英文字體 Psygen — Modern Pixel Font

Psygen 是一種像素化等寬字體&#xff0c;具有強烈的復古未來主義和網絡風格美學。塊狀的、基于網格的字體采用了早期的計算機界面、街機游戲排版和 ASCII 藝術。 該字體支持拉丁文、西里爾文和希臘文腳本&#xff0c;使其適用于多語言設計。擴展的字符集還具有唯一的符號和方…