React useEffect、useLayoutEffect底層機(jī)制及區(qū)別介紹
useEffect
useEffect 是 React 中的一個 Hook,允許你在函數(shù)組件中執(zhí)行副作用操作。副作用(Side Effects)是指組件中不直接涉及渲染過程的行為,例如數(shù)據(jù)獲取、事件監(jiān)聽、訂閱、設(shè)置定時器、手動修改 DOM 等。
基本用法:
useEffect(() => { // 執(zhí)行副作用操作 // 可以是數(shù)據(jù)獲取、訂閱等操作 return () => { // 可選的清理操作,清理副作用 }; }, [dependencies]);
不設(shè)置依賴
useEffect(()=>{ //獲取最新的狀態(tài)值 })
- 第一次渲染完成后,執(zhí)行
callback
,等價于componentDidMount
- 在組件每一次更新完畢后,也會執(zhí)行
callback
,等價于componentDidUpdate
下面的寫法可以獲取到最新的狀態(tài)值
const Demo = function Demo() { let [num, setNum] = useState(0), [x, setX] = useState(100); useEffect(() => { // 獲取最新的狀態(tài)值 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>; };
設(shè)置空數(shù)組,無依賴
useEffect(()=>{ },[])
只有第一次渲染完畢后,才會執(zhí)行callback
,每一次視圖更新完畢后,callback
不再執(zhí)行,「類似于componentDidMount
」
初次渲染,打印@1 @2 ,點(diǎn)擊按鈕之后,只打印出@1
const Demo = function Demo() { let [num, setNum] = useState(0), [x, setX] = useState(100); useEffect(() => { // 獲取最新的狀態(tài)值 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>; };
設(shè)置多個依賴
useEffect(() => { }, [依賴項(xiàng)1,依賴項(xiàng)2,依賴項(xiàng)3]);
- 第一次渲染完畢會執(zhí)行
callback
- 依賴的狀態(tài)值(或者多個依賴狀態(tài)中的一個)發(fā)生變化,也會出發(fā)
callback
執(zhí)行 - 但是依賴的狀態(tài)如果沒有變化,在組件更新的時候,
callback
是不會執(zhí)行
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>; };
返回值是一個函數(shù)
useEffect(()=>{ return ()=>{ //獲取的是上一次狀態(tài)的值 //返回的函數(shù),會在組件釋放的時候執(zhí)行 } } )
- 初始渲染之后返回一個小函數(shù),放到鏈表當(dāng)中
- 如果組件更新,會通過
updateEffect
會把上一次返回的函數(shù)執(zhí)行「可以“理解為”上一次渲染的組件釋放了」
const Demo = function Demo() { let [num, setNum] = useState(0), [x, setX] = useState(100); useEffect(() => { return () => { // 獲取的是上一次的狀態(tài)值 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>; };
總結(jié)
useEffect的使用環(huán)境
useEffect必須是在函數(shù)的最外層上下文中調(diào)用,不能把其嵌入到條件判斷、循環(huán)等操作語句中。
下面是錯誤的寫法:
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>; };
正確的應(yīng)該是這樣,把邏輯寫在useEffect內(nèi)部:
useEffect(() => { if (num > 5) { console.log('OK'); } }, [num]);
useEffect 中發(fā)送請求
首先模擬一個請求
// 模擬從服務(wù)器異步獲取數(shù)據(jù) const queryData = () => { return new Promise(resolve => { setTimeout(() => { resolve([10, 20, 30]); }, 1000); }); };
錯誤示例
這樣寫會直接進(jìn)行報(bào)錯。useEffect
如果設(shè)置返回值,則返回值必須是一個函數(shù)
「代表組件銷毀時觸發(fā)」;下面案例中,callback
經(jīng)過async
的修飾,返回的是一個promise
實(shí)例,不符合要求,所以報(bào)錯!
useEffect(async ()=>{ let data = await queryData(); console.log(”成功“,data) },[])
用.then獲取數(shù)據(jù)
直接調(diào)用queryData
,通過.then
獲取數(shù)據(jù)
useEffect(async ()=>{ queryData().then(data=>{ console.log(”成功“,data) }) },[])
在useEffect創(chuàng)建一個函數(shù)
在useEffect
返回值里創(chuàng)建一個函數(shù)并調(diào)用
useEffect( ()=>{ const next = async()=>{ let data = await queryData(); console.log(”成功“,data) }; next(); },[])
總結(jié)
useLayoutEffect
useLayoutEffect
和 useEffect
具有相似的 API 和用法,但它們的執(zhí)行時機(jī)不同。useLayoutEffect 是 同步執(zhí)行 的,它會在瀏覽器 繪制(paint)之前 執(zhí)行副作用操作。
基本用法:
useLayoutEffect(() => { // 執(zhí)行副作用操作,特別是需要與 DOM 布局相關(guān)的操作 return () => { // 可選的清理操作 }; }, [dependencies]);
useLayoutEffect 和useEffect區(qū)別
useLayoutEffect
會阻塞瀏覽器渲染真實(shí)DOM,優(yōu)先執(zhí)行Effect鏈表
中的callback
;
useEffect
不會阻塞瀏覽器渲染真實(shí)DOM,在渲染真實(shí)DOM的同時,去執(zhí)行Effect鏈表中的callback
;
useLayoutEffect
設(shè)置的callback
要優(yōu)先于useEffect
去執(zhí)行- 在兩者設(shè)置的
callback
中,依然可以獲取DOM元素「因?yàn)檫@是的DOM對象已經(jīng)創(chuàng)建了,區(qū)別只是瀏覽器是否渲染」 - 如果在
callback
函數(shù)中又修改了狀態(tài)值「視圖又要更新」- useEffect:瀏覽器肯定是把第一次的真實(shí)DOM已經(jīng)繪制,再去渲染第二次的真實(shí)
- DOMuseLayoutEffect:瀏覽器是把兩次真實(shí)DOM的渲染,合并在一起渲染
視圖更新的步驟
1、基于babel-preset-react-app把JSX便衣乘
createElement`格式
2、把createElement
執(zhí)行,創(chuàng)建virtualDOM
3、基于root.render
方法把virtual
變?yōu)檎鎸?shí)DOM對象「DOM- DIFF」useLayoutEffect
阻塞第4步操作,先去執(zhí)行Effect鏈表中的方法「同步操作」useEffect
第4步操作和Effect鏈表中的方法執(zhí)行,是同時進(jìn)行的「異步操作」
4、瀏覽器渲染和繪制真實(shí)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>; };
執(zhí)行時機(jī):瀏覽器渲染的關(guān)系
useEffect:
useEffect 是 異步 執(zhí)行的,它是在 React 更新完 DOM 后(即瀏覽器繪制之后)執(zhí)行的。瀏覽器渲染通常分為幾個階段:
瀏覽器渲染:更新 DOM、進(jìn)行布局計(jì)算、繪制頁面等。
React 執(zhí)行副作用(useEffect):在頁面渲染完成后,再去執(zhí)行副作用。
這種順序意味著 useEffect 中的副作用操作不會阻塞瀏覽器渲染。換句話說,React 在觸發(fā) useEffect 后,會立即開始瀏覽器的繪制過程,所以不會影響頁面的視覺展示。
舉個例子,如果你使用 useEffect 來發(fā)起 API 請求,React 會等到瀏覽器完成渲染后,再去發(fā)起請求,不會影響渲染速度。
useLayoutEffect:
useLayoutEffect 與 useEffect 的最大區(qū)別是它會 同步執(zhí)行,并且會在 DOM 更新后但在瀏覽器渲染(繪制)之前執(zhí)行。執(zhí)行順序如下:
React 更新虛擬 DOM 和 DOM:這一步會根據(jù)組件的變化更新頁面結(jié)構(gòu)。
useLayoutEffect 執(zhí)行:同步執(zhí)行副作用,這時 DOM 已經(jīng)更新,但瀏覽器還沒進(jìn)行繪制。
瀏覽器繪制:完成頁面渲染。
這意味著 useLayoutEffect 會 阻塞 渲染,直到它執(zhí)行完成后,瀏覽器才會進(jìn)行頁面渲染。因此,如果 useLayoutEffect 中執(zhí)行的操作非常耗時,可能會導(dǎo)致頁面渲染延遲,影響用戶體驗(yàn)。
對瀏覽器渲染的影響
useEffect 的影響:
異步執(zhí)行:不會阻塞頁面渲染,可以在渲染完成后執(zhí)行副作用操作。
不會影響頁面視覺:由于 useEffect 在瀏覽器渲染完成后才執(zhí)行,它不會導(dǎo)致頁面布局變化,也不會造成視覺閃爍。
性能優(yōu)化:因?yàn)槭钱惒綀?zhí)行的,所以瀏覽器渲染不會被卡住,頁面的響應(yīng)速度和流暢性得到保證。
useLayoutEffect 的影響:
同步執(zhí)行:會在 DOM 更新后但在頁面渲染之前立即執(zhí)行副作用,阻塞瀏覽器的繪制過程。
可能影響性能:由于同步執(zhí)行,瀏覽器渲染必須等待 useLayoutEffect 完成,如果副作用中有復(fù)雜的操作,可能會導(dǎo)致頁面加載時間延遲或出現(xiàn)白屏現(xiàn)象。
影響布局計(jì)算:適合用于獲取 DOM 元素的大小、位置等布局信息,因?yàn)樗陧撁驿秩局皥?zhí)行,你可以確保你拿到的是最新的、正確的布局信息。
使用場景
useEffect 的常見場景:
數(shù)據(jù)獲取:例如從 API 獲取數(shù)據(jù),或者發(fā)起網(wǎng)絡(luò)請求。
事件監(jiān)聽和取消訂閱:如為組件添加事件監(jiān)聽器(例如 resize 或 scroll),并在組件卸載時移除它們。
定時器/計(jì)時器:設(shè)置定時任務(wù)(如 setInterval 或 setTimeout),并在組件卸載時清理。
更新狀態(tài):例如當(dāng)某個副作用觸發(fā)時更新組件狀態(tài),通常與 DOM 操作無關(guān)。
例如: 通過 useEffect 實(shí)現(xiàn)獲取數(shù)據(jù)并更新狀態(tài):
useEffect(() => { fetchData().then(data => { setData(data); }); }, []); // 依賴空數(shù)組,表示只在組件掛載時執(zhí)行
在組件中監(jiān)聽 resize 或 scroll 事件時,useEffect 是一個常見的選擇。你可以在 useEffect 中添加事件監(jiān)聽器,并在組件卸載時清除這些監(jiān)聽器。
import React, { useState, useEffect } from 'react'; function WindowResize() { const [windowWidth, setWindowWidth] = useState(window.innerWidth); useEffect(() => { // 定義事件處理函數(shù) const handleResize = () => { setWindowWidth(window.innerWidth); // 更新寬度狀態(tài) }; // 在組件掛載時添加事件監(jiān)聽器 window.addEventListener('resize', handleResize); // 返回清理函數(shù),在組件卸載時移除事件監(jiān)聽器 return () => { window.removeEventListener('resize', handleResize); }; }, []); // 空數(shù)組,表示只在組件掛載和卸載時執(zhí)行 return ( <div> <p>Window width: {windowWidth}px</p> </div> ); } export default WindowResize;
使用定時器來執(zhí)行某些定期操作,如每隔一定時間更新狀態(tài)。
import React, { useState, useEffect } from 'react'; function Timer() { const [seconds, setSeconds] = useState(0); useEffect(() => { // 創(chuàng)建定時器,每秒增加一次秒數(shù) const intervalId = setInterval(() => { setSeconds((prevSeconds) => prevSeconds + 1); }, 1000); // 清理函數(shù),組件卸載時清除定時器 return () => clearInterval(intervalId); }, []); // 空數(shù)組,表示只在組件掛載時設(shè)置定時器,卸載時清理 return ( <div> <p>Seconds: {seconds}</p> </div> ); } export default Timer;
副作用可能會觸發(fā)狀態(tài)更新,特別是在某些條件發(fā)生變化時,比如從 API 獲取數(shù)據(jù)或處理輸入事件等。useEffect 在 inputValue 改變時觸發(fā),設(shè)置一個 500 毫秒的延遲,用 setTimeout 更新 delayedValue。每次 inputValue 更新時,都會清理上一個定時器,避免舊的定時器執(zhí)行。這樣,delayedValue 會延遲顯示輸入框的值,實(shí)現(xiàn)了一個防抖的效果。
import React, { useState, useEffect } from 'react'; function InputWithDelay() { const [inputValue, setInputValue] = useState(''); const [delayedValue, setDelayedValue] = useState(''); useEffect(() => { // 設(shè)置延遲更新的效果 const timeoutId = setTimeout(() => { setDelayedValue(inputValue); }, 500); // 輸入后 500ms 更新 delayedValue // 清理函數(shù):在輸入值變化時清除上一個 timeout return () => clearTimeout(timeoutId); }, [inputValue]); // 依賴于 inputValue,每次輸入值變化時都會觸發(fā) return ( <div> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Type something..." /> <p>Delayed value: {delayedValue}</p> </div> ); } export default InputWithDelay;
useLayoutEffect 的常見場景:
DOM 操作:需要在頁面渲染之前操作 DOM(比如滾動條位置、修改樣式、元素大小調(diào)整等)。
獲取布局信息:例如測量 DOM 元素的寬度、高度或位置,因?yàn)檫@些信息可能在瀏覽器繪制過程中發(fā)生變化,所以你必須在渲染之前獲取。
修復(fù)布局閃爍:如果你需要在頁面渲染之前進(jìn)行 DOM 操作,否則會導(dǎo)致閃爍或視覺不一致。
例如: 使用 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 }); } }, []); // 只在掛載時執(zhí)行 return ( <div ref={divRef}> Width: {size.width}, Height: {size.height} </div> ); }
使用 useLayoutEffect 來確保在瀏覽器繪制頁面之前,能獲取到最新的 DOM 元素的尺寸。
性能對比
- useEffect 的性能優(yōu)勢:由于是異步執(zhí)行,它不會阻塞瀏覽器的渲染過程。即使副作用中有較重的操作(如網(wǎng)絡(luò)請求、設(shè)置定時器等),它們也會在瀏覽器渲染完成后執(zhí)行,不會影響頁面渲染速度。
- useLayoutEffect 的性能成本:由于它是同步執(zhí)行,并且會阻塞瀏覽器繪制,可能會導(dǎo)致頁面渲染的延遲,特別是在副作用操作比較復(fù)雜時(比如大量的 DOM 計(jì)算)。如果在 useLayoutEffect 中執(zhí)行了復(fù)雜的邏輯,它可能會影響頁面的響應(yīng)速度,給用戶帶來不流暢的體驗(yàn)。
到此這篇關(guān)于React useEffect、useLayoutEffect底層機(jī)制的文章就介紹到這了,更多相關(guān)React useEffect、useLayoutEffect內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React使用Canvas繪制大數(shù)據(jù)表格的實(shí)例代碼
之前一直想用Canvas做表格渲染的,最近發(fā)現(xiàn)了一個很不錯的Canvas繪圖框架Leafer,api很友好就試著寫了一下,文中有詳細(xì)的代碼示例供大家參考,感興趣的小伙伴可以自己動手試試2023-09-09新建的React Native就遇到vscode報(bào)警解除方法
這篇文章主要為大家介紹了新建的React Native就遇到vscode報(bào)警解除方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10react?Scheduler?實(shí)現(xiàn)示例教程
這篇文章主要為大家介紹了react?Scheduler?實(shí)現(xiàn)示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09reset.css瀏覽器默認(rèn)樣式表重置(user?agent?stylesheet)的示例代碼
這篇文章主要介紹了reset.css瀏覽器默認(rèn)樣式表重置(user?agent?stylesheet),本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-12-12學(xué)習(xí)ahooks useRequest并實(shí)現(xiàn)手寫
這篇文章主要為大家介紹了學(xué)習(xí)ahooks useRequest并實(shí)現(xiàn)手寫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03