Managementul de stat în React: un exemplu practic

reacționează
management de stat
api de context
Managementul de stat în React: un exemplu practic cover image

React este cadrul de bază pentru construirea de aplicații dinamice la nivelul clientului pentru mulți dezvoltatori. Natura dinamică a acestor aplicații vine din flexibilitatea și lista extinsă de capabilități și caracteristici care sunt posibile pe partea clientului, care a permis dezvoltatorilor să construiască aplicații complete care se încarcă în browser în câteva secunde, o performanță care nu a fost. posibil (sau foarte greoaie) în zilele rețelei statice.

Odată cu această extindere a posibilităților, a apărut conceptul de gestionare a stării, pe măsură ce complexitatea crește în aplicațiile client, nevoia de a păstra starea locală crește pentru a deveni un blocaj în sine dacă nu este gestionată corect și concepută având în vedere scalabilitate.

Această problemă a fost abordată de multe cadre, urmând abordări diferite și concentrându-se pe diferite seturi de subprobleme, de aceea este important să avem o înțelegere la nivel înalt a ecosistemului cadrului de alegere pentru a evalua nevoile fiecărei aplicații și pentru a utiliza abordarea potrivită urmând acelea. metrici. Acest articol vă va oferi o scurtă prezentare generală a problemelor comune de management al statului și va încerca să introduceți diferite abordări (useState, Context API) ca răspuns la aceasta. Deși acest articol va prezenta mai multe soluții, se va concentra doar pe provocări la o scară mai mică, vom acoperi subiecte mai avansate în articolele viitoare.

Flux de lucru de autentificare

Codul prezentat pe parcursul articolului poate fi găsit aici.

Un link de previzualizare live poate fi accesat aici.

Luați în considerare cazul în care implementăm procesul de autentificare pentru o aplicație React.

User Login

După cum se arată în GIF-ul de mai sus, dorim să le permitem utilizatorilor să se autentifice sau să se înscrie la aplicația noastră folosind acreditările. Dacă au fost furnizate acreditări valide, utilizatorul va fi autentificat, aplicația va naviga automat la pagina de pornire și utilizatorul poate continua să utilizeze aplicația.

În mod similar, dacă utilizatorul se deconectează, resursele paginii de pornire vor fi protejate în spatele logării, pagina de autentificare va fi singura pagină accesibilă de către utilizator.

Gândindu-ne la acest flux de lucru în termeni de implementare, am avea o componentă principală numită App, componenta App va redirecționa utilizatorul către una dintre cele două pagini: Acasă sau Autentificare, starea curentă a utilizatorului (conectat, deconectat) va dicta care pagina către care este redirecționat utilizatorul, o modificare a stării curente a utilizatorului (de exemplu schimbarea de la conectat la deconectat) ar trebui să declanșeze o redirecționare instantanee către pagina corespunzătoare.

State

După cum se arată în ilustrația de mai sus, dorim ca componenta App să ia în considerare starea curentă și să redea doar una dintre cele două pagini – Acasă sau Conectare – pe baza acelei stări curente.

Dacă utilizatorul este nul, înseamnă că nu avem niciun utilizator autentificat, așa că navigăm automat la pagina de autentificare și protejăm pagina de pornire folosind randarea condiționată. Dacă utilizatorul există, facem invers.

Acum că avem o înțelegere solidă a ceea ce ar trebui implementat, haideți să explorăm câteva opțiuni, dar mai întâi să configuram proiectul nostru React,

Repo-ul proiectului conține un exemplu de aplicație backend pe care o vom folosi pentru a implementa partea frontală (nu vom intra în ea, deoarece nu este accentul principal aici, dar codul a fost menținut simplu în mod intenționat, astfel încât să nu aveți dificultăți cu el )

Începem prin a crea următoarele pagini și componente:

  • Pagina principala

  • Pagina de logare

  • Editați pagina utilizatorului

  • Componenta Navbar

  • Componenta UserDropdown

O aplicație React cu mai multe pagini necesită o navigare adecvată, pentru asta putem folosi react-router-dom pentru a crea un context global de ruter de browser și pentru a înregistra diferite rute React.


yarn add react-router-dom

Știm că mulți cititori preferă să urmeze împreună cu tutorialele, așa că iată șablonul de pornire pentru a vă pune la curent. Această ramură de pornire folosește DaisyUI pentru componentele TailwindCSS JSX predefinite. Include toate componentele, paginile și routerul deja configurat. Dacă vă gândiți să urmați acest tutorial, să construiți singur întregul flux de autorizare urmând pași simpli, începeți prin a bifurca mai întâi depozitul. După ce bifurcați depozitul, clonați-l și începeți de la start-herebranch:


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

