فرانت اندی | FrontEndi
فرانت اندی | FrontEndi
خواندن ۷ دقیقه·۱۰ ماه پیش

پورتال در ری اکت چیست ؟ + مثال و تیکه کد از آموزش Portals در ریکت !

درواقع Portals در ری اکت یکی ویژگی خیلی جالب و کاربردی هست که به ما اجازه میده کامپوننت خودمون رو خارج از سلسله بندی کامپوننت پدر در DOM رندر کنیم. اگه این جمله براتون گنگ بود یا متوجه کاربرد پورتال در ری اکت نشدید، تو این مقاله با ما همراه باشید که میخوایم حسابی Portals در ری اکت رو به همراه تیکه کد و مثال عملی بررسی کنیم 🙂

مفهوم Portals در ری اکت چیست ؟

درواقع Portals در ری اکت یک روش کاربردی برای render کردن کامپوننت، خارج از سلسه مراتب کامپوننت والد در DOM هست. یعنی ما کامپوننت خودمون رو بدون توجه به سلسه مراتبش داخل DOM قرار میدیم .

حالا شاید سوال برامون پیش بیاد که اصلا سلسله مراتب یعنی چی ؟

قبل از هرچیز لازمه اشاره کنم که برای درک پورتال در ری اکت، باید درمورد دام مجازی در ری اکت مطالعه کرده باشیم و بدونیم DOM چیه .. ( اگه درمورد دام مجازی اطلاعاتی ندارید، روی لینک آبی رنگ بالا کلیک کنید و مطالعه کنید )

موضوع این هست که کامپوننت های ما طبق یک سلسله مراتب مشخص داخل DOM مرورگر قرار میگیرن. سلسله مراتب یعنی هر کامپوننت یک پدر، پدربزرگ، فرزند و .. داره. طبیعی هست که خصوصیات پدر، به فرزند منتقل میشه.

گاهی اوغات ما نمیخوایم خصوصیات پدر به فرزند منتقل بشه.

این یعنی ما لازم داریم کامپوننت خودمون رو خارج از سلسله مراتب اون کامپوننت داخل DOM قرار بدیم. در چنین مواقعی باید از Portals در ری اکت استفاده کنیم.

کاربرد Portals در ری اکت کجاست ؟

ما اشاره کردیم که در React کامپوننت های فرزند، خصوصیات و ویژگی های کامپوننت پدرشون رو به ارث میبرن. مثلا اگه کامپوننت پدر 400px طول داشته باشه، کامپوننت فرزند نمیتونه بیشتر از 400px باشه.

حالا اگه ما لازم داشته باشیم که کامپوننت فرزند رو بصورت 800px نمایش بدیم باید چیکار کنیم ؟

راه حل استفاده از پورتال در React هست چون بدون در نظر گرفتن سلسه مراتبِ اون کامپوننت، کامپوننت رو داخل DOM رندر میکنه.

اینجوری کامپوننت ما دیگه خصوصیات کامپوننت پدرش رو به ارث نمیبره.

این مثال معمولا برای Modal ( مودال )، منو های پاپ آپ، نوتیفیکیشن ها و .. کاربرد داره چون ما میخوایم این موارد استایل های پدر خودشون رو به ارث نبرن.

چطور میشه Portals در ریکت ساخت ؟

نحوه ساخت Portals در React بصورت زیر هست :

ReactDOM.createPortal(child, container)

تابع createPortal، ساخت پورتال رو برای ما انجام میده. این تابع 2 آرگومان ورودی از ما میگیره:

آرگومان اول همون کامپوننت ماست که میخوایم خارج از سلسه مراتب render بشه ( مثل مودال )

آرگومان دوم یک اِلِمان هست که میخوایم کامپوننت ما داخل این اِلِمان رندر بشه ( ما باید این اِلِمان رو بسازیم، در ادامه میبینیم که چطور این اِلِمان رو میسازیم )

استفاده از Portals در ری اکت !

بیاید یه مثال عملی در مورد پورتال در ریکت رو باهمدیگه بررسی کنیم ..

تو مثال زیر من یک مودال ساده ساختم :

export const Modal = (props) => {
let { children, close, ...rest } = props;
if (!children) {
children = <p>This is a example modal</p>;
}
return (
<div id=&quotmodal-dialog&quot {...rest}>
<div className=&quotflex flex-col justify-center items-center&quot>
{children}
<button ={close}>
Close this modal
</button>
</div>
</div>
);
};

کامپوننت من چندین Props میگیره که کمک میکنن بتونیم کامپوننت خودمون رو کنترل کنیم مثل تابع close برای بستن مودال و children.

