Valtionhallinta Reactissa: Käytännön esimerkki

reagoida
tilanhallinta
konteksti api
Valtionhallinta Reactissa: Käytännön esimerkki cover image

React on monille kehittäjille tarkoitettu kehys dynaamisten asiakaspuolen sovellusten rakentamiseen. Näiden sovellusten dynaaminen luonne johtuu joustavuudesta ja laajennetusta luettelosta ominaisuuksista ja ominaisuuksista, jotka ovat mahdollisia asiakaspuolella, minkä ansiosta kehittäjät pystyivät rakentamaan täysimittaisia ​​sovelluksia, jotka latautuvat selaimeen muutamassa sekunnissa, mikä ei ollut onnistunutta. mahdollista (tai erittäin hankalaa) staattisen verkon päivinä.

Tämän mahdollisuuksien laajenemisen myötä syntyi hallinnan käsite, sillä asiakaspuolen sovellusten monimutkaisuuden kasvaessa paikallisen tilan pitämisen tarve kasvaa pullonkaulaksi, jos sitä ei käsitellä oikein ja skaalautuvuutta ajatellen.

Tätä kysymystä käsiteltiin monilla viitekehyksellä, jotka noudattivat erilaisia ​​lähestymistapoja ja keskittyvät erilaisiin osaongelmiin, minkä vuoksi on tärkeää, että sinulla on korkeatasoinen ymmärrys valitun viitekehyksen ekosysteemistä, jotta voidaan arvioida kunkin sovelluksen tarpeita ja käyttää oikeaa lähestymistapaa näiden vaatimusten mukaisesti. mittareita. Tämä artikkeli antaa sinulle lyhyen yleiskatsauksen yleisistä tilanhallintaongelmista ja yrittää ottaa käyttöön erilaisia ​​lähestymistapoja (useState, Context API) vastauksena siihen. Vaikka tässä artikkelissa esitellään useita ratkaisuja, se keskittyy vain pienemmän mittakaavan haasteisiin. Tulevissa artikkeleissa käsitellään edistyneempiä aiheita.

Todennuksen työnkulku

Artikkelissa esitelty koodi löytyy täältä.

Suoraan esikatselulinkkiin pääsee täältä.

Harkitse tapausta, jossa toteutamme todennusprosessin React-sovellukselle.

User Login

Kuten yllä olevasta GIF-kuvasta näkyy, haluamme antaa käyttäjien kirjautua sisään tai kirjautua sovellukseemme käyttämällä tunnistetietoja. Jos kelvolliset tunnistetiedot on annettu, käyttäjä kirjautuu sisään, sovellus siirtyy automaattisesti kotisivulle ja käyttäjä voi jatkaa sovelluksen käyttöä.

Vastaavasti jos käyttäjä kirjautuu ulos, kotisivun resurssit suojataan sisäänkirjautumisen takana, kirjautumissivu on ainoa sivu, johon käyttäjä pääsee.

Kun ajatellaan tätä työnkulkua toteutuksen kannalta, meillä olisi pääkomponentti nimeltä App. Sovelluskomponentti ohjaa käyttäjän jommallekummalle kahdesta sivusta: Etusivu tai Kirjaudu, käyttäjän nykyinen tila (kirjautunut sisään, uloskirjautunut) määrää, mikä sivu -sivulle, jolle käyttäjä uudelleenohjataan, käyttäjän nykyisen tilan muutoksen (esimerkiksi vaihtamisen kirjautuneesta uloskirjautuneeksi) pitäisi laukaista välitön uudelleenohjaus vastaavalle sivulle.

State

Kuten yllä olevasta kuvasta näkyy, haluamme, että sovelluskomponentti ottaa huomioon nykyisen tilan ja hahmontaa vain toisen kahdesta sivusta – aloitussivun tai kirjautumissivun – tämän nykyisen tilan perusteella.

Jos käyttäjä on tyhjä, se tarkoittaa, että meillä ei ole todennettua käyttäjää, joten siirrymme kirjautumissivulle automaattisesti ja suojaamme kotisivun ehdollisen hahmontamisen avulla. Jos käyttäjä on olemassa, teemme päinvastoin.

Nyt kun meillä on vankka käsitys siitä, mitä pitäisi toteuttaa, tutkitaan muutamia vaihtoehtoja, mutta määritetään ensin React-projektimme,

