Statsledelse i reaktion: Et praktisk eksempel

reagere
statsforvaltning
kontekst-API
Statsledelse i reaktion: Et praktisk eksempel cover image

React er go-to-rammen til at bygge dynamiske klientside-applikationer for mange udviklere. Den dynamiske karakter af disse applikationer kommer fra fleksibiliteten og den udvidede liste over muligheder og funktioner, der er mulige på klientsiden, hvilket gjorde det muligt for udviklere at bygge fuldgyldige applikationer, der indlæses på browseren på få sekunder, en bedrift, der ikke var muligt (eller meget besværligt) i det statiske vævs dage.

Med denne udvidelse af muligheder kom konceptet med at administrere staten, efterhånden som kompleksiteten vokser i klientsidens applikationer, vokser behovet for at beholde den lokale stat til at blive en flaskehals i sig selv, hvis det ikke håndteres korrekt og udtænkt med skalerbarhed i tankerne.

Dette problem blev behandlet af mange rammer, der fulgte forskellige tilgange og med fokus på forskellige sæt af underproblemer, derfor er det vigtigt at have en høj forståelse af økosystemet i den valgte ramme for at vurdere hver applikations behov og anvende den rigtige tilgang efter disse. målinger. Denne artikel vil give dig et kort overblik over de almindelige statsforvaltningsproblemer og forsøge at introducere forskellige tilgange (useState, Context API) som et svar på det. Selvom denne artikel vil præsentere flere løsninger, vil den kun fokusere på udfordringer i mindre skala, vi vil dække mere avancerede emner i kommende artikler.

Authentication Workflow

Koden, der vises gennem artiklen, kan findes her.

Et live forhåndsvisningslink kan tilgås her.

Overvej det tilfælde, hvor vi implementerer godkendelsesprocessen for en React-applikation.

User Login

Som vist i GIF'en ovenfor, ønsker vi at tillade brugere at logge ind eller tilmelde sig vores applikation ved hjælp af legitimationsoplysninger. Hvis gyldige legitimationsoplysninger blev angivet, vil brugeren blive logget ind, applikationen vil automatisk navigere til startsiden, og brugeren kan fortsætte med at bruge applikationen.

På samme måde, hvis brugeren logger ud, vil startsidens ressourcer være beskyttet bag login, loginsiden vil være den eneste side, som brugeren har adgang til.

Når vi tænker på denne arbejdsgang i form af implementering, ville vi have en hovedkomponent ved navn App, App-komponenten vil omdirigere brugeren til en af ​​to sider: Hjem eller Login, den aktuelle tilstand af bruger (logget ind, logget ud) vil diktere hvilken siden brugeren omdirigeres til, bør en ændring i brugerens aktuelle tilstand (f.eks. ændring fra logget ind til logget ud) udløse en øjeblikkelig omdirigering til den tilsvarende side.

State

Som vist i illustrationen ovenfor, ønsker vi, at app-komponenten skal tage højde for den aktuelle tilstand og kun gengive en af ​​de to sider – Home eller Login – baseret på den aktuelle tilstand.

Hvis brugeren er null, betyder det, at vi ikke har nogen godkendt bruger, så vi navigerer automatisk til login-siden og beskytter startsiden ved hjælp af betinget gengivelse. Hvis brugeren eksisterer, gør vi det modsatte.

Nu hvor vi har en solid forståelse af, hvad der skal implementeres, lad os undersøge et par muligheder, men lad os først opsætte vores React-projekt,

Projektets repo indeholder et eksempel på en backend-applikation, som vi vil bruge til at implementere frontend-siden (vi vil ikke gå ind i det, da det ikke er hovedfokus her, men koden blev med vilje holdt simpel, så du ikke vil have svært ved det )

Vi starter med at oprette følgende sider og komponenter:

  • Hjemmeside

  • Login side

  • Rediger brugerside

  • Navbar-komponent

  • UserDropdown-komponent