Odată ce trageți de ramura start-aici:

  • Deschideți proiectul cu editorul de cod preferat

  • Schimbați directorul în frontend/

  • Instalați dependențe: fire

  • Porniți un server de dezvoltare: yarn dev·

Previzualizarea ar trebui să arate cam așa:

Changing user name

Numele redat în Navbar – partea dreaptă sus – este o variabilă de stare definită în componenta principală a aplicației. Aceeași variabilă este transmisă atât la Navbar, cât și la pagina de pornire. Forma simplă folosită mai sus actualizează de fapt variabila de stare „nume” din componenta EditPage.

Cele două abordări prezentate mai jos vor intra în detaliile implementării:

Prima abordare: useState

useState()

useState este unul dintre cele mai frecvent utilizate cârlige React, vă permite să creați și să modificați starea într-o componentă React Functional. Componenta useState are o implementare foarte simplă și este ușor de utilizat: pentru a crea o stare nouă, trebuie să apelați useState cu valoarea inițială a stării dvs., iar hook-ul useState va returna o matrice care conține două variabile: prima este starea. variabilă pe care o puteți folosi pentru a face referire la starea dvs., iar a doua o funcție pe care o utilizați pentru a schimba valoarea stării: destul de simplu.

Ce zici să vedem asta în acțiune? Numele redat în Navbar – partea dreaptă sus – este o variabilă de stare definită în componenta principală a aplicației. Aceeași variabilă este transmisă atât la Navbar, cât și la pagina de pornire. Forma simplă folosită mai sus actualizează de fapt variabila de stare „nume” din componenta EditPage. Concluzia este aceasta: useState este un hook de bază care acceptă o stare inițială ca parametru și returnează două variabile care dețin două valori, variabila de stare care conține starea inițială și o funcție de setare pentru aceeași variabilă de stare.

Să o descompunem și să vedem cum a fost implementat în primul rând.

  1. Crearea variabilei de stare „nume”:

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

Recuzită

Recuzitele sunt unul dintre elementele de bază ale unei componente React, din punct de vedere conceptual, dacă vă gândiți la o componentă funcțională React ca la o funcție Javascript, atunci recuzita nu este mai mult decât parametrii funcției, combinând recuzita și cârligul useState vă poate oferi un cadru solid. pentru gestionarea stării printr-o aplicație simplă React.

Elementele de recuzită React sunt transmise ca atribute componentelor personalizate. Atributele transmise ca elemente de recuzită pot fi destructurate din obiectul de recuzită atunci când se acceptă ca argument, similar cu acesta:

Recuzită în trecere

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

Recuzitele pot fi acceptate și utilizate în interiorul unei componente funcționale similare cu argumentele funcției normale. Pentru că „numele” este transmis ca o recuzită la componenta Acasă, îl putem reda în aceeași componentă. În exemplul următor, acceptăm prop-ul transmis folosind sintaxa de destructurare pentru a extrage proprietatea nume din obiectul props.

Se acceptă recuzită

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

Sfat pro

Deschideți consola browserului și observați cum toate componentele care folosesc elementul „nume” sunt redate din nou atunci când starea se schimbă. Când manipulează o variabilă de stare, React va stoca următoarea stare, va reda componenta din nou cu noile valori și va actualiza interfața de utilizare.

Components Re-rendering

useState Dezavantaje

Recuzită-Foraj

Găurirea elementelor de recuzită este un termen care se referă la o ierarhie de componente în care un set de componente necesită anumite elemente de recuzită furnizate de o componentă părinte, o soluție obișnuită pe care o folosesc de obicei dezvoltatorii neexperimentați este de a trece aceste elemente de recuzită în întregul lanț de componente, problema cu aceasta. abordarea este că o schimbare a oricăreia dintre aceste elemente de recuzită va declanșa redarea întregului lanț de componente, încetinind efectiv întreaga aplicație ca urmare a acestor randări inutile, componentele din mijlocul lanțului care nu necesită aceste elemente de recuzită. acționează ca medii pentru transferul recuzită.

Exemplu:

  • O variabilă de stare a fost definită în componenta principală a aplicației folosind cârligul useState().

  • O prop de nume a fost transmisă componentei Navbar

  • Aceeași prop este acceptată în Navbar și transmisă ca prop încă o dată către componenta UserDropdown

  • UserDropdown este ultimul element copil care acceptă 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>
    </>
  );
}

Imaginați-vă cât de greu este să mențineți o aplicație React cu diferite straturi de componente, toate care trec și redă aceeași stare.