Projektirepo sisältää esimerkkitaustasovelluksen, jota käytämme käyttöliittymäpuolen toteuttamiseen (emme mene siihen koska se ei ole pääpaino tässä, mutta koodi on tarkoituksella pidetty yksinkertaisena, jotta sen kanssa ei olisi vaikeaa )

Aloitamme luomalla seuraavat sivut ja komponentit:

  • Kotisivu

  • Sisäänkirjautumissivu

  • Muokkaa käyttäjän sivua

  • Navbar-komponentti

  • UserDropdown-komponentti

React-sovellus, jossa on useita sivuja, tarvitsee kunnollisen navigoinnin, jota varten voimme käyttää react-router-domia globaalin selaimen reititinkontekstin luomiseen ja eri React-reittien rekisteröimiseen.


yarn add react-router-dom

Tiedämme, että monet lukijat haluavat seurata opetusohjelmia, joten tässä on aloitusmalli, jonka avulla pääset vauhtiin. Tämä aloitushaara käyttää DaisyUI:ta ennalta määritetyille TailwindCSS JSX -komponenteille. Se sisältää kaikki komponentit, sivut ja jo asennetun reitittimen. Jos harkitset tämän opetusohjelman seuraamista ja koko todennusprosessin rakentamista itse seuraamalla yksinkertaisia ​​​​vaiheita, aloita haaroittamalla arkisto ensin. Kun olet haaroittanut arkiston, kloonaa se ja aloita start-herehaarasta:


git clone git@github.com:<your-username>/fullstack-resourcify.git

Kun vedät aloitushaaran:

  • Avaa projekti haluamallasi koodieditorilla

  • Vaihda hakemistoon frontend/

  • Asenna riippuvuudet: lanka

  • Käynnistä kehityspalvelin: yarn dev·

Esikatselun pitäisi näyttää suunnilleen tältä:

Changing user name

Navigointipalkissa oikealla ylhäällä näkyvä nimi on sovelluksen pääkomponentissa määritetty tilamuuttuja. Sama muuttuja välitetään sekä Navbarille että kotisivulle. Yllä käytetty yksinkertainen lomake itse asiassa päivittää "name"-tilamuuttujan EditPage-komponentista.

Alla esitetyt kaksi lähestymistapaa käsittelevät täytäntöönpanon yksityiskohtia:

Ensimmäinen lähestymistapa: useState

useState()

useState on yksi yleisimmin käytetyistä React-koukkuista, jonka avulla voit luoda ja mutatoida tilaa React Functional -komponentissa. UseState-komponentin toteutus on todella yksinkertainen ja helppokäyttöinen: uuden tilan luomiseksi sinun on kutsuttava useState tilasi alkuarvolla ja useState-koukku palauttaa taulukon, joka sisältää kaksi muuttujaa: ensimmäinen on tila. muuttuja, jolla voit viitata tilaan, ja toinen funktio, jolla voit muuttaa tilan arvoa: melko yksinkertaista.

Mitä jos näkisimme sen toiminnassa? Navigointipalkissa oikealla ylhäällä näkyvä nimi on sovelluksen pääkomponentissa määritetty tilamuuttuja. Sama muuttuja välitetään sekä Navbarille että kotisivulle. Yllä käytetty yksinkertainen lomake itse asiassa päivittää "name"-tilamuuttujan EditPage-komponentista. Lopputulos on tämä: useState on ydinkoukku, joka hyväksyy alkutilan parametriksi ja palauttaa kaksi muuttujaa, joissa on kaksi arvoa, tilamuuttuja, joka sisältää alkutilan, ja asettajafunktion samalle tilamuuttujalle.

Puretaan se ja katsotaan, kuinka se alun perin toteutettiin.

  1. Tilamuuttujan "nimi" luominen:

./src/App.jsx


import { useState } from "react";

function App() {
const testValue = "CLA";
 //using the useState hook to create a state variable out of an initial value passed as an argument
 const [name, setName] = useState(testValue);
 console.log(`Rendering: &#36;{name}`);

 return (...)};

Rekvisiitta

