Den beste måten å binde hendelseshåndtere i React på

Bilde: Flickr by liz west

Bindende hendelsesbehandlere i React kan være vanskelig (du har JavaScript å takke for det). For de som kjenner historien til Perl og Python, bør TMTOWTDI (There’s More Than One Way To Do It) og TOOWTDI (There’s Only One Way To Do It) være kjente ord. Dessverre er JavaScript i det minste for hendelsesbinding et TMTOWTDI-språk, som alltid gjør utviklere forvirret.

I dette innlegget skal vi utforske de vanlige måtene å lage hendelsesbindinger i React, og jeg vil vise deg deres fordeler og ulemper. Og viktigst av alt, vil jeg hjelpe deg med å finne den “eneste ene veien” - eller i det minste favoritten min.

Dette innlegget forutsetter at du forstår nødvendigheten av binding, for eksempel hvorfor vi trenger å gjøre dette.handler.bind (dette), eller forskjellen mellom funksjon () {console.log (dette); } og () => {console.log (dette); }. Hvis du blir forvirret over disse spørsmålene, hadde Saurabh Misra et fantastisk innlegg som forklarte dem.

Dynamisk innbinding i gjengivelse ()

Det første tilfellet som ofte brukes er å ringe .bind (dette) i render () -funksjonen. For eksempel:

klasse HelloWorld utvider komponent {
  handleClick (event) {}
  gjengi () {
    komme tilbake (
      

Hallo, {this.state.name}!

      <-knapp onClick = {dette.handleClick.bind (dette)}> Klikk på     );   } }

Selvfølgelig vil dette fungere. Men tenk på en ting: Hva skjer hvis this.state.namechanges?

Du kan si at å endre this.state.name vil føre til at komponenten gjengis på nytt (). God. Komponenten vil gjengis for å oppdatere navnedelen. Men blir knappen gjengitt?

Tenk på det faktum at React bruker den virtuelle DOM. Når gjengivelse oppstår, vil den sammenligne det oppdaterte virtuelle DOM med det forrige virtuelle DOM, og deretter bare oppdatere de endrede elementene til det faktiske DOM-treet.

I vårt tilfelle, når render () kalles, vil dette.handleClick.bind (dette) også bli kalt for å binde behandleren. Denne samtalen vil generere en helt ny behandler, som er helt annerledes enn den behandleren som ble brukt da render () ble kalt første gang!

Virtuell DOM for dynamisk binding. Elementer i blått blir gjengitt på nytt.

Som i diagrammet ovenfor, når render () ble kalt tidligere, returnerte this.handleClick.bind (dette) funcA slik at React visste atChange var funcA.

Senere, når render () blir kalt igjen, returnerte this.handleClick.bind (dette) funcB (merk at det returnerer en ny funksjon hver gang den blir kalt). På denne måten vet React at onChange ikke lenger er funcA, noe som betyr at knappen må gjengis på nytt.

Én knapp er kanskje ikke noe problem. Men hva hvis du har 100 knapper gjengitt på en liste?

gjengi () {
  komme tilbake (
    {this.state.buttons.map (btn => (
      
        {} Btn.label
      
    ))}
  );
}

I eksemplet over vil enhver knappetikettendring føre til at alle knappene blir gjengitt på nytt, siden alle knapper vil generere en ny onChange-behandler.

Bind i konstruktør ()

En gammel skolevei er å gjøre bindingen i konstruktøren. Ikke noe spesielt:

klasse HelloWorld utvider komponent {
  konstruktør () {
    this.handleClick = this.handleClickFunc.bind (dette);
  }
  gjengi () {
    return ();
  }
}

Denne måten er mye bedre enn den forrige. Samtale gjengivelse () vil ikke generere en ny behandler for onClick, så vil ikke bli gjengitt på nytt så lenge knappen ikke endres.

Virtuell DOM for innbinding i konstruktør. Elementer i blått blir gjengitt på nytt.

Bind med pilfunksjonen

