Eesti

Sügav sukeldumine Reacti komponentide arhitektuuri, võrreldes kompositsiooni ja pärilikkust. Uuri, miks React eelistab kompositsiooni ja avasta mustreid nagu HOC-d, Render Props ja Hooks, et luua skaleeritavaid ja korduvkasutatavaid komponente.

Reacti komponentide arhitektuur: Miks kompositsioon on pärilikkusest üle

Tarkvaraarenduse maailmas on arhitektuur esmatähtis. See, kuidas me oma koodi struktureerime, määrab selle skaleeritavuse, hooldatavuse ja korduvkasutatavuse. Reactiga töötavate arendajate jaoks on üks fundamentaalsemaid arhitektuurilisi otsuseid seotud sellega, kuidas jagada loogikat ja kasutajaliidest komponentide vahel. See viib meid klassikalise objektorienteeritud programmeerimise debatini, mis on Reacti komponentidepõhise maailma jaoks ümber mõeldud: Kompositsioon vs. pärilikkus.

Kui teil on kogemusi klassikaliste objektorienteeritud keeltega nagu Java või C++, võib pärilikkus tunduda loomuliku esimese valikuna. See on võimas kontseptsioon 'on-üks' suhete loomiseks. Kuid ametlik Reacti dokumentatsioon annab selge ja tugeva soovituse: "Facebookis kasutame Reacti tuhandetes komponentides ja me ei ole leidnud ühtegi kasutusjuhtu, kus soovitaksime luua komponentide pärilikkuse hierarhiaid."

See postitus pakub põhjaliku ülevaate sellest arhitektuurilisest valikust. Me selgitame lahti, mida pärilikkus ja kompositsioon Reacti kontekstis tähendavad, demonstreerime, miks kompositsioon on idiomaatiline ja parem lähenemisviis, ning uurime võimsaid mustreid – alates kõrgema järgu komponentidest kuni moodsate Hookideni –, mis muudavad kompositsiooni arendaja parimaks sõbraks robustsete ja paindlike rakenduste ehitamisel ülemaailmsele publikule.

Vana kaardiväe mõistmine: Mis on pärilikkus?

Pärilikkus on objektorienteeritud programmeerimise (OOP) üks alustalasid. See võimaldab uuel klassil (alamklass ehk laps) omandada olemasoleva klassi (ülemklass ehk vanem) omadused ja meetodid. See loob tihedalt seotud 'on-üks' suhte. Näiteks GoldenRetriever on Dog, mis on Animal.

Pärilikkus Reacti-välises kontekstis

Vaatame lihtsat JavaScripti klassi näidet, et kontseptsiooni paremini mõista:

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

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Calls the parent constructor
    this.breed = breed;
  }

  speak() { // Overrides the parent method
    console.log(`${this.name} barks.`);
  }

  fetch() {
    console.log(`${this.name} is fetching the ball!`);
  }
}

const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // Output: "Buddy barks."
myDog.fetch(); // Output: "Buddy is fetching the ball!"

Selles mudelis saab Dog klass automaatselt name omaduse ja speak meetodi klassilt Animal. Samuti saab see lisada oma meetodeid (fetch) ja olemasolevaid üle kirjutada. See loob jäiga hierarhia.

Miks pärilikkus Reactis ebaõnnestub

Kuigi see 'on-üks' mudel sobib mõnede andmestruktuuride jaoks, tekitab see olulisi probleeme, kui seda rakendada Reacti kasutajaliidese komponentidele:

Nende probleemide tõttu disainis Reacti meeskond teegi paindlikuma ja võimsama paradigma ümber: kompositsiooni.

Reacti viisi omaksvõtmine: Kompositsiooni jõud

Kompositsioon on disainipõhimõte, mis eelistab 'omab-üht' või 'kasutab-üht' suhet. Selle asemel, et komponent oleks teine komponent, see omab teisi komponente või kasutab nende funktsionaalsust. Komponente käsitletakse ehitusplokkidena – nagu LEGO klotsid –, mida saab kombineerida erinevatel viisidel, et luua keerukaid kasutajaliideseid, ilma et oleks lukustatud jäigasse hierarhiasse.

Reacti kompositsioonimudel on uskumatult mitmekülgne ja avaldub mitmes võtmemustris. Uurime neid, alustades kõige lihtsamast kuni kõige kaasaegsema ja võimsamani.

Tehnika 1: Sisaldamine props.children abil

Kõige otsesem kompositsiooni vorm on sisaldamine. See on olukord, kus komponent toimib üldise konteineri või 'kastina' ja selle sisu edastatakse vanemkomponendist. Reactil on selleks spetsiaalne, sisseehitatud prop: props.children.

Kujutage ette, et vajate Card komponenti, mis suudab mässida mis tahes sisu ühtse raami ja varjuga. Selle asemel, et luua pärilikkuse kaudu TextCard, ImageCard ja ProfileCard variante, loote ühe üldise Card komponendi.