En React-applikation med flere sider har brug for ordentlig navigation, til det kan vi bruge react-router-dom til at skabe en global browser-router-kontekst og registrere forskellige React-ruter.


yarn add react-router-dom

Vi ved, at mange læsere foretrækker at følge med i tutorials, så her er startskabelonen for at få dig op i fart. Denne startgren bruger DaisyUI til foruddefinerede TailwindCSS JSX-komponenter. Det inkluderer alle komponenter, sider og routeren, der allerede er konfigureret. Hvis du overvejer at følge med i denne vejledning, opbygge hele godkendelsesflowet selv ved at følge ligetil trin, start med at forgrene depotet først. Når du har forgrenet depotet, skal du klone det og starte fra start-here-grenen**:**


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

Når du trækker start-her-grenen:

  • Åbn projektet med din foretrukne kodeeditor

  • Skift mappe til frontend/

  • Installationsafhængigheder: garn

  • Start en udviklingsserver: yarn dev·

Forhåndsvisningen skulle se nogenlunde sådan ud:

Changing user name

Navnet, der gengives i Navbar - øverst til højre - er en tilstandsvariabel, der er defineret i app-hovedkomponenten. Den samme variabel sendes til både Navbar og Hjemmeside. Den simple form, der bruges ovenfor, opdaterer faktisk tilstandsvariablen "navn" fra EditPage-komponenten.

De to fremgangsmåder, der præsenteres nedenfor, vil gå ind på detaljerne i implementeringen:

Første tilgang: useState

useState()

useState er en af ​​de mest brugte React hooks, den giver dig mulighed for at oprette og mutere tilstand i en React Functional komponent. UseState-komponenten har en virkelig simpel implementering og er nem at bruge: for at oprette en ny tilstand skal du kalde useState med startværdien af ​​din tilstand, og useState-hooken vil returnere et array, der indeholder to variable: den første er tilstanden variabel, som du kan bruge til at referere til din tilstand, og den anden en funktion, som du bruger til at ændre værdien af ​​tilstanden: ret ligetil.

Hvad med at vi ser det i aktion? Navnet, der gengives i Navbar - øverst til højre - er en tilstandsvariabel, der er defineret i app-hovedkomponenten. Den samme variabel sendes til både Navbar og Hjemmeside. Den simple form, der bruges ovenfor, opdaterer faktisk tilstandsvariablen "navn" fra EditPage-komponenten. Nederste linje er dette: useState er en kernehook, der accepterer en initial tilstand som en parameter og returnerer to variable, der har to værdier, tilstandsvariablen, der indeholder den initiale tilstand, og en indstillerfunktion for den samme tilstandsvariabel.

Lad os bryde det ned og se, hvordan det blev implementeret i første omgang.

  1. Oprettelse af tilstandsvariablen "navn":

./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 (...)};

Rekvisitter

Rekvisitter er en af ​​de grundlæggende byggesten i en react-komponent, konceptuelt, hvis du tænker på en React-funktionel komponent som en Javascript-funktion, så er rekvisitter ikke mere end funktionsparametrene, en kombination af rekvisitter og useState-krogen kan tilbyde dig en solid ramme til styring af tilstand på tværs af en simpel React-applikation.

React-rekvisitter overføres som attributter til brugerdefinerede komponenter. Attributter, der sendes som rekvisitter, kan destruktureres fra rekvisitobjektet, når det accepteres som et argument, svarende til dette:

Passerende rekvisitter

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

Rekvisitter kan accepteres og bruges i en funktionel komponent svarende til normale funktionsargumenter. Det er fordi "navn" overføres som en rekvisit til Home-komponenten, at vi kan gengive den i den samme komponent. I det følgende eksempel accepterer vi den beståede prop ved hjælp af destruktureringssyntaksen til at udtrække navnegenskaben fra props-objektet.

Accepter rekvisitter

./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>
... ...

Pro Tip