Rekvisiitta on yksi react-komponentin perusrakennuspalikoista, käsitteellisesti, jos ajattelet toiminnallista React-komponenttia Javascript-funktiona, rekvisiitta ei ole muuta kuin funktioparametreja, rekvisiitta ja useState-koukun yhdistäminen voi tarjota sinulle vankan kehyksen. tilan hallintaan yksinkertaisessa React-sovelluksessa.

Reaktio-ehdotukset välitetään attribuutteina mukautetuille komponenteille. Rekvisiittana välitetyt attribuutit voidaan destrukturoida props-objektista, kun se hyväksytään argumentiksi, kuten tämä:

Rekvisiitta

<Routes>
  <Route path="/" element={<Home name={name} />} /> // Passing name as a prop to
  Home Component
  <Route
    path="/user"
    element={<EditUser name={name} setName={setName} />} // passing both name and setItem function as props to EditUser component
  />
  <Route path="/login" element={<Login />} />
</Routes>

Propsia voidaan hyväksyä ja käyttää funktionaalisen komponentin sisällä, joka on samanlainen kuin normaalifunktion argumentit. Koska "nimi" välitetään ehdotuksena Home-komponentille, voimme hahmontaa sen samassa komponentissa. Seuraavassa esimerkissä hyväksymme hyväksytyn ehdotuksen käyttämällä destructuring syntaksia poimimaan nimiominaisuuden props-objektista.

Rekvisiitta otetaan vastaan

./src/pages/Home.jsx