// Card.js - A generic container component
function Card(props) {
  return (
    <div className="card">
      {props.children}
    </div>
  );
}

// App.js - Using the Card component
function App() {
  return (
    <div>
      <Card>
        <h1>Welcome!</h1>
        <p>This content is inside a Card component.</p>
      </Card>

      <Card>
        <img src="/path/to/image.jpg" alt="An example image" />
        <p>This is an image card.</p>
      </Card>
    </div>
  );
}

Siin ei tea ega hooli Card komponent sellest, mida see sisaldab. See pakub lihtsalt ümbritsevat stiili. Sisu avava ja sulgeva <Card> sildi vahel edastatakse automaatselt kui props.children. See on ilus näide lahtisidumisest ja korduvkasutatavusest.

Tehnika 2: Spetsialiseerimine prop'ide abil

Mõnikord vajab komponent mitut 'auku', mis tuleb täita teiste komponentidega. Kuigi võiksite kasutada props.children, on selgesõnalisem ja struktureeritum viis edastada komponente tavaliste prop'idena. Seda mustrit nimetatakse sageli spetsialiseerimiseks.

Mõelge Modal komponendile. Modaalsel aknel on tavaliselt päiseosa, sisuosa ja tegevuste osa (nuppudega nagu "Kinnita" või "Tühista"). Me saame oma Modal komponendi disainida nii, et see aktsepteerib neid osi prop'idena.

// Modal.js - A more specialized container
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 - Using the Modal with specific components
function App() {
  const confirmationTitle = <h2>Confirm Action</h2>;
  const confirmationBody = <p>Are you sure you want to proceed with this action?</p>;
  const confirmationActions = (
    <div>
      <button>Confirm</button>
      <button>Cancel</button>
    </div>
  );

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

Selles näites on Modal väga korduvkasutatav paigutuskomponent. Me spetsialiseerime selle, edastades selle title, body ja actions jaoks spetsiifilisi JSX elemente. See on palju paindlikum kui ConfirmationModal ja WarningModal alamklasside loomine. Me lihtsalt komponeerime Modal erineva sisuga vastavalt vajadusele.

Tehnika 3: Kõrgema järgu komponendid (HOC-d)

Mitte-UI loogika, nagu andmete pärimine, autentimine või logimine, jagamiseks pöördusid Reacti arendajad ajalooliselt mustri poole, mida nimetatakse kõrgema järgu komponentideks (HOC-deks). Kuigi tänapäevases Reactis on need suures osas asendatud Hooksidega, on oluline neid mõista, kuna need esindavad olulist evolutsioonilist sammu Reacti kompositsiooniloos ja eksisteerivad endiselt paljudes koodibaasides.

HOC on funktsioon, mis võtab argumendiks komponendi ja tagastab uue, täiustatud komponendi.

Loome HOC nimega withLogger, mis logib komponendi prop'id iga kord, kui see uueneb. See on kasulik silumiseks.

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

function withLogger(WrappedComponent) {
  // It returns a new component...
  return function EnhancedComponent(props) {
    useEffect(() => {
      console.log('Component updated with new props:', props);
    }, [props]);

    // ... that renders the original component with the original props.
    return <WrappedComponent {...props} />;
  };
}

// MyComponent.js - A component to be enhanced
function MyComponent({ name, age }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You are {age} years old.</p>
    </div>
  );
}

// Exporting the enhanced component
export default withLogger(MyComponent);

Funktsioon withLogger mässib MyComponent'i, andes sellele uued logimisvõimalused ilma MyComponent'i sisemist koodi muutmata. Me võiksime sama HOC-d rakendada mis tahes teisele komponendile, et anda sellele sama logimisfunktsioon.

HOC-dega seotud väljakutsed:

Tehnika 4: Render Props

Render Prop muster tekkis lahendusena mõnedele HOC-de puudustele. See pakub selgesõnalisemat viisi loogika jagamiseks.

Render prop'iga komponent võtab prop'ina funktsiooni (tavaliselt nimega `render`) ja kutsub selle funktsiooni välja, et määrata, mida renderdada, andes sellele argumentidena kaasa mis tahes oleku või loogika.

Loome MouseTracker komponendi, mis jälgib hiire X ja Y koordinaate ning teeb need kättesaadavaks igale komponendile, mis soovib neid kasutada.

// MouseTracker.js - Component with a 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);
    };
  }, []);

  // Call the render function with the state
  return render(position);
}

// App.js - Using the MouseTracker
function App() {
  return (
    <div>
      <h1>Move your mouse around!</h1>
      <MouseTracker
        render={mousePosition => (
          <p>The current mouse position is ({mousePosition.x}, {mousePosition.y})</p>
        )}
      />
    </div>
  );
}

Siin kapseldab MouseTracker kogu hiire liikumise jälgimise loogika. See ei renderda ise midagi. Selle asemel delegeerib see renderdamisloogika oma render prop'ile. See on selgesõnalisem kui HOC-d, sest näete otse JSX-i sees, kust mousePosition andmed pärinevad.

Prop'i children saab kasutada ka funktsioonina, mis on selle mustri levinud ja elegantne variatsioon:

// Using children as a function
<MouseTracker>
  {mousePosition => (
    <p>The current mouse position is ({mousePosition.x}, {mousePosition.y})</p>
  )}
</MouseTracker>

Tehnika 5: Hookid (Kaasaegne ja eelistatud lähenemine)

React 16.8-s tutvustatud Hookid revolutsioneerisid viisi, kuidas me Reacti komponente kirjutame. Need võimaldavad teil kasutada olekut ja muid Reacti funktsioone funktsionaalsetes komponentides. Kõige tähtsam on see, et kohandatud Hookid pakuvad kõige elegantsemat ja otsesemat lahendust olekuga loogika jagamiseks komponentide vahel.

Hookid lahendavad HOC-de ja Render Props'ide probleemid palju puhtamal viisil. Refaktoorime oma MouseTracker näite kohandatud hookiks nimega useMousePosition.

// hooks/useMousePosition.js - A custom 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);
    };
  }, []); // Empty dependency array means this effect runs only once

  return position;
}

// DisplayMousePosition.js - A component using the Hook
import { useMousePosition } from './hooks/useMousePosition';

function DisplayMousePosition() {
  const position = useMousePosition(); // Just call the hook!

  return (
    <p>
      The mouse position is ({position.x}, {position.y})
    </p>
  );
}

// Another component, maybe an interactive element
import { useMousePosition } from './hooks/useMousePosition';

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

  const style = {
    position: 'absolute',
    top: y - 25, // Center the box on the cursor
    left: x - 25,
    width: '50px',
    height: '50px',
    backgroundColor: 'lightblue',
  };

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

See on tohutu edasiminek. Pole 'mähkimise põrgut', pole prop'ide nimede kokkupõrkeid ega keerulisi render prop funktsioone. Loogika on täielikult lahti seotud korduvkasutatavasse funktsiooni (useMousePosition) ja iga komponent saab selle olekuga loogikaga 'haakuda' ühe selge koodireaga. Kohandatud Hookid on kompositsiooni ülim väljendus kaasaegses Reactis, võimaldades teil ehitada oma korduvkasutatavate loogikablokkide teeki.

Kiire võrdlus: Kompositsioon vs. pärilikkus Reactis

Et võtta kokku peamised erinevused Reacti kontekstis, on siin otsene võrdlus:

Aspekt Pärilikkus (Reacti anti-muster) Kompositsioon (Eelistatud Reactis)
Suhe 'on-üks' suhe. Spetsialiseeritud komponent on baaskomponendi versioon. 'omab-üht' või 'kasutab-üht' suhe. Keeruline komponent omab väiksemaid komponente või kasutab jagatud loogikat.
Sidusus Kõrge. Lapskomponendid on tihedalt seotud oma vanema implementatsiooniga. Madal. Komponendid on sõltumatud ja neid saab erinevates kontekstides ilma muudatusteta korduvalt kasutada.
Paindlikkus Madal. Jäigad, klassipõhised hierarhiad muudavad loogika jagamise erinevate komponentide puude vahel keeruliseks. Kõrge. Loogikat ja kasutajaliidest saab kombineerida ja korduvalt kasutada lugematutel viisidel, nagu ehitusplokke.
Koodi korduvkasutatavus Piiratud eelnevalt määratletud hierarhiaga. Saate terve "gorilla", kui soovite ainult "banaani". Suurepärane. Väikeseid, fokusseeritud komponente ja hooke saab kasutada kogu rakenduses.
Reacti idioom Ametliku Reacti meeskonna poolt mittesoovitatav. Soovitatav ja idiomaatiline lähenemine Reacti rakenduste ehitamiseks.

Kokkuvõte: Mõtle kompositsioonipõhiselt

Debatt kompositsiooni ja pärilikkuse vahel on tarkvaradisaini alustala. Kuigi pärilikkusel on oma koht klassikalises OOP-s, muudab kasutajaliidese arenduse dünaamiline, komponendipõhine olemus selle Reacti jaoks halvasti sobivaks. Teek oli põhimõtteliselt disainitud kompositsiooni omaks võtma.

Eelistades kompositsiooni, saate:

Ülemaailmse Reacti arendajana ei tähenda kompositsiooni valdamine ainult parimate tavade järgimist – see tähendab põhilise filosoofia mõistmist, mis muudab Reacti nii võimsaks ja produktiivseks tööriistaks. Alustage väikeste, fokusseeritud komponentide loomisest. Kasutage props.children üldiste konteinerite jaoks ja prop'e spetsialiseerimiseks. Loogika jagamiseks kasutage esmalt kohandatud Hooke. Kompositsioonipõhiselt mõeldes olete heal teel elegantsete, robustsete ja skaleeritavate Reacti rakenduste ehitamiseni, mis peavad ajaproovile vastu.

Reacti komponentide arhitektuur: Miks kompositsioon on pärilikkusest üle | MLOG