Išsami React komponentų architektūros analizė, lyginanti kompoziciją ir paveldimumą. Sužinokite, kodėl React teikia pirmenybę kompozicijai, ir išnagrinėkite HOC, Render Props ir Hooks šablonus, kad sukurtumėte lanksčius, pakartotinai naudojamus komponentus.
React Komponentų Architektūra: Kodėl Kompozicija Triumfuoja Prieš Paveldimumą
Programinės įrangos kūrimo pasaulyje architektūra yra svarbiausia. Būdas, kaip struktūrizuojame savo kodą, lemia jo mastelio keitimo galimybes, palaikomumą ir pakartotinį panaudojamumą. Kūrėjams, dirbantiems su React, vienas iš fundamentaliausių architektūrinių sprendimų yra susijęs su tuo, kaip dalytis logika ir vartotojo sąsaja (UI) tarp komponentų. Tai mus priveda prie klasikinės objektinio programavimo diskusijos, pritaikytos komponentais pagrįstam React pasauliui: Kompozicija prieš Paveldimumą.
Jei turite patirties su klasikinėmis objektinio programavimo kalbomis, tokiomis kaip Java ar C++, paveldimumas gali atrodyti kaip natūralus pirmasis pasirinkimas. Tai galinga koncepcija kuriant „yra“ (is-a) ryšius. Tačiau oficiali React dokumentacija siūlo aiškią ir tvirtą rekomendaciją: „Facebook'e mes naudojame React tūkstančiuose komponentų ir neradome jokių naudojimo atvejų, kuriais rekomenduotume kurti komponentų paveldimumo hierarchijas.“
Šiame įraše pateiksime išsamią šio architektūrinio pasirinkimo analizę. Išsiaiškinsime, ką paveldimumas ir kompozicija reiškia React kontekste, parodysime, kodėl kompozicija yra idiomatiškas ir pranašesnis požiūris, ir išnagrinėsime galingus šablonus – nuo Aukštesnės Eilės Komponentų iki modernių Hooks – kurie paverčia kompoziciją geriausiu kūrėjo draugu kuriant tvirtas ir lanksčias programas pasaulinei auditorijai.
Senosios Gvardijos Supratimas: Kas Yra Paveldimumas?
Paveldimumas yra pagrindinis objektinio programavimo (OOP) ramstis. Jis leidžia naujai klasei (poklasiui arba vaikui) perimti esamos klasės (superklasės arba tėvo) savybes ir metodus. Tai sukuria glaudžiai susietą „yra“ ryšį. Pavyzdžiui, AuksaspalvisRetriveris
yra Šuo
, kuris yra Gyvūnas
.
Paveldimumas Ne React Kontekste
Pažvelkime į paprastą JavaScript klasės pavyzdį, kad įtvirtintume šią koncepciją:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Iškviečia tėvinį konstruktorių
this.breed = breed;
}
speak() { // Perrašo tėvinį metodą
console.log(`${this.name} barks.`);
}
fetch() {
console.log(`${this.name} is fetching the ball!`);
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // Išvestis: "Buddy barks."
myDog.fetch(); // Išvestis: "Buddy is fetching the ball!"
Šiame modelyje Dog
klasė automatiškai gauna name
savybę ir speak
metodą iš Animal
. Ji taip pat gali pridėti savo metodų (fetch
) ir perrašyti esamus. Tai sukuria griežtą hierarchiją.
Kodėl Paveldimumas Stringa React'e
Nors šis „yra“ modelis veikia kai kurioms duomenų struktūroms, jis sukelia rimtų problemų, kai taikomas UI komponentams React'e:
- Glaudus Susiejimas: Kai komponentas paveldi iš bazinio komponento, jis tampa glaudžiai susietas su savo tėvo įgyvendinimu. Pakeitimas baziniame komponente gali netikėtai sugadinti kelis vaiko komponentus grandinėje. Dėl to kodo pertvarkymas ir priežiūra tampa trapūs.
- Nelankstus Logikos Dalijimasis: O kas, jei norite pasidalyti konkrečia funkcionalumo dalimi, pavyzdžiui, duomenų gavimu, su komponentais, kurie netelpa į tą pačią „yra“ hierarchiją? Pavyzdžiui, tiek
UserProfile
, tiekProductList
gali reikėti gauti duomenis, tačiau nėra prasmės, kad jie paveldėtų iš bendroDataFetchingComponent
. - „Prop-Drilling“ Pragaras: Gilioje paveldimumo grandinėje tampa sudėtinga perduoti savybes (props) iš aukščiausio lygio komponento giliai įdėtam vaikui. Gali tekti perduoti savybes per tarpinius komponentus, kurie jų net nenaudoja, o tai veda prie painaus ir išpūsto kodo.
- „Gorilos-Banano Problema“: Garsi OOP eksperto Joe Armstrongo citata puikiai apibūdina šią problemą: „Jūs norėjote banano, bet gavote gorilą, laikančią bananą ir visas džiungles.“ Su paveldimumu negalite tiesiog gauti norimos funkcionalumo dalies; esate priversti pasiimti visą superklasę kartu.
Dėl šių problemų React komanda sukūrė biblioteką, remdamasi lankstesne ir galingesne paradigma: kompozicija.
React Būdo Priėmimas: Kompozicijos Galia
Kompozicija yra dizaino principas, kuris teikia pirmenybę „turi“ (has-a) arba „naudoja“ (uses-a) ryšiui. Užuot komponentas būtų kitas komponentas, jis turi kitus komponentus arba naudoja jų funkcionalumą. Komponentai traktuojami kaip statybiniai blokai – kaip LEGO kaladėlės – kuriuos galima derinti įvairiais būdais, norint sukurti sudėtingas vartotojo sąsajas, neįstringant griežtoje hierarchijoje.
React kompozicinis modelis yra neįtikėtinai universalus ir pasireiškia keliais pagrindiniais šablonais. Panagrinėkime juos, pradedant nuo paprasčiausių ir baigiant moderniausiais bei galingiausiais.
1 Technika: Talpinimas su `props.children`
Pati paprasčiausia kompozicijos forma yra talpinimas. Tai atvejis, kai komponentas veikia kaip bendrinis konteineris arba „dėžutė“, o jo turinys yra perduodamas iš tėvinio komponento. React tam turi specialią, integruotą savybę: props.children
.
Įsivaizduokite, kad jums reikia `Card` komponento, kuris galėtų apgaubti bet kokį turinį su nuosekliu rėmeliu ir šešėliu. Užuot kūrę `TextCard`, `ImageCard` ir `ProfileCard` variantus per paveldimumą, jūs sukuriate vieną bendrinį `Card` komponentą.
// Card.js - Bendrinis talpinimo komponentas
function Card(props) {
return (
<div className="card">
{props.children}
</div>
);
}
// App.js - Naudojant Card komponentą
function App() {
return (
<div>
<Card>
<h1>Sveiki!</h1>
<p>Šis turinys yra Card komponente.</p>
</Card>
<Card>
<img src="/path/to/image.jpg" alt="An example image" />
<p>Tai yra paveikslėlio kortelė.</p>
</Card>
</div>
);
}
Čia Card
komponentas nežino ir jam nerūpi, ką jis talpina. Jis tiesiog suteikia apgaubiantį stilių. Turinys tarp atidarymo ir uždarymo <Card>
žymų yra automatiškai perduodamas kaip props.children
. Tai puikus atsiejimo ir pakartotinio panaudojamumo pavyzdys.
2 Technika: Specializacija su Props
Kartais komponentui reikia kelių „skylių“, kurias užpildytų kiti komponentai. Nors galėtumėte naudoti `props.children`, aiškesnis ir labiau struktūrizuotas būdas yra perduoti komponentus kaip įprastas savybes (props). Šis šablonas dažnai vadinamas specializacija.
Apsvarstykite `Modal` komponentą. Modalinis langas paprastai turi pavadinimo sekciją, turinio sekciją ir veiksmų sekciją (su mygtukais, tokiais kaip „Patvirtinti“ ar „Atšaukti“). Galime suprojektuoti savo `Modal` taip, kad jis priimtų šias sekcijas kaip savybes (props).
// Modal.js - Labiau specializuotas konteineris
function Modal(props) {
return (
<div className="modal-backdrop">
<div className="modal-content">
<div className="modal-header">{props.title}</div>
<div className="modal-body">{props.body}</div>
<div className="modal-footer">{props.actions}</div>
</div>
</div>
);
}
// App.js - Naudojant Modal su konkrečiais komponentais
function App() {
const confirmationTitle = <h2>Patvirtinti veiksmą</h2>;
const confirmationBody = <p>Ar tikrai norite tęsti šį veiksmą?</p>;
const confirmationActions = (
<div>
<button>Patvirtinti</button>
<button>Atšaukti</button>
</div>
);
return (
<Modal
title={confirmationTitle}
body={confirmationBody}
actions={confirmationActions}
/>
);
}
Šiame pavyzdyje Modal
yra labai pakartotinai naudojamas išdėstymo komponentas. Mes jį specializuojame, perduodami konkrečius JSX elementus jo `title`, `body` ir `actions` savybėms. Tai daug lanksčiau nei kurti `ConfirmationModal` ir `WarningModal` poklasius. Mes tiesiog komponuojame `Modal` su skirtingu turiniu pagal poreikį.
3 Technika: Aukštesnės Eilės Komponentai (HOC)
Dalijimuisi ne-UI logika, tokia kaip duomenų gavimas, autentifikavimas ar registravimas, React kūrėjai istoriškai naudojo šabloną, vadinamą Aukštesnės Eilės Komponentais (HOC). Nors šiuolaikiniame React'e juos didžiąja dalimi pakeitė Hooks, svarbu juos suprasti, nes jie atspindi svarbų evoliucinį žingsnį React kompozicijos istorijoje ir vis dar egzistuoja daugelyje kodų bazių.
HOC yra funkcija, kuri priima komponentą kaip argumentą ir grąžina naują, patobulintą komponentą.
Sukurkime HOC, pavadintą `withLogger`, kuris registruoja komponento savybes (props) kiekvieną kartą, kai jis atnaujinamas. Tai naudinga derinant kodą.
// withLogger.js - HOC
import React, { useEffect } from 'react';
function withLogger(WrappedComponent) {
// Ji grąžina naują komponentą...
return function EnhancedComponent(props) {
useEffect(() => {
console.log('Komponentas atnaujintas su naujomis savybėmis:', props);
}, [props]);
// ... kuris atvaizduoja pradinį komponentą su pradinėmis savybėmis (props).
return <WrappedComponent {...props} />;
};
}
// MyComponent.js - Komponentas, kurį reikia patobulinti
function MyComponent({ name, age }) {
return (
<div>
<h1>Sveiki, {name}!</h1>
<p>Jums yra {age} metai.</p>
</div>
);
}
// Eksportuojamas patobulintas komponentas
export default withLogger(MyComponent);
`withLogger` funkcija apgaubia `MyComponent`, suteikdama jam naujų registravimo galimybių, nekeičiant `MyComponent` vidinio kodo. Galėtume pritaikyti tą patį HOC bet kuriam kitam komponentui, kad suteiktume jam tą pačią registravimo funkciją.
Iššūkiai su HOC:
- Įvilkimo Pragaras (Wrapper Hell): Pritaikant kelis HOC vienam komponentui, React DevTools gali atsirasti giliai įdėtų komponentų (pvz., `withAuth(withRouter(withLogger(MyComponent)))`), o tai apsunkina derinimą.
- Prop Pavadinimų Susidūrimai: Jei HOC įterpia savybę (prop) (pvz., `data`), kurią jau naudoja apgaubtas komponentas, ji gali būti netyčia perrašyta.
- Numanoma Logika: Iš komponento kodo ne visada aišku, iš kur ateina jo savybės (props). Logika yra paslėpta HOC viduje.
4 Technika: Render Props
Render Prop šablonas atsirado kaip sprendimas kai kuriems HOC trūkumams. Jis siūlo aiškesnį logikos dalijimosi būdą.
Komponentas su „render prop“ priima funkciją kaip savybę (prop) (paprastai pavadintą `render`) ir iškviečia tą funkciją, kad nustatytų, ką atvaizduoti, perduodamas jai bet kokią būseną ar logiką kaip argumentus.
Sukurkime `MouseTracker` komponentą, kuris seka pelės X ir Y koordinates ir padaro jas prieinamas bet kuriam komponentui, kuris nori jas naudoti.
// MouseTracker.js - Komponentas su „render prop“
import React, { useState, useEffect } from 'react';
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
// Iškvieskite „render“ funkciją su būsena
return render(position);
}
// App.js - Naudojant MouseTracker
function App() {
return (
<div>
<h1>Judinkite pelę!</h1>
<MouseTracker
render={mousePosition => (
<p>Dabartinė pelės pozicija yra ({mousePosition.x}, {mousePosition.y})</p>
)}
/>
</div>
);
}
Čia `MouseTracker` apima visą pelės judėjimo sekimo logiką. Jis pats nieko neatvaizduoja. Vietoj to, jis deleguoja atvaizdavimo logiką savo `render` savybei. Tai yra aiškiau nei HOC, nes jūs galite matyti, iš kur tiksliai ateina `mousePosition` duomenys, tiesiai JSX viduje.
children
savybė taip pat gali būti naudojama kaip funkcija, kas yra įprasta ir elegantiška šio šablono variacija:
// Naudojant „children“ kaip funkciją
<MouseTracker>
{mousePosition => (
<p>Dabartinė pelės pozicija yra ({mousePosition.x}, {mousePosition.y})</p>
)}
</MouseTracker>
5 Technika: Hooks (Modernus ir Rekomenduojamas Požiūris)
Pristatyti React 16.8 versijoje, Hooks sukėlė revoliuciją, kaip mes rašome React komponentus. Jie leidžia naudoti būseną ir kitas React funkcijas funkciniuose komponentuose. Svarbiausia, individualizuoti Hooks (custom Hooks) suteikia elegantiškiausią ir tiesiausią sprendimą būsenos logikai dalytis tarp komponentų.
Hooks išsprendžia HOC ir Render Props problemas daug švaresniu būdu. Pertvarkykime mūsų `MouseTracker` pavyzdį į individualizuotą hook, pavadintą `useMousePosition`.
// hooks/useMousePosition.js - Individualizuotas Hook
import { useState, useEffect } from 'react';
export function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []); // Tuščias priklausomybių masyvas reiškia, kad šis efektas paleidžiamas tik vieną kartą
return position;
}
// DisplayMousePosition.js - Komponentas, naudojantis Hook
import { useMousePosition } from './hooks/useMousePosition';
function DisplayMousePosition() {
const position = useMousePosition(); // Tiesiog iškvieskite hook!
return (
<p>
Pelės pozicija yra ({position.x}, {position.y})
</p>
);
}
// Kitas komponentas, galbūt interaktyvus elementas
import { useMousePosition } from './hooks/useMousePosition';
function InteractiveBox() {
const { x, y } = useMousePosition();
const style = {
position: 'absolute',
top: y - 25, // Centruokite langelį ant kursoriaus
left: x - 25,
width: '50px',
height: '50px',
backgroundColor: 'lightblue',
};
return <div style={style} />;
}
Tai yra didžiulis patobulinimas. Nėra jokio „įvilkimo pragaro“, jokių savybių pavadinimų susidūrimų ir jokių sudėtingų „render prop“ funkcijų. Logika yra visiškai atsieta į pakartotinai naudojamą funkciją (`useMousePosition`), ir bet kuris komponentas gali „prisijungti“ prie tos būsenos logikos viena, aiškia kodo eilute. Individualizuoti Hooks yra aukščiausia kompozicijos išraiška šiuolaikiniame React'e, leidžianti jums susikurti savo pakartotinai naudojamų logikos blokų biblioteką.
Greitas Palyginimas: Kompozicija Prieš Paveldimumą React'e
Norint apibendrinti pagrindinius skirtumus React kontekste, štai tiesioginis palyginimas:
Aspektas | Paveldimumas (Anti-šablonas React'e) | Kompozicija (Rekomenduojama React'e) |
---|---|---|
Ryšys | 'yra' ryšys. Specializuotas komponentas yra bazinio komponento versija. | 'turi' arba 'naudoja' ryšys. Sudėtingas komponentas turi mažesnius komponentus arba naudoja bendrą logiką. |
Susiejimas | Aukštas. Vaiko komponentai yra glaudžiai susieti su jų tėvo įgyvendinimu. | Žemas. Komponentai yra nepriklausomi ir gali būti pakartotinai naudojami skirtinguose kontekstuose be pakeitimų. |
Lankstumas | Žemas. Griežtos, klasėmis pagrįstos hierarchijos apsunkina logikos dalijimąsi tarp skirtingų komponentų medžių. | Aukštas. Logiką ir UI galima derinti ir pakartotinai naudoti nesuskaičiuojamais būdais, kaip statybinius blokus. |
Kodo Pakartotinis Panaudojamumas | Apribotas iš anksto nustatyta hierarchija. Jūs gaunate visą „gorilą“, kai norite tik „banano“. | Puikus. Maži, sufokusuoti komponentai ir hooks gali būti naudojami visoje programoje. |
React Idioma | Nerekomenduojama oficialios React komandos. | Rekomenduojamas ir idiomatiškas požiūris kuriant React programas. |
Išvada: Mąstykite Kompozicijos Terminais
Diskusija tarp kompozicijos ir paveldimumo yra pamatinė programinės įrangos kūrimo tema. Nors paveldimumas turi savo vietą klasikinėje OOP, dinamiška, komponentais pagrįsta UI kūrimo prigimtis daro jį prastai tinkamu React'ui. Biblioteka buvo iš esmės sukurta siekiant priimti kompoziciją.
Teikdami pirmenybę kompozicijai, jūs gaunate:
- Lankstumą: Galimybę maišyti ir derinti UI bei logiką pagal poreikį.
- Palaikomumą: Laisvai susietus komponentus lengviau suprasti, testuoti ir pertvarkyti atskirai.
- Mastelio Keitimo Galimybę: Kompozicinis mąstymas skatina kurti dizaino sistemą iš mažų, pakartotinai naudojamų komponentų ir hooks, kurie gali būti naudojami efektyviai kuriant dideles, sudėtingas programas.
Kaip pasaulinio lygio React kūrėjui, kompozicijos įvaldymas nėra tik geriausių praktikų laikymasis – tai yra pagrindinės filosofijos, kuri daro React tokiu galingu ir produktyviu įrankiu, supratimas. Pradėkite nuo mažų, sufokusuotų komponentų kūrimo. Naudokite `props.children` bendriniams konteineriams ir savybes specializacijai. Dalijimuisi logika pirmiausia rinkitės individualizuotus Hooks. Mąstydami kompozicijos terminais, būsite kelyje į elegantiškų, tvirtų ir keičiamo mastelio React programų kūrimą, kurios atlaikys laiko išbandymą.