目錄
- useEffect vs useLayoutEffect
- useEffect
- useLayoutEffect
- 主要區別總結
- 選擇建議
- 注意事項
- useLayoutEffect 使用示例
- 測量 DOM 元素的尺寸和位置
- 示例:自適應彈出框定位
- 同步更新樣式以避免閃爍
- 示例:根據內容動態調整容器高度
- 圖像或 Canvas 繪制前的準備工作
- 示例:Canvas 繪制自定義圖形
useEffect vs useLayoutEffect
useLayoutEffect
和 useEffect
都是 React Hooks 中用于處理副作用的鉤子,但它們在執行時機和用途上有一些關鍵區別。
理解這些區別有助于在不同的場景下選擇合適的鉤子來優化組件的性能和用戶體驗。
useEffect
-
執行時機:
useEffect
的回調函數會在瀏覽器完成渲染之后異步執行。這意味著它不會阻塞瀏覽器的繪制過程。
-
用途:
- 適用于大多數副作用操作,如數據獲取、訂閱、手動操作 DOM(例如添加事件監聽器)、設置定時器等。
- 因為它在渲染后異步執行,所以不會導致用戶界面的阻塞,適合處理不影響當前渲染結果的副作用。
-
示例:
import React, { useEffect } from 'react';function Example() {useEffect(() => {// 這里的代碼會在組件渲染到屏幕后異步執行console.log('useEffect 執行');return () => {// 清理函數console.log('useEffect 清理');};}, []); // 空依賴數組表示只在組件掛載和卸載時執行return <div>Hello World</div>;
}
useLayoutEffect
-
執行時機:
useLayoutEffect
的回調函數會在 DOM 更新完成后、瀏覽器進行繪制之前同步執行。這意味著它會阻塞瀏覽器的繪制過程,直到回調函數執行完畢。
-
用途:
- 適用于需要在瀏覽器繪制之前同步執行的操作,如測量 DOM 元素的尺寸、位置,或者進行需要同步更新樣式的操作。
- 由于它會阻塞渲染,應謹慎使用,避免在高頻率更新的場景下使用,以免影響性能。
-
示例:
import React, { useLayoutEffect, useRef, useState } from 'react';function LayoutEffectExample() {const divRef = useRef(null);const [width, setWidth] = useState(0);useLayoutEffect(() => {// 在瀏覽器繪制之前同步獲取 DOM 元素的寬度if (divRef.current) {const { width } = divRef.current.getBoundingClientRect();setWidth(width);}}, []);return (<div><div ref={divRef} style={{ width: '50%', border: '1px solid black' }}>測量寬度</div><p>寬度: {width}px</p></div>);
}
主要區別總結
特性 | useEffect | useLayoutEffect |
---|---|---|
執行時機 | 渲染后異步執行 | 渲染后、繪制前同步執行 |
阻塞渲染 | 不阻塞 | 阻塞 |
適用場景 | 大多數副作用操作,如數據獲取、訂閱等 | 需要同步操作 DOM 或測量布局的場景 |
性能影響 | 較低,適合頻繁使用的副作用 | 高,需謹慎使用以避免阻塞渲染 |
選擇建議
-
優先使用
useEffect
:在大多數情況下,useEffect
足以滿足需求,并且由于其異步執行的特性,不會影響用戶界面的渲染性能。 -
必要時使用
useLayoutEffect
:只有在確實需要在瀏覽器繪制之前同步執行某些操作時,才使用useLayoutEffect
。例如,調整布局、測量元素尺寸等。
注意事項
-
避免過度使用
useLayoutEffect
:由于其會阻塞渲染,頻繁或不當使用可能導致頁面卡頓或響應緩慢。 -
清理函數:與
useEffect
一樣,useLayoutEffect
也支持返回一個清理函數,用于在組件卸載或依賴項變化時執行清理操作。
通過理解 useEffect
和 useLayoutEffect
的區別,可以更有效地管理組件的副作用,優化性能,并提升用戶體驗。
useLayoutEffect 使用示例
測量 DOM 元素的尺寸和位置
在某些情況下,你需要精確知道某個 DOM 元素的實際尺寸(寬度、高度)或在頁面中的位置信息,以便根據這些信息進行后續的操作,而這些操作必須在瀏覽器繪制之前完成,否則可能會導致測量的不準確。
示例:自適應彈出框定位
當你有一個彈出框,需要根據觸發它的元素的相對位置來動態定位時,就需要先測量觸發元素的尺寸和位置,然后根據這些信息計算出彈出框的合適位置。
import React, { useRef, useState, useLayoutEffect } from 'react';const PopupExample = () => {const triggerRef = useRef(null);const popupRef = useRef(null);const [position, setPosition] = useState({ top: 0, left: 0 });useLayoutEffect(() => {if (triggerRef.current && popupRef.current) {const triggerRect = triggerRef.current.getBoundingClientRect();// 簡單示例,將彈出框定位在觸發元素的下方const newTop = triggerRect.bottom + window.scrollY;const newLeft = triggerRect.left + window.scrollX;setPosition({ top: newTop, left: newLeft });}}, []);return (<div><button ref={triggerRef}>點擊顯示彈出框</button><div ref={popupRef} style={{ position: 'absolute', top: position.top, left: position.left }}>這是一個彈出框</div></div>);
};export default PopupExample;
在上述代碼中,useLayoutEffect
會在瀏覽器繪制之前同步執行,確保能夠準確獲取觸發元素的尺寸和位置信息,從而正確地定位彈出框。
同步更新樣式以避免閃爍
有時候,你需要根據某些計算結果立即更新元素的樣式,而且希望這些更新在瀏覽器繪制之前完成,以避免出現樣式閃爍的問題。
示例:根據內容動態調整容器高度
當容器內的內容動態變化時,你可能希望容器的高度能夠立即適應內容的變化,而不讓用戶看到內容先溢出再調整高度的過程。
import React, { useRef, useState, useLayoutEffect } from 'react';const DynamicHeightContainer = () => {const containerRef = useRef(null);const [content, setContent] = useState('初始內容');const toggleContent = () => {setContent(prevContent => prevContent === '初始內容' ? '這是一段更長更長的內容,用于測試容器高度的動態調整。' : '初始內容');};useLayoutEffect(() => {if (containerRef.current) {// 這里可以根據內容做一些樣式調整,例如設置高度// 示例中簡單打印信息,實際應用中可根據需求修改樣式console.log('根據內容調整容器樣式');}}, [content]);return (<div><button onClick={toggleContent}>切換內容</button><div ref={containerRef} style={{ border: '1px solid black' }}>{content}</div></div>);
};export default DynamicHeightContainer;
在這個例子中,useLayoutEffect
確保在瀏覽器繪制之前根據內容的變化同步更新容器的樣式,避免了內容溢出或高度閃爍的問題。
圖像或 Canvas 繪制前的準備工作
在進行圖像處理或使用 Canvas 進行繪圖時,有時需要在繪制之前獲取一些必要的信息或進行一些預處理操作,這些操作也需要在瀏覽器繪制之前完成。
示例:Canvas 繪制自定義圖形
在使用 Canvas 繪制復雜圖形時,可能需要先測量某些元素的位置或尺寸,然后根據這些信息進行繪制。
import React, { useRef, useLayoutEffect } from 'react';const CanvasDrawing = () => {const canvasRef = useRef(null);useLayoutEffect(() => {const canvas = canvasRef.current;const ctx = canvas.getContext('2d');// 假設這里需要根據某個 DOM 元素的位置來繪制圖形// 先進行相關測量等準備工作(此處簡化)// 開始繪制ctx.fillStyle = 'red';ctx.fillRect(10, 10, 50, 50);}, []);return <canvas ref={canvasRef} width="200" height="200"></canvas>;
};export default CanvasDrawing;
在上述代碼中,useLayoutEffect
可以確保在進行 Canvas 繪制之前完成所有必要的準備工作,保證繪制的準確性和流暢性 。