و فایل css کامپوننت بالارو بصورت زیر ایجاد میکنیم :

* {
font-size: 62.5%;
font-family: &quotRoboto"
margin: 0;
padding: 0;
}
#App {
overflow: hidden;
height: 20vh;
background-color: #ccc;
}
#App > h1 {
font-size: 2rem;
}
div#modal-dialog {
background-color: rgba(0, 0, 0, 0.8);
position: fixed;
z-index: 999;
height: 100vh;
width: 100vw;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}
div#modal-dialog > div {
background-color: #f5f5f5;
padding: 2rem;
border-radius: 1.2rem;
}
p {
margin: 1.4rem 0;
font-size: 1.5rem;
}
button {
padding: 1rem;
border-radius: 1rem;
border: none;
background-color: #9b59b6;
color: #fff;
cursor: pointer;
transition: all 0.3s ease-in-out;
}
button:hover {
background-color: #8e44ad;
}
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.justify-center {
justify-content: center;
}
.items-center {
align-items: center;
}

تا به اینجای کار فقط یک مودال ساده درست کردیم.

ما میدونیم که تو ری اکت، اپیکیشن ما داخل یک div با آیدی root رندر میشه. حالا من به فایل index.html میرم و یک div جدید با آیدی modals درست میکنم. این دو div باید رابطه خواهر برادری داشته باشن، دقیقا مثل زیر :

