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