Creșterea complexității și a calității codului

Cu utilizarea useState și props ca singurul mijloc de gestionare a stării într-o aplicație de reacții, baza de cod poate crește rapid în complexitate, fiind nevoită să gestioneze zeci sau sute de variabile de stare, care pot fi duplicate una ale altora, împrăștiate în diferite fișiere și componentele pot fi destul de descurajante, orice modificare a unei anumite variabile de stare va necesita o analiză atentă a dependențelor dintre componente pentru a evita orice potențială redare suplimentară într-o aplicație deja lentă.

A doua abordare: Context API

API-ul Context este încercarea lui React de a rezolva dezavantajele utilizării doar a elementelor de recuzită și a useState pentru managementul statului, în special, API-ul context vine ca un răspuns la problema menționată anterior cu privire la necesitatea transmiterii elementelor de recuzită în întregul arbore de componente. Folosind contextul, puteți defini o stare pentru datele pe care le considerați globale și puteți accesa starea acesteia din orice punct din arborele componente: nu mai aveți nevoie de foraj de sprijin.

Este important de subliniat că API-ul context a fost conceput inițial pentru a rezolva problema partajării globale a datelor, a datelor cum ar fi temele UI, informațiile de autentificare care sunt cazul nostru de utilizare, limbi și altele), pentru alte tipuri de date care trebuie partajate. între mai mult de o componentă, dar nu este neapărat global pentru toată aplicația, contextul ar putea să nu fie cea mai bună opțiune, în funcție de cazul de utilizare, ați putea lua în considerare alte tehnici, cum ar fi component composition, care nu intră în domeniul de aplicare al acestui articol.

Trecerea la context

Crearea contextului Auth

createContext este simplu, creează o nouă variabilă de context, ia un singur parametru opțional: valoarea implicită a variabilei de context.

Să vedem acest lucru în acțiune, mai întâi creați un nou folder „contexts” și un nou fișier în interiorul acestuia „Auth.jsx”. Pentru a crea un nou context, trebuie să invocăm funcția createContext(), să atribuim valoarea returnată unei noi variabile Auth care va fi exportată în continuare:

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

Furnizați contextul de autentificare

Acum trebuie să expunem variabila de context „Auth” pe care am creat-o anterior, pentru a realiza acest lucru folosim furnizorul de context, furnizorul de context este o componentă care are o prop „valoare”, putem folosi această prop pentru a partaja o valoare – nume – de-a lungul arborelui componente, prin împachetarea arborelui componente cu componenta furnizor, facem acea valoare accesibilă de oriunde în interiorul arborelui componente, fără a fi nevoie să transmitem acea prop la fiecare componentă copil în mod individual.

În exemplul nostru de autentificare, avem nevoie de o variabilă care să dețină obiectul utilizator și de un fel de setter pentru a manipula acea variabilă de stare, acesta este un caz de utilizare perfect pentru useState. Când utilizați Context, trebuie să vă asigurați că definiți datele pe care doriți să le furnizați și să transmiteți acele date - utilizatorul în exemplul nostru - către toate arborele de componente imbricate în interior, deci definiți un utilizator nou variabilă de stare în componenta furnizorului și în cele din urmă, trecem atât user cât și setUser într-o matrice ca valoare pe care furnizorul o va expune arborelui componente:

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

Un alt lucru pe care îl putem face este să ne mutăm variabila de stare „nume” din componenta principală a aplicației în contextul Auth și să o expunem la componente imbricate:

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

Acum tot ce a mai rămas este imbricarea aplicației noastre în aceeași componentă AuthProvider pe care tocmai am exportat-o.

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

Deoarece redăm prop de copii în interiorul Auth.Provider, toate elementele care sunt imbricate în interiorul componentei AuthProvider sunt acum capabile să consume prop valoarea pe care am transmis-o către Auth.Provider. Poate părea confuz, dar odată ce experimentați cu el, încercați să oferiți și să consumați o stare globală - Context. La urma urmei, a avut sens pentru mine doar după ce am experimentat cu API-ul Context.

Consumarea contextului de autentificare

Pasul final este simplu, folosim cârligul context „useContext” pentru a accesa valoarea pe care o furnizează furnizorul de context „Auth”, care în cazul nostru este matricea care conține user și setUser, în codul următor, putem să utilizați useContext pentru a consuma contextul Auth în interiorul Navbar. Acest lucru este posibil numai deoarece Navbar este imbricată în interiorul componentei App și, deoarece AuthProvider se încadrează în jurul componentei App, valoarea prop poate fi consumată numai folosind cârligul useContext. Un alt instrument minunat pe care React îl oferă imediat pentru a gestiona orice date care pot fi accesate la nivel global și, de asemenea, manipulate de orice componentă de consum.

