Lag et React-program fra bunnen av (Del 7): Konfigurere React og Best Practices

Dette innlegget er en del av en serie med nybegynnerinnlegg som er ment for folk som bruker
ferdige verktøy, maler eller kjeleplater for React, men ønsker å lære og forstå hvordan du bygger en React-applikasjon helt fra begynnelsen.

Alle innlegg i denne serien:
Del 1: Innledning
Del 2: Initialisering og den første filen
Del 3: Bruke ES2015 Syntax
Del 4: Håndheving av en stilguide
Del 5: Sette opp en ekspressserver
Del 6: Bruke en modulbundler
Del 7: Sette opp reaksjon og beste praksis
Del 8: Sette opp Redux
Del 9: Sette opp React Router
Del 10: TDD og Sette opp Jest

Sette opp React

I dette innlegget skal vi konfigurere React og lage en veldig enkel komponent, så vil vi gjennomgå noen av de beste fremgangsmåtene du bør huske på når du utvikler React-komponenter. Det er her den morsomme delen starter, så la oss grave rett inn!

Installer React og React Dom-pakker som avhengighet:

$ npm installasjon - lagre react react dom

Åpne deretter index.js og lag en veldig enkel React-komponent som representerer applikasjonen vår. Vi har allerede et element med id-app i index.pug-malfilen vår, så la oss bruke den til å montere applikasjonen.

/ **
 * index.js
 * /
import React fra 'react';
import {render} fra 'react-dom';
const MainApp = () => (
  

Hallo reaksjon!

);
// gjengi appen
render (, document.getElementById ('app'));

Denne enkle koden lager en statløs funksjonell komponent MainApp og monterer den på et DOM-element som har en ID-app. Denne koden vil ikke fungere umiddelbart, og du vil få en feil hvis du prøver å bygge pakken eller starte serveren.

Årsaken til denne feilen er at vi har JSX-syntaks i index.js-filen vår som Babel ennå ikke forstår. For å tillate Babel å tolke denne syntaks til vanlig JavaScript, kommer vi til å bruke forhåndsinnstillingen React for Babel.

Installer pakken som en avhengighet:

$ npm installasjon - lagre babel-forhåndsinnstilt reaksjon

Legg deretter forhåndsinnstillingen til listen over forhåndsinnstillinger i .babelrc-fil:

{
  "forhåndsinnstillinger": [
    "Es2015",
    "Scenen-0",
    "reagere"
  ]
  "plugins": ["transform-inline-miljø-variabler"]
}

Det bør også være en lo som kan forhindre at du bygger bunten. Linnen klager fordi index.js er en JavaScript-fil som inneholder JSX-syntaks, men bruker js-utvidelsen i stedet for jsx.

Du kan lese beskrivelsen for denne regelen her. Delen 'Når du ikke skal bruke den' sier at du ikke bør bruke denne regelen hvis du ikke bryr deg om å begrense utvidelsen av filene som inneholder JSX-syntaks.

Du kan fortsette å bruke denne regelen, men jeg foretrekker å bruke js-utvidelsen for alle filer, så jeg kommer til å deaktivere denne regelen:

{
  "extends": "airfy",
  "env": {
    "es6": sant,
    "nettleser": sant,
    "node": sant
  }
  "regler": {
    "react / jsx-filnavn-utvidelse": 0
  }
}

Aktiverer HMR

Å aktivere erstatning av moduler er så enkelt som å legge til en blokkkode:

/ **
 * index.js
 * /
import React fra 'react';
import {render} fra 'react-dom';
if (module.hot) {
  module.hot.accept ();
}
const MainApp = () => (
  

Hallo reaksjon!

);
// gjengi appen
render (, document.getElementById ('app'));

Tips og beste praksis

Før vi fortsetter med opplæringen, vil vi gå gjennom en samlet liste over tips og beste praksis som jeg har lært av min erfaring med React, og også fra å lese og søke på nettet. Husk de når du lager React-komponenter.

Avhengighetsimport kontra lokalimport

