Suomi

Syvällinen sukellus Reactin komponenttiarkkitehtuuriin, jossa vertaillaan koostamista ja periytymistä. Opi, miksi React suosii koostamista ja tutustu malleihin, kuten HOC:eihin, Render Props -ominaisuuksiin ja Hookeihin skaalautuvien, uudelleenkäytettävien komponenttien rakentamiseksi.

React-komponenttiarkkitehtuuri: Miksi koostaminen voittaa periytymisen

Ohjelmistokehityksen maailmassa arkkitehtuuri on ensiarvoisen tärkeää. Tapa, jolla rakennamme koodimme, määrittää sen skaalautuvuuden, ylläpidettävyyden ja uudelleenkäytettävyyden. Reactia käyttäville kehittäjille yksi perustavanlaatuisimmista arkkitehtonisista päätöksistä pyörii sen ympärillä, miten logiikkaa ja käyttöliittymää jaetaan komponenttien välillä. Tämä tuo meidät klassiseen keskusteluun olio-ohjelmoinnissa, joka on kuviteltu uudelleen Reactin komponenttipohjaista maailmaa varten: Koostaminen vs. Periytyminen.

Jos sinulla on tausta klassisissa olio-ohjelmointikielissä, kuten Javassa tai C++:ssa, periytyminen voi tuntua luonnolliselta ensivalinnalta. Se on tehokas konsepti 'on-a' -suhteiden luomiseen. Kuitenkin virallinen React-dokumentaatio tarjoaa selkeän ja vahvan suosituksen: "Facebookissa käytämme Reactia tuhansissa komponenteissa, emmekä ole löytäneet mitään käyttötapauksia, joissa suosittelisimme komponenttiperiytymishierarkioiden luomista."

Tämä postaus tarjoaa kattavan tarkastelun tästä arkkitehtonisesta valinnasta. Avaamme, mitä periytyminen ja koostaminen tarkoittavat React-kontekstissa, osoitamme, miksi koostaminen on idiomaattinen ja parempi lähestymistapa, ja tutustumme tehokkaisiin malleihin – korkeamman asteen komponenteista moderneihin Hookeihin – jotka tekevät koostamisesta kehittäjän parhaan ystävän luotaessa vahvoja ja joustavia sovelluksia globaalille yleisölle.

Vanhan kaartin ymmärtäminen: Mitä periytyminen on?

Periytyminen on olio-ohjelmoinnin (OOP) peruspilari. Se mahdollistaa uuden luokan (aliluokan tai lapsen) hankkia olemassa olevan luokan (yliluokan tai vanhemman) ominaisuudet ja metodit. Tämä luo tiiviisti sidotun 'on-a'-suhteen. Esimerkiksi GoldenRetriever on Dog, joka on Animal.

Periytyminen ei-React-kontekstissa

Katsotaan yksinkertaista JavaScript-luokkaesimerkkiä konseptin vakiinnuttamiseksi:

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} tekee ääntä.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Kutsuu vanhemman konstruktoria
    this.breed = breed;
  }

  speak() { // Ohittaa vanhemman metodin
    console.log(`${this.name} haukkuu.`);
  }

  fetch() {
    console.log(`${this.name} hakee palloa!`);
  }
}

const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // Output: "Buddy haukkuu."
myDog.fetch(); // Output: "Buddy hakee palloa!"

Tässä mallissa Dog-luokka saa automaattisesti name-ominaisuuden ja speak-metodin Animal-luokasta. Se voi myös lisätä omia metodejaan (fetch) ja ohittaa olemassa olevia. Tämä luo jäykän hierarkian.

Miksi periytyminen kompastuu Reactissa

Vaikka tämä 'on-a'-malli toimii joillekin tietorakenteille, se luo merkittäviä ongelmia, kun sitä sovelletaan Reactin käyttöliittymäkomponentteihin:

Näiden ongelmien takia React-tiimi suunnitteli kirjaston joustavammaksi ja tehokkaammaksi paradigmaksi: koostamiseksi.

React-tavan omaksuminen: Koostamisen voima

Koostaminen on suunnitteluperiaate, joka suosii 'has-a'- tai 'uses-a'-suhdetta. Sen sijaan, että komponentti olisi toinen komponentti, sillä on muita komponentteja tai se käyttää niiden toimintoja. Komponentteja käsitellään rakennuspalikoina – kuten LEGO-tiiliä – jotka voidaan yhdistää eri tavoilla monimutkaisten käyttöliittymien luomiseksi joutumatta lukittuna jäykkään hierarkiaan.