./src/components/Navbar.jsx

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

 return (...)};

Observați cum nu mai acceptăm nicio recuzită în componenta funcțională Navbar(). În schimb, folosim useContext(Auth) pentru a consuma contextul Auth, luând atât numele, cât și setName. Acest lucru înseamnă, de asemenea, că nu mai trebuie să transmitem recuzita către Navbar:

./src/App.jsx

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

Actualizarea contextului de autentificare

De asemenea, putem folosi funcția setName furnizată pentru a manipula variabila de stare „nume”:

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

Introducerea cârligului useReducer().

În exemplul anterior, am folosit API-ul context pentru a gestiona și partaja starea noastră în arborele componente, s-ar putea să fi observat că încă folosim useState ca bază a logicii noastre, iar acest lucru este în mare parte ok, deoarece starea noastră este încă un obiect simplu. în acest moment, dar dacă ar fi să extindem capacitățile fluxului nostru de autentificare, cu siguranță va trebui să stocăm mai mult decât doar e-mailul utilizatorului conectat în prezent și aici ne întoarcem la limitările în care am intrat anterior în ceea ce privește utilizarea useState cu stare complexă, din fericire, React rezolvă această problemă oferind o alternativă la useState pentru gestionarea stării complexe: introduceți useReducer.

useReducer poate fi gândit ca o versiune generalizată a useState, are nevoie de doi parametri: o funcție de reducere și o stare inițială.

Nimic interesant de remarcat pentru starea inițială, magia se întâmplă în interiorul funcției reductorului: verifică tipul de acțiune care a avut loc și, în funcție de acțiunea respectivă, reductorul va determina ce actualizări să se aplice stării și va returna noua sa valoare. .

Privind codul de mai jos, funcția de reducere are două tipuri de acțiuni posibile:

  • LOGIN”: caz în care starea utilizatorului va fi actualizată cu noile acreditări de utilizator furnizate în sarcina de acțiune.

  • LOGOUT”: caz în care starea utilizatorului va fi eliminată din stocarea locală și va fi setată din nou la null.

Este important de reținut că obiectul de acțiune conține atât un câmp de tip care determină ce logică să se aplice, cât și un câmp opțional de sarcină utilă pentru a furniza date necesare pentru aplicarea acelei logici.

În cele din urmă, cârligul useReducer returnează starea curentă și o funcție de expediere pe care o folosim pentru a transmite o acțiune reductorului.

Pentru restul logicii, este identic cu exemplul anterior:

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

Trimiterea acțiunilor în loc de a utiliza funcția setState setter – de exemplu: setName –

După cum tocmai am menționat, folosim funcția de expediere pentru a transmite o acțiune reductorului, în codul următor, inițiem o acțiune LOGIN și oferim e-mailul utilizatorului ca sarcină utilă, acum starea utilizatorului va fi actualizată și această modificare se va declanșa o re-rendare a tuturor componentelor abonate la starea de utilizator. Este important de subliniat că o re-rendare va fi declanșată numai dacă are loc o schimbare reală a stării, nicio re-rendare dacă reductorul revine la aceeași stare anterioară.

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

Logare utilizator

JWT Local storage

Sfat pro

Observați cum obiectul utilizator pe care îl primim după o conectare cu succes este acum stocat în localStorage.

Cârlig personalizat pentru autentificare

Acum că avem o bună înțelegere a useReducer, putem încapsula și mai mult logica noastră de conectare și deconectare în propriile lor cârlige personalizate separate, printr-un singur apel la cârligul Login, putem gestiona un apel API către ruta de conectare, prelua noul utilizator acreditările și stocați-le în stocarea locală, trimiteți un apel LOGIN pentru a actualiza starea utilizatorului, totul în timp ce vă ocupați de gestionarea erorilor:

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

Notă: pentru utilizatorii React mai avansați printre cititori, s-ar putea să vă întrebați de ce nu am folosit caracteristica de inițializare leneșă a useReducer pentru a prelua acreditările utilizatorului, useReducer acceptă un al treilea parametru opțional numit funcție init, această funcție este utilizată în cazul în care trebuie să aplicăm ceva logică înainte de a putea obține valoarea inițială a stării, motivul pentru care nu am optat pentru aceasta este o simplă chestiune de separare a preocupărilor, codul în acest fel este mai simplu de înțeles și, ca urmare, mai ușor de întreținut .

Pagina de logare