Skill avhengighetsimporten fra den lokale importen med en ny linje. Avhengighetsimport bør komme først.

import React, {Component} fra 'react';
importer nyttigModule fra 'nyttig-modul';
importer myLocalModule fra './my-local-module';

Statsløse funksjonelle komponenter

Hvis komponenten er en gjengivelseskomponent eller ikke trenger å bruke et tilstandsobjekt, bruker du en vanlig JavaScript-funksjon i stedet for en klasse. Dette kalles en statsløs funksjonell komponent.

Så i stedet for å gjøre dette:

import React, {Component} fra 'react';
klasse MyComponent utvider komponent {
  gjengi () {
    komme tilbake (
      
Hei!     );   } }
eksporter standard MyComponent;

Gjør dette:

import React fra 'react';
const MyComponent = () => 
Hei!
;
eksporter standard MyComponent;

Ser du hvor mye rot som ble fjernet? Du kan også gjøre det enklere ved å eksportere selve funksjonen:

import React fra 'react';
eksport standard () => 
Hei!
;

Jeg foretrekker imidlertid ikke å gjøre dette fordi det vanskeliggjør feilsøking. Hvis du sjekker React Dev Tools, vil du se at komponentnavnet er ‘Ukjent’ fordi funksjonen er anonym.

Anonym funksjonell komponent

En bedre tilnærming ville være å bruke en normal navngitt funksjon i stedet for en anonym funksjon:

import React fra 'react';
eksporter standardfunksjon MyComponent () {
  return 
Hallo!
; }
Navngitt funksjonell komponent

Begynn med presentasjonskomponenter

Presentasjonskomponenter er enklere å definere, lettere å forstå og kan brukes om og om igjen fordi de er uavhengige fra resten av applikasjonen.

Hvis du deler applikasjonen din ned til et sett presentasjonskomponenter, kan du legge dem alle på en enkelt side og finpusse design og variasjoner for å oppnå et enhetlig utseende og preg i hele applikasjonen.

Bygg komponenten din som en presentasjonskomponent, og legg bare til tilstand når du trenger det, noe som bringer oss til neste tips.

Minimer bruken av staten

Bruk tilstand sparsomt i komponentene dine, og sørg for at de bruker tilstand for brukergrensesnitt i stedet for data, med andre ord, hvis du ikke bruker den i render (), bør den ikke være i staten. Husk at du bare skal bruke setState hvis du vil gjengi komponenten din på nytt.

La oss si at vi har en komponent som består av en enkelt knapp. Denne knappen kan bare klikkes en gang, og når den klikkes, logges en melding på konsollen:

import React, {Component} fra 'react';

klasse MyComponent utvider komponent {
  tilstand = {
    clickedOnce: falsk,
  };
  handleClick = () => {
    hvis (! this.state.clickedOnce) {
      console.log ( 'klikket');
    }
    this.setState ({
      clickedOnce: true,
    });
  }
  componentDidUpdate () {
    console.log ( 'Oppdatert!');
  }
  gjengi () {
    komme tilbake (
      
        <-knapp onClick = {dette.handleClick}> Klikk meg            );   } }
eksporter standard MyComponent;

Dette er et eksempel på en dårlig implementering, ved å bruke staten til å sette et flagg klikket på, som spesifiserer om knappen kan klikkes på nytt. Hver gang du klikker på knappen, vil komponenten gjengis på nytt selv om den ikke trenger det.

Programmet gjengis på nytt på knappeklikk

Dette vil være bedre:

import React, {Component} fra 'react';

klasse MyComponent utvider komponent {
  clickedOnce = falsk;
  
  handleClick = () => {
    if (! this.clickedOnce) {
      console.log ( 'klikket');
    }
    this.clickedOnce = true;
  }
  componentDidUpdate () {
    console.log ( 'Oppdatert!');
  }
  gjengi () {
    komme tilbake (
      
        <-knapp onClick = {dette.handleClick}> Klikk meg            );   } }
eksporter standard MyComponent;

