React useEffect、useLayoutEffect底層機(jī)制及區(qū)別介紹

useEffect
useEffect 是 React 中的一個(gè) Hook,允許你在函數(shù)組件中執(zhí)行副作用操作。副作用(Side Effects)是指組件中不直接涉及渲染過(guò)程的行為,例如數(shù)據(jù)獲取、事件監(jiān)聽(tīng)、訂閱、設(shè)置定時(shí)器、手動(dòng)修改 DOM 等。
基本用法:
useEffect(() => {
// 執(zhí)行副作用操作
// 可以是數(shù)據(jù)獲取、訂閱等操作
return () => {
// 可選的清理操作,清理副作用
};
}, [dependencies]);不設(shè)置依賴(lài)
useEffect(()=>{
//獲取最新的狀態(tài)值
})- 第一次渲染完成后,執(zhí)行
callback,等價(jià)于componentDidMount - 在組件每一次更新完畢后,也會(huì)執(zhí)行
callback,等價(jià)于componentDidUpdate
下面的寫(xiě)法可以獲取到最新的狀態(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ù)組,無(wú)依賴(lài)
useEffect(()=>{ },[])只有第一次渲染完畢后,才會(huì)執(zhí)行callback,每一次視圖更新完畢后,callback不再執(zhí)行,「類(lèi)似于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è)依賴(lài)
useEffect(() => {
}, [依賴(lài)項(xiàng)1,依賴(lài)項(xiàng)2,依賴(lài)項(xiàng)3]);- 第一次渲染完畢會(huì)執(zhí)行
callback - 依賴(lài)的狀態(tài)值(或者多個(gè)依賴(lài)狀態(tài)中的一個(gè))發(fā)生變化,也會(huì)出發(fā)
callback執(zhí)行 - 但是依賴(lài)的狀態(tài)如果沒(mé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ì)通過(guò)
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)等操作語(yǔ)句中。
下面是錯(cuò)誤的寫(xiě)法:
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)該是這樣,把邏輯寫(xiě)在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ò)誤示例
這樣寫(xiě)會(huì)直接進(jìn)行報(bào)錯(cuò)。useEffect如果設(shè)置返回值,則返回值必須是一個(gè)函數(shù)「代表組件銷(xiāo)毀時(shí)觸發(fā)」;下面案例中,callback經(jīng)過(guò)async的修飾,返回的是一個(gè)promise實(shí)例,不符合要求,所以報(bào)錯(cuò)!
useEffect(async ()=>{
let data = await queryData();
console.log(”成功“,data)
},[])
用.then獲取數(shù)據(jù)
直接調(diào)用queryData,通過(guò).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ì)算、繪制頁(yè)面等。
React 執(zhí)行副作用(useEffect):在頁(yè)面渲染完成后,再去執(zhí)行副作用。
這種順序意味著 useEffect 中的副作用操作不會(huì)阻塞瀏覽器渲染。換句話(huà)說(shuō),React 在觸發(fā) useEffect 后,會(huì)立即開(kāi)始瀏覽器的繪制過(guò)程,所以不會(huì)影響頁(yè)面的視覺(jué)展示。
舉個(gè)例子,如果你使用 useEffect 來(lái)發(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ù)組件的變化更新頁(yè)面結(jié)構(gòu)。
useLayoutEffect 執(zhí)行:同步執(zhí)行副作用,這時(shí) DOM 已經(jīng)更新,但瀏覽器還沒(méi)進(jìn)行繪制。
瀏覽器繪制:完成頁(yè)面渲染。
這意味著 useLayoutEffect 會(huì) 阻塞 渲染,直到它執(zhí)行完成后,瀏覽器才會(huì)進(jìn)行頁(yè)面渲染。因此,如果 useLayoutEffect 中執(zhí)行的操作非常耗時(shí),可能會(huì)導(dǎo)致頁(yè)面渲染延遲,影響用戶(hù)體驗(yàn)。
對(duì)瀏覽器渲染的影響
useEffect 的影響:
異步執(zhí)行:不會(huì)阻塞頁(yè)面渲染,可以在渲染完成后執(zhí)行副作用操作。
不會(huì)影響頁(yè)面視覺(jué):由于 useEffect 在瀏覽器渲染完成后才執(zhí)行,它不會(huì)導(dǎo)致頁(yè)面布局變化,也不會(huì)造成視覺(jué)閃爍。
性能優(yōu)化:因?yàn)槭钱惒綀?zhí)行的,所以瀏覽器渲染不會(huì)被卡住,頁(yè)面的響應(yīng)速度和流暢性得到保證。
useLayoutEffect 的影響:
同步執(zhí)行:會(huì)在 DOM 更新后但在頁(yè)面渲染之前立即執(zhí)行副作用,阻塞瀏覽器的繪制過(guò)程。
可能影響性能:由于同步執(zhí)行,瀏覽器渲染必須等待 useLayoutEffect 完成,如果副作用中有復(fù)雜的操作,可能會(huì)導(dǎo)致頁(yè)面加載時(shí)間延遲或出現(xiàn)白屏現(xiàn)象。
影響布局計(jì)算:適合用于獲取 DOM 元素的大小、位置等布局信息,因?yàn)樗陧?yè)面渲染之前執(zhí)行,你可以確保你拿到的是最新的、正確的布局信息。
使用場(chǎng)景
useEffect 的常見(jiàn)場(chǎng)景:
數(shù)據(jù)獲取:例如從 API 獲取數(shù)據(jù),或者發(fā)起網(wǎng)絡(luò)請(qǐng)求。
事件監(jiān)聽(tīng)和取消訂閱:如為組件添加事件監(jiān)聽(tīng)器(例如 resize 或 scroll),并在組件卸載時(shí)移除它們。
定時(shí)器/計(jì)時(shí)器:設(shè)置定時(shí)任務(wù)(如 setInterval 或 setTimeout),并在組件卸載時(shí)清理。
更新?tīng)顟B(tài):例如當(dāng)某個(gè)副作用觸發(fā)時(shí)更新組件狀態(tài),通常與 DOM 操作無(wú)關(guān)。
例如: 通過(guò) useEffect 實(shí)現(xiàn)獲取數(shù)據(jù)并更新?tīng)顟B(tài):
useEffect(() => {
fetchData().then(data => {
setData(data);
});
}, []); // 依賴(lài)空數(shù)組,表示只在組件掛載時(shí)執(zhí)行在組件中監(jiān)聽(tīng) resize 或 scroll 事件時(shí),useEffect 是一個(gè)常見(jiàn)的選擇。你可以在 useEffect 中添加事件監(jiān)聽(tīng)器,并在組件卸載時(shí)清除這些監(jiān)聽(tīng)器。
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)聽(tīng)器
window.addEventListener('resize', handleResize);
// 返回清理函數(shù),在組件卸載時(shí)移除事件監(jiān)聽(tīng)器
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // 空數(shù)組,表示只在組件掛載和卸載時(shí)執(zhí)行
return (
<div>
<p>Window width: {windowWidth}px</p>
</div>
);
}
export default WindowResize;使用定時(shí)器來(lái)執(zhí)行某些定期操作,如每隔一定時(shí)間更新?tīng)顟B(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]); // 依賴(lài)于 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 的常見(jiàn)場(chǎng)景:
DOM 操作:需要在頁(yè)面渲染之前操作 DOM(比如滾動(dòng)條位置、修改樣式、元素大小調(diào)整等)。
獲取布局信息:例如測(cè)量 DOM 元素的寬度、高度或位置,因?yàn)檫@些信息可能在瀏覽器繪制過(guò)程中發(fā)生變化,所以你必須在渲染之前獲取。
修復(fù)布局閃爍:如果你需要在頁(yè)面渲染之前進(jìn)行 DOM 操作,否則會(huì)導(dǎo)致閃爍或視覺(jué)不一致。
例如: 使用 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 來(lái)確保在瀏覽器繪制頁(yè)面之前,能獲取到最新的 DOM 元素的尺寸。
性能對(duì)比
- useEffect 的性能優(yōu)勢(shì):由于是異步執(zhí)行,它不會(huì)阻塞瀏覽器的渲染過(guò)程。即使副作用中有較重的操作(如網(wǎng)絡(luò)請(qǐng)求、設(shè)置定時(shí)器等),它們也會(huì)在瀏覽器渲染完成后執(zhí)行,不會(huì)影響頁(yè)面渲染速度。
- useLayoutEffect 的性能成本:由于它是同步執(zhí)行,并且會(huì)阻塞瀏覽器繪制,可能會(huì)導(dǎo)致頁(yè)面渲染的延遲,特別是在副作用操作比較復(fù)雜時(shí)(比如大量的 DOM 計(jì)算)。如果在 useLayoutEffect 中執(zhí)行了復(fù)雜的邏輯,它可能會(huì)影響頁(yè)面的響應(yīng)速度,給用戶(hù)帶來(lái)不流暢的體驗(yàn)。
到此這篇關(guān)于React useEffect、useLayoutEffect底層機(jī)制的文章就介紹到這了,更多相關(guān)React useEffect、useLayoutEffect內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Form組件的實(shí)現(xiàn)封裝雜談
這篇文章主要介紹了React Form組件的實(shí)現(xiàn)封裝雜談,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
React根據(jù)當(dāng)前頁(yè)面路由進(jìn)行自動(dòng)高亮示例代碼
要根據(jù)當(dāng)前頁(yè)面路由自動(dòng)高亮頂部菜單項(xiàng),可以使用 React Router 的 useLocation 鉤子來(lái)獲取當(dāng)前路徑,并根據(jù)路徑動(dòng)態(tài)設(shè)置菜單項(xiàng)的高亮效果,本文給大家介紹了一個(gè)完整的示例,展示如何根據(jù)當(dāng)前頁(yè)面路由自動(dòng)高亮頂部菜單項(xiàng),需要的朋友可以參考下2024-07-07
React項(xiàng)目使用ES6解決方案及JSX使用示例詳解
這篇文章主要為大家介紹了React項(xiàng)目使用ES6解決方案及JSX使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
React組件的創(chuàng)建與state同步異步詳解
這篇文章主要介紹了react組件實(shí)例屬性state,有狀態(tài)state的組件稱(chēng)作復(fù)雜組件,沒(méi)有狀態(tài)的組件稱(chēng)為簡(jiǎn)單組件,狀態(tài)里存儲(chǔ)數(shù)據(jù),數(shù)據(jù)的改變驅(qū)動(dòng)頁(yè)面的展示,本文結(jié)合實(shí)例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-03-03
React Native按鈕Touchable系列組件使用教程示例
這篇文章主要為大家介紹了React Native按鈕Touchable系列組件使用教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
React?Hook實(shí)現(xiàn)對(duì)話(huà)框組件
這篇文章主要為大家詳細(xì)介紹了React?Hook實(shí)現(xiàn)對(duì)話(huà)框組件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
在React項(xiàng)目中使用TypeScript詳情
這篇文章主要介紹了在React項(xiàng)目中使用TypeScript詳情,文章通過(guò)圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
React Native 集成jpush-react-native的示例代碼
這篇文章主要介紹了React Native 集成jpush-react-native的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

