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