Põhjalik ülevaade Reacti portaalidest ja edasijõudnud sündmuste käsitlemisest, keskendudes sündmuste vaheltlõikamisele ja püüdmisele erinevate portaalide vahel.
Reacti portaalide sündmuste püüdmine: Portaalideülene sündmuste vaheltlõikamine
Reacti portaalid pakuvad võimsat mehhanismi laste renderdamiseks DOM-sõlme, mis asub vanemkomponendi DOM-hierarhiast väljaspool. See on eriti kasulik modaalide, tööriistavihjete ja muude kasutajaliidese elementide jaoks, mis peavad pääsema oma vanemkonteinerite piirangutest. Samas tekitab see ka keerukusi sündmustega tegelemisel, eriti kui on vaja vahelt lõigata või püüda sündmusi, mis pärinevad portaalist, kuid on suunatud sellest väljaspool asuvatele elementidele. See artikkel uurib neid keerukusi ja pakub praktilisi lahendusi portaalideülese sündmuste vaheltlõikamise saavutamiseks.
Reacti portaalide mõistmine
Enne sündmuste püüdmisesse süvenemist loome kindla arusaama Reacti portaalidest. Portaal võimaldab teil renderdada lapskomponendi DOM-i teise ossa. Kujutage ette, et teil on sügavalt pesastatud komponent ja soovite renderdada modaali otse `body` elemendi alla. Ilma portaalita alluks modaal oma esivanemate stiilidele ja paigutusele, mis võib põhjustada paigutusprobleeme. Portaal väldib seda, paigutades modaali otse sinna, kuhu soovite.
Portaali loomise põhisüntaks on:
ReactDOM.createPortal(child, domNode);
Siin on `child` Reacti element (või komponent), mida soovite renderdada, ja `domNode` on DOM-sõlm, kuhu soovite selle renderdada.
Näide:
import React from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ children, isOpen, onClose }) => {
if (!isOpen) return null;
const modalRoot = document.getElementById('modal-root');
if (!modalRoot) return null; // Käsitse juhtumit, kus modal-root'i ei eksisteeri
return ReactDOM.createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
modalRoot
);
};
export default Modal;
Selles näites renderdab `Modal` komponent oma lapsed DOM-sõlme, mille ID on `modal-root`. `.modal-overlay` elemendi `onClick` käsitleja võimaldab modaali sulgeda, kui klõpsatakse väljaspool sisu, samas kui `e.stopPropagation()` takistab ülekatte klõpsu modaali sulgemast, kui klõpsatakse sisul.
Portaalideülese sündmuste käsitlemise väljakutse
Kuigi portaalid lahendavad paigutusprobleeme, tekitavad nad väljakutseid sündmustega tegelemisel. Täpsemalt, standardne sündmuste mullitamise mehhanism DOM-is võib käituda ootamatult, kui sündmused saavad alguse portaali seest.
Stsenaarium: Kujutage ette stsenaariumi, kus teil on portaali sees nupp ja te soovite selle nupu klõpse jälgida komponendist, mis on Reacti puus kõrgemal (kuid *väljaspool* portaali renderdamise asukohta). Kuna portaal rikub DOM-i hierarhiat, ei pruugi sündmus mullitada oodatud vanemkomponendini Reacti puus.
Põhiprobleemid:
- Sündmuste mullitamine: Sündmused levivad DOM-puus ülespoole, kuid portaal tekitab selles puus katkestuse. Sündmus mullitab üles läbi DOM-hierarhia portaali sihtsõlme *sees*, kuid mitte tingimata tagasi Reacti komponendini, mis portaali lõi.
- `stopPropagation()`: Kuigi paljudel juhtudel kasulik, võib `stopPropagation()` valimatu kasutamine takistada sündmuste jõudmist vajalike kuulajateni, sealhulgas nendeni, mis asuvad väljaspool portaali.
- Sündmuse sihtmärk: `event.target` omadus osutab endiselt DOM-elemendile, kust sündmus pärines, isegi kui see element on portaali sees.
Strateegiad portaalideüleseks sündmuste vaheltlõikamiseks
Võib kasutada mitmeid strateegiaid, et käsitleda portaalides alguse saanud sündmusi, mis jõuavad neist väljaspool asuvate komponentideni:
1. Sündmuste delegeerimine
Sündmuste delegeerimine hõlmab ühe sündmusekuulaja lisamist vanemelemendile (sageli dokumendile või ühisele esivanemale) ja seejärel sündmuse tegeliku sihtmärgi kindlaksmääramist. See lähenemine väldib arvukate sündmusekuulajate lisamist üksikutele elementidele, parandades jõudlust ja lihtsustades sündmuste haldamist.
Kuidas see töötab:
- Lisage sündmusekuulaja ühisele esivanemale (nt `document.body`).
- Sündmusekuulajas kontrollige `event.target` omadust, et tuvastada element, mis sündmuse käivitas.
- Teostage soovitud toiming sündmuse sihtmärgi põhjal.
Näide:
import React, { useEffect } from 'react';
const PortalAwareComponent = () => {
useEffect(() => {
const handleClick = (event) => {
if (event.target.classList.contains('portal-button')) {
console.log('Button inside portal clicked!', event.target);
// Teosta toimingud vastavalt klõpsatud nupule
}
};
document.body.addEventListener('click', handleClick);
return () => {
document.body.removeEventListener('click', handleClick);
};
}, []);
return (
<div>
<p>See on komponent väljaspool portaali.</p>
</div>
);
};
export default PortalAwareComponent;
Selles näites lisab `PortalAwareComponent` klõpsukuulaja `document.body` külge. Kuulaja kontrollib, kas klõpsatud elemendil on klass `portal-button`. Kui on, logib see teate konsooli ja teostab muud vajalikud toimingud. See lähenemine töötab olenemata sellest, kas nupp on portaali sees või väljas.
Eelised:
- Jõudlus: Vähendab sündmusekuulajate arvu.
- Lihtsus: Tsentraliseerib sündmuste käsitlemise loogika.
- Paindlikkus: Käsitleb kergesti dünaamiliselt lisatud elementide sündmusi.
Kaalutlused:
- Spetsiifilisus: Nõuab sündmuste allikate hoolikat sihtimist, kasutades `event.target` ja potentsiaalselt DOM-puus ülespoole liikumist, kasutades `event.target.closest()`.
- Sündmuse tüüp: Sobib kõige paremini sündmustele, mis mullitavad.
2. Kohandatud sündmuste saatmine
Kohandatud sündmused võimaldavad teil sündmusi programmiliselt luua ja saata. See on kasulik, kui teil on vaja suhelda komponentide vahel, mis ei ole Reacti puus otseselt ühendatud, või kui peate käivitama sündmusi kohandatud loogika alusel.
Kuidas see töötab:
- Looge uus `Event` objekt, kasutades `Event` konstruktorit.
- Saatke sündmus, kasutades `dispatchEvent` meetodit DOM-elemendil.
- Kuulake kohandatud sündmust, kasutades `addEventListener`.
Näide:
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
const PortalContent = () => {
const handleClick = () => {
const customEvent = new CustomEvent('portalButtonClick', {
detail: { message: 'Button clicked inside portal!' },
});
document.dispatchEvent(customEvent);
};
return (
<button className="portal-button" onClick={handleClick}>
Klõpsa siia (portaali sees)
</button>
);
};
const PortalAwareComponent = () => {
useEffect(() => {
const handlePortalButtonClick = (event) => {
console.log(event.detail.message);
};
document.addEventListener('portalButtonClick', handlePortalButtonClick);
return () => {
document.removeEventListener('portalButtonClick', handlePortalButtonClick);
};
}, []);
const modalRoot = document.getElementById('modal-root');
return (
<>
<div>
<p>See on komponent väljaspool portaali.</p>
</div>
{modalRoot && ReactDOM.createPortal(<PortalContent/>, modalRoot)}
</
>
);
};
export default PortalAwareComponent;
Selles näites, kui portaali sees olevat nuppu klõpsatakse, saadetakse `document`-ile kohandatud sündmus nimega `portalButtonClick`. `PortalAwareComponent` kuulab seda sündmust ja logib teate konsooli.
Eelised:
- Paindlikkus: Võimaldab suhelda komponentide vahel sõltumata nende asukohast Reacti puus.
- Kohandatavus: Saate lisada kohandatud andmeid sündmuse `detail` omadusse.
- Lahtisidumine: Vähendab komponentidevahelisi sõltuvusi.
Kaalutlused:
- Sündmuste nimetamine: Valige unikaalsed ja kirjeldavad sündmuste nimed konfliktide vältimiseks.
- Andmete serialiseerimine: Veenduge, et kõik `detail` omadusse lisatud andmed on serialiseeritavad.
- Globaalne ulatus: `document`-ile saadetud sündmused on globaalselt kättesaadavad, mis võib olla nii eelis kui ka potentsiaalne puudus.
3. Ref'ide ja otse DOM-manipuleerimise kasutamine (kasutada ettevaatusega)
Kuigi Reacti arenduses üldiselt mittesoovitatav, võib DOM-i otsene juurdepääs ja manipuleerimine ref'ide abil olla mõnikord vajalik keeruliste sündmuste käsitlemise stsenaariumide puhul. Siiski on ülioluline minimeerida otsest DOM-manipuleerimist ja eelistada võimaluse korral Reacti deklaratiivset lähenemist.
Kuidas see töötab:
- Looge ref, kasutades `React.createRef()` või `useRef()`.
- Kinnitage ref portaali sees oleva DOM-elemendi külge.
- Juurdepääs DOM-elemendile, kasutades `ref.current`.
- Lisage sündmusekuulajad otse DOM-elemendile.
Näide:
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
const PortalContent = () => {
const buttonRef = useRef(null);
useEffect(() => {
const handleClick = () => {
console.log('Nupule klõpsatud (otsene DOM-manipulatsioon)');
};
if (buttonRef.current) {
buttonRef.current.addEventListener('click', handleClick);
}
return () => {
if (buttonRef.current) {
buttonRef.current.removeEventListener('click', handleClick);
}
};
}, []);
return (
<button className="portal-button" ref={buttonRef}>
Klõpsa siia (portaali sees)
</button>
);
};
const PortalAwareComponent = () => {
const modalRoot = document.getElementById('modal-root');
return (
<>
<div>
<p>See on komponent väljaspool portaali.</p>
</div>
{modalRoot && ReactDOM.createPortal(<PortalContent/>, modalRoot)}
</
>
);
};
export default PortalAwareComponent;
Selles näites on ref kinnitatud portaali sees oleva nupu külge. Seejärel lisatakse sündmusekuulaja otse nupu DOM-elemendile, kasutades `buttonRef.current.addEventListener()`. See lähenemine möödub Reacti sündmuste süsteemist ja annab otsese kontrolli sündmuste käsitlemise üle.
Eelised:
- Otsene kontroll: Annab peeneteralise kontrolli sündmuste käsitlemise üle.
- Reacti sündmuste süsteemist möödumine: Võib olla kasulik teatud juhtudel, kui Reacti sündmuste süsteem on ebapiisav.
Kaalutlused:
- Konfliktide oht: Võib põhjustada konflikte Reacti sündmuste süsteemiga, kui seda ei kasutata hoolikalt.
- Hoolduse keerukus: Muudab koodi raskemini hooldatavaks ja mõistetavaks.
- Anti-muster: Sageli peetakse Reacti arenduses anti-mustriks. Kasutage säästlikult ja ainult siis, kui see on vajalik.
4. Ühise olekuhalduse lahenduse kasutamine (nt Redux, Zustand, Context API)
Kui portaali sees ja väljas asuvad komponendid peavad jagama olekut ja reageerima samadele sündmustele, võib ühine olekuhalduse lahendus olla puhas ja tõhus lähenemine.
Kuidas see töötab:
- Looge jagatud olek, kasutades Reduxit, Zustandi või Reacti Context API-d.
- Portaali sees olevad komponendid saavad saata toiminguid või värskendada jagatud olekut.
- Portaalist väljaspool asuvad komponendid saavad tellida jagatud oleku ja reageerida muudatustele.
Näide (kasutades React Context API-d):
import React, { createContext, useContext, useState } from 'react';
import ReactDOM from 'react-dom';
const EventContext = createContext(null);
const EventProvider = ({ children }) => {
const [buttonClicked, setButtonClicked] = useState(false);
const handleButtonClick = () => {
setButtonClicked(true);
};
return (
<EventContext.Provider value={{ buttonClicked, handleButtonClick }}>
{children}
</EventContext.Provider>
);
};
const useEventContext = () => {
const context = useContext(EventContext);
if (!context) {
throw new Error('useEventContext peab olema kasutatud EventProvider\'i sees');
}
return context;
};
const PortalContent = () => {
const { handleButtonClick } = useEventContext();
return (
<button className="portal-button" onClick={handleButtonClick}>
Klõpsa siia (portaali sees)
</button>
);
};
const PortalAwareComponent = () => {
const { buttonClicked } = useEventContext();
const modalRoot = document.getElementById('modal-root');
return (
<>
<div>
<p>See on komponent väljaspool portaali. Nupule klõpsatud: {buttonClicked ? 'Jah' : 'Ei'}</p>
</div>
{modalRoot && ReactDOM.createPortal(<PortalContent/>, modalRoot)}
</
>
);
};
const App = () => (
<EventProvider>
<PortalAwareComponent />
</EventProvider>
);
export default App;
Selles näites pakub `EventContext` jagatud olekut (`buttonClicked`) ja käsitlejat (`handleButtonClick`). `PortalContent` komponent kutsub `handleButtonClick` funktsiooni, kui nuppu klõpsatakse, ja `PortalAwareComponent` komponent tellib `buttonClicked` oleku ning renderdab end uuesti, kui see muutub.
Eelised:
- Tsentraliseeritud olekuhaldus: Lihtsustab olekuhaldust ja komponentidevahelist suhtlust.
- Ennustatav andmevoog: Tagab selge ja ennustatava andmevoo.
- Testitavus: Muudab koodi testimise lihtsamaks.
Kaalutlused:
- Lisakoormus: Olekuhalduse lahenduse lisamine võib tekitada lisakoormust, eriti lihtsate rakenduste puhul.
- Õppimiskõver: Nõuab valitud olekuhalduse teegi või API õppimist ja mõistmist.
Parimad praktikad portaalideüleseks sündmuste käsitlemiseks
Portaalideülese sündmuste käsitlemisel arvestage järgmiste parimate praktikatega:
- Minimeerige otsest DOM-manipuleerimist: Eelistage võimaluse korral Reacti deklaratiivset lähenemist. Vältige DOM-i otsest manipuleerimist, kui see pole absoluutselt vajalik.
- Kasutage sündmuste delegeerimist targalt: Sündmuste delegeerimine võib olla võimas tööriist, kuid veenduge, et sihiksite sündmuste allikaid hoolikalt.
- Kaaluge kohandatud sündmusi: Kohandatud sündmused võivad pakkuda paindlikku ja lahtiseotud viisi komponentidevaheliseks suhtluseks.
- Valige õige olekuhalduse lahendus: Kui komponendid peavad jagama olekut, valige olekuhalduse lahendus, mis sobib teie rakenduse keerukusega.
- Põhjalik testimine: Testige oma sündmuste käsitlemise loogikat põhjalikult, et tagada selle ootuspärane toimimine kõigis stsenaariumides. Pöörake erilist tähelepanu äärmuslikele juhtumitele ja võimalikele konfliktidele teiste sündmusekuulajatega.
- Dokumenteerige oma kood: Dokumenteerige selgelt oma sündmuste käsitlemise loogika, eriti kui kasutate keerulisi tehnikaid või otsest DOM-manipuleerimist.
Kokkuvõte
Reacti portaalid pakuvad võimsat viisi kasutajaliidese elementide haldamiseks, mis peavad pääsema oma vanemkomponentide piiridest. Samas nõuab sündmuste käsitlemine portaalide vahel hoolikat kaalumist ja sobivate tehnikate rakendamist. Mõistes väljakutseid ja kasutades strateegiaid nagu sündmuste delegeerimine, kohandatud sündmused ja jagatud olekuhaldus, saate tõhusalt vahelt lõigata ja püüda portaalidest pärinevaid sündmusi ning tagada, et teie rakendus käitub ootuspäraselt. Pidage meeles, et puhta, hooldatava ja testitava koodibaasi säilitamiseks tuleb eelistada Reacti deklaratiivset lähenemist ja minimeerida otsest DOM-manipuleerimist.