Introduktion til React Hooks

Hooks gør det muligt at organisere logik i komponenter, hvilket gør dem små og genanvendelige uden at skrive en klasse. På en måde er de Reacts måde at læne sig ind i funktioner på, fordi vi før dem skulle skrive dem i en komponent, og selvom komponenter har vist sig at være kraftfulde og funktionelle i sig selv, skal de gengive noget på forenden. Det er til en vis grad i orden, men resultatet er en DOM, der er fyldt med div'er, der gør det knudret at grave igennem DevTools og fejlfinde.

Nå, React Hooks ændrer det. I stedet for at stole på top-down flowet af komponenter eller abstrahere komponenter på forskellige måder, som komponenter af højere orden, kan vi kalde og styre flow inde i en komponent. Dan Abramov forklarer det godt i sit Making Sense of React-indlæg:

Hooks anvender React-filosofien (eksplicit dataflow og sammensætning) indvendigt en komponent i stedet for kun mellem komponenterne. Derfor føler jeg, at Hooks passer naturligt til React-komponentmodellen.

I modsætning til mønstre som gengivelsesrekvisitter eller komponenter af højere orden, introducerer Hooks ikke unødvendig indlejring i dit komponenttræ. De lider heller ikke af ulemperne ved mixins.

Resten af ​​Dans indlæg giver en masse nyttig kontekst for, hvorfor React-teamet bevæger sig i denne retning (de er nu tilgængelige i React v16.7.0-alpha) og de forskellige problemer, som hooks er designet til at løse. React-dokumenterne har en introduktion til hooks, der til gengæld indeholder et afsnit om, hvad der motiverede teamet til at lave dem. Vi er mere optaget af, hvordan pokker skal bruge dem, så lad os gå videre til nogle eksempler!

Det vigtige at bemærke, når vi går i gang, er, at der i øjeblikket er ni kroge tilgængelige, men vi vil se på, hvad React-dokumenterne kalder de tre grundlæggende:useState() , useEffect og setContext() . Vi vil grave i hver enkelt i dette indlæg med en oversigt over de avancerede kroge til sidst.

Definition af tilstand med useState()

Hvis du har arbejdet med React på et hvilket som helst niveau, så er du sikkert bekendt med, hvordan tilstand generelt defineres:skriv en klasse og brug this.state for at initialisere en klasse:

class SomeComponent extends React.component {
  constructor(props)
  super(props);
  this.state = {
    name: Barney Stinson // Some property with the default state value    
  }
}

React hooks giver os mulighed for at skrotte alle de klasseting og sætte useState() krog til at bruge i stedet. Noget som dette:

import { useState } from 'react';
    
function SomeComponent() {
  const [name, setName] = useState('Barney Stinson'); // Defines state variable (name) and call (setName) -- both of which can be named anything
}

Hvad siger du?! Det er det! Bemærk, at vi arbejder uden for en klasse. Kroge fungerer ikke i en klasse, fordi de bruges i stedet for dem. Vi bruger krogen direkte i komponenten:

import { useState } from 'react';
    
function SomeComponent() {
  const [name, setName] = useState('Barney Stinson');
  
  return
    <div>
      <p>Howdy, {name}</p>
    </div>
}

Åh, vil du opdatere navnetilstanden? Lad os tilføje en input- og send-knap til outputtet og kalde setName for at opdatere standardnavnet ved indsendelse.

import { useState } from 'react'
    
function SomeComponent() {
  const [input, setValue] = useState("");
  const [name, setName] = useState('Barney Stinson');
  
  handleInput = (event) => {
    setValue(event.target.value);
  }
  
  updateName = (event) => {
    event.preventDefault();
    setName(input);
    setValue("");
  }
  
  return (
    <div>
      <p>Hello, {name}!</p>
      <div>
        <input type="text" value={input} onChange={handleInput} />
        <button onClick={updateName}>Save</button>
      </div>
    </div>
  )
}

Lægger du mærke til noget andet i dette eksempel? Vi konstruerer to forskellige tilstande (input og navn). Det er fordi useState() krog tillader håndtering af flere tilstande i samme komponent! I dette tilfælde input er egenskaben og setValue holder tilstanden for input-elementet, som kaldes af handleInput funktionen udløser derefter updateName funktion, der tager inputværdien og indstiller den som den nye name tilstand.