Med ES7-klasseegenskaper (som for øyeblikket støttes med Babel), kan vi gjøre bindinger etter metodedefinisjonen:

klasse HelloWorld utvider komponent {
  handleClick = (hendelse) => {
    console.log (this.state.name);
  }
  gjengi () {
    return ()
  }
}

I koden ovenfor er handleClick en oppgave som tilsvarer:

konstruktør () {
  this.handleClick = (event) => {...} ;
}

Så når komponenten er initialisert, vil dette.handleClick aldri endre seg igjen. På denne måten sikrer det at ikke blir gjengitt på nytt. Denne tilnærmingen er sannsynligvis den beste måten å gjøre bindinger på. Det er enkelt, lett å lese, og viktigst av alt, det fungerer.

Dynamisk binding med pilfunksjonen for flere elementer

Ved å bruke det samme pilfunksjonstriket, kan vi bruke den samme behandleren for flere innganger:

klasse HelloWorld utvider komponent {
  handleChange = name => event => {
    this.setState ({[name]: event.target.value});
  }
  gjengi () {
    komme tilbake (
      
      
    )
  }
}

Ved første øyekast ser dette ganske fantastisk ut på grunn av dets enkelhet. Hvis du vurderer nøye, vil du oppdage at det har samme problem som den første tilnærmingen: hver gang render () kalles både vil bli gjengitt på nytt.

Jeg synes faktisk denne tilnærmingen er smart, og jeg ønsker ikke å skrive flere håndtakXXXRange for hvert felt heller. Heldigvis er det mindre sannsynlig at denne typen "flerbrukshåndterer" vises på listen. Dette betyr at det bare vil være et par komponenter som blir gjengitt på nytt, og det vil sannsynligvis ikke være et ytelsesproblem.

Uansett er fordelene det gir oss mye større enn ytelsestapet. Derfor vil jeg foreslå at du bruker denne tilnærmingen direkte.

I tilfelle disse ytelsesproblemene blir viktige, vil jeg foreslå å cache behandlerne når du utfører bindingene (men dette vil gjøre koden mindre lesbar):

klasse HelloWorld utvider komponent {
  handleChange = name => {
    if (! this.handlers [name]) {
      this.handlers [name] = event => {
        this.setState ({[name]: event.target.value});
      };
    }
    returner dette. håndtere [navn];
  }
  gjengi () {
    komme tilbake (
      
      
    )
  }
}

Konklusjon

Når vi utfører hendelsesbindinger i React, må vi sjekke nøye om behandlerne genereres dynamisk. Vanligvis er dette ikke et problem når de berørte komponentene bare vises en eller to ganger. Men når hendelsesbehandlere vises på en liste, kan dette føre til alvorlige prestasjonsproblemer.

Solutions

  • Bruk bindingsfunksjon når det er mulig
  • Hvis du må generere bindinger dynamisk, kan du vurdere å lagre behandlerne hvis bindingene blir et ytelsesproblem

Takk for at du leste! Jeg håper dette innlegget var nyttig. Hvis du synes dette innlegget er nyttig, kan du dele det med flere mennesker ved å anbefale det.

Oppdater:

Omri Luzon og Shesh nevnte lodash-dekoratører og reaktive autobindpakker for mer praktiske bindinger. Personlig er jeg ikke en stor tilhenger av automatisk å gjøre noe (jeg prøver alltid å holde ting som bindinger minimalt), men auto bind er absolutt en fin måte å skrive ren kode på og spare mer krefter. Koden vil være som:

importer autoBind fra 'react-autobind';
klasse HelloWorld () {
  konstruktør () {
    automatiske bindingen (this);
  }
  handleClick () {
    ...
  }
  gjengi () {
    return ();
  }
}

Siden autoBind vil håndtere bindingene automatisk, er det ikke nødvendig å bruke pilfunksjonstrik (handleClick = () => {}) for å gjøre bindingen, og i render () -funksjonen kan denne.handleClick brukes direkte.