Denne implementeringen bruker en klasseegenskap i stedet for en tilstandsnøkkel fordi det clickedOnce-flagget ikke representerer en brukergrensesnittstatus og derfor ikke bør leve i komponenttilstanden. Med denne nye implementeringen utløser ikke lenger en oppdatering ved å klikke på knappen mer enn en gang.

Definer alltid proptyper og defaultProps

Alle komponenter skal ha proptyper og defaultProps definert så høye som mulig i komponenten. De fungerer som komponentdokumentasjon og bør være umiddelbart synlige for andre utviklere som leser filen.

Siden React v15.5 har React.PropTypes flyttet inn i en annen pakke, så la oss installere pakken som en avhengighet:

$ npm installering - lagrer prop-typer

For statsløse funksjonelle komponenter:

Funksjoner heises i JavaScript, noe som betyr at du kan bruke en funksjon før erklæringen:

import React fra 'react';
importer PropTypes fra 'prop-typer';
MyComponent.propTypes = {
  tittel: PropTypes.string,
};
MyComponent.defaultProps = {
  tittel: 'En enkel teller',
};
eksporter standardfunksjon MyComponent (rekvisitter) {
  returner 

{props.title}

; }

ESLint vil klage på å bruke funksjonen før definisjonen, men av hensyn til bedre komponentdokumentasjon, la oss deaktivere denne lofteregelen for funksjoner ved å endre .eslintrc-filen:

{
  ...
  "regler": {
    "react / jsx-filnavn-utvidelse": 0,
    "ingen bruk-før-definere": [
      "feil",
      {
        "funksjoner": falsk
      }
    ]
  }
}

For klassebaserte komponenter:

I motsetning til funksjoner heises ikke klasser i JavaScript, så vi kan ikke bare gjøre MyComponent.propTypes = ... før vi definerer selve klassen, men vi kan definere propTypes og defaultProps som statiske klasseegenskaper:

import React, {Component} fra 'react';
importer PropTypes fra 'prop-typer';
klasse MyComponent utvider komponent {
  statiske propTypes = {
    tittel: PropTypes.string,
  };
  statisk defaultProps = {
    tittel: 'En enkel teller',
  };
  gjengi () {
    returner 

{this.props.title}

;   } }
eksporter standard MyComponent;

Initierer staten

Tilstand kan initialiseres i komponentkonstruktøren:

klasse MyComponent utvider komponent {
  konstruktør (rekvisitter) {
    super (støtter);
    this.state = {
      teller: 0,
    };
  }
}

En bedre måte er å initialisere staten som en klasseiendom:

klasse MyComponent utvider komponent {
  konstruktør (rekvisitter) {
    super (støtter);
  }
  tilstand = {
    teller: 0,
  };
}

Dette ser mye bedre ut, renere, mer leselig og bidrar også til komponentdokumentasjon. Tilstandsobjektet skal initialiseres etter proptyper og defaultProps:

import React, {Component} fra 'react';
importer PropTypes fra 'prop-typer';
klasse MyComponent utvider komponent {
  // propTypes kommer først
  statiske propTypes = {
    tittel: PropTypes.string,
  };
  // defaultProps kommer på andreplass
  statisk defaultProps = {
    tittel: 'En enkel teller',
  };
  // konstruktør kommer hit
  konstruktør () {
    ...
  }
  // så kommer staten
  tilstand = {
    teller: 0,
  };
}

Gi en funksjon til setState

React-dokumentasjon fraråder å stole på this.state og this.roprops verdier for beregning av neste tilstand fordi React oppdaterer dem asynkront. Det betyr at staten ikke kan endre seg umiddelbart etter å ha ringt setState ().

klasse MyComponent utvider komponent {
  tilstand = {
    teller: 10,
  }
  onClick = () => {
    console.log (this.state.count); // 10
    
    // teller vil ikke endres umiddelbart
    this.setState ({count: this.state.count + this.props.step});
    
    console.log (this.state.count); // fremdeles 10
  }
}