Opret bivirkninger med useEffect()

Så det er fint at definere og indstille tilstande, men der er en anden krog kaldet useEffect() der kan bruges til – du gættede det – at definere og genbruge effekter direkte i en komponent uden behov for en klasse eller behov for at bruge både redundant kode for hver livscyklus af en metode (dvs. componentDidMount , componentDidUpdate og componentWillUnmount ).

Når vi taler om effekter, refererer vi til ting som API-kald, opdateringer til DOM og begivenhedslyttere, blandt andet. React-dokumentationen nævner eksempler som datahentning, opsætning af abonnementer og ændring af DOM som mulige use cases for denne hook. Måske den største differentiator fra useState() er det useEffect() kører efter rendering. Tænk på det som at give React en instruktion om at holde fast i den funktion, der sendes, og derefter foretage justeringer af DOM'et efter gengivelsen er sket plus eventuelle opdateringer derefter. Igen, React-dokumentationen præciserer det fint:

Som standard kører den både efter den første gengivelse og efter hver opdatering. […] I stedet for at tænke i "montering" og "opdatering", vil du måske finde det lettere at tro, at effekter opstår "efter gengivelse". React garanterer, at DOM er blevet opdateret på det tidspunkt, det kører effekterne.

Lige på, så hvordan kører vi disse effekter? Nå, vi starter med at importere krogen, som vi gjorde for useState() .

import { useEffect } from 'react';

Faktisk kan vi kalde begge useState() og useEffect() i samme import:

import { useState, useEffect } from 'react';

Eller konstruer dem:

const { useState, useEffect } = React;

Så lad os afvige fra vores tidligere navneeksempel ved at tilslutte os en ekstern API, der indeholder brugerdata ved hjælp af aksioer inde i useEffect() hook renderer derefter disse data til en liste over brugere.

Lad os først hente vores hooks og initialisere appen.

const { useState, useEffect } = React

const App = () => {
  // Hooks and render UI
}

Lad os nu sætte useState() for at definere users som en variabel, der indeholder tilstanden setUsers som vi videregiver brugerdataene til, når de er blevet hentet, så de er klar til gengivelse.

const { useState, useEffect } = React

const App = () => {
  const [users, setUsers] = useState([]);
  // Our effects come next
}

Her er useEffect() kommer i spil. Vi skal bruge den til at oprette forbindelse til en API og hente data fra den, og derefter kortlægge disse data til variabler, vi kan kalde på gengivelse.

const { useState, useEffect } = React

const App = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // Connect to the Random User API using axios
    axios("https://randomuser.me/api/?results=10")
      // Once we get a response, fetch name, username, email and image data
      // and map them to defined variables we can use later.
      .then(response =>
        response.data.results.map(user => ({
          name: `{user.name.first} ${user.name.last}`,
          username: `{user.login.username}`,
          email: `{user.email}`,
          image: `{user.picture.thumbnail}`
        }))
      )
      // Finally, update the `setUsers` state with the fetched data
      // so it stores it for use on render
      .then(data => {
        setUsers(data);
      });
  }, []);
  
  // The UI to render
}

OK, lad os nu gengive vores komponent!

const { useState, useEffect } = React

const App = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    axios("https://randomuser.me/api/?results=10")
      .then(response =>
        response.data.results.map(user => ({
          name: `{user.name.first} ${user.name.last}`,
          username: `{user.login.username}`,
          email: `{user.email}`,
          image: `{user.picture.thumbnail}`
        }))
      )
      .then(data => {
        setUsers(data);
      });
  }, []);
  
  return (
    <div className="users">
      {users.map(user => (
        <div key={user.username} className="users__user">
          <img src={user.image} className="users__avatar" />
          <div className="users__meta">
            <h1>{user.name}</h1>
            <p>{user.email}</p>
          </div>
        </div>
      ))}
    </div>
  )
}

Her er, hvad det giver os:

Det er værd at bemærke, at useEffect() er i stand til så, så, så meget mere, som at kæde effekter og udløse dem på betingelse. Derudover er der tilfælde, hvor vi er nødt til at rydde op efter en effekt er kørt - som at abonnere på en ekstern ressource - for at forhindre hukommelseslækager. Fuldstændig værd at gennemgå den detaljerede forklaring af effekter med oprydning i React-dokumentationen.

