目錄
- 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 中執行了復雜的邏輯,它可能會影響頁面的響應速度,給用戶帶來不流暢的體驗。