... ...
export default function Home({name}) { //Destructuring the name property from the props object, another approach would be: Home(props.name)
 console.log("Rendering: Home");
 return (
   <div className="flex flex-col bg-white m-auto p-auto">
     <h1 className="flex py-5 lg:px-20 md:px-10 mx-5 font-bold text-2xl text-gray-800">
       Welcome {name}
     </h1>
... ...

Provinkki

Avaa selaimen konsoli ja huomaa, kuinka kaikki komponentit, jotka käyttävät "nimi" -pottiota, renderöivät uudelleen, kun tila muuttuu. Kun käsittelet tilamuuttujaa, React tallentaa seuraavan tilan, renderöi komponentin uudelleen uusilla arvoilla ja päivittää käyttöliittymän.

Components Re-rendering

käytä tilan haittoja

Rekvisiitta-poraus

Rekvisiittaporaus on termi, joka viittaa komponenttien hierarkiaan, jossa komponenttijoukko tarvitsee tiettyjä emokomponentin tarjoamia rekvisiitta. yleinen ratkaisu, jota kokematon kehittäjä yleensä käyttää, on ohjata nämä rekvisiittat läpi koko komponenttiketjun. lähestymistapa on, että muutos missä tahansa näistä varusteista käynnistää koko komponenttiketjun uudelleen renderöimisen, mikä tehokkaasti hidastaa koko sovellusta näiden tarpeettomien renderöintien seurauksena, ketjun keskellä olevat komponentit, jotka eivät vaadi näitä rekvisiitta. toimivat välittäjänä rekvisiitta siirrettäessä.

Esimerkki:

  • Tilamuuttuja määritettiin sovelluksen pääkomponentissa useState()-koukun avulla

  • Navbar-komponenttiin välitettiin nimiehdotus

  • Sama ehdotus hyväksytty Navbarissa ja välitetty rekvisiittana vielä kerran UserDropdown-komponenttiin

  • UserDropdown on viimeinen alielementti, joka hyväksyy ehdotuksen.

Prop-drilling

./src/App.jsx

... ...
function App() {
 const [name, setName] = useState(test);
 console.log("Rendering: App");

 return (
   <BrowserRouter>
     <div className="h-screen">
       <Navbar name={name} />

       <main className="px-4">
... ...

./src/components/Navbar.jsx

import React from "react";
import Logo from "../assets/cla.svg";
import { BiSearchAlt } from "react-icons/bi";
import { Link } from "react-router-dom";
import UserDropdown from "./UserDropdown";

export default function Navbar({ name }) {
  console.log("Rendering: Navbar");

  return (
    <>
      <div className="navbar bg-base-100 drop-shadow-sm">
        <div className="flex-1">
          <Link
            to="/"
            className="btn btn-ghost normal-case text-md md:text-xl px-2 gap-1"
          >
            <img src={Logo} className="h-6" alt="" />
            Resources
          </Link>
        </div>

        <UserDropdown name={name} />
      </div>
    </>
  );
}

Kuvittele, kuinka vaikeaa on ylläpitää React-sovellusta, jossa eri kerrokset komponentit kulkevat ja renderöivät saman tilan.

Kasvava monimutkaisuus ja koodin laatu

Käyttämällä useStatea ja propsia ainoana tilanhallintakeinona react-sovelluksessa koodikanta voi nopeasti kasvaa monimutkaisemmaksi, koska sen on hallittava kymmeniä tai satoja tilamuuttujia, jotka voivat olla toistensa kopioita, hajallaan eri tiedostoissa ja komponentit voivat olla melko pelottavia, kaikki muutokset tiettyyn tilamuuttujaan edellyttävät komponenttien välisten riippuvuuksien huolellista harkintaa, jotta vältytään mahdolliselta ylimääräiseltä uudelleen renderöinniltä jo ennestään hitaassa sovelluksessa.

Toinen lähestymistapa: Context API

Context API on Reactin yritys ratkaista vain rekvisiitta ja useStaten käytön haitat tilanhallintaan, erityisesti konteksti-API tulee vastauksena aiemmin mainittuun ongelmaan koskien tarvetta välittää rekvisiitta koko komponenttipuussa. Kontekstin avulla voit määrittää tilan datalle, jonka katsot globaaliksi, ja käyttää sen tilaa mistä tahansa komponenttipuun kohdasta: ei enää potkuriporausta.

On tärkeää huomauttaa, että konteksti-API suunniteltiin alun perin ratkaisemaan maailmanlaajuisen tiedon jakamisen, datan, kuten käyttöliittymäteemat, käyttötapamme todennustiedot, kielet ja vastaavat), muun tyyppisten tietojen jakamiseen. useiden komponenttien joukossa, mutta se ei välttämättä ole globaali kaikille sovelluksille, konteksti ei ehkä ole paras vaihtoehto. Käyttötapauksesta riippuen voit harkita muita tekniikoita, kuten komponentin koostumus, joka ei kuulu tämän artikkelin piiriin.

Vaihtaminen kontekstiin

Todennuskontekstin luominen

createContext on yksinkertainen, se luo uuden kontekstimuuttujan, se ottaa yhden valinnaisen parametrin: kontekstimuuttujan oletusarvon.

Katsotaanpa tätä toiminnassa, luo ensin uusi kansio "contexts" ja uusi tiedosto sen sisään "Auth.jsx". Uuden kontekstin luomiseksi meidän on käynnistettävä createContext()-funktio ja määritettävä palautettu arvo uudelle muuttujalle Auth, joka viedään seuraavaksi:

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

Anna todennuskonteksti

Nyt meidän on paljastettava aiemmin luomamme "Auth"-kontekstimuuttuja. Tämän saavuttamiseksi käytämme kontekstin tarjoajaa, kontekstin tarjoaja on komponentti, jolla on "arvo"-potti, voimme käyttää tätä ehdotusta arvon jakamiseen - nimi – Käärimällä komponenttipuun toimittajakomponenttiin koko komponenttipuussa, saamme tämän arvon käytettäväksi mistä tahansa komponenttipuun sisältä ilman, että sitä tarvitsee välittää jokaiselle alikomponentille erikseen.

Todennusesimerkissämme tarvitsemme muuttujan pitämään käyttäjäobjektin ja jonkinlaisen asettajan tämän tilamuuttujan manipuloimiseksi. Tämä on täydellinen käyttötapa useStatelle. Kun käytät kontekstia, sinun on varmistettava, että määrität tiedot, jotka haluat antaa, ja välität tiedot – esimerkissämme käyttäjä – kaikkeen sisäkkäiseen komponenttipuuhun, joten määritä uusi tilamuuttuja käyttäjä toimittajakomponentin sisällä ja lopuksi välitämme sekä user- että setUser-arvon taulukon sisällä arvona, jonka toimittaja paljastaa komponenttipuulle:

./src/contexts/Auth.jsx

import { createContext, useState } from "react";

export const Auth = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  return <Auth.Provider value={[user, setUser]}>{children}</Auth.Provider>;
};

Toinen asia, jonka voimme tehdä, on siirtää "name"-tilamuuttujamme pääsovelluksen komponentista Auth-kontekstiin ja paljastaa sen sisäkkäisille komponenteille:

import { createContext, useState } from "react";

export const Auth = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [name, setName] = useState("CLA");
  return <Auth.Provider value={[name, setName]}>{children}</Auth.Provider>;
};

Nyt on jäljellä vain sovelluksemme sisäkkäisyys samaan AuthProvider-komponenttiin, jonka juuri vietimme.

./src/main.jsx:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { AuthProvider } from "./contexts/Auth";

import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <AuthProvider>
    <App />
  </AuthProvider>
);

