Latviešu

Padziļināts ieskats React komponentu arhitektūrā, salīdzinot kompozīciju un mantošanu. Uzziniet, kāpēc React dod priekšroku kompozīcijai, un apgūstiet tādus modeļus kā HOC, Render Props un Hooks, lai veidotu mērogojamus, atkārtoti lietojamus komponentus.

React Komponentu Arhitektūra: Kāpēc Kompozīcija Pārspēj Mantošanu

Programmatūras izstrādes pasaulē arhitektūrai ir milzīga nozīme. Veids, kā mēs strukturējam savu kodu, nosaka tā mērogojamību, uzturamību un atkārtotu lietojamību. Izstrādātājiem, kas strādā ar React, viens no fundamentālākajiem arhitektūras lēmumiem ir saistīts ar to, kā koplietot loģiku un lietotāja saskarni (UI) starp komponentiem. Tas mūs noved pie klasiskām debatēm objektorientētajā programmēšanā, kas pārdomātas React komponentu balstītajai pasaulei: Kompozīcija pret Mantošanu.

Ja jums ir pieredze ar klasiskām objektorientētām valodām, piemēram, Java vai C++, mantošana varētu šķist dabiska pirmā izvēle. Tas ir spēcīgs jēdziens, lai izveidotu 'ir-a' (is-a) attiecības. Tomēr oficiālā React dokumentācija sniedz skaidru un stingru ieteikumu: "Facebook mēs izmantojam React tūkstošiem komponentu, un mēs neesam atraduši nevienu gadījumu, kurā mēs ieteiktu veidot komponentu mantošanas hierarhijas."

Šis raksts sniegs visaptverošu šīs arhitektūras izvēles izpēti. Mēs analizēsim, ko mantošana un kompozīcija nozīmē React kontekstā, parādīsim, kāpēc kompozīcija ir idiomatiskā un pārākā pieeja, un izpētīsim spēcīgos modeļus — no augstākās kārtas komponentiem līdz mūsdienīgiem Hooks —, kas padara kompozīciju par izstrādātāja labāko draugu, veidojot stabilas un elastīgas lietojumprogrammas globālai auditorijai.

Izpratne par veco gvardi: Kas ir mantošana?

Mantošana ir viens no galvenajiem objektorientētās programmēšanas (OOP) pīlāriem. Tā ļauj jaunai klasei (apakšklasei jeb bērnam) iegūt esošās klases (superklases jeb vecāka) īpašības un metodes. Tas rada cieši saistītas 'ir-a' attiecības. Piemēram, ZeltaRetrīvers ir Suns, kas ir Dzīvnieks.

Mantošana ārpus React konteksta

Apskatīsim vienkāršu JavaScript klašu piemēru, lai nostiprinātu šo jēdzienu:

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!"

Šajā modelī Dog klase automātiski saņem name īpašību un speak metodi no Animal. Tā var arī pievienot savas metodes (fetch) un pārrakstīt esošās. Tas rada stingru hierarhiju.

Kāpēc mantošana React vidē nedarbojas labi

Lai gan šis 'ir-a' modelis der dažām datu struktūrām, tas rada būtiskas problēmas, ja to piemēro UI komponentiem React vidē:

Šo problēmu dēļ React komanda izstrādāja bibliotēku, balstoties uz elastīgāku un spēcīgāku paradigmu: kompozīciju.

React pieejas pieņemšana: Kompozīcijas spēks

Kompozīcija ir dizaina princips, kas dod priekšroku 'satur-a' (has-a) vai 'izmanto-a' (uses-a) attiecībām. Tā vietā, lai komponents būtu cits komponents, tam pieder citi komponenti vai tas izmanto to funkcionalitāti. Komponenti tiek uzskatīti par būvblokiem — kā LEGO klucīši —, kurus var dažādos veidos kombinēt, lai izveidotu sarežģītus UI, neesot ieslēgtiem stingrā hierarhijā.