Åbn browserens konsol, og læg mærke til, hvordan alle komponenter, der bruger "navn"-rekvisitten, gengives, når tilstanden ændres. Når du manipulerer en tilstandsvariabel, gemmer React den næste tilstand, gengiver din komponent igen med de nye værdier og opdaterer brugergrænsefladen.

Components Re-rendering

useState ulemper

Props-Drilling

Props drilling er et udtryk, der refererer til et hierarki af komponenter, hvor et sæt komponenter har brug for visse rekvisitter leveret af en overordnet komponent, en almindelig løsning, som uerfarne udviklere normalt bruger, er at sende disse rekvisitter gennem hele kæden af ​​komponenter, problemet med dette tilgang er, at en ændring i nogen af ​​disse rekvisitter vil udløse hele kæden af ​​komponenter til at gengive, hvilket effektivt bremser hele applikationen som et resultat af disse unødvendige renderinger, komponenterne i midten af ​​kæden, der ikke kræver disse rekvisitter fungere som medier til at overføre rekvisitter.

Eksempel:

  • En tilstandsvariabel blev defineret i app-hovedkomponenten ved hjælp af useState()-krogen

  • En navneprop blev videregivet til Navbar-komponenten

  • Samme rekvisit accepteret i Navbar og sendt som rekvisit igen til UserDropdown-komponenten

  • UserDropdown er det sidste underordnede element, der accepterer prop.

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>
    </>
  );
}

Forestil dig, hvor svært det er at opretholde en React-app med forskellige lag af komponenter, der alle passerer og gengiver den samme tilstand.

Voksende kompleksitet og kodekvalitet

Med brugen af ​​useState og rekvisitter som det eneste middel til tilstandsstyring i en react-applikation, kan kodebasen hurtigt vokse i kompleksitet, idet den skal administrere titusinder eller hundredvis af tilstandsvariabler, som kan være duplikater af hinanden, spredt ud over forskellige filer og komponenter kan være ret skræmmende, enhver ændring af en given tilstandsvariabel vil kræve omhyggelig overvejelse af afhængigheder mellem komponenter for at undgå enhver potentiel yderligere gengivelse i en allerede langsom applikation.

Anden tilgang: Context API

Context API er Reacts forsøg på at løse ulemperne ved kun at bruge rekvisitter og useState til statsstyring, især kontekst-API'en kommer som et svar på det tidligere nævnte problem vedrørende behovet for at sende rekvisitter ned i hele komponenttræet. Med brug af kontekst kan du definere en tilstand for data, som du anser for global, og få adgang til dens tilstand fra et hvilket som helst punkt i komponenttræet: ikke mere prop-boring.

Det er vigtigt at påpege, at kontekst-API'en oprindeligt blev udtænkt til at løse problemet med global datadeling, data såsom UI-temaer, autentificeringsinformation, som er vores use case, sprog og lignende), for andre typer data, der skal deles Blandt mere end én komponent, men er ikke nødvendigvis global for hele applikationen, er kontekst muligvis ikke den bedste løsning, afhængigt af brugssituationen kan du overveje andre teknikker såsom komponentsammensætning, som er uden for denne artikels omfang.

Skifter til kontekst

Oprettelse af godkendelseskonteksten

createContext er ligetil, den opretter en ny kontekstvariabel, den tager en enkelt valgfri parameter ind: standardværdien for kontekstvariablen.

Lad os se dette i aktion, opret først en ny mappe "kontekster" og en ny fil inde i den "Auth.jsx". For at oprette en ny kontekst, skal vi aktivere funktionen createContext() og tildele den returnerede værdi til en ny variabel Auth, som derefter eksporteres:

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

Angiv godkendelseskonteksten