Selv om dette fungerer for enkle scenarier, og staten fremdeles vil bli oppdatert riktig, kan det føre til uventet oppførsel i mer komplekse scenarier.

Tenk på dette scenariet, du har en komponent som gjengir en enkelt knapp. Når du klikker på denne knappen, kalles en handleClick-metode:

klasse MyComponent utvider komponent {
  statisk defaultProps = {
    trinn: 5,
  }
  statiske propTypes = {
    trinn: PropTypes.number,
  }
  
  tilstand = {
    teller: 10,
  }
  
  handleClick = () => {
    this.doSomething ();
    this.doSomethingElse ();
  }
  doSomething = () => {
    this.setState ({count: this.state.count + this.props.step});
  }
  doSomethingElse = () => {
    this.setState ({count: this.state.count - 1});
  }
  gjengi () {
    komme tilbake (
      
        

Nåværende antall er: {this.state.count}

        <-knapp onClick = {dette.handleClick}> Klikk meg            );   } }

Knappen ringer håndtereKlikk () når du klikker på den, som igjen ringer doSomething () og deretter doSomethingElse (). Begge funksjonene vil endre telleverdien i staten.

Logisk sett er 10 + 5 15 og trekker deretter fra 1, og resultatet skal være 14, ikke sant? Vel, i dette tilfellet er det ikke - verdien av telling etter det første klikket er 9, ikke 14. Dette skjer fordi verdien av this.state.count fremdeles er 10 når doSomethingElse () kalles, ikke 15.

For å fikse dette kan du bruke en andre form for setState () som godtar en funksjon i stedet for et objekt. Denne funksjonen vil motta den forrige tilstanden som det første argumentet, og rekvisittene på det tidspunktet oppdateringen brukes som det andre argumentet:

this.setState ((prevState, rekvisitter) => ({
  telle: prevState.count + props.step
}))

Vi kan bruke dette skjemaet for å fikse vårt eksempel:

klasse MyComponent utvider komponent {
  ...
  handleClick = () => {
    this.doSomething ();
    this.doSomethingElse ();
  }
  doSomething = () => {
    this.setState ((prevState, rekvisitter) => ({
      telle: prevState.count + props.step
    }));
  }
  doSomethingElse = () => {
    this.setState (prevState => ({
      telle: prevState.count - 1
    }));
  }
  ...
}

Med denne implementeringen oppdateres tellingen riktig fra 10 til 14 til 18 og så videre. Simple Maths er fornuftig igjen!

Bruk pilfunksjoner som klasseegenskaper

Dette nøkkelordet har alltid vært forvirrende for JavaScript-utviklere, og dets oppførsel er ikke mindre forvirrende i React-komponenter. Vet du hvordan dette nøkkelordet endres i en React-komponent? Tenk på følgende eksempel:

import React, {Component} fra 'react';
klasse MyComponent utvider komponent {
  tilstand = {
    teller: 0,
  };
  ved trykk() {
    console.log (this.state);
  }
  gjengi () {
    komme tilbake (
      
        

Antall er: {this.state.count}

         Klikk meg            );   } }
eksporter standard MyComponent;

Å klikke på knappen vil føre til en feil:

Ikke fanget TypeError: Kan ikke lese egenskapen 'tilstand' for udefinert

Dette fordi onClick, som klassemetode, ikke er bundet som standard. Det er noen måter å rette dette på. (ordspill beregnet, får du det til?)

En måte er å binde funksjonen til riktig kontekst når du passerer den i render () -funksjonen:

 Klikk meg 

Eller du kan unngå å endre konteksten ved å bruke en pilfunksjon i render ():

 this.onClick (e)}> Klikk meg 

Imidlertid har disse to metodene en liten ytelseskostnad fordi funksjonen vil bli tildelt på hver gjengivelse. For å unngå denne lette ytelseskostnaden, kan du binde funksjonen inne i konstruktøren:

import React, {Component} fra 'react';
klasse MyComponent utvider komponent {
  konstruktør (rekvisitter) {
    super (støtter);
    this.onClick = this.onClick.bind (dette);
  }
  ...
  gjengi () {
    ...
     Klikk meg 
    ...
  }
}
eksporter standard MyComponent;

Denne teknikken er bedre, men du kan lett bli revet med og ende opp med noe som ser slik ut:

konstruktør (rekvisitter) {
  // dette er ille, virkelig ille
  this.onClick = this.onClick.bind (dette);
  this.onChange = this.onChange.bind (dette);
  this.onSubmit = this.onSubmit.bind (dette);
  this.increaseCount = this.increaseCount.bind (dette);
  this.decreaseCount = this.decreaseCount.bind (dette);
  this.resetCount = this.resetCount.bind (dette);
  ...
}

Siden vi bruker Babel og har støtte for klasseegenskaper, vil den bedre måten være å bruke pilfunksjoner når vi definerer klassemetodene:

klasse MyComponent utvider komponent {
  ...
  onClick = () => {
    // 'dette' er bevart
    console.log (this.state);
  }
  gjengi () {
    komme tilbake (
      
        

{this.state.count}          Klikk meg            );   } }

Ødelegg Props-objektet

Når en komponent har mange rekvisitter, ødelegger du rekvisittene som plasserer hver eiendom på sin egen linje.

For statsløse funksjonelle komponenter:

eksporter standardfunksjon MyComponent ({
  fornavn,
  etternavn,
  epostadresse,
  beskrivelse,
  onChange,
  onsubmit,
}) {
  komme tilbake (
    
      

{fornavn}       ...        ); }

Standardargumenter er ikke en unnskyldning for å slippe defaultProps. Som nevnt tidligere, bør du alltid definere propTypes og defaultProps.

For klassebaserte komponenter:

klasse MyComponent utvider komponent {
  ...
  gjengi () {
    const {
      fornavn,
      etternavn,
      epostadresse,
      beskrivelse,
      onChange,
      onsubmit,
    } = this.props;
    komme tilbake (
      
        

{fornavn}         ...            );   } }

Dette er renere, gjør det lettere å ombestille egenskaper og gjør det lettere å legge til / fjerne egenskaper til / fra listen mens du genererer en lesbar diff for Git. Vurder følgende:

Forskjell er mer lesbar når hver eiendom er på en ny linje

På høyre side kan du enkelt fortelle hvilken eiendom som ble lagt til, men på venstre side vet du bare at noe har endret seg på den linjen, og du må se veldig nøye for å finne ut hvilken del av linjen som var endret.

Betinget gjengivelse

Når du trenger å gjengi en av to komponenter eller blokker med JSX-kode basert på en tilstand, bruker du et ternært uttrykk:

isLoggedIn
  ? 
Velkommen, {brukernavn}!
  : Logg inn

Hvis koden består av mer enn en linje, bruk parenteser:

er logget? (
  
    Velkommen, {brukernavn}!    ): (   <-knappen onClick = {this.login}>     Logg Inn    )

Hvis du trenger å gjengi en enkelt komponent eller en blokk med JSX-kode basert på en tilstand, kan du bruke en kortslutningsevaluering i stedet:

isComplete && 
Du er ferdig!

Bruk parenteser i mer enn én linje:

er ferdig && (
  
    Du er ferdig!    )

Nøkkelattributtet

Det er et vanlig mønster å bruke Array.prototype.map og lignende array-metoder i render () -funksjonen, og det er lett å glemme nøkkelattributtet. Forsoning er vanskelig nok, ikke gjør det vanskeligere. Husk alltid å plassere nøkkelen der den hører til. (ordspill ment igjen, får du det til?)