Reactin koostemalli on uskomattoman monipuolinen, ja se ilmenee useissa keskeisissä malleissa. Katsotaan niitä yksinkertaisimmasta moderneimpaan ja tehokkaimpaan.

Tekniikka 1: Sisältäminen `props.children` avulla

Yksinkertaisin koostamisen muoto on sisältäminen. Tässä komponentti toimii yleisenä säiliönä tai 'laatikona', ja sen sisältö välitetään vanhempikomponentista. Reactilla on tätä varten erityinen, sisäänrakennettu prop: props.children.

Kuvittele, että tarvitset Card-komponentin, joka voi paketoida minkä tahansa sisällön johdonmukaisella reunuksella ja varjolla. Sen sijaan, että loisit TextCard-, ImageCard- ja ProfileCard-variantteja periytymällä, luot yhden yleisen Card-komponentin.

// Card.js - Yleinen säiliökomponentti
function Card(props) {
  return (
    <div className="card">
      {props.children}
    </div>
  );
}

// App.js - Card-komponentin käyttö
function App() {
  return (
    <div>
      <Card>
        <h1>Tervetuloa!</h1>
        <p>Tämä sisältö on Card-komponentin sisällä.</p>
      </Card>

      <Card>
        <img src="/path/to/image.jpg" alt="Esimerkkikuva" />
        <p>Tämä on kuvakortti.</p>
      </Card>
    </div>
  );
}

Tässä Card-komponentti ei tiedä tai välitä siitä, mitä se sisältää. Se yksinkertaisesti tarjoaa kääretyylin. Sisältö avaavien ja sulkevien <Card>-tunnisteiden välillä välitetään automaattisesti props.children-ominaisuutena. Tämä on kaunis esimerkki irrottamisesta ja uudelleenkäytettävyydestä.

Tekniikka 2: Erikoistuminen Props-ominaisuuksilla

Joskus komponentti tarvitsee useita 'reikiä', jotka täytetään muilla komponenteilla. Vaikka voisit käyttää props.children-ominaisuutta, selkeämpi ja jäsennellympi tapa on välittää komponentteja tavallisina props-ominaisuuksina. Tätä mallia kutsutaan usein erikoistumiseksi.

Harkitse Modal-komponenttia. Modalilla on tyypillisesti otsikko-osio, sisältö-osio ja toimintaosio (painikkeilla, kuten "Vahvista" tai "Peruuta"). Voimme suunnitella Modal-komponenttimme hyväksymään nämä osiot props-ominaisuuksina.

// Modal.js - Erikoistuneempi säiliö
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 - Modalin käyttö erityisillä komponenteilla
function App() {
  const confirmationTitle = <h2>Vahvista toiminto</h2>;
  const confirmationBody = <p>Oletko varma, että haluat jatkaa tätä toimintoa?</p>;
  const confirmationActions = (
    <div>
      <button>Vahvista</button>
      <button>Peruuta</button>
    </div>
  );

  return (
    <Modal
      title={confirmationTitle}
      body={confirmationBody}
      actions={confirmationActions}
    />
  );
}

Tässä esimerkissä Modal on erittäin uudelleenkäytettävä asettelukomponentti. Erikoistamme sitä välittämällä erityisiä JSX-elementtejä sen title-, body- ja actions-ominaisuuksille. Tämä on paljon joustavampaa kuin luoda ConfirmationModal- ja WarningModal-aliluokkia. Yksinkertaisesti koostamme Modal-komponentin eri sisällöllä tarpeen mukaan.

Tekniikka 3: Korkeamman asteen komponentit (HOC:t)

Ei-käyttöliittymälogiikan, kuten tietojen noutamisen, todennuksen tai lokituksen, jakamiseksi React-kehittäjät kääntyivät historiallisesti mallin puoleen, jota kutsutaan korkeamman asteen komponenteiksi (HOC:t). Vaikka Hookit ovat suurelta osin korvanneet ne modernissa Reactissa, on ratkaisevan tärkeää ymmärtää ne, koska ne edustavat keskeistä kehitysaskelta Reactin koostamistarinaan ja ovat edelleen olemassa monissa koodikannoissa.