Iată cum arată partea de sus a paginii noastre de conectare după ce ați folosit cârligul useLogin() pentru a extrage funcția de autentificare și a invocat funcția de autentificare cu acreditările trimise de un utilizator:

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

Dezactivăm și funcția de trimitere atunci când utilizatorul trimite formularul:

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

Și redarea oricăror erori de autentificare pe care le primim de la backend-ul nostru:

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

Rendering Errors

Menținerea stării de utilizator

S-ar putea să vă întrebați de ce trebuie să stocăm obiectul utilizator în localStorage, pur și simplu, dorim să păstrăm utilizatorul conectat atâta timp cât token-ul nu este expirat. Utilizarea localStorage este o modalitate excelentă de a stoca biți de JSON, la fel ca în exemplul nostru. Observați cum se șterge starea dacă reîmprospătați pagina după conectare. Acest lucru poate fi rezolvat cu ușurință utilizând un cârlig useEffect pentru a verifica dacă avem un obiect utilizator stocat în localStorage, dacă există unul, conectăm utilizatorul automat:

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

Cârlige personalizate pentru deconectare

Același lucru se aplică și cu hook-ul Logout, aici trimitem o acțiune LOGOUT pentru a elimina acreditările curente ale utilizatorului atât din stat, cât și din stocarea locală:

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

Deconectare utilizator

Pentru a deconecta un utilizator, să adăugăm un eveniment de clic la butonul Deconectare găsit în UserDropdown.jsx și să îl gestionăm în consecință:

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

Protejarea rutelor de reacție

Ultimul pas în implementarea aplicației noastre este să folosim starea globală a utilizatorului pentru a controla navigarea utilizatorului, un memento rapid despre ce comportament ar trebui să realizăm: inițial utilizatorul este întâmpinat de pagina de autentificare, din acel moment utilizatorul poate accesa doar pagina de pornire după o conectare cu succes, în mod similar, utilizatorul va fi redirecționat către pagina de autentificare la deconectare.

Realizăm acest lucru cu ajutorul bibliotecii react-router-dom prin definirea a 2 rute: „/” și „/login”, controlăm ce componentă să redăm la fiecare rută folosind starea de autentificare globală, auth evaluating null reprezintă un utilizator neautentificat și invers:

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

Recapitulare diagramă

Diagram

Învelire

În acest articol am încercat să abordăm cazul de utilizare simplu, dar foarte comun, al implementării managementului de stat pentru un flux de lucru de autentificare, parcurgând diferitele abordări, rațiunea din spatele fiecăreia și compromisurile lor. Managementul statelor în cadrul clientului și în special în React este unul dintre cele mai discutate subiecte în comunitatea frontend, pur și simplu pentru că poate crește sau distruge performanța și scalabilitatea aplicației dvs. Numărul mare de tehnici, modele, biblioteci și instrumente care încearcă să rezolve această problemă a managementului de stat este copleșitoare, scopul nostru a fost să vă oferim o înțelegere solidă a practicilor, astfel încât să le puteți implementa în propria aplicație, pentru mai avansate. tehnici și modele de gestionare a stării în aplicații mai complexe, consultați următorul nostru articol în care intrăm în redux pentru gestionarea stării scalabile în React.

In curand

ContextAPI vs Redux Toolkit

Redux a avut mulți ani de adoptare în comunitate înainte de setul de instrumente Redux, de fapt, RTK a fost introdus ca șablon de pornire pentru a bootstrap gestionarea stării redux în aplicații noi (numele său inițial era „redux-starter-kit” în octombrie 2019), deși astăzi, există un consens general între întreținerii Redux și comunitate că setul de instrumente Redux este modalitatea validă de a lucra cu redux.RTX retrage o mare parte din logica Redux, ceea ce o face mai ușor de utilizat, cu mult mai puțin pronunțată și încurajează dezvoltatorii să urmați cele mai bune practici, o diferență principală între cele două este că Redux a fost creat pentru a nu avea opinii, oferind un API minim și așteaptă ca dezvoltatorii să facă cea mai mare parte a sarcinilor grele scriind propriile biblioteci pentru sarcini comune și ocupându-se de structura codului, acest lucru a dus la un timp de dezvoltare lent și un cod dezordonat, setul de instrumente Redux a fost adăugat ca un strat suplimentar de abstracție, împiedicând dezvoltatorii să cadă în capcanele sale comune, consultați documentația oficială pentru mai multe informații și raționamentul de la întreținătorii săi aici.


Career Services background pattern

Servicii de carieră

Contact Section background image

Să rămânem în legătură

Code Labs Academy © 2024 Toate drepturile rezervate.