gjengi () {
  komme tilbake (
    
    {       {         items.map (item => (           
  •             {gjenstandsnavn}                    ))        }        ); }

Når du bruker kart (), filter () eller lignende array-metoder, er den andre parameteren for tilbakeringingen indeksen til elementet. Det er generelt en dårlig idé å bruke denne indeksen som en nøkkel. React bruker nøkkelattributtet for å identifisere hvilke elementer som er endret, har blitt lagt til eller har blitt fjernet, og nøkkelverdien skal være stabil og bør identifisere hvert element unikt.

I tilfeller der matrisen er sortert eller et element legges til i begynnelsen av matrisen, vil indeksen bli endret selv om elementet som representerer indeksen kan være det samme. Dette resulterer i unødvendige gjengivelser og i noen tilfeller resulterer det i feil data.

Bruk UUID eller ShortID for å generere en unik ID for hvert element når det opprettes første gang, og bruk det som nøkkelverdi.

Normaliser staten

Forsøk å normalisere tilstandsobjektet og hold det så flatt som mulig. Hekkingsdata i staten betyr at det er nødvendig med mer kompleks logikk for å oppdatere dem. Tenk hvor stygt det ville være å oppdatere et dypt nestet felt - vent, ikke forestill deg, her:

// statsobjektet
tilstand = {
  ...
  innlegg: [
    ...,
    {
      meta: {
        id: 12,
        forfatter: '...',
        offentlig: falsk,
        ...
      }
      ...
    }
    ...
  ]
  ...
};
// for å oppdatere 'offentlig' til 'sann'
this.setState ({
  ... this.state,
  innlegg: [
    ... this.state.posts.slice (0, indeks),
    {
      ... this.state.posts [index]
      meta: {
        ... this.state.posts [index] .meta,
        offentlig: sant,
      }
    }
    ... this.state.posts.slice (indeks + 1),
  ]
});

Ikke bare er denne koden stygg, men den kan også tvinge ubeslektede komponenter til å gjengi på nytt selv om dataene de viser faktisk ikke har endret seg. Dette fordi vi har oppdatert alle aner i statstreet med nye objektreferanser.

Det ville være enklere å oppdatere det samme feltet hvis vi strukturerer statstreet:

tilstand = {
  innlegg: [
    10,
    11,
    12,
  ]
  meta: {
    10: {
      id: 10,
      forfatter: 'forfatter-a',
      offentlig: falsk,
    }
    11: {
      id: 11,
      forfatter: 'forfatter-b',
      offentlig: falsk,
    }
    12: {
      id: 12,
      forfatter: 'forfatter-c',
      offentlig: falsk,
    }
  }
}
this.setState ({
  meta: {
    ... this.state.meta,
    [id]: {
      ... this.state.meta [id]
      offentlig: sant,
    }
  }
})

Bruk klassenavn

Hvis du noen gang har vært i en situasjon hvor du trenger å bruke et betinget klassesnavn, kan du ha skrevet noe som ser slik ut:

  ...

Dette blir veldig stygt hvis du har flere betingede klassenavn. I stedet for å bruke en ternary, bruk klassenavnpakken:

importer klassenavn fra 'klassenavn';
const Classes = classnames ('tab', {'is-active': isActive});
  ...

Én komponent per fil

Dette er ikke en vanskelig regel, men jeg foretrekker å beholde hver komponent i en enkelt fil og standardeksport av den komponenten. Jeg har ingen spesifikk grunn til dette, men jeg synes det er mer rent og organisert - high five hvis du gjør det også!

Konklusjon

Det er ikke en riktig måte å utvikle en React-komponent på. Vi har gjennomgått noen av de beste fremgangsmåtene og mønstrene som vil hjelpe deg med å utvikle gjenbrukbare og vedlikeholdbare komponenter, og jeg er veldig nysgjerrig på å vite hva slags mønstre og praksis du foretrekker og hvorfor.

I neste del av denne serien skal vi bygge et enkelt gjøremål og bruke noen av disse beste praksis og mønstre.

Var denne artikkelen nyttig? Klikk Clap-knappen nedenfor, eller følg meg for mer.

Takk for at du leste! Hvis du har noen tilbakemeldinger, legg igjen en kommentar nedenfor.

Gå til del 7-b: Bygge en enkel ToDo-app (snart)