HOC on funktio, joka ottaa komponentin argumenttina ja palauttaa uuden, parannetun komponentin.

Luodaan HOC nimeltä withLogger, joka kirjaa komponentin props-ominaisuudet aina, kun se päivittyy. Tämä on hyödyllistä virheenkorjaukseen.

// withLogger.js - HOC
import React, { useEffect } from 'react';

function withLogger(WrappedComponent) {
  // Se palauttaa uuden komponentin...
  return function EnhancedComponent(props) {
    useEffect(() => {
      console.log('Komponentti päivitetty uusilla props-ominaisuuksilla:', props);
    }, [props]);

    // ... joka renderöi alkuperäisen komponentin alkuperäisillä props-ominaisuuksilla.
    return <WrappedComponent {...props} />;
  };
}

// MyComponent.js - Parannettava komponentti
function MyComponent({ name, age }) {
  return (
    <div>
      <h1>Hei, {name}!</h1>
      <p>Olet {age} vuotta vanha.</p>
    </div>
  );
}

// Parannetun komponentin vienti
export default withLogger(MyComponent);

withLogger-funktio kietoo MyComponent-komponentin, antaen sille uusia lokitusominaisuuksia muokkaamatta MyComponent-komponentin sisäistä koodia. Voisimme soveltaa tätä samaa HOC-komponenttia mihin tahansa muuhun komponenttiin antaaksemme sille saman lokitusominaisuuden.

HOC:ien haasteet:

Tekniikka 4: Render Props -ominaisuudet

Render Prop -malli syntyi ratkaisuksi joihinkin HOC:ien puutteisiin. Se tarjoaa selkeämmän tavan jakaa logiikkaa.

Komponentti, jolla on render prop, ottaa funktion prop-ominaisuutena (yleensä nimeltään render) ja kutsuu tätä funktiota määrittääkseen, mitä renderöidä, välittäen sille kaikki tilat tai logiikan argumentteina.

Luodaan MouseTracker-komponentti, joka seuraa hiiren X- ja Y-koordinaatteja ja tekee ne saataville kaikille komponenteille, jotka haluavat käyttää niitä.

// MouseTracker.js - Komponentti, jossa on 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);
    };
  }, []);

  // Kutsu render-funktiota tilan kanssa
  return render(position);
}

// App.js - MouseTrackerin käyttö
function App() {
  return (
    <div>
      <h1>Siirrä hiirtäsi ympärillä!</h1>
      <MouseTracker
        render={mousePosition => (
          <p>Nykyinen hiiren sijainti on ({mousePosition.x}, {mousePosition.y})</p>
        )}
      />
    </div>
  );
}

Tässä MouseTracker kapseloi kaiken hiiren liikkeen seurantaan liittyvän logiikan. Se ei renderöi mitään itsessään. Sen sijaan se delegoi renderöintilogiikan sen render-ominaisuudelle. Tämä on selkeämpää kuin HOC:it, koska näet täsmälleen, mistä mousePosition-tiedot tulevat suoraan JSX:n sisällä.

children-ominaisuutta voidaan myös käyttää funktiona, mikä on yleinen ja elegantti muunnelma tästä mallista:

// Käyttämällä lapsia funktiona
<MouseTracker>
  {mousePosition => (
    <p>Nykyinen hiiren sijainti on ({mousePosition.x}, {mousePosition.y})</p>
  )}
</MouseTracker>

Tekniikka 5: Hookit (Moderni ja suositeltava lähestymistapa)

React 16.8:ssa esitellyt Hookit mullistivat tavan, jolla kirjoitamme React-komponentteja. Ne mahdollistavat tilan ja muiden React-ominaisuuksien käytön funktionaalisissa komponenteissa. Tärkeintä on, että mukautetut Hookit tarjoavat elegantimman ja suoremman ratkaisun tilallisen logiikan jakamiseen komponenttien välillä.

Hookit ratkaisevat HOC:ien ja Render Propsien ongelmat paljon siistimmällä tavalla. Uudistetaan MouseTracker-esimerkki mukautetuksi Hookiksi nimeltä useMousePosition.

// hooks/useMousePosition.js - Mukautettu 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);
    };
  }, []); // Tyhjä riippuvuustaulukko tarkoittaa, että tämä efekti suoritetaan vain kerran

  return position;
}