Koska renderöimme Auth.Providerin sisällä olevia lapsipropaaleja, kaikki AuthProvider-komponentin sisällä olevat elementit voivat nyt kuluttaa Auth.Providerille välittämämme arvon. Se saattaa tuntua hämmentävältä, mutta kun olet kokeillut sitä, yritä tarjota ja kuluttaa globaali - konteksti -tila. Loppujen lopuksi se oli minulle järkevää vasta Context API:n kokeilun jälkeen.

Auth-kontekstin käyttäminen

Viimeinen vaihe on suoraviivainen, käytämme kontekstikoukkua "useContext" päästäksemme arvoon, jonka "Auth"-kontekstitoimittaja tarjoaa, joka meidän tapauksessamme on käyttäjän ja setUserin sisältävä matriisi. Seuraavassa koodissa voimme käytä useContextiä todennuskontekstin käyttämiseen Navigointipalkin sisällä. Tämä on mahdollista vain, koska Navbar on sisäkkäinen App-komponentin sisällä ja koska AuthProvider kietoo App-komponentin ympärille, arvoa voidaan käyttää vain useContext-koukun avulla. Toinen mahtava työkalu React tarjoaa heti käyttöösi minkä tahansa datan hallintaan, jota voidaan käyttää maailmanlaajuisesti ja jota myös mikä tahansa kuluttajakomponentti voi käsitellä.

./src/components/Navbar.jsx

export default function Navbar() {
 const [name, setName] = useContext(Auth);
 console.log(name)

 return (...)};

Huomaa, että emme enää hyväksy mitään rekvisiitta Navbar()-toiminnallisessa komponentissa. Käytämme useContext(Auth) -toimintoa Auth-kontekstin käyttämiseen sen sijaan, että otamme sekä nimen että setNamen. Tämä tarkoittaa myös sitä, että meidän ei enää tarvitse välittää ehdotusta Navbarille:

./src/App.jsx