Kontekst og useContext()

Context in React gør det muligt at overføre rekvisitter fra en overordnet komponent til en underordnet komponent. Dette sparer dig for besværet med propboring. Du kunne dog kun gøre brug af kontekst i klassekomponenter, men nu kan du gøre brug af kontekst i funktionelle komponenter ved hjælp af useContext() . Lad os oprette et tællereksempel, vi vil videregive tilstanden og funktionerne, som vil blive brugt til at øge eller mindske antallet fra den overordnede komponent til den underordnede komponent ved hjælp af useContext() . Lad os først skabe vores kontekst:

const CountContext = React.createContext();

Vi erklærer tælletilstanden og øger/sænker metoderne for vores tæller i vores app-komponent og opsætter den indpakning, der skal indeholde komponenten. Vi sætter kontekstkrogen i brug i selve tællerkomponenten om lidt.

const App = () => {
  // Use `useState()` to define a count variable and its state
  const [count, setCount] = useState(0);
  
  // Construct a method that increases the current `setCount` variable state by 1 with each click
  const increase = () => {
    setCount(count + 1);
  };
  
  // Construct a method that decreases the current `setCount` variable state by 1 with each click.
  const decrease = () => {
    setCount(count - 1);
  };

  // Create a wrapper for the counter component that contains the provider that will supply the context value.
  return (
    <div>
      <CountContext.Provider
        // The value is takes the count value and updates when either the increase or decrease methods are triggered.
        value={{ count, increase, decrease }}
      >
        // Call the Counter component we will create next
        <Counter />
      </CountContext.Provider>
    </div>
  );
};

Okay, til tællerkomponenten! useContext() accepterer et objekt (vi sender CountContext ind udbyder) og giver os mulighed for at fortælle React præcis, hvilken værdi vi ønsker (`tæller), og hvilke metoder der udløser opdaterede værdier (increase og decrease ). Så runder vi selvfølgelig tingene af ved at gengive komponenten, som kaldes af appen.

const Counter = () => {
  const { count, increase, decrease } = useContext(CountContext);
  return (
    <div className="counter">
      <button onClick={decrease}>-</button>
      <span className="count">{count}</span>
      <button onClick={increase}>+</button>
    </div>
  );
};

Og voilà ! Se vores mægtige tæller med tælleren drevet af kontekstobjekter og værdier.

Afslutning

Vi har blot ridset overfladen af, hvad React hooks er i stand til at gøre, men forhåbentlig giver dette dig et solidt fundament. For eksempel er der endnu mere avancerede kroge, der er tilgængelige ud over de grundlæggende, vi dækkede i dette indlæg. Her er en liste over disse kroge med de beskrivelser, der tilbydes af dokumentationen, så du kan nå et niveau, nu hvor du er udstyret med det grundlæggende:

Hook Beskrivelse
userReducer() Et alternativ til useState . Accepterer en reducering af typen (state, action) => newState , og returnerer den aktuelle tilstand parret med en dispatch metode.
useCallback() Returnerer et husket tilbagekald. Send et inline-tilbagekald og en række input. useCallback vil returnere en husket version af tilbagekaldet, der kun ændres, hvis et af inputs er ændret.
useMemo() Returnerer en husket værdi. Bestå en "opret" funktion og en række input. useMemo vil kun genberegne den gemte værdi, når en af ​​inputs er ændret.
useRef() useRef returnerer et foranderligt ref-objekt, hvis .current egenskaben initialiseres til det beståede argument (initialValue ). Det returnerede objekt vil bestå i hele komponentens levetid.
useImperativeMethods useImperativeMethods tilpasser den instansværdi, der eksponeres for overordnede komponenter, når du bruger ref . Som altid bør imperativ kode ved hjælp af refs undgås i de fleste tilfælde. useImperativeMethods skal bruges med forwardRef .
useLayoutEffect Signaturen er identisk med useEffect , men den udløses synkront efter alle DOM-mutationer. Brug dette til at læse layout fra DOM og synkront gengive. Opdateringer planlagt i useLayoutEffect vil blive skyllet synkront, før browseren har mulighed for at male.