<!DOCTYPE html>
<html lang=&quoten&quot>
<head>
<meta charset=&quotutf-8&quot />
<meta
name=&quotviewport&quot
content=&quotwidth=device-width, initial-scale=1, shrink-to-fit=no&quot
/>
<meta name=&quottheme-color&quot content=&quot#000000&quot />
<link rel=&quotmanifest&quot href=&quot%PUBLIC_URL%/manifest.json&quot />
<link rel=&quotshortcut icon&quot href=&quot%PUBLIC_URL%/favicon.ico&quot />
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id=&quotroot&quot></div>
<div id=&quotmodals&quot></div>
<script data-no-optimize=&quot1&quot>!function(t,e){&quotobject&quot==typeof exports&&&quotundefined&quot!=typeof module?module.exports=e():&quotfunction&quot==typeof define&&define.amd?define(e):(t=&quotundefined&quot!=typeof globalThis?globalThis:t||self).LazyLoad=e()}(this,function(){&quotuse strict"function e(){return(e=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,a=arguments[e];for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(t[n]=a[n])}return t}).apply(this,arguments)}function i(t){return e({},it,t)}function o(t,e){var n,a=&quotLazyLoad::Initialized&quot,i=new t(e);try{n=new CustomEvent(a,{detail:{instance:i}})}catch(t){(n=document.createEvent(&quotCustomEvent&quot)).initCustomEvent(a,!1,!1,{instance:i})}window.dispatchEvent(n)}function l(t,e){return t.getAttribute(gt+e)}function c(t){return l(t,bt)}function s(t,e){return function(t,e,n){e=gt+e;null!==n?t.setAttribute(e,n):t.removeAttribute(e)}(t,bt,e)}function r(t){return s(t,null),0}function u(t){return null===c(t)}function d(t){return c(t)===vt}function f(t,e,n,a){t&&(void 0===a?void 0===n?t(e):t(e,n):t(e,n,a))}function _(t,e){nt?t.classList.add(e):t.className+=(t.className?&quot &quot:&quot&quot)+e}function v(t,e){nt?t.classList.remove(e):t.className=t.className.replace(new RegExp(&quot(^|\\s+)&quot+e+&quot(\\s+|$)&quot),&quot &quot).replace(/^\s+/,&quot&quot).replace(/\s+$/,&quot&quot)}function g(t){return t.llTempImage}function b(t,e){!e||(e=e._observer)&&e.unobserve(t)}function p(t,e){t&&(t.loadingCount+=e)}function h(t,e){t&&(t.toLoadCount=e)}function n(t){for(var e,n=[],a=0;e=t.children[a];a+=1)&quotSOURCE&quot===e.tagName&&n.push(e);return n}function m(t,e){(t=t)&&&quotPICTURE&quot===t.tagName&&n(t).forEach(e)}function a(t,e){n(t).forEach(e)}function E(t){return!!t[st]}function I(t){return t[st]}function y(t){return delete t[st]}function A(e,t){var n;E(e)||(n={},t.forEach(function(t){n[t]=e.getAttribute(t)}),e[st]=n)}function k(a,t){var i;E(a)&&(i=I(a),t.forEach(function(t){var e,n;e=a,(t=i[n=t])?e.setAttribute(n,t):e.removeAttribute(n)}))}function L(t,e,n){_(t,e.class_loading),s(t,ut),n&&(p(n,1),f(e.callback_loading,t,n))}function w(t,e,n){n&&t.setAttribute(e,n)}function x(t,e){w(t,ct,l(t,e.data_sizes)),w(t,rt,l(t,e.data_srcset)),w(t,ot,l(t,e.data_src))}function O(t,e,n){var a=l(t,e.data_bg_multi),i=l(t,e.data_bg_multi_hidpi);(a=at&&i?i:a)&&(t.style.backgroundImage=a,n=n,_(t=t,(e=e).class_applied),s(t,ft),n&&(e.unobserve_completed&&b(t,e),f(e.callback_applied,t,n)))}function N(t,e){!e||0<e.loadingCount||0<e.toLoadCount||f(t.callback_finish,e)}function C(t,e,n){t.addEventListener(e,n),t.llEvLisnrs[e]=n}function M(t){return!!t.llEvLisnrs}function z(t){if(M(t)){var e,n,a=t.llEvLisnrs;for(e in a){var i=a[e];n=e,i=i,t.removeEventListener(n,i)}delete t.llEvLisnrs}}function R(t,e,n){var a;delete t.llTempImage,p(n,-1),(a=n)&&--a.toLoadCount,v(t,e.class_loading),e.unobserve_completed&&b(t,n)}function T(o,r,c){var l=g(o)||o;M(l)||function(t,e,n){M(t)||(t.llEvLisnrs={});var a=&quotVIDEO&quot===t.tagName?&quotloadeddata&quot:&quotload"C(t,a,e),C(t,&quoterror&quot,n)}(l,function(t){var e,n,a,i;n=r,a=c,i=d(e=o),R(e,n,a),_(e,n.class_loaded),s(e,dt),f(n.callback_loaded,e,a),i||N(n,a),z(l)},function(t){var e,n,a,i;n=r,a=c,i=d(e=o),R(e,n,a),_(e,n.class_error),s(e,_t),f(n.callback_error,e,a),i||N(n,a),z(l)})}function G(t,e,n){var a,i,o,r,c;t.llTempImage=document.createElement(&quotIMG&quot),T(t,e,n),E(c=t)||(c[st]={backgroundImage:c.style.backgroundImage}),o=n,r=l(a=t,(i=e).data_bg),c=l(a,i.data_bg_hidpi),(r=at&&c?c:r)&&(a.style.backgroundImage='url(&quot'.concat(r,'&quot)'),g(a).setAttribute(ot,r),L(a,i,o)),O(t,e,n)}function D(t,e,n){var a;T(t,e,n),a=e,e=n,(t=It[(n=t).tagName])&&(t(n,a),L(n,a,e))}function V(t,e,n){var a;a=t,(-1<yt.indexOf(a.tagName)?D:G)(t,e,n)}function F(t,e,n){var a;t.setAttribute(&quotloading&quot,&quotlazy&quot),T(t,e,n),a=e,(e=It[(n=t).tagName])&&e(n,a),s(t,vt)}function j(t){t.removeAttribute(ot),t.removeAttribute(rt),t.removeAttribute(ct)}function P(t){m(t,function(t){k(t,Et)}),k(t,Et)}function S(t){var e;(e=At[t.tagName])?e(t):E(e=t)&&(t=I(e),e.style.backgroundImage=t.backgroundImage)}function U(t,e){var n;S(t),n=e,u(e=t)||d(e)||(v(e,n.class_entered),v(e,n.class_exited),v(e,n.class_applied),v(e,n.class_loading),v(e,n.class_loaded),v(e,n.class_error)),r(t),y(t)}function $(t,e,n,a){var i;n.cancel_on_exit&&(c(t)!==ut||&quotIMG&quot===t.tagName&&(z(t),m(i=t,function(t){j(t)}),j(i),P(t),v(t,n.class_loading),p(a,-1),r(t),f(n.callback_cancel,t,e,a)))}function q(t,e,n,a){var i,o,r=(o=t,0<=pt.indexOf(c(o)));s(t,&quotentered&quot),_(t,n.class_entered),v(t,n.class_exited),i=t,o=a,n.unobserve_entered&&b(i,o),f(n.callback_enter,t,e,a),r||V(t,n,a)}function H(t){return t.use_native&&&quotloading&quotin HTMLImageElement.prototype}function B(t,i,o){t.forEach(function(t){return(a=t).isIntersecting||0<a.intersectionRatio?q(t.target,t,i,o):(e=t.target,n=t,a=i,t=o,void(u(e)||(_(e,a.class_exited),$(e,n,a,t),f(a.callback_exit,e,n,t))));var e,n,a})}function J(e,n){var t;et&&!H(e)&&(n._observer=new IntersectionObserver(function(t){B(t,e,n)},{root:(t=e).container===document?null:t.container,rootMargin:t.thresholds||t.threshold+&quotpx&quot}))}function K(t){return Array.prototype.slice.call(t)}function Q(t){return t.container.querySelectorAll(t.elements_selector)}function W(t){return c(t)===_t}function X(t,e){return e=t||Q(e),K(e).filter(u)}function Y(e,t){var n;(n=Q(e),K(n).filter(W)).forEach(function(t){v(t,e.class_error),r(t)}),t.update()}function t(t,e){var n,a,t=i(t);this._settings=t,this.loadingCount=0,J(t,this),n=t,a=this,Z&&window.addEventListener(&quotonline&quot,function(){Y(n,a)}),this.update(e)}var Z=&quotundefined&quot!=typeof window,tt=Z&&!(&quotonscroll&quotin window)||&quotundefined&quot!=typeof navigator&&/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent),et=Z&&&quotIntersectionObserver&quotin window,nt=Z&&&quotclassList&quotin document.createElement(&quotp&quot),at=Z&&1<window.devicePixelRatio,it={elements_selector:&quot.lazy&quot,container:tt||Z?document:null,threshold:300,thresholds:null,data_src:&quotsrc&quot,data_srcset:&quotsrcset&quot,data_sizes:&quotsizes&quot,data_bg:&quotbg&quot,data_bg_hidpi:&quotbg-hidpi&quot,data_bg_multi:&quotbg-multi&quot,data_bg_multi_hidpi:&quotbg-multi-hidpi&quot,data_poster:&quotposter&quot,class_applied:&quotapplied&quot,class_loading:&quotlitespeed-loading&quot,class_loaded:&quotlitespeed-loaded&quot,class_error:&quoterror&quot,class_entered:&quotentered&quot,class_exited:&quotexited&quot,unobserve_completed:!0,unobserve_entered:!1,cancel_on_exit:!0,callback_enter:null,callback_exit:null,callback_applied:null,callback_loading:null,callback_loaded:null,callback_error:null,callback_finish:null,callback_cancel:null,use_native:!1},ot=&quotsrc&quot,rt=&quotsrcset&quot,ct=&quotsizes&quot,lt=&quotposter&quot,st=&quotllOriginalAttrs&quot,ut=&quotloading&quot,dt=&quotloaded&quot,ft=&quotapplied&quot,_t=&quoterror&quot,vt=&quotnative&quot,gt=&quotdata-&quot,bt=&quotll-status&quot,pt=[ut,dt,ft,_t],ht=[ot],mt=[ot,lt],Et=[ot,rt,ct],It={IMG:function(t,e){m(t,function(t){A(t,Et),x(t,e)}),A(t,Et),x(t,e)},IFRAME:function(t,e){A(t,ht),w(t,ot,l(t,e.data_src))},VIDEO:function(t,e){a(t,function(t){A(t,ht),w(t,ot,l(t,e.data_src))}),A(t,mt),w(t,lt,l(t,e.data_poster)),w(t,ot,l(t,e.data_src)),t.load()}},yt=[&quotIMG&quot,&quotIFRAME&quot,&quotVIDEO&quot],At={IMG:P,IFRAME:function(t){k(t,ht)},VIDEO:function(t){a(t,function(t){k(t,ht)}),k(t,mt),t.load()}},kt=[&quotIMG&quot,&quotIFRAME&quot,&quotVIDEO&quot];return t.prototype={update:function(t){var e,n,a,i=this._settings,o=X(t,i);{if(h(this,o.length),!tt&&et)return H(i)?(e=i,n=this,o.forEach(function(t){-1!==kt.indexOf(t.tagName)&&F(t,e,n)}),void h(n,0)):(t=this._observer,i=o,t.disconnect(),a=t,void i.forEach(function(t){a.observe(t)}));this.loadAll(o)}},destroy:function(){this._observer&&this._observer.disconnect(),Q(this._settings).forEach(function(t){y(t)}),delete this._observer,delete this._settings,delete this.loadingCount,delete this.toLoadCount},loadAll:function(t){var e=this,n=this._settings;X(t,n).forEach(function(t){b(t,e),V(t,n,e)})},restoreAll:function(){var e=this._settings;Q(e).forEach(function(t){U(t,e)})}},t.load=function(t,e){e=i(e);V(t,e)},t.resetStatus=function(t){r(t)},Z&&function(t,e){if(e)if(e.length)for(var n,a=0;n=e[a];a+=1)o(t,n);else o(t,e)}(t,window.lazyLoadOptions),t});!function(e,t){&quotuse strict"function a(){t.body.classList.add(&quotlitespeed_lazyloaded&quot)}function n(){console.log(&quot[LiteSpeed] Start Lazy Load Images&quot),d=new LazyLoad({elements_selector:&quot[data-lazyloaded]&quot,callback_finish:a}),o=function(){d.update()},e.MutationObserver&&new MutationObserver(o).observe(t.documentElement,{childList:!0,subtree:!0,attributes:!0})}var d,o;e.addEventListener?e.addEventListener(&quotload&quot,n,!1):e.attachEvent(&quotonload&quot,n)}(window,document);</body>
</html>

اگه کد بالا هم ریختس، بخاطر ویرگوله. لطفا همین مقاله رو در فِرانت اِندی مطالعه کنید تا بهم ریخته نباشه.

اگه به خط 19 دقت کنیم، میبینیم که یک div جدید با آیدی modals داریم که خارج از div اپیکیشن با آیدی root هست!

یعنی هرچیزی که داخل div دوم قرار بگیره، خارج از سلسه مراتب div اول هست و خصوصیات اون رو به ارث نمیبره!

حالا بریم فایل App.js رو بسازیم :

import { useState } from &quotreact"
import ReactDOM from &quotreact-dom"
import { Modal } from &quot./Components/Modal"
import &quot./styles.css"
const domElement = document.getElementById(&quotmodals&quot);
export default function App() {
const [stateModal, setStateModal] = useState(false);
const openModal = () => setStateModal(true);
const closeModal = () => setStateModal(false);
return (
<div id=&quotApp&quot className=&quotflex flex-col justify-center items-center&quot>
<h1>Portals Example</h1>
<div className=&quotflex flex-col items-center justify-center&quot>
<p>This is a div with a defined height and overflow hidden</p>
<button ={openModal}>
Open modal
</button>
</div>
{stateModal &&
ReactDOM.createPortal(
<Modal close={closeModal}>
<p>Modal from App.js</p>
</Modal>,
domElement
)}
</div>
);
}

تو خط 3 ما Modal خودمون رو فراخوانی کردیم و در خط 6 تونستیم به div خودمون که آیدی portals داشت، دسترسی پیدا کنیم. ( همون اِلِمانی که میخوایم Portals رو داخلش قرار بدیم )

همچنین در خط 9 یک state برای مشخص کردن باز یا بسته بودن modal درست کردیم.

اما مهمترین بخش تیکه کد بالا، در خط 22 وجود داره. ما یک شرط گذاشتیم ( شرط باز بودن مودال بر اساس State ) و گفتیم زمانیکه این state مقدار True داشت یک portal جدید بساز.

ساختن portal جدید به کمک ReactDOM انجام میشه. تابع createPortal از ما 2 آرگومان میگیره :

آرگومان اول همون چیزی هست که میخوایم داخل پورتال render کنیم و نمایش بدیم.

آرگومان دوم اِلِمان Portal ما هست، اگه به خط 6 دقت کنیم میبینیم که این اِلِمان رو تعریف کردیم.

در حقیقت، کل ماجرای Portals در React تو تیکه کد زیر نهفته شده :

{stateModal &&
ReactDOM.createPortal(
<Modal close={closeModal}>
<p>Modal from App.js</p>
</Mode>,
domElement
)}

ما طبق یک شرط، یک Portals در ری اکت ساختیم و نمایش دادیم.

ساخت پورتال در React به همین سادگی امکانپذیره 🙂

نتیجه تیکه کد های بالا این هست که کامپوننت Modal ما خارج از سلسله مراتب کل اپیکیشن ما ( بیرون div با آیدی root ) رندر میشه.

کاربرد اصلی پورتال در ری اکت چیست ؟

لطفا برای مطالعه ادامه مقاله روی لینک زیر کلیک کنید :

آموزش Portals در ری اکت

پورتالری اکت
فرانت اِندی مرجع تخصصی فرانت اند برای فارسی زبانان است . هدف ما گردآوری و انتشار تخصصی ترین و بهترین مقالات و دوره های آموزشی حوزه فرانت اند در یک منبع فارسی زبان است .
شاید از این پست‌ها خوشتان بیاید