// DisplayMousePosition.js - Hookia käyttävä komponentti
import { useMousePosition } from './hooks/useMousePosition';

function DisplayMousePosition() {
  const position = useMousePosition(); // Kutsu vain Hookia!

  return (
    <p>
      Hiiren sijainti on ({position.x}, {position.y})
    </p>
  );
}

// Toinen komponentti, ehkä interaktiivinen elementti
import { useMousePosition } from './hooks/useMousePosition';

function InteractiveBox() {
  const { x, y } = useMousePosition();

  const style = {
    position: 'absolute',
    top: y - 25, // Keskitä laatikko kursoriin
    left: x - 25,
    width: '50px',
    height: '50px',
    backgroundColor: 'lightblue',
  };

  return <div style={style} />;
}

Tämä on valtava parannus. Ei ole 'wrapper helliä', prop-nimien törmäyksiä eikä monimutkaisia render prop -funktioita. Logiikka on täysin irrotettu uudelleenkäytettäväksi funktioksi (useMousePosition), ja mikä tahansa komponentti voi 'kiinnittyä' tähän tilalliseen logiikkaan yhdellä, selkeällä koodirivillä. Mukautetut Hookit ovat koostamisen äärimmäinen ilmaus modernissa Reactissa, jonka avulla voit rakentaa oman kirjaston uudelleenkäytettävistä logiikkalohkoista.

Lyhyt vertailu: Koostaminen vs. Periytyminen Reactissa

Avainerojen yhteenvetona React-kontekstissa tässä on suora vertailu:

Näkökulma Periytyminen (Anti-Pattern Reactissa) Koostaminen (Suositellaan Reactissa)
<strong>Suhde</strong> 'on-a'-suhde. Erikoistunut komponentti on versio peruskomponentista. 'has-a'- tai 'uses-a'-suhde. Monimutkaisella komponentilla on pienempiä komponentteja tai se käyttää jaettua logiikkaa.
<strong>Kytkentä</strong> <strong>Korkea.</strong> Lapsikomponentit ovat tiiviisti sidoksissa niiden vanhempien toteutukseen. <strong>Alhainen.</strong> Komponentit ovat riippumattomia ja niitä voidaan käyttää eri konteksteissa ilman muutoksia.
<strong>Joustavuus</strong> <strong>Alhainen.</strong> Jäykät, luokkapohjaiset hierarkiat vaikeuttavat logiikan jakamista eri komponenttipuissa. <strong>Korkea.</strong> Logiikkaa ja käyttöliittymää voidaan yhdistellä ja käyttää uudelleen lukemattomilla tavoilla, kuten rakennuspalikoilla.
<strong>Koodin uudelleenkäytettävyys</strong> Rajoitettu ennalta määritettyyn hierarkiaan. Saat koko "gorillan", kun haluat vain "banaanin". Erinomainen. Pieniä, kohdennettuja komponentteja ja Hookeja voidaan käyttää koko sovelluksessa.
<strong>React-idiomi</strong> React-tiimi ei suosittele. Suositeltava ja idiomaattinen tapa React-sovellusten rakentamiseen.

Johtopäätös: Ajattele koostamista

Koostamisen ja periytymisen välinen keskustelu on ohjelmistosuunnittelun perustavanlaatuinen aihe. Vaikka periytymisellä on paikkansa klassisessa OOP:ssä, käyttöliittymäkehityksen dynaaminen, komponenttipohjainen luonne tekee siitä huonon sopivuuden Reactille. Kirjasto suunniteltiin pohjimmiltaan omaksumaan koostamista.

Suosimalla koostamista saat:

Globaalina React-kehittäjänä koostamisen hallitseminen ei ole vain parhaiden käytäntöjen noudattamista – se on ymmärrystä ydinfilosofiasta, joka tekee Reactista niin tehokkaan ja tuottavan työkalun. Aloita luomalla pieniä, kohdennettuja komponentteja. Käytä props.children-ominaisuutta yleisiin säiliöihin ja props-ominaisuuksia erikoistumiseen. Logiikan jakamiseen käytä ensin mukautettuja Hookeja. Ajattelemalla koostamista olet hyvällä matkalla rakentamaan tyylikkäitä, vahvoja ja skaalautuvia React-sovelluksia, jotka kestävät aikaa.