Nu skal vi eksponere "Auth" kontekstvariablen, som vi oprettede tidligere, for at opnå dette bruger vi kontekstudbyderen, kontekstudbyderen er en komponent, der har en "værdi" prop, vi kan bruge denne prop til at dele en værdi – navn – på tværs af komponenttræet, ved at pakke komponenttræet med udbyderkomponenten, gør vi denne værdi tilgængelig fra hvor som helst inde i komponenttræet, uden at det er nødvendigt at videregive denne prop til hver underordnede komponent individuelt.

I vores autentificeringseksempel har vi brug for en variabel til at holde brugerobjektet, og en slags setter til at manipulere denne tilstandsvariabel, dette er et perfekt brugstilfælde for useState. Når du bruger kontekst, skal du sikre dig, at du definerer de data, du vil levere, og videregiver disse data – brugeren i vores eksempel – til hele komponenttræet, der er indlejret inde, så definere en ny tilstandsvariabel bruger inde i udbyderkomponenten og til sidst sender vi både bruger og setUser inde i et array som den værdi, som udbyderen vil udsætte for komponenttræet:

./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>;
};

En anden ting, vi kan gøre, er at flytte vores "navn" tilstandsvariabel fra hovedappkomponenten til Auth-konteksten og udsætte den for indlejrede komponenter:

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>;
};

Nu er det eneste, der er tilbage, at indlejre vores app i den samme AuthProvider-komponent, som vi lige har eksporteret.

./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>
);

Fordi vi gengiver børnepropen inde i Auth.Provider, er alle elementer, der er indlejret inde i AuthProvider-komponenten, nu i stand til at forbruge den værdiprop, vi sendte til Auth.Provider. Det kan virke forvirrende, men når du først har eksperimenteret med det, så prøv at give og indtage en global – kontekst – tilstand. Det gav trods alt først mening for mig efter at have eksperimenteret med Context API.

Forbruger godkendelseskonteksten

Det sidste trin er ligetil, vi bruger konteksthooken "useContext" til at få adgang til den værdi, som kontekstudbyderen af ​​"Auth" leverer, som i vores tilfælde er det array, der indeholder bruger og setUser, i den følgende kode er vi i stand til at brug useContext til at bruge Auth-konteksten inde i Navbar. Dette er kun muligt, fordi Navbar er indlejret inde i app-komponenten, og da AuthProvider omslutter app-komponenten, kan værdien kun forbruges ved hjælp af useContext-hook. Et andet fantastisk værktøj, som React leverer ud af boksen til at administrere data, der kan tilgås globalt og også manipuleres af enhver forbrugerkomponent.

./src/components/Navbar.jsx

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

 return (...)};

Læg mærke til, hvordan vi ikke længere accepterer rekvisitter i den funktionelle Navbar()-komponent. Vi bruger useContext(Auth) til at forbruge Auth-konteksten i stedet, og griber både navn og setName. Dette betyder også, at vi ikke længere behøver at videregive rekvisitten til Navbar:

./src/App.jsx

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

Opdatering af godkendelseskonteksten

Vi kan også bruge den medfølgende setName-funktion til at manipulere tilstandsvariablen "navn":

./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");

Introduktion til useReducer() hook

I det forrige eksempel brugte vi kontekst-API'en til at styre og dele vores tilstand ned i komponenttræet, du har måske bemærket, at vi stadig bruger useState som basis for vores logik, og dette er for det meste ok, da vores tilstand stadig er et simpelt objekt på dette tidspunkt, men hvis vi skulle udvide mulighederne for vores autentificeringsflow, bliver vi helt sikkert nødt til at gemme mere end blot e-mailen fra den bruger, der aktuelt er logget på, og det er her, vi vender tilbage til de begrænsninger, vi tidligere gik ind i mht. brugen af ​​useState med kompleks tilstand, heldigvis løser React dette problem ved at give et alternativ til useState til styring af kompleks tilstand: indtast useReducer.

useReducer kan opfattes som en generaliseret version af useState, den tager to parametre: en reduceringsfunktion og en initial tilstand.

