React如何使用Portal實(shí)現(xiàn)跨層級(jí)DOM渲染
什么是 React Portal
在 React 里,組件渲染的元素默認(rèn)會(huì)在組件的 DOM 層級(jí)里出現(xiàn)。不過(guò)有時(shí)候,我們可能希望把元素渲染到當(dāng)前組件層級(jí)之外的地方,這時(shí)候 React Portal 就能派上用場(chǎng)啦。Portal 就像是一個(gè)“傳送門”,能讓你把組件里的元素“傳送到”其他 DOM 節(jié)點(diǎn)下面去渲染,即便這些 DOM 節(jié)點(diǎn)不在當(dāng)前組件的 DOM 層級(jí)里。
為什么需要 Portal
通常在處理一些特殊的 UI 場(chǎng)景時(shí),默認(rèn)的渲染方式可能沒(méi)辦法滿足需求。比如,模態(tài)框、下拉菜單、提示框這類需要在整個(gè)頁(yè)面層級(jí)上顯示的元素,用 Portal 就能很方便地實(shí)現(xiàn)跨層級(jí)渲染,避免一些樣式和布局上的問(wèn)題。
如何使用 Portal
下面是一個(gè)簡(jiǎn)單的例子,我們來(lái)創(chuàng)建一個(gè)模態(tài)框組件,用 Portal 把它渲染到 body 元素下面。
import React from 'react';
import ReactDOM from 'react-dom';
// 創(chuàng)建一個(gè)模態(tài)框組件
const Modal = ({ isOpen, onClose, children }) => {
// 如果模態(tài)框沒(méi)打開(kāi),就不渲染任何內(nèi)容
if (!isOpen) {
return null;
}
// 使用 createPortal 方法把模態(tài)框內(nèi)容渲染到指定的 DOM 節(jié)點(diǎn)
return ReactDOM.createPortal(
// 模態(tài)框的外層容器,添加了一些樣式和事件處理
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{/* 這里是模態(tài)框的內(nèi)容 */}
{children}
{/* 關(guān)閉模態(tài)框的按鈕 */}
<button onClick={onClose}>關(guān)閉</button>
</div>
</div>,
// 指定要渲染到的 DOM 節(jié)點(diǎn),這里是 body 元素
document.body
);
};
// 創(chuàng)建一個(gè)父組件來(lái)使用模態(tài)框
const App = () => {
// 使用 useState 鉤子來(lái)管理模態(tài)框的打開(kāi)和關(guān)閉狀態(tài)
const [isModalOpen, setIsModalOpen] = React.useState(false);
// 打開(kāi)模態(tài)框的函數(shù)
const openModal = () => {
setIsModalOpen(true);
};
// 關(guān)閉模態(tài)框的函數(shù)
const closeModal = () => {
setIsModalOpen(false);
};
return (
<div>
{/* 打開(kāi)模態(tài)框的按鈕 */}
<button onClick={openModal}>打開(kāi)模態(tài)框</button>
{/* 使用模態(tài)框組件 */}
<Modal isOpen={isModalOpen} onClose={closeModal}>
<h2>這是一個(gè)模態(tài)框</h2>
<p>這里可以放模態(tài)框的具體內(nèi)容。</p>
</Modal>
</div>
);
};
export default App;
代碼解釋
1.Modal 組件:
- 它接收三個(gè)屬性:isOpen 用來(lái)判斷模態(tài)框是否打開(kāi),onClose 是關(guān)閉模態(tài)框的回調(diào)函數(shù),children 是模態(tài)框里要顯示的內(nèi)容。
- 當(dāng) isOpen 為 false 時(shí),組件不渲染任何內(nèi)容。
- 用 ReactDOM.createPortal 方法把模態(tài)框的內(nèi)容渲染到 document.body 下面。
- 模態(tài)框的外層容器有一個(gè)點(diǎn)擊事件,點(diǎn)擊時(shí)會(huì)調(diào)用 onClose 函數(shù)關(guān)閉模態(tài)框。
- 模態(tài)框的內(nèi)容部分也有一個(gè)點(diǎn)擊事件,用 e.stopPropagation() 阻止事件冒泡,避免點(diǎn)擊內(nèi)容時(shí)也關(guān)閉模態(tài)框。
2.App 組件:
- 用 useState 鉤子來(lái)管理模態(tài)框的打開(kāi)和關(guān)閉狀態(tài)。
- 有一個(gè)“打開(kāi)模態(tài)框”的按鈕,點(diǎn)擊時(shí)調(diào)用 openModal 函數(shù)把 isModalOpen 設(shè)為 true。
- 使用 Modal 組件,并傳入相應(yīng)的屬性。
適用場(chǎng)景
模態(tài)框:像上面的例子一樣,模態(tài)框通常需要覆蓋整個(gè)頁(yè)面,用 Portal 渲染到 body 下面能避免樣式受父組件的影響。
下拉菜單:下拉菜單可能需要在頁(yè)面的任何位置顯示,用 Portal 可以讓它獨(dú)立于父組件的布局。
提示框:提示框也需要在頁(yè)面的合適位置顯示,使用 Portal 能更好地控制其顯示位置和樣式。
通過(guò)使用 React Portal,你可以更靈活地控制組件的渲染位置,解決一些跨層級(jí)渲染的問(wèn)題。
React Portal優(yōu)勢(shì)
React Portal 是 React 提供的一種強(qiáng)大特性,和傳統(tǒng)的 DOM 操作方式相比,它具有下面這些顯著優(yōu)勢(shì):
1. 保持 React 組件的聲明式特性
在 React 里,開(kāi)發(fā)基于聲明式的編程范式,也就是通過(guò)描述 UI 最終呈現(xiàn)的樣子,讓 React 來(lái)處理具體的渲染邏輯。傳統(tǒng)的 DOM 操作通常是命令式的,需要手動(dòng)操作 DOM 元素,像創(chuàng)建、插入、刪除節(jié)點(diǎn)等。而 React Portal 可以讓你在保持聲明式風(fēng)格的同時(shí),把組件渲染到任意的 DOM 節(jié)點(diǎn)下。
例如,使用 React Portal 創(chuàng)建模態(tài)框時(shí),你只需定義好模態(tài)框的組件結(jié)構(gòu),然后通過(guò) ReactDOM.createPortal 把它渲染到指定位置,而不需要手動(dòng)操作 DOM 來(lái)創(chuàng)建和顯示模態(tài)框。
import React from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
if (!isOpen) {
return null;
}
return ReactDOM.createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>關(guān)閉</button>
</div>
</div>,
document.body
);
};
2. 組件的邏輯與渲染分離
使用 React Portal 能夠讓組件的邏輯和渲染位置解耦。組件內(nèi)部的邏輯可以保持獨(dú)立,不受渲染位置的影響,這樣可以提高組件的可復(fù)用性和可維護(hù)性。
比如,一個(gè)下拉菜單組件,不管它是在頁(yè)面的頂部、側(cè)邊還是其他位置渲染,組件內(nèi)部的邏輯(如菜單項(xiàng)的顯示、點(diǎn)擊事件處理等)都不需要改變。
import React from 'react';
import ReactDOM from 'react-dom';
???????const Dropdown = ({ isOpen, items }) => {
if (!isOpen) {
return null;
}
return ReactDOM.createPortal(
<div className="dropdown">
{items.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>,
document.body
);
};3. 更好地管理事件和狀態(tài)
React Portal 依然遵循 React 的事件系統(tǒng)和狀態(tài)管理機(jī)制。這意味著你可以使用 React 的事件處理函數(shù)和狀態(tài)鉤子(如 useState、useReducer)來(lái)管理組件的交互和狀態(tài),而不需要擔(dān)心 DOM 操作帶來(lái)的事件冒泡和狀態(tài)同步問(wèn)題。
例如,在上面的模態(tài)框例子中,點(diǎn)擊關(guān)閉按鈕時(shí),直接調(diào)用 onClose 函數(shù)來(lái)更新?tīng)顟B(tài),從而關(guān)閉模態(tài)框,React 會(huì)自動(dòng)處理狀態(tài)更新和 UI 重新渲染。
4. 避免樣式和布局沖突
當(dāng)把組件渲染到不同的 DOM 層級(jí)時(shí),傳統(tǒng)的 DOM 操作可能會(huì)導(dǎo)致樣式和布局沖突。而 React Portal 可以讓組件在合適的 DOM 節(jié)點(diǎn)下渲染,避免父組件的樣式和布局對(duì)其產(chǎn)生影響。
比如,模態(tài)框通常需要覆蓋整個(gè)頁(yè)面,使用 React Portal 把模態(tài)框渲染到 body 下面,可以避免父組件的 overflow 樣式影響模態(tài)框的顯示。
5. 方便與 React 生態(tài)系統(tǒng)集成
React Portal 是 React 生態(tài)系統(tǒng)的一部分,可以和其他 React 特性(如上下文、高階組件、鉤子等)無(wú)縫集成。這讓開(kāi)發(fā)者能夠更方便地構(gòu)建復(fù)雜的應(yīng)用程序。
例如,你可以在使用 React Portal 的組件中使用 useContext 鉤子來(lái)共享狀態(tài),或者使用高階組件來(lái)增強(qiáng)組件的功能。
綜上所述,React Portal 提供了一種更簡(jiǎn)潔、高效、可維護(hù)的方式來(lái)處理跨層級(jí)的 DOM 渲染,避免了傳統(tǒng) DOM 操作帶來(lái)的一些問(wèn)題。
有哪些場(chǎng)景不適合使用React Portal
雖然 React Portal 為跨層級(jí) DOM 渲染提供了便利,但在某些實(shí)際開(kāi)發(fā)場(chǎng)景中,使用它可能并不是最佳選擇,以下是一些不適合使用 React Portal 的場(chǎng)景:
1. 簡(jiǎn)單的組件嵌套場(chǎng)景
當(dāng)組件的嵌套結(jié)構(gòu)簡(jiǎn)單,并且不需要跨層級(jí)渲染時(shí),使用 React Portal 會(huì)增加代碼的復(fù)雜性。例如,一個(gè)簡(jiǎn)單的表單組件,其所有子組件都可以自然地嵌套在表單組件內(nèi)部,沒(méi)有必要使用 Portal 來(lái)渲染這些子組件。
import React from 'react';
const SimpleForm = () => {
return (
<form>
<label>姓名: </label>
<input type="text" />
<button type="submit">提交</button>
</form>
);
};
export default SimpleForm;
在這個(gè)例子中,表單組件及其子元素可以正常嵌套渲染,使用 Portal 只會(huì)讓代碼變得復(fù)雜,沒(méi)有實(shí)際的好處。
2. 對(duì)性能要求極高的場(chǎng)景
使用 React Portal 會(huì)增加一定的性能開(kāi)銷,因?yàn)樗枰~外的邏輯來(lái)處理跨層級(jí)的渲染。在對(duì)性能要求極高的場(chǎng)景中,如高頻更新的游戲界面或者實(shí)時(shí)數(shù)據(jù)展示界面,頻繁使用 Portal 可能會(huì)導(dǎo)致性能下降。
例如,一個(gè)實(shí)時(shí)股票價(jià)格顯示組件,每秒需要更新多次數(shù)據(jù),如果使用 Portal 來(lái)渲染價(jià)格顯示區(qū)域,可能會(huì)因?yàn)轭~外的渲染邏輯導(dǎo)致界面卡頓。
3. 組件的渲染依賴于父組件布局的場(chǎng)景
如果組件的渲染和布局高度依賴于其父組件的樣式和布局,使用 React Portal 將組件渲染到其他 DOM 節(jié)點(diǎn)可能會(huì)破壞這種依賴關(guān)系。
比如,一個(gè)卡片組件,其內(nèi)部的子元素需要根據(jù)卡片的寬度和高度進(jìn)行自適應(yīng)布局。如果使用 Portal 將子元素渲染到其他地方,就無(wú)法利用父組件的布局信息,導(dǎo)致布局錯(cuò)亂。
import React from 'react';
???????const Card = () => {
return (
<div className="card">
{/* 子元素依賴于卡片的布局 */}
<div className="card-content">
<p>這是卡片內(nèi)容</p>
</div>
</div>
);
};在這種情況下,將 card-content 用 Portal 渲染到其他地方,就會(huì)破壞其與 card 的布局依賴關(guān)系。
4. 代碼維護(hù)和調(diào)試?yán)щy的場(chǎng)景
當(dāng)項(xiàng)目規(guī)模較大、組件關(guān)系復(fù)雜時(shí),過(guò)度使用 React Portal 會(huì)使代碼的維護(hù)和調(diào)試變得困難。因?yàn)?Portal 打破了常規(guī)的組件嵌套結(jié)構(gòu),開(kāi)發(fā)人員在查找和理解組件之間的關(guān)系時(shí)會(huì)花費(fèi)更多的精力。
例如,在一個(gè)大型的企業(yè)級(jí)應(yīng)用中,如果有大量的組件使用 Portal 進(jìn)行跨層級(jí)渲染,當(dāng)出現(xiàn)問(wèn)題時(shí),很難快速定位到問(wèn)題所在的組件和渲染位置。
5. 兼容性要求嚴(yán)格的場(chǎng)景
雖然 React Portal 是一個(gè)強(qiáng)大的特性,但在一些舊版本的瀏覽器或者特定的環(huán)境中,可能存在兼容性問(wèn)題。如果項(xiàng)目對(duì)瀏覽器兼容性有嚴(yán)格的要求,使用 Portal 可能會(huì)帶來(lái)不必要的麻煩。
例如,一些老舊的瀏覽器可能不支持 React 所依賴的某些特性,導(dǎo)致 Portal 無(wú)法正常工作。在這種情況下,需要考慮其他替代方案來(lái)實(shí)現(xiàn)跨層級(jí)渲染。
React Portal的工作原理
React Portal 是 React 提供的一個(gè)強(qiáng)大特性,它允許你將子組件渲染到父組件 DOM 層級(jí)之外的 DOM 節(jié)點(diǎn)中,下面詳細(xì)解釋其工作原理。
1. 核心概念
在 React 中,組件默認(rèn)的渲染方式是將其生成的元素插入到其父組件對(duì)應(yīng)的 DOM 節(jié)點(diǎn)內(nèi)部,形成嵌套的 DOM 結(jié)構(gòu)。而 React Portal 打破了這種默認(rèn)的渲染規(guī)則,讓組件可以將其內(nèi)容渲染到指定的任意 DOM 節(jié)點(diǎn)下,即便這個(gè)節(jié)點(diǎn)不在當(dāng)前組件的正常 DOM 層級(jí)里。
2. 關(guān)鍵 API:ReactDOM.createPortal
ReactDOM.createPortal 是實(shí)現(xiàn) React Portal 的核心方法,它的函數(shù)簽名如下:
ReactDOM.createPortal(child, container);
- child:這是要渲染的 React 元素、組件或者片段等,也就是你想要通過(guò) Portal 渲染的內(nèi)容。
- container:這是一個(gè) DOM 節(jié)點(diǎn),它指定了 child 最終要被渲染到的目標(biāo)位置。
3. 工作流程
下面結(jié)合一個(gè)簡(jiǎn)單的示例來(lái)詳細(xì)說(shuō)明 React Portal 的工作流程。
import React from 'react';
import ReactDOM from 'react-dom';
// 創(chuàng)建一個(gè) Portal 組件
const PortalComponent = () => {
return ReactDOM.createPortal(
<div className="portal-content">
這是通過(guò) Portal 渲染的內(nèi)容
</div>,
document.getElementById('portal-root')
);
};
// 父組件
const ParentComponent = () => {
return (
<div className="parent">
<h1>父組件</h1>
<PortalComponent />
</div>
);
};
// 假設(shè)在 HTML 文件中有一個(gè) id 為 portal-root 的元素
// <div id="portal-root"></div>
???????export default ParentComponent;步驟 1:渲染父組件
當(dāng) React 渲染 ParentComponent 時(shí),它會(huì)按照常規(guī)的渲染流程處理組件內(nèi)的子元素。在 ParentComponent 中遇到 PortalComponent 時(shí),會(huì)調(diào)用 PortalComponent 的渲染邏輯。
步驟 2:調(diào)用 createPortal 方法
在 PortalComponent 中,調(diào)用 ReactDOM.createPortal 方法。這個(gè)方法接收兩個(gè)參數(shù):要渲染的內(nèi)容(這里是一個(gè)包含文本的 div 元素)和目標(biāo) DOM 節(jié)點(diǎn)(通過(guò) document.getElementById('portal-root') 獲取)。
步驟 3:查找目標(biāo) DOM 節(jié)點(diǎn)
createPortal 方法會(huì)在瀏覽器的 DOM 樹(shù)中查找指定的目標(biāo) DOM 節(jié)點(diǎn)(這里是 id 為 portal-root 的元素)。如果找到了目標(biāo)節(jié)點(diǎn),就會(huì)繼續(xù)下一步;如果沒(méi)找到,可能會(huì)拋出錯(cuò)誤或者不進(jìn)行渲染。
步驟 4:渲染內(nèi)容到目標(biāo)節(jié)點(diǎn)
一旦找到目標(biāo) DOM 節(jié)點(diǎn),createPortal 方法會(huì)將 child 元素渲染到這個(gè)目標(biāo)節(jié)點(diǎn)內(nèi)部。此時(shí),雖然 PortalComponent 是 ParentComponent 的子組件,但它渲染的內(nèi)容不會(huì)出現(xiàn)在 ParentComponent 對(duì)應(yīng)的 DOM 節(jié)點(diǎn)內(nèi)部,而是出現(xiàn)在 portal-root 節(jié)點(diǎn)下。
4. 事件冒泡和 React 樹(shù)的關(guān)系
盡管 Portal 的內(nèi)容渲染在 DOM 樹(shù)的其他位置,但在 React 組件樹(shù)中,它仍然被視為其父組件的子元素。這意味著事件冒泡會(huì)按照 React 組件樹(shù)的結(jié)構(gòu)進(jìn)行,而不是按照實(shí)際的 DOM 樹(shù)結(jié)構(gòu)。
例如,在上面的示例中,如果在 portal-content 元素上觸發(fā)了一個(gè)點(diǎn)擊事件,這個(gè)事件會(huì)冒泡到 PortalComponent 及其父組件 ParentComponent,就好像 portal-content 元素是直接嵌套在 ParentComponent 中的一樣。
總結(jié)
React Portal 通過(guò) ReactDOM.createPortal 方法打破了組件渲染受限于父組件 DOM 層級(jí)的規(guī)則,允許將組件內(nèi)容渲染到任意 DOM 節(jié)點(diǎn)。同時(shí),它保持了 React 組件樹(shù)的邏輯結(jié)構(gòu),使得事件處理等操作仍然遵循 React 的規(guī)則。這種特性在處理模態(tài)框、下拉菜單等需要跨層級(jí)渲染的場(chǎng)景中非常有用。
到此這篇關(guān)于React如何使用Portal實(shí)現(xiàn)跨層級(jí)DOM渲染的文章就介紹到這了,更多相關(guān)React Portal內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
useEffect?返回函數(shù)執(zhí)行過(guò)程源碼解析
這篇文章主要為大家介紹了useEffect?返回函數(shù)執(zhí)行過(guò)程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Webpack3+React16代碼分割的實(shí)現(xiàn)
這篇文章主要介紹了Webpack3+React16代碼分割的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
React.InputHTMLAttributes實(shí)踐和注意事項(xiàng)
文章討論了如何在封裝組件中使用React.InputHTMLAttributes,以及如何通過(guò) {...inputProps} 動(dòng)態(tài)傳遞屬性,最后,文章總結(jié)了使用React.InputHTMLAttributes的最佳實(shí)踐和注意事項(xiàng),感興趣的朋友一起看看吧2024-11-11
React跨端動(dòng)態(tài)化之從JS引擎到RN落地詳解
這篇文章主要為大家介紹了React跨端動(dòng)態(tài)化之從JS引擎到RN落地,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
DOM 注入實(shí)踐之如何在 React 中優(yōu)雅地?cái)U(kuò)展第三方組件
本文將介紹一種更優(yōu)雅的解決方案——DOM 注入 + React Portal,通過(guò)抽象的示例展示這種技術(shù)方案的應(yīng)用場(chǎng)景和實(shí)現(xiàn)方法,感興趣的朋友跟隨小編一起看看吧2026-03-03
React如何使用localStorage及實(shí)現(xiàn)刪除筆記操作過(guò)程
這篇文章主要介紹了React如何使用localStorage及實(shí)現(xiàn)刪除筆記操作過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-12-12