// ... //
return (
   <BrowserRouter>
     <div className="h-screen">
       <Navbar/> // no need to pass prop anymore
// ... //

Todennuskontekstin päivittäminen

Voimme myös käyttää toimitettua setName-funktiota "name"-tilamuuttujan käsittelemiseen:

./src/pages/EditUser.jsx

export default function EditUser() { // no need to accept props anymore
 const [name, setName] = useContext(Auth); // grabbing the name and setName variables from Auth context
 console.log("Rendering: EditUser");

Esittelyssä useReducer() hook

Edellisessä esimerkissä käytimme konteksti-APIa tilamme hallintaan ja jakamiseen komponenttipuussa. Olet ehkä huomannut, että käytämme edelleen useStatea logiikkamme perustana, ja tämä on enimmäkseen ok, koska tilamme on edelleen yksinkertainen objekti. tässä vaiheessa, mutta jos aiomme laajentaa todennusprosessimme ominaisuuksia, meidän on ehdottomasti tallennettava enemmän kuin vain tällä hetkellä kirjautuneen käyttäjän sähköposti, ja tässä palaamme takaisin rajoituksiin, joihin aiemmin menimme koskien UseStaten käyttö monimutkaisen tilan kanssa, onneksi React ratkaisee tämän ongelman tarjoamalla vaihtoehdon useStatelle monimutkaisen tilan hallintaan: syötä useReducer.

useReducer voidaan ajatella yleistettynä versiona useStatesta, se vaatii kaksi parametria: vähennystoiminnon ja alkutilan.

Alkutilalle ei ole mitään mielenkiintoista huomautettavaa, taikuutta tapahtuu vähennystoiminnon sisällä: se tarkistaa tapahtuneen toiminnon tyypin, ja siitä riippuen vähennysyksikkö määrittää, mitä päivityksiä tilaan sovelletaan ja palauttaa sen uuden arvon. .

Kun tarkastellaan alla olevaa koodia, vähennystoiminnolla on kaksi mahdollista toimintotyyppiä:

  • "KIRJAUDU": jolloin käyttäjän tila päivitetään toiminnon hyötykuorman sisällä olevilla uusilla käyttäjätunnuksilla.

  • "LOGOUT": jolloin käyttäjän tila poistetaan paikallisesta tallennustilasta ja palautetaan tyhjäksi.

On tärkeää huomata, että toimintoobjekti sisältää sekä tyyppikentän, joka määrittää käytettävän logiikan, että valinnaisen hyötykuormakentän, joka tarjoaa logiikan soveltamiseen tarvittavia tietoja.

Lopuksi useReducer-koukku palauttaa nykyisen tilan ja lähetystoiminnon, jota käytämme toiminnon välittämiseen supistimelle.

Muun logiikan osalta se on identtinen edellisen esimerkin kanssa:

./src/contexts/Auth.jsx

import { createContext, useEffect, useReducer, useState } from "react";

export const Auth = createContext();
import { createContext, useReducer } from "react";

export const Auth = createContext();
const reducer = (state, action) => {
  switch (action.type) {
    case "LOGIN":
      return { user: action.payload };

    case "LOGOUT":
      localStorage.removeItem("user");
      return { user: null };

    default:
      break;
  }
};

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  return (
    <Auth.Provider value={{ ...state, dispatch }}>{children}</Auth.Provider>
  );
};

Toimintojen lähettäminen setState setter -funktion käytön sijaan – esim. setName –

Kuten juuri mainitsimme, käytämme lähetystoimintoa toiminnon välittämiseen reduktorille, seuraavassa koodissa käynnistämme LOGIN-toiminnon ja tarjoamme käyttäjän sähköpostiosoitteen hyötykuormana, nyt käyttäjän tila päivitetään ja tämä muutos laukaisee kaikkien käyttäjätilaan tilattujen komponenttien uudelleenrenderöinti. On tärkeää huomauttaa, että uudelleenrenderöinti käynnistyy vain, jos tilassa tapahtuu todellinen muutos, ei uudelleenrenderöimistä, jos supistin palauttaa saman edellisen tilan.

export default function Login() {
 const { dispatch } = useContext(Auth);


 const handleLogin = async (e) => {
   // Updating the global Auth context
   dispatch({ type: "LOGIN", payload: {email: email.current.value} });
 };

 return (...)};

Käyttäjän kirjautuminen

JWT Local storage

Provinkki

Huomaa, kuinka onnistuneen kirjautumisen jälkeen vastaanottamamme käyttäjäobjekti on nyt tallennettu localStorageen.

Mukautettu koukku kirjautumiseen

Nyt kun meillä on hyvä ote useReducerista, voimme edelleen kapseloida sisään- ja uloskirjautumislogiikkamme omiin erillisiin mukautettuihin koukkuihinsa. Yhdellä kirjautumiskoukun kutsulla voimme käsitellä API-kutsun kirjautumisreitille, noutaa uuden käyttäjän. valtuustiedot ja tallenna ne paikalliseen tallennustilaan, lähetä LOGIN-kutsu käyttäjän tilan päivittämiseksi, samalla kun käsittelet virheiden käsittelyä:

./src/hooks/useLogin.jsx

import { useContext, useState } from "react";
import { Auth } from "../contexts/Auth";

export const useLogin = () => {
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  const { dispatch } = useContext(Auth);

  const login = async (email, password) => {
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch("/api/users/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });

      const json = await response.json();

      if (json.name === "Error") {
        setError(json.message);
        setIsLoading(false);
      }

      if (!response.ok) {
        setIsLoading(false);
        setError(json);
      }

      if (response.ok) {
        // Save the user and token in the localstorage
        localStorage.setItem("user", JSON.stringify(json));

        // Updating the global Auth context
        dispatch({ type: "LOGIN", payload: json });

        setIsLoading(false);
      }
    } catch (error) {
      console.log(error);
    }
  };

  return { error, isLoading, login };
};

Huomaa: edistyneemmille React-käyttäjille lukijoiden joukossa saatat ihmetellä, miksi emme käyttäneet useReducerin laiska alustusominaisuutta käyttäjän tunnistetietojen hakemiseen, useReducer hyväksyy kolmannen valinnaisen parametrin, jota kutsutaan init-funktioksi. Tätä toimintoa käytetään meidän täytyy soveltaa logiikkaa ennen kuin saamme tilan alkuarvon, syy, miksi emme valinneet tätä, on yksinkertainen huolenaiheiden erottaminen, koodi tällä tavalla on helpompi ymmärtää ja sen seurauksena yksinkertaisempi ylläpitää .

Sisäänkirjautumissivu

Kirjautumissivumme yläosa näyttää tältä, kun käytämme useLogin()-koukkua kirjautumisfunktion purkamiseen ja käynnistämme sisäänkirjautumistoiminnon käyttäjän toimittamilla tunnistetiedoilla:

// ... ... //
export default function Login() {
 const { login, isLoading, error } = useLogin();
 console.log("Rendering: Login");
 const email = createRef(null);
 const password = createRef(null);

 const handleLogin = async (e) => {
   await login(email.current.value, password.current.value);
 };
 return (...)
// ... ... //

Poistamme myös Lähetä-toiminnon käytöstä, kun käyttäjä lähettää lomakkeen:

<button
  onClick={handleLogin}
  disabled={isLoading}
  className="btn btn-square w-full bg-gray-100 text-gray-600 hover:bg-gray-300 border-none"
>
  {isLoading && "A moment please!"}
  {!isLoading && "Login"}
</button>

Ja todennusvirheet, jotka saamme taustajärjestelmästämme:

{
  error && <span className="text-red-500 p-2">{error.message}</span>;
}

Rendering Errors

Käyttäjän tilan ylläpito

Saatat ihmetellä, miksi meidän on tallennettava käyttäjäobjekti localStorageen, yksinkertaisesti sanottuna, haluamme pitää käyttäjän kirjautuneena niin kauan kuin tunnus ei ole vanhentunut. LocalStoragen käyttäminen on loistava tapa tallentaa JSON-bittejä, aivan kuten esimerkissämme. Huomaa, kuinka tila pyyhitään pois, jos päivität sivun sisäänkirjautumisen jälkeen. Tämä voidaan ratkaista helposti käyttämällä useEffect-koukkua, joka tarkistaa, onko meillä tallennettu käyttäjäobjektia localStoragessa, jos sellainen on, kirjaudumme käyttäjän sisään automaattisesti:

// ... ... //
export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  useEffect(() => {
    const user = JSON.parse(localStorage.getItem("user"));

    if (user) {
      return dispatch({ type: "LOGIN", payload: user });
    }
  }, []);

  return (
    <Auth.Provider value={{ ...state, dispatch }}>{children}</Auth.Provider>
  );
};

Mukautetut koukut uloskirjautumista varten

Sama pätee uloskirjautumiskoukkuun, tässä lähetämme LOGOUT-toiminnon nykyisten käyttäjän tunnistetietojen poistamiseksi sekä tilasta että paikallisesta tallennustilasta:

./src/hooks/useLogout.jsx

import { useContext } from "react";
import { Auth } from "../contexts/Auth";

export const useLogout = () => {
  const { dispatch } = useContext(Auth);

  const logout = () => {
    // delete user from the localstorage
    localStorage.removeItem("user");
    // Wipe out the Auth context (user:null) / dipatch 'LOGOUT'
    dispatch({ type: "LOGOUT" });
  };

  return { logout };
};

Käyttäjän uloskirjautuminen

Kirjaudu ulos käyttäjästä lisäämällä napsautustapahtuma UserDropdown.jsx-tiedoston Logout-painikkeeseen ja käsittelemällä sitä vastaavasti:

./src/components/UserDropdown.jsx

// Extracting the logout function from useLogout() and handling the click event listener //
export default function UserDropdown() {
 const { user } = useContext(Auth);
 const { logout } = useLogout();
 console.log("Rendering: UserDropdown");

 const handleLogout = () => {
   logout();
 };
// ... ... //

// Adding a click event listener to logout button //
<li>
   <button onClick={handleLogout}>Logout</button>
</li>
// ... ... //

User Logout

Reaktioreittien suojaaminen

Viimeinen vaihe sovelluksemme toteutuksessa on hyödyntää globaalia käyttäjän tilaa ohjaamaan käyttäjän navigointia, nopea muistutus siitä, mitä käyttäytymistä meidän tulisi saavuttaa: aluksi käyttäjä tervehtii kirjautumissivulta, josta lähtien käyttäjä pääsee vain kotisivulle. onnistuneen kirjautumisen jälkeen käyttäjä ohjataan samalla tavalla kirjautumissivulle uloskirjautumisen yhteydessä.

Saavutamme tämän react-router-dom-kirjaston avulla määrittämällä 2 reittiä: "/" ja "/login", hallitsemme, mikä komponentti renderöidään kullakin reitillä käyttämällä globaalia todennustilaa., auth Evaluing to null edustaa todentamatonta käyttäjää ja päinvastoin:

./src/App.jsx

import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import "./App.css";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";
import Login from "./pages/Login";
import { useContext } from "react";
import { Auth } from "./contexts/Auth";

function App() {
  const { user } = useContext(Auth);
  return (
    <BrowserRouter>
      <div className="h-screen">
        <Navbar />
        <main className="px-4">
          <Routes>
            <Route
              path="/"
              element={user ? <Home /> : <Navigate to="/login" />}
            />
            <Route
              path="/login"
              element={!user ? <Login /> : <Navigate to="/" />}
            />
          </Routes>
        </main>
      </div>
    </BrowserRouter>
  );
}

export default App;

Kertauskaavio

Diagram

Paketoida

Tässä artikkelissa yritimme käsitellä yksinkertaista mutta hyvin yleistä käyttötapausta, jossa tilanhallinta toteutetaan todennustyönkulussa, käymällä läpi erilaisia ​​lähestymistapoja, niiden taustalla olevia perusteita ja niiden kompromisseja. Tilanhallinta asiakaspuolen kehyksissä ja erityisesti Reactissa on yksi puhutuimmista aiheista käyttöliittymäyhteisössä yksinkertaisesti siksi, että se voi joko heikentää tai rikkoa sovelluksesi suorituskykyä ja skaalautuvuutta. Erilaisten tekniikoiden, mallien, kirjastojen ja työkalujen valtava määrä, jotka yrittävät ratkaista tämän valtionhallinnon ongelman, on valtava. Tavoitteenamme oli antaa sinulle vankka käsitys käytännöistä, jotta voit ottaa ne käyttöön omassa sovelluksessasi edistyneemmille tilanhallinnan tekniikoita ja malleja monimutkaisemmissa sovelluksissa, tutustu seuraavaan artikkeliimme, jossa perehdymme Reactin skaalautuvan tilanhallinnan reduxiin.

Tulossa pian

ContextAPI vs Redux Toolkit

Redux oli käytössä yhteisössä monta vuotta ennen Redux-työkalupakkia, itse asiassa RTK otettiin käyttöön aloitusmallina redux-tilanhallinnan käynnistämiseksi uusissa sovelluksissa (sen alkuperäinen nimi oli "redux-starter-kit" lokakuussa 2019), vaikka nykyään Redux-ylläpitäjät ja yhteisö ovat yhtä mieltä siitä, että Redux-työkalupakki on oikea tapa työskennellä reduxin kanssa. RTX tiivistää paljon Redux-logiikkaa, mikä tekee sen käytöstä helpompaa, paljon vähemmän monisanaista ja rohkaisee kehittäjiä Kun noudatat parhaita käytäntöjä, yksi tärkeimmistä eroista näiden kahden välillä on se, että Redux on rakennettu olemaan mielipiteitä sisältämätön, tarjoten minimaalisen sovellusliittymän ja odottavan kehittäjien tekevän suurimman osan raskaasta työnteosta kirjoittamalla omia kirjastojaan yleisiä tehtäviä varten ja käsittelemällä koodirakennetta, tämä johti hitaaseen kehitysaikaan ja sotkuiseen koodiin, Redux-työkalupakki lisättiin ylimääräiseksi abstraktiokerrokseksi, joka estää kehittäjiä joutumasta sen yleisiin sudenkuoppiin. Katso virallisesta dokumentaatiosta lisätietoja ja ylläpitäjien perusteluja täältä.


Career Services background pattern

Urapalvelut

Contact Section background image

Pidetään yhteyttä

Code Labs Academy © 2024 Kaikki oikeudet pidätetään.