Intet interessant at bemærke for den oprindelige tilstand, magien sker inde i reduceringsfunktionen: den tjekker for den type handling, der fandt sted, og afhængigt af den handling, vil reduceringen bestemme, hvilke opdateringer der skal anvendes på tilstanden og returnere dens nye værdi .

Ser man på koden nedenfor, har reduceringsfunktionen to mulige handlingstyper:

  • "LOGIN": i hvilket tilfælde brugertilstanden bliver opdateret med de nye brugerlegitimationsoplysninger, der er angivet i handlingens nyttelast.

  • "LOGOUT": i hvilket tilfælde brugertilstanden vil blive fjernet fra det lokale lager og sat tilbage til null.

Det er vigtigt at bemærke, at handlingsobjektet indeholder både et typefelt, der bestemmer, hvilken logik der skal anvendes, og et valgfrit nyttelastfelt til at levere data, der er nødvendige for at anvende denne logik.

Til sidst returnerer useReducer-krogen den aktuelle tilstand og en afsendelsesfunktion, som vi bruger til at videregive en handling til reduceringen.

For resten af ​​logikken er den identisk med det forrige eksempel:

./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>
  );
};

Afsender handlinger i stedet for at bruge setState seter-funktionen – f.eks.: setName –

Som vi lige har nævnt, bruger vi afsendelsesfunktionen til at videregive en handling til reducereren, i den følgende kode starter vi en LOGIN-handling, og vi leverer brugerens e-mail som en nyttelast, nu vil brugertilstanden blive opdateret, og denne ændring vil udløse en gengivelse af alle komponenter, der er abonneret på brugertilstanden. Det er vigtigt at påpege, at en gengivelse kun vil blive udløst, hvis der sker en faktisk ændring i tilstanden, ingen gengivelser, hvis reducereren returnerer den samme tidligere tilstand.

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 (...)};

Brugerlogin

JWT Local storage

Pro Tip

Læg mærke til, hvordan det brugerobjekt, vi modtager efter et vellykket login, nu er gemt i localStorage.

Brugerdefineret krog til login

Nu hvor vi har et godt greb om useReducer, kan vi yderligere indkapsle vores login- og logoutlogik i deres egne separate tilpassede hooks, ved et enkelt kald til Login-hooken kan vi håndtere et API-kald til login-ruten, hente den nye bruger legitimationsoplysninger og gem dem i det lokale lager, send et LOGIN-opkald for at opdatere brugertilstanden, alt imens du håndterer fejlhåndtering:

./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 };
};

Bemærk: for mere avancerede React-brugere blandt læserne, undrer du dig måske over, hvorfor vi ikke brugte den dovne initialiseringsfunktion i useReducer til at hente brugeroplysningerne, useReducer accepterer en tredje valgfri parameter kaldet en init-funktion, denne funktion bruges i tilfælde af vi er nødt til at anvende noget logik, før vi kan få den oprindelige værdi af staten, grunden til at vi ikke valgte dette er et simpelt spørgsmål om adskillelse af bekymringer, koden på denne måde er lettere at forstå og som et resultat nemmere at vedligeholde .

Loginside

Sådan ser den øverste del af vores login-side ud efter brug af useLogin()-krogen til at udtrække login-funktionen og påkaldelse af login-funktionen med legitimationsoplysninger indsendt af en bruger:

// ... ... //
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 (...)
// ... ... //

Vi deaktiverer også indsend-funktionen, når brugeren indsender formularen:

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

Og gengivelse af alle godkendelsesfejl, vi modtager fra vores backend:

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

Rendering Errors

Vedligeholdelse af brugertilstanden

Du undrer dig måske over, hvorfor vi skal gemme brugerobjektet i localStorage, ganske enkelt sagt, vi ønsker at beholde brugeren logget ind, så længe tokenet ikke er udløbet. Brug af localStorage er en fantastisk måde at gemme stykker af JSON på, ligesom i vores eksempel. Læg mærke til, hvordan staten bliver udslettet, hvis du opdaterer siden efter at have logget ind. Dette kan nemt løses ved hjælp af en useEffect-hook for at kontrollere, om vi har et gemt brugerobjekt i localStorage, hvis der er et, logger vi brugeren automatisk på:

// ... ... //
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>
  );
};

Brugerdefinerede kroge til logout

Det samme gælder for logout-krogen, her sender vi en LOGOUT-handling for at fjerne de aktuelle brugeroplysninger fra både staten og den lokale lagring:

./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 };
};

Brugerlog ud

For at logge en bruger ud, lad os tilføje en klikhændelse til knappen Log ud, der findes i UserDropdown.jsx, og håndtere det i overensstemmelse hermed:

./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

Beskyttelse af reaktionsruter

Det sidste trin i implementeringen af ​​vores applikation er at udnytte den globale brugertilstand til at kontrollere brugernavigation, en hurtig påmindelse om, hvilken adfærd vi skal opnå: i første omgang bliver brugeren mødt af login-siden, fra det tidspunkt kan brugeren kun få adgang til hjemmesiden efter et vellykket login, vil brugeren på samme måde blive omdirigeret til login-siden ved et logout.

Vi opnår dette ved hjælp af react-router-dom-biblioteket ved at definere 2 ruter: "/" og "/login", vi kontrollerer, hvilken komponent der skal gengives på hver rute ved hjælp af den globale godkendelsestilstand, auth-evaluering til null repræsenterer en ikke-godkendt bruger og omvendt:

./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;

Oversigtsdiagram

Diagram

Afslutning

I denne artikel forsøgte vi at tackle den enkle, men meget almindelige brugssag med at implementere tilstandsstyring til en autentificeringsworkflow, idet vi gennemgår de forskellige tilgange, rationalet bag hver enkelt og deres afvejninger. Statsstyring i rammer på klientsiden og i React i særdeleshed er et af de mest omtalte emner i frontend-fællesskabet, simpelthen fordi det enten kan skabe eller ødelægge ydeevnen og skalerbarheden af ​​din applikation. Alene mængden af ​​de forskellige teknikker, mønstre, biblioteker og værktøjer, der forsøger at løse dette spørgsmål om statsforvaltning, er overvældende, vores mål var at give dig en solid forståelse af praksis, så du kan implementere den i din egen applikation, for mere avanceret teknikker og mønstre for statsstyring i mere komplekse applikationer, se vores næste artikel, hvor vi kommer ind på redux for skalerbar tilstandsstyring i React.

Kommer snart

ContextAPI vs Redux Toolkit

Redux havde mange års adoption i samfundet, før Redux-værktøjssættet, faktisk blev RTK introduceret som en startskabelon til at bootstrap redux-tilstandsstyring i nye applikationer (dets oprindelige navn var "redux-starter-kit" i oktober 2019), selvom i dag er der en generel konsensus mellem Redux-vedligeholderne og fællesskabet om, at Redux-værktøjssættet er den gyldige måde at arbejde med redux på. RTX abstraherer meget af Redux-logikken, hvilket gør den nemmere at bruge, med langt mindre udførlig, og opfordrer udviklere til at følg de bedste praksisser, en væsentlig forskel mellem de to er, at Redux blev bygget til at være meningsløs, hvilket giver en minimal API og forventer, at udviklere gør det meste af det tunge løft ved at skrive deres egne biblioteker til almindelige opgaver og håndtere kodestruktur, dette resulterede i langsom udviklingstid og rodet kode. Redux-værktøjssættet blev tilføjet som et ekstra lag af abstraktion, der forhindrede udviklere i at falde i dets almindelige faldgruber. Se den officielle dokumentation for mere indsigt og begrundelsen fra dets vedligeholdere her.


Career Services background pattern

Karriereservice

Contact Section background image

Lad os holde kontakten

Code Labs Academy © 2024 Alle rettigheder forbeholdes.