React är det bästa ramverket för att bygga dynamiska applikationer på klientsidan för många utvecklare. Den dynamiska karaktären hos dessa applikationer kommer från flexibiliteten och den utökade listan över möjligheter och funktioner som är möjliga på klientsidan, vilket gjorde det möjligt för utvecklare att bygga fullfjädrade applikationer som laddas i webbläsaren på några sekunder, en bedrift som inte var möjligt (eller mycket besvärligt) under den statiska banans dagar.
Med denna expansion av möjligheter, kom konceptet att hantera tillstånd, eftersom komplexiteten växer i applikationer på klientsidan, växer behovet av att behålla lokala tillstånd till att bli en flaskhals i sig om det inte hanteras korrekt och utformas med skalbarhet i åtanke.
Denna fråga togs upp av många ramverk, efter olika tillvägagångssätt och med fokus på olika uppsättningar av delproblem, det är därför det är viktigt att ha en hög nivå av förståelse för ekosystemet i det valda ramverket för att bedöma varje applikations behov och använda rätt tillvägagångssätt efter dessa metrik. Den här artikeln kommer att ge dig en kort översikt över de vanligaste tillståndshanteringsfrågorna och försöka introducera olika tillvägagångssätt (useState, Context API) som ett svar på det. Även om den här artikeln kommer att presentera flera lösningar, kommer den bara att fokusera på utmaningar i mindre skala, vi kommer att täcka mer avancerade ämnen i kommande artiklar.
Autentiseringsarbetsflöde
Koden som visas i hela artikeln finns här.
En länk för liveförhandsvisning kan nås här.
Tänk på fallet där vi implementerar autentiseringsprocessen för en React-applikation.
Som visas i GIF ovan vill vi tillåta användare att logga in eller registrera sig för vår applikation med hjälp av referenser. Om giltiga referenser angavs kommer användaren att loggas in, applikationen navigerar automatiskt till startsidan och användaren kan fortsätta att använda applikationen.
På liknande sätt, om användaren loggar ut kommer hemsidans resurser att skyddas bakom inloggning, inloggningssidan kommer att vara den enda sidan som användaren kan komma åt.
Om vi tänker på detta arbetsflöde i termer av implementering, skulle vi ha en huvudkomponent som heter App, App-komponenten kommer att omdirigera användaren till en av två sidor: Hem eller Logga in, användarens nuvarande tillstånd (inloggad, utloggad) kommer att diktera vilken sida som användaren omdirigeras till, bör en ändring i användarens nuvarande tillstånd (till exempel ändring från inloggad till utloggad) utlösa en omdirigering direkt till motsvarande sida.
Som visas i illustrationen ovan vill vi att appkomponenten ska beakta det aktuella tillståndet och endast återge en av de två sidorna – Hem eller Logga in – baserat på det aktuella tillståndet.
Om användaren är null betyder det att vi inte har någon autentiserad användare, så vi navigerar till inloggningssidan automatiskt och skyddar hemsidan med villkorlig rendering. Om användaren finns gör vi tvärtom.
Nu när vi har en gedigen förståelse för vad som bör implementeras, låt oss utforska några alternativ, men låt oss först sätta upp vårt React-projekt,
Projektet repo innehåller ett exempel på en backend-applikation som vi kommer att använda för att implementera frontend-sidan (vi kommer inte att gå in på det eftersom det inte är huvudfokus här men koden hölls avsiktligt enkel så att du inte kommer att ha svårt med det )
Vi börjar med att skapa följande sidor och komponenter:
-
Hemsida
-
Login sida
-
Redigera användarsida
-
Navbar-komponent
-
UserDropdown-komponent
En React-applikation med flera sidor behöver korrekt navigering, för det kan vi använda react-router-domen för att skapa en global webbläsarrouterkontext och registrera olika React-rutter.
yarn add react-router-dom
Vi vet att många läsare föredrar att följa med i tutorials, så här är startmallen för att få dig igång. Denna startgren använder DaisyUI för fördefinierade TailwindCSS JSX-komponenter. Den inkluderar alla komponenter, sidor och routern som redan är konfigurerad. Om du funderar på att följa den här handledningen, bygga hela autentiseringsflödet själv genom att följa enkla steg, börja med att dela förvaret först. När du delat förvaret, klona det och börja från start-här-grenen**:**
git clone git@github.com:<your-username>/fullstack-resourcify.git
När du drar start-här-grenen:
-
Öppna projektet med din föredragna kodredigerare
-
Ändra katalog till frontend/
-
Installationsberoenden: garn
-
Starta en utvecklingsserver: yarn dev·
Förhandsvisningen ska se ut ungefär så här:
Namnet som återges i navigeringsfältet – övre högra sidan – är en tillståndsvariabel som definieras i appens huvudkomponent. Samma variabel skickas till både Navbar och Hemsida. Det enkla formuläret som används ovan uppdaterar faktiskt tillståndsvariabeln "namn" från EditPage-komponenten.
De två tillvägagångssätten som presenteras nedan kommer att gå in på detaljerna för genomförandet:
Första tillvägagångssätt: useState
useState()
useState är en av de mest använda React-hakarna, den låter dig skapa och mutera tillstånd i en React Functional-komponent. UseState-komponenten har en väldigt enkel implementering och är lätt att använda: för att skapa ett nytt tillstånd måste du anropa useState med det initiala värdet för ditt tillstånd och useState-kroken returnerar en array som innehåller två variabler: den första är tillståndet variabel som du kan använda för att referera till ditt tillstånd, och den andra en funktion som du använder för att ändra värdet på tillståndet: ganska enkelt.
Vad sägs om att vi ser det i praktiken? Namnet som återges i navigeringsfältet – övre högra sidan – är en tillståndsvariabel som definieras i appens huvudkomponent. Samma variabel skickas till både Navbar och Hemsida. Det enkla formuläret som används ovan uppdaterar faktiskt tillståndsvariabeln "namn" från EditPage-komponenten. Nedersta raden är detta: useState är en kärnhak som accepterar ett initialtillstånd som en parameter och returnerar två variabler som har två värden, tillståndsvariabeln som innehåller initialtillståndet och en sätterfunktion för samma tillståndsvariabel.
Låt oss bryta ner det och se hur det implementerades i första hand.
- Skapa tillståndsvariabeln "namn":
./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: ${name}`);
return (...)};
Rekvisita
Rekvisita är en av de grundläggande byggstenarna i en react-komponent, begreppsmässigt, om du tänker på en React-funktionell komponent som en Javascript-funktion, så är rekvisita inte mer än funktionsparametrarna, en kombination av rekvisita och useState-kroken kan erbjuda dig ett solidt ramverk för att hantera tillstånd över en enkel React-applikation.
React rekvisita skickas som attribut till anpassade komponenter. Attribut som skickas som rekvisita kan destruktureras från rekvisitaobjektet när man accepterar det som ett argument, liknande detta:
Passerande rekvisita
<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>
Rekvisita kan accepteras och användas i en funktionell komponent som liknar normala funktionsargument. Det är för att "namn" skickas som en rekvisita till Home-komponenten, som vi kan återge det i samma komponent. I följande exempel accepterar vi den godkända rekvisiten med hjälp av destruktureringssyntaxen för att extrahera namnegenskapen från props-objektet.
Accepterar rekvisita
./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>
... ...
Proffstips
Öppna webbläsarens konsol och lägg märke till hur alla komponenter som använder "namn"-rekvisiten renderas om när tillståndet ändras. När du manipulerar en tillståndsvariabel kommer React att lagra nästa tillstånd, rendera din komponent igen med de nya värdena och uppdatera användargränssnittet.
useState nackdelar
Props-Drilling
Props drilling är en term för att hänvisa till en hierarki av komponenter där en uppsättning komponenter behöver vissa rekvisita som tillhandahålls av en överordnad komponent, en vanlig lösning som oerfaren utvecklare vanligtvis använder är att skicka dessa rekvisita genom hela kedjan av komponenter, problemet med detta tillvägagångssätt är att en förändring i någon av dessa rekvisita kommer att utlösa hela kedjan av komponenter att återrendera, vilket effektivt saktar ner hela applikationen som ett resultat av dessa onödiga renderingar, komponenterna i mitten av kedjan som inte kräver dessa rekvisita fungera som medium för överföring av rekvisita.
Exempel:
-
En tillståndsvariabel definierades i appens huvudkomponent med hjälp av useState()-kroken
-
En namnrekvisita skickades till Navbar-komponenten
-
Samma rekvisita accepteras i Navbar och skickas som rekvisita igen till UserDropdown-komponenten
-
UserDropdown är det sista underordnade elementet som accepterar rekvisiten.
./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>
</>
);
}
Föreställ dig hur svårt det är att underhålla en React-app med olika lager av komponenter som alla passerar och återger samma tillstånd.
Växande komplexitet och kodkvalitet
Med användningen av useState och rekvisita som det enda sättet för tillståndshantering i en react-applikation kan kodbasen snabbt växa i komplexitet och behöva hantera tiotals eller hundratals tillståndsvariabler, som kan vara dubbletter av varandra, utspridda över olika filer och komponenter kan vara ganska skrämmande, varje förändring av en given tillståndsvariabel kommer att kräva noggrant övervägande av beroenden mellan komponenter för att undvika eventuell ytterligare återgivning i en redan långsam applikation.
Andra tillvägagångssätt: Context API
Context API är Reacts försök att lösa nackdelarna med att endast använda rekvisita och useState för tillståndshantering, speciellt context API kommer som ett svar på problemet som tidigare nämnts angående behovet av att skicka rekvisita ner i hela komponentträdet. Med användning av sammanhang kan du definiera ett tillstånd för data som du anser vara globalt och få tillgång till dess tillstånd från valfri punkt i komponentträdet: ingen mer prop-borrning.
Det är viktigt att påpeka att kontext-API:et ursprungligen utformades för att lösa problemet med global datadelning, data som UI-teman, autentiseringsinformation som är vårt användningsfall, språk och sådant), för andra typer av data som behöver delas bland mer än en komponent men är inte nödvändigtvis global för alla applikationer, sammanhang kanske inte är det bästa alternativet, beroende på användningsfallet kan du överväga andra tekniker som komponentkomposition som inte omfattas av denna artikel.
Byter till kontext
Skapar Auth-kontexten
createContext är enkel, den skapar en ny kontextvariabel, den tar in en enda valfri parameter: standardvärdet för kontextvariabeln.
Låt oss se detta i aktion, skapa först en ny mapp "contexts" och en ny fil inuti den "Auth.jsx". För att skapa ett nytt sammanhang måste vi anropa funktionen createContext() och tilldela det returnerade värdet till en ny variabel Auth som kommer att exporteras härnäst:
./src/contexts/Auth.jsx
import { createContext } from "react";
export const Auth = createContext();
Ange autentiseringskontext
Nu måste vi exponera kontextvariabeln "Auth" som vi skapade tidigare, för att uppnå detta använder vi kontextleverantören, kontextleverantören är en komponent som har en "värde"-prop, vi kan använda denna prop för att dela ett värde – namn – tvärs över komponentträdet, genom att linda in komponentträdet med leverantörskomponenten, gör vi det värdet tillgängligt från var som helst inuti komponentträdet, utan att behöva skicka ned den rekvisiten till varje underordnad komponent individuellt.
I vårt autentiseringsexempel behöver vi en variabel för att hålla användarobjektet, och någon slags setter för att manipulera den tillståndsvariabeln, detta är ett perfekt användningsfall för useState. När du använder Context måste du se till att du definierar den data du vill tillhandahålla och skickar den data – användaren i vårt exempel – till alla komponentträd som är kapslade inuti, så definiera en ny tillståndsvariabelanvändare inuti leverantörskomponenten och slutligen skickar vi både användare och setUser in i en array som värdet som leverantören kommer att exponera för komponentträdet:
./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 annan sak vi kan göra är att flytta vår tillståndsvariabel "namn" från huvudappkomponenten till Auth-kontexten och exponera den för kapslade 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 återstår bara att kapsla in vår app i samma AuthProvider-komponent som vi just exporterade.
./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>
);
Eftersom vi renderar underordnad prop i Auth.Provider, kan alla element som är kapslade inuti AuthProvider-komponenten nu konsumera den värdeprop som vi skickade till Auth.Provider. Det kan verka förvirrande, men när du väl har experimenterat med det, försök att tillhandahålla och konsumera ett globalt – sammanhang – tillstånd. Det var trots allt vettigt för mig efter att ha experimenterat med Context API.
Konsumerar Auth-kontexten
Det sista steget är enkelt, vi använder kontextkroken "useContext" för att komma åt värdet som kontextleverantören för "Auth" tillhandahåller, vilket i vårt fall är arrayen som innehåller användare och setUser, i följande kod kan vi använd useContext för att konsumera Auth-kontexten i navigeringsfältet. Detta är bara möjligt eftersom Navbar är kapslad inuti appkomponenten, och eftersom AuthProvider omsluter appkomponenten kan värdepropen endast konsumeras med useContext-kroken. Ett annat fantastiskt verktyg som React tillhandahåller direkt för att hantera all data som kan nås globalt och även manipuleras av vilken konsumentkomponent som helst.
./src/components/Navbar.jsx
export default function Navbar() {
const [name, setName] = useContext(Auth);
console.log(name)
return (...)};
Lägg märke till hur vi inte accepterar någon rekvisita längre i funktionskomponenten Navbar(). Vi använder useContext(Auth) för att konsumera Auth-kontexten istället, och tar både namn och setName. Detta betyder också att vi inte längre behöver skicka rekvisitan till Navbar:
./src/App.jsx
// ... //
return (
<BrowserRouter>
<div className="h-screen">
<Navbar/> // no need to pass prop anymore
// ... //
Uppdatering av Auth-kontexten
Vi kan också använda den tillhandahållna setName-funktionen för att manipulera tillståndsvariabeln "name":
./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");
Introducerar useReducer() hook
I det föregående exemplet använde vi context API för att hantera och dela vårt tillstånd nere i komponentträdet, du kanske har märkt att vi fortfarande använder useState som bas för vår logik, och detta är för det mesta ok eftersom vårt tillstånd fortfarande är ett enkelt objekt vid denna tidpunkt, men om vi skulle utöka kapaciteten för vårt autentiseringsflöde kommer vi definitivt att behöva lagra mer än bara e-postmeddelandet från användaren som för närvarande är inloggad, och det är här vi återgår till de begränsningar vi tidigare gick in på i fråga om användningen av useState med komplext tillstånd, lyckligtvis löser React detta problem genom att tillhandahålla ett alternativ till useState för att hantera komplext tillstånd: ange useReducer.
useReducer kan ses som en generaliserad version av useState, den tar två parametrar: en reducerfunktion och ett initialt tillstånd.
Inget intressant att notera för initialtillståndet, magin händer inuti reducerfunktionen: den kontrollerar vilken typ av åtgärd som inträffade, och beroende på den åtgärden kommer reduceraren att avgöra vilka uppdateringar som ska tillämpas på tillståndet och returnera dess nya värde .
Om du tittar på koden nedan har reduceringsfunktionen två möjliga åtgärdstyper:
-
"LOGGA IN": i vilket fall kommer användartillståndet att uppdateras med de nya användaruppgifterna som tillhandahålls i handlingens nyttolast.
-
"LOGGA UT": i vilket fall kommer användartillståndet att tas bort från det lokala minnet och återställas till null.
Det är viktigt att notera att åtgärdsobjektet innehåller både ett typfält som bestämmer vilken logik som ska tillämpas och ett valfritt nyttolastfält för att tillhandahålla data som är nödvändig för att tillämpa den logiken.
Slutligen returnerar useReducer-kroken det aktuella tillståndet och en sändningsfunktion som vi använder för att skicka en åtgärd till reduceraren.
För resten av logiken är den identisk med föregående exempel:
./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>
);
};
Skickar åtgärder istället för att använda setState-inställningsfunktionen – t.ex.: setName –
Som vi nyss nämnde använder vi sändningsfunktionen för att skicka en åtgärd till reduceraren, i följande kod initierar vi en LOGIN-åtgärd och vi tillhandahåller användarens e-post som en nyttolast, nu kommer användartillståndet att uppdateras och denna ändring kommer att utlösas en återgivning av alla komponenter som abonneras på användartillståndet. Det är viktigt att påpeka att en omrendering endast kommer att triggas om en faktisk förändring i tillståndet inträffar, ingen omrendering om reduceraren returnerar samma tidigare tillstånd.
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 (...)};
Användarnamn
Proffstips
Lägg märke till hur användarobjektet vi får efter en lyckad inloggning nu lagras i localStorage.
Anpassad krok för inloggning
Nu när vi har ett bra grepp om useReducer kan vi ytterligare kapsla in vår inloggnings- och utloggningslogik i sina egna separata anpassade krokar, genom ett enda anrop till inloggningskroken kan vi hantera ett API-anrop till inloggningsvägen, hämta den nya användaren autentiseringsuppgifter och lagra dem i den lokala lagringen, skicka ett LOGIN-anrop för att uppdatera användartillståndet, allt medan du hanterar felhantering:
./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 };
};
Obs: för mer avancerade React-användare bland läsarna kanske du undrar varför vi inte använde den lata initialiseringsfunktionen i useReducer för att hämta användaruppgifterna, useReducer accepterar en tredje valfri parameter som kallas en init-funktion, denna funktion används i fall vi måste tillämpa lite logik innan vi kan få det initiala värdet av staten, anledningen till att vi inte valde detta är en enkel fråga om separation av bekymmer, koden på detta sätt är enklare att förstå och som ett resultat enklare att underhålla .
Login sida
Så här ser den övre delen av vår inloggningssida ut efter att ha använt useLogin()-kroken för att extrahera inloggningsfunktionen och anropa inloggningsfunktionen med inloggningsuppgifter som skickats av en användare:
// ... ... //
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 inaktiverar också funktionen Skicka när användaren skickar formuläret:
<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>
Och rendera eventuella autentiseringsfel vi får från vår backend:
{
error && <span className="text-red-500 p-2">{error.message}</span>;
}
Upprätthålla användarstatus
Du kanske undrar varför vi behöver lagra användarobjektet i localStorage, enkelt uttryckt, vi vill behålla användaren inloggad så länge som token inte har gått ut. Att använda localStorage är ett bra sätt att lagra bitar av JSON, precis som i vårt exempel. Lägg märke till hur staten raderas ut om du uppdaterar sidan efter att du har loggat in. Detta kan enkelt lösas med en useEffect-hook för att kontrollera om vi har ett lagrat användarobjekt i localStorage, om det finns ett så loggar vi in användaren automatiskt:
// ... ... //
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>
);
};
Anpassade krokar för utloggning
Samma sak gäller med utloggningskroken, här skickar vi en LOGGAUT-åtgärd för att ta bort de aktuella användaruppgifterna från både staten och den lokala lagringen:
./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 };
};
Användarlogga ut
För att logga ut en användare, låt oss lägga till en klickhändelse till knappen Logga ut som finns i UserDropdown.jsx, och hantera det därefter:
./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>
// ... ... //
Skydda reaktionsrutter
Det sista steget i implementeringen av vår applikation är att utnyttja det globala användartillståndet för att kontrollera användarnavigering, en snabb påminnelse om vilket beteende vi ska uppnå: till en början hälsas användaren av inloggningssidan, från den punkten kan användaren bara komma åt hemsidan efter en lyckad inloggning kommer användaren på samma sätt att omdirigeras till inloggningssidan vid utloggning.
Vi uppnår detta med hjälp av react-router-dom-biblioteket genom att definiera 2 rutter: "/" och "/login", vi styr vilken komponent som ska renderas vid varje rutt med hjälp av det globala autentiseringstillståndet, autentisering som utvärderas till null representerar en oautentiserad användare och vice versa:
./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;
Sammanfattningsdiagram
Sammanfatta
I den här artikeln försökte vi ta itu med det enkla men mycket vanliga användningsfallet att implementera tillståndshantering för ett autentiseringsarbetsflöde, genom att gå igenom de olika tillvägagångssätten, logiken bakom var och en och deras avvägningar. State management i ramverk på klientsidan och i React i synnerhet är ett av de mest omtalade ämnena i frontend-gemenskapen, helt enkelt för att det antingen kan göra eller bryta prestandan och skalbarheten för din applikation. Den stora mängden av olika tekniker, mönster, bibliotek och verktyg som försöker lösa denna fråga om statlig förvaltning är överväldigande, vårt mål var att ge dig en gedigen förståelse för praxis så att du kan implementera den i din egen applikation, för mer avancerad tekniker och mönster för tillståndshantering i mer komplexa applikationer, kolla in vår nästa artikel där vi kommer in på redux för skalbar tillståndshantering i React.
Kommer snart
ContextAPI vs Redux Toolkit
Redux hade många år av adoption i samhället innan Redux-verktygslådan, faktiskt, RTK introducerades som en startmall för att bootstrap redux-tillståndshantering i nya applikationer (dess ursprungliga namn var "redux-starter-kit" i oktober 2019), även om idag finns det en allmän konsensus mellan Redux-underhållarna och communityn att Redux-verktygslådan är det giltiga sättet att arbeta med redux. RTX sammanfattar mycket av Redux-logiken vilket gör det lättare att använda, med mycket mindre utförligt, och uppmuntrar utvecklare att följ de bästa metoderna, en huvudskillnad mellan de två är att Redux byggdes för att vara ointressant, ger ett minimalt API och förväntar sig att utvecklare ska göra det mesta av det tunga arbetet genom att skriva sina egna bibliotek för vanliga uppgifter och hantera kodstruktur, detta resulterade i långsam utvecklingstid och rörig kod, Redux verktygslåda lades till som ett extra lager av abstraktion som förhindrade utvecklare att hamna i dess vanliga fallgropar, se den officiella dokumentationen för mer insikter och resonemanget från dess underhållare här.