React kompozīcijas modelis ir neticami daudzpusīgs, un tas izpaužas vairākos galvenajos modeļos. Izpētīsim tos, sākot no visvienkāršākajiem līdz vismodernākajiem un spēcīgākajiem.

1. tehnika: Ietveršana ar `props.children`

Vistiešākā kompozīcijas forma ir ietveršana. Tas ir gadījums, kad komponents darbojas kā vispārīgs konteiners vai 'kaste', un tā saturs tiek nodots no vecākkomponenta. React šim nolūkam ir īpašs, iebūvēts prop: props.children.

Iedomājieties, ka jums ir nepieciešams `Card` komponents, kas jebkuru saturu var ietīt konsekventā rāmī ar ēnu. Tā vietā, lai ar mantošanu veidotu `TextCard`, `ImageCard` un `ProfileCard` variantus, jūs izveidojat vienu vispārīgu `Card` komponentu.

// Card.js - Vispārīgs konteinera komponents
function Card(props) {
  return (
    <div className="card">
      {props.children}
    </div>
  );
}

// App.js - Card komponenta izmantošana
function App() {
  return (
    <div>
      <Card>
        <h1>Laipni lūdzam!</h1>
        <p>Šis saturs atrodas Card komponentā.</p>
      </Card>

      <Card>
        <img src="/path/to/image.jpg" alt="An example image" />
        <p>Šī ir attēla kartīte.</p>
      </Card>
    </div>
  );
}

Šeit Card komponents nezina un tam nerūp, ko tas satur. Tas vienkārši nodrošina ietverošo stilu. Saturs starp atverošo un aizverošo <Card> tagu automātiski tiek nodots kā props.children. Tas ir skaists atsaistes un atkārtotas lietojamības piemērs.

2. tehnika: Specializācija ar Props

Dažreiz komponentam ir nepieciešami vairāki 'caurumi', kurus aizpilda citi komponenti. Lai gan jūs varētu izmantot `props.children`, skaidrāks un strukturētāks veids ir nodot komponentus kā parastus props. Šo modeli bieži sauc par specializāciju.

Apsveriet `Modal` komponentu. Modālajam logam parasti ir virsraksta sadaļa, satura sadaļa un darbību sadaļa (ar pogām, piemēram, "Apstiprināt" vai "Atcelt"). Mēs varam izveidot savu `Modal` tā, lai tas pieņemtu šīs sadaļas kā props.

// Modal.js - Specializētāks konteiners
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 - Modal izmantošana ar konkrētiem komponentiem
function App() {
  const confirmationTitle = <h2>Apstiprināt darbību</h2>;
  const confirmationBody = <p>Vai esat pārliecināts, ka vēlaties turpināt šo darbību?</p>;
  const confirmationActions = (
    <div>
      <button>Apstiprināt</button>
      <button>Atcelt</button>
    </div>
  );

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

Šajā piemērā Modal ir ļoti atkārtoti lietojams izkārtojuma komponents. Mēs to specializējam, nododot konkrētus JSX elementus tā `title`, `body` un `actions` props. Tas ir daudz elastīgāk nekā veidot `ConfirmationModal` un `WarningModal` apakšklases. Mēs vienkārši sastādām `Modal` ar dažādu saturu pēc vajadzības.

3. tehnika: Augstākās kārtas komponenti (HOCs)

Lai koplietotu ne-UI loģiku, piemēram, datu ielādi, autentifikāciju vai žurnalēšanu, React izstrādātāji vēsturiski izmantoja modeli, ko sauc par augstākās kārtas komponentiem (HOCs). Lai gan mūsdienu React tos lielā mērā ir aizstājuši Hooks, ir svarīgi tos saprast, jo tie pārstāv galveno evolūcijas soli React kompozīcijas stāstā un joprojām pastāv daudzās kodu bāzēs.

HOC ir funkcija, kas pieņem komponentu kā argumentu un atgriež jaunu, uzlabotu komponentu.

Izveidosim HOC ar nosaukumu `withLogger`, kas reģistrē komponenta props katru reizi, kad tas tiek atjaunināts. Tas ir noderīgi atkļūdošanai.

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

function withLogger(WrappedComponent) {
  // Tas atgriež jaunu komponentu...
  return function EnhancedComponent(props) {
    useEffect(() => {
      console.log('Component updated with new props:', props);
    }, [props]);

    // ... kas renderē oriģinālo komponentu ar oriģinālajiem props.
    return <WrappedComponent {...props} />;
  };
}

// MyComponent.js - Komponents, kuru uzlabot
function MyComponent({ name, age }) {
  return (
    <div>
      <h1>Sveiki, {name}!</h1>
      <p>Jums ir {age} gadi.</p>
    </div>
  );
}

// Uzlabotā komponenta eksportēšana
export default withLogger(MyComponent);

Funkcija `withLogger` ietin `MyComponent`, piešķirot tam jaunas žurnalēšanas iespējas, nemainot `MyComponent` iekšējo kodu. Mēs varētu piemērot šo pašu HOC jebkuram citam komponentam, lai piešķirtu tam to pašu žurnalēšanas funkciju.

Izaicinājumi ar HOCs:

4. tehnika: Render Props

Render Prop modelis parādījās kā risinājums dažiem HOC trūkumiem. Tas piedāvā skaidrāku loģikas koplietošanas veidu.

Komponents ar render prop pieņem funkciju kā prop (parasti nosauktu par `render`) un izsauc šo funkciju, lai noteiktu, ko renderēt, nododot tai kā argumentus jebkādu stāvokli vai loģiku.

Izveidosim `MouseTracker` komponentu, kas seko peles X un Y koordinātām un padara tās pieejamas jebkuram komponentam, kas vēlas tās izmantot.

// MouseTracker.js - Komponents ar 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);
    };
  }, []);

  // Izsaucam render funkciju ar stāvokli
  return render(position);
}

// App.js - MouseTracker izmantošana
function App() {
  return (
    <div>
      <h1>Kustiniet peli!</h1>
      <MouseTracker
        render={mousePosition => (
          <p>Pašreizējā peles pozīcija ir ({mousePosition.x}, {mousePosition.y})</p>
        )}
      />
    </div>
  );
}

Šeit `MouseTracker` ietver visu loģiku peles kustības izsekošanai. Tas pats par sevi neko nerenderē. Tā vietā tas deleģē renderēšanas loģiku savam `render` prop. Tas ir skaidrāk nekā HOC, jo jūs varat redzēt tieši no kurienes nāk `mousePosition` dati tieši JSX iekšienē.

`children` prop var izmantot arī kā funkciju, kas ir izplatīta un eleganta šī modeļa variācija:

// Izmantojot children kā funkciju
<MouseTracker>
  {mousePosition => (
    <p>Pašreizējā peles pozīcija ir ({mousePosition.x}, {mousePosition.y})</p>
  )}
</MouseTracker>

5. tehnika: Hooks (Mūsdienīgā un ieteicamā pieeja)

Ieviests React 16.8, Hooks radikāli mainīja veidu, kā mēs rakstām React komponentus. Tie ļauj izmantot stāvokli un citas React funkcijas funkcionālajos komponentos. Vissvarīgākais ir tas, ka pielāgotie Hooks nodrošina elegantāko un tiešāko risinājumu stāvokļa loģikas koplietošanai starp komponentiem.

Hooks atrisina HOC un Render Props problēmas daudz tīrākā veidā. Pārveidosim mūsu `MouseTracker` piemēru par pielāgotu hook, ko sauc par `useMousePosition`.

// hooks/useMousePosition.js - Pielāgots 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);
    };
  }, []); // Tukšs atkarību masīvs nozīmē, ka šis efekts tiek izpildīts tikai vienu reizi

  return position;
}

// DisplayMousePosition.js - Komponents, kas izmanto Hook
import { useMousePosition } from './hooks/useMousePosition';

function DisplayMousePosition() {
  const position = useMousePosition(); // Vienkārši izsauciet hook!

  return (
    <p>
      Peles pozīcija ir ({position.x}, {position.y})
    </p>
  );
}

// Cits komponents, varbūt interaktīvs elements
import { useMousePosition } from './hooks/useMousePosition';

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

  const style = {
    position: 'absolute',
    top: y - 25, // Centrē kasti uz kursora
    left: x - 25,
    width: '50px',
    height: '50px',
    backgroundColor: 'lightblue',
  };

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

Tas ir milzīgs uzlabojums. Nav 'ietīšanas elles', nav prop nosaukumu sadursmju un nav sarežģītu render prop funkciju. Loģika ir pilnībā atsaistīta atkārtoti lietojamā funkcijā (`useMousePosition`), un jebkurš komponents var 'pieķerties' šai stāvokļa loģikai ar vienu, skaidru koda rindiņu. Pielāgotie Hooks ir kompozīcijas galvenā izpausme mūsdienu React, ļaujot jums izveidot savu atkārtoti lietojamu loģikas bloku bibliotēku.

Ātrs salīdzinājums: Kompozīcija pret Mantošanu React vidē

Lai apkopotu galvenās atšķirības React kontekstā, šeit ir tiešs salīdzinājums:

Aspekts Mantošana (Anti-modelis React vidē) Kompozīcija (Ieteicamā pieeja React vidē)
Attiecības 'ir-a' (is-a) attiecības. Specializēts komponents ir bāzes komponenta versija. 'satur-a' (has-a) vai 'izmanto-a' (uses-a) attiecības. Sarežģītam komponentam pieder mazāki komponenti vai tas izmanto kopīgu loģiku.
Sasaiste Augsta. Bērnu komponenti ir cieši saistīti ar to vecāka implementāciju. Zema. Komponenti ir neatkarīgi un tos var atkārtoti izmantot dažādos kontekstos bez modifikācijām.
Elastība Zema. Stingras, uz klasēm balstītas hierarhijas apgrūtina loģikas koplietošanu starp dažādiem komponentu kokiem. Augsta. Loģiku un UI var kombinēt un atkārtoti izmantot neskaitāmos veidos, kā būvblokus.
Koda atkārtota lietojamība Ierobežota ar iepriekš definētu hierarhiju. Jūs saņemat visu "gorillu", kad vēlaties tikai "banānu". Lieliska. Mazus, fokusētus komponentus un hooks var izmantot visā lietojumprogrammā.
React idioma Oficiālā React komanda to neiesaka. Ieteicamā un idiomatiskā pieeja React lietojumprogrammu veidošanai.

Noslēgums: Domājiet kompozīcijā

Debates par kompozīciju un mantošanu ir fundamentāls temats programmatūras dizainā. Lai gan mantošanai ir sava vieta klasiskajā OOP, UI izstrādes dinamiskā, uz komponentiem balstītā daba padara to par sliktu izvēli React videi. Bibliotēka tika fundamentāli izstrādāta, lai pieņemtu kompozīciju.

Dodot priekšroku kompozīcijai, jūs iegūstat:

Kā globālam React izstrādātājam kompozīcijas apgūšana nav tikai labāko prakšu ievērošana — tā ir izpratne par galveno filozofiju, kas padara React par tik spēcīgu un produktīvu rīku. Sāciet, veidojot mazus, fokusētus komponentus. Izmantojiet `props.children` vispārīgiem konteineriem un props specializācijai. Loģikas koplietošanai vispirms izmantojiet pielāgotos Hooks. Domājot kompozīcijā, jūs būsiet ceļā uz elegantu, stabilu un mērogojamu React lietojumprogrammu veidošanu, kas izturēs laika pārbaudi.