Ang React ay ang go-to framework para sa pagbuo ng mga dynamic na client-side na application para sa maraming developer. Ang dynamic na katangian ng mga application na ito ay nagmumula sa flexibility at ang pinahabang listahan ng mga kakayahan at feature na posible sa panig ng kliyente na nagbigay-daan sa mga developer na bumuo ng ganap na mga application na naglo-load sa browser sa loob ng ilang segundo, isang tagumpay na hindi posible (o napakahirap) sa mga araw ng static na web.
Sa pagpapalawak na ito sa mga posibilidad, dumating ang konsepto ng pamamahala ng estado, habang lumalaki ang pagiging kumplikado sa mga application sa panig ng kliyente, ang pangangailangan ng pagpapanatiling lokal na estado ay lumalaki upang maging isang bottleneck sa sarili nito kung hindi mahawakan nang tama at naisip na may scalability sa isip.
Ang isyung ito ay natugunan ng maraming mga balangkas, na sumusunod sa iba't ibang mga diskarte at nakatuon sa iba't ibang hanay ng mga subproblema, kaya naman mahalagang magkaroon ng mataas na antas ng pag-unawa sa ecosystem ng balangkas ng pagpili upang masuri ang mga pangangailangan ng bawat aplikasyon at gumamit ng tamang diskarte kasunod ng mga iyon. mga sukatan. Ang artikulong ito ay magbibigay sa iyo ng maikling pangkalahatang-ideya ng mga karaniwang isyu sa pamamahala ng estado at subukang magpakilala ng iba't ibang diskarte (useState, Context API) bilang tugon dito. Bagama't ang artikulong ito ay magpapakita ng maraming solusyon, ito ay tututuon lamang sa mga hamon sa mas maliit na sukat, sasaklawin namin ang mga mas advanced na paksa sa mga paparating na artikulo.
Workflow ng Pagpapatotoo
Ang code na ipinakita sa buong artikulo ay makikita dito.
Maaaring ma-access ang isang live na link ng preview dito.
Isaalang-alang ang kaso kung saan ipinatupad namin ang proseso ng pagpapatunay para sa isang React na aplikasyon.
Gaya ng ipinapakita sa GIF sa itaas, gusto naming payagan ang mga user na mag-login o mag-sign up sa aming application gamit ang mga kredensyal. Kung ang mga wastong kredensyal ay ibinigay, ang user ay mai-log in, ang application ay awtomatikong mag-navigate sa home page at ang user ay maaaring magpatuloy sa paggamit ng application.
Katulad nito, Kung mag-log out ang gumagamit, ang mga mapagkukunan ng home page ay mapoprotektahan sa likod ng pag-login, ang pahina sa pag-login ay ang tanging page na maa-access ng user.
Kung iisipin ang daloy ng trabaho na ito sa mga tuntunin ng pagpapatupad, magkakaroon tayo ng pangunahing bahagi na pinangalanang App, ire-redirect ng bahagi ng App ang user sa isa sa dalawang page: Home o Login, ang kasalukuyang estado ng user (naka-log in, naka-log out) ang magdidikta kung alin pahina kung saan na-redirect ang user, ang pagbabago sa kasalukuyang estado ng user (halimbawa, pagbabago mula sa naka-log in hanggang sa naka-log out) ay dapat mag-trigger ng agarang pag-redirect sa kaukulang pahina.
Gaya ng ipinapakita sa ilustrasyon sa itaas, gusto naming isaalang-alang ng bahagi ng App ang kasalukuyang estado at i-render lang ang isa sa dalawang page – Home o Login – batay sa kasalukuyang estadong iyon.
Kung null ang user, nangangahulugan ito na wala kaming na-authenticate na user, kaya awtomatiko kaming nagna-navigate sa login page at pinoprotektahan ang home page gamit ang conditional rendering. Kung umiiral ang gumagamit, ginagawa namin ang kabaligtaran.
Ngayon na mayroon na tayong matibay na pag-unawa sa kung ano ang dapat ipatupad, tuklasin natin ang ilang mga opsyon, ngunit i-set up muna natin ang ating React project,
Ang project repo ay naglalaman ng isang sample na backend application na gagamitin namin para ipatupad ang frontend side (hindi namin ito papasok dahil hindi ito ang pangunahing pokus dito ngunit ang code ay sadyang pinananatiling simple para hindi ka mahirapan dito )
Nagsisimula kami sa pamamagitan ng paglikha ng mga sumusunod na pahina at mga bahagi:
-
Home Page
-
Pahina sa Pag-login
-
Pahina ng EditUser
-
Navbar Component
-
Component ng UserDropdown
Ang isang React na application na may maraming pahina ay nangangailangan ng wastong pag-navigate, para doon ay magagamit natin ang react-router-dom upang lumikha ng isang pandaigdigang konteksto ng router ng browser at magrehistro ng iba't ibang mga ruta ng React.
yarn add react-router-dom
Alam naming mas gusto ng maraming mambabasa na sumunod kasama ng mga tutorial, kaya narito ang template ng panimula upang mapabilis ka. Gumagamit ang starter branch na ito ng DaisyUI para sa mga paunang natukoy na bahagi ng TailwindCSS JSX. Kabilang dito ang lahat ng mga bahagi, mga pahina at ang router na naka-setup na. Kung isinasaalang-alang mo ang pagsunod kasama ng tutorial na ito, ang pagbuo ng buong daloy ng auth sa pamamagitan ng iyong sarili na sumusunod sa mga direktang hakbang, magsimula sa pamamagitan ng pag-forking muna ng repository. Pagkatapos mong i-fork ang repository, i-clone ito at magsimula sa start-herebranch:
git clone git@github.com:<your-username>/fullstack-resourcify.git
Sa sandaling hilahin mo ang simula-dito na sangay:
-
Buksan ang proyekto gamit ang iyong gustong editor ng code
-
Baguhin ang direktoryo sa frontend/
-
Mag-install ng mga dependencies: sinulid
-
Magsimula ng development server: yarn dev·
Ang preview ay dapat magmukhang ganito:
Ang pangalang na-render sa Navbar – kanang bahagi sa itaas – ay isang variable ng estado na tinukoy sa pangunahing bahagi ng App. Ang parehong variable ay ipinapasa sa parehong Navbar at Home page. Ang simpleng form na ginamit sa itaas ay aktwal na ina-update ang variable ng estado ng "pangalan" mula sa bahagi ng EditPage.
Ang dalawang diskarte na ipinakita sa ibaba ay pupunta sa mga detalye ng pagpapatupad:
Unang Diskarte: useState
useState()
Ang useState ay isa sa mga pinakakaraniwang ginagamit na React hook, pinapayagan ka nitong lumikha at mag-mutate ng estado sa isang React Functional na bahagi. Ang bahagi ng useState ay may talagang simpleng pagpapatupad at madaling gamitin: upang lumikha ng isang bagong estado, kailangan mong tawagan ang useState na may paunang halaga ng iyong estado at ang useState hook ay magbabalik ng array na naglalaman ng dalawang variable: ang una ay ang estado variable na maaari mong gamitin upang i-reference ang iyong estado, at ang pangalawa ay isang function na ginagamit mo upang baguhin ang halaga ng estado: medyo prangka.
Paano kung makita natin iyon sa aksyon? Ang pangalang na-render sa Navbar – kanang bahagi sa itaas – ay isang variable ng estado na tinukoy sa pangunahing bahagi ng App. Ang parehong variable ay ipinapasa sa parehong Navbar at Home page. Ang simpleng form na ginamit sa itaas ay aktwal na ina-update ang variable ng estado ng "pangalan" mula sa bahagi ng EditPage. Bottom line ay ito: useState ay isang core hook na tumatanggap ng isang paunang estado bilang isang parameter at nagbabalik ng dalawang variable na may hawak na dalawang value, ang state variable na naglalaman ng inisyal na estado, at isang setter function para sa parehong state variable.
Hatiin natin ito, at tingnan kung paano ito ipinatupad noong una.
- Paglikha ng variable ng estado ng "pangalan":
./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 (...)};
Props
Ang mga prop ay isa sa mga pangunahing elemento ng isang react component, sa konsepto, kung sa tingin mo ng isang React functional component bilang isang Javascript function, ang props ay hindi hihigit sa mga parameter ng function, ang pagsasama-sama ng mga props at ang useState hook ay maaaring mag-alok sa iyo ng isang solidong framework para sa pamamahala ng estado sa isang simpleng React application.
Ang mga react props ay ipinapasa bilang mga katangian sa mga custom na bahagi. Ang mga katangiang ipinasa bilang props ay maaaring sirain mula sa props object kapag tinatanggap ito bilang argumento, katulad nito:
Nagpapasa ng mga props
<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>
Maaaring tanggapin at gamitin ang mga props sa loob ng isang functional na bahagi na katulad ng mga argumento ng normal na function. Ito ay dahil ang "pangalan" ay ipinasa bilang isang prop sa bahagi ng Home, na maaari naming i-render ito sa parehong bahagi. Sa sumusunod na halimbawa, tinatanggap namin ang naipasa na prop gamit ang destructuring syntax para i-extract ang name property mula sa props object.
Tumatanggap ng props
./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
Buksan ang console ng browser, at pansinin kung paano muling nagre-render ang lahat ng bahagi na gumagamit ng prop na "pangalan" kapag nagbago ang estado. Kapag nagmamanipula ng isang variable ng estado, iimbak ng React ang susunod na estado, ire-render muli ang iyong component kasama ang mga bagong value, at i-update ang UI.
useState drawbacks
Props-Pagbabarena
Ang props drilling ay isang terminong tumutukoy sa isang hierarchy ng mga bahagi kung saan ang isang set ng mga bahagi ay nangangailangan ng ilang partikular na props na ibinigay ng isang parent na bahagi, isang karaniwang solusyon na karaniwang ginagamit ng mga bagitong developer ay ang pagpasa sa mga props na ito sa buong hanay ng mga bahagi, ang isyu dito Ang diskarte ay ang pagbabago sa alinman sa mga props na ito ay magti-trigger sa buong chain ng mga bahagi na muling mag-render, na epektibong nagpapabagal sa buong aplikasyon bilang resulta ng mga hindi kinakailangang pag-render na ito, ang mga bahagi sa gitna ng chain na hindi nangangailangan ng mga props na ito. kumilos bilang mga daluyan para sa paglilipat ng mga props.
Halimbawa:
-
Ang isang variable ng estado ay tinukoy sa pangunahing bahagi ng App gamit ang useState() hook
-
Ipinasa ang isang prop ng pangalan sa bahagi ng Navbar
-
Parehong prop na tinanggap sa Navbar at ipinasa bilang prop minsan pa sa UserDropdown component
-
Ang UserDropdown ay ang huling child element na tumatanggap ng prop.
./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>
</>
);
}
Isipin kung gaano kahirap magpanatili ng React app na may iba't ibang layer ng mga bahagi na lahat ay pumasa at nagre-render sa parehong estado.
Lumalagong Kumplikado at Kalidad ng Code
Sa paggamit ng useState at props bilang ang tanging paraan ng pamamahala ng estado sa isang react na application, ang codebase ay maaaring mabilis na lumago sa pagiging kumplikado, na kailangang pamahalaan ang sampu o daan-daang mga variable ng estado, na maaaring mga duplicate ng bawat isa, na nakakalat sa iba't ibang mga file at Ang mga bahagi ay maaaring medyo nakakatakot, ang anumang pagbabago sa isang naibigay na variable ng estado ay mangangailangan ng maingat na pagsasaalang-alang ng mga dependency sa pagitan ng mga bahagi upang maiwasan ang anumang potensyal na karagdagang muling pag-render sa isang mabagal na aplikasyon.
Pangalawang Diskarte: Context API
Ang Context API ay ang pagtatangka ng React na lutasin ang mga disbentaha ng paggamit lamang ng mga props at useState para sa pamamahala ng estado, lalo na, ang context API ay dumating bilang isang sagot sa isyu na naunang nabanggit tungkol sa pangangailangang ipasa ang mga props sa buong component tree. Sa paggamit ng konteksto, maaari mong tukuyin ang isang estado para sa data na itinuturing mong pandaigdigan at i-access ang estado nito mula sa anumang punto sa puno ng bahagi: wala nang prop-drill.
Mahalagang ituro na ang context API ay unang ginawa upang malutas ang isyu ng pandaigdigang pagbabahagi ng data, data tulad ng mga tema ng UI, impormasyon sa pagpapatunay na kung saan ay ang aming kaso ng paggamit, mga wika at iba pa), para sa iba pang mga uri ng data na kailangang ibahagi sa pagitan ng higit sa isang bahagi ngunit hindi kinakailangang global sa lahat ng application, maaaring hindi ang konteksto ang pinakamahusay na opsyon, depende sa kaso ng paggamit, maaari mong isaalang-alang ang iba pang mga diskarte gaya ng component composition na wala sa saklaw ng artikulong ito.
Lumipat sa Konteksto
Paggawa ng konteksto ng Auth
Ang createContext ay diretso, lumilikha ito ng bagong variable ng konteksto, tumatagal ito sa isang opsyonal na parameter: ang default na halaga ng variable ng konteksto.
Tingnan natin ito sa pagkilos, gumawa muna ng bagong folder na "mga konteksto" at isang bagong file sa loob nito na "Auth.jsx". Upang lumikha ng bagong konteksto, kailangan nating gamitin ang createContext() function, italaga ang ibinalik na halaga sa isang bagong variable na Auth na susunod na ie-export:
./src/contexts/Auth.jsx
import { createContext } from "react";
export const Auth = createContext();
Ibigay ang Awth Context
Ngayon ay kailangan nating ilantad ang variable ng konteksto ng "Auth" na nilikha namin dati, para makamit ito ay ginagamit namin ang provider ng konteksto, ang provider ng konteksto ay isang bahagi na mayroong prop na "value", maaari naming gamitin ang prop na ito upang magbahagi ng isang halaga – pangalan – sa kabuuan ng component tree, sa pamamagitan ng pagbalot sa component tree gamit ang provider component ginagawa naming accessible ang value na iyon mula saanman sa loob ng component tree, nang hindi kailangang ipasa ang prop na iyon sa bawat component ng bata nang paisa-isa.
Sa aming halimbawa ng pagpapatotoo, kailangan namin ng variable para hawakan ang object ng user, at ilang uri ng setter para manipulahin ang state variable na iyon, ito ay isang perpektong use case para sa useState. Kapag gumagamit ng Konteksto, kailangan mong tiyakin na tinutukoy mo ang data na gusto mong ibigay, at ipinapasa ang data na iyon - ang user sa aming halimbawa - sa lahat ng puno ng sangkap na nakalagay sa loob, kaya't tukuyin ang isang bagong user ng variable ng estado sa loob ng bahagi ng provider at sa wakas ay ipinapasa namin ang parehong user at setUser sa loob ng isang array bilang ang halaga na ilalantad ng provider sa component tree:
./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>;
};
Ang isa pang bagay na maaari naming gawin ay ilipat ang aming "pangalan" na variable ng estado mula sa pangunahing bahagi ng app patungo sa konteksto ng Auth at ilantad ito sa mga nested na bahagi:
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>;
};
Ngayon ang natitira na lang ay ang paglalagay ng aming app sa parehong bahagi ng AuthProvider na kaka-export lang namin.
./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>
);
Dahil nire-render namin ang prop ng mga bata sa loob ng Auth.Provider, lahat ng elemento na naka-nest sa loob ng component ng AuthProvider ay nagagamit na ngayon ang value prop na ipinasa namin sa Auth.Provider. Maaaring mukhang nakakalito, ngunit sa sandaling mag-eksperimento ka dito, subukang magbigay at kumonsumo ng pandaigdigang - Konteksto - estado. Pagkatapos ng lahat, ito ay naging makabuluhan lamang sa akin pagkatapos mag-eksperimento sa Context API.
Kinukonsumo ang Konteksto ng Auth
Ang huling hakbang ay diretso, ginagamit namin ang context hook na "useContext" para ma-access ang value na ibinibigay ng context provider ng "Auth", na sa aming kaso ay ang array na naglalaman ng user at setUser, sa sumusunod na code, nagagawa naming gamitin ang useContext upang ubusin ang konteksto ng Auth sa loob ng Navbar. Posible lang ito dahil ang Navbar ay naka-nest sa loob ng App component, at dahil ang AuthProvider ay bumabalot sa App component, ang value prop ay maaaring gamitin gamit ang useContext hook lang. Ang isa pang kahanga-hangang tool na React ay nagbibigay ng out of the box upang pamahalaan ang anumang data na maaaring ma-access sa buong mundo at manipulahin din ng anumang bahagi ng consumer.
./src/components/Navbar.jsx
export default function Navbar() {
const [name, setName] = useContext(Auth);
console.log(name)
return (...)};
Pansinin kung paano hindi na kami tumatanggap ng anumang props sa Navbar() functional component. Gumagamit kami ng useContext(Auth) upang ubusin ang konteksto ng Auth sa halip, kinuha ang parehong pangalan at setName. Nangangahulugan din ito na hindi na namin kailangang ipasa ang prop sa Navbar:
./src/App.jsx
// ... //
return (
<BrowserRouter>
<div className="h-screen">
<Navbar/> // no need to pass prop anymore
// ... //
Pag-update ng Auth Context
Magagamit din namin ang ibinigay na function na setName upang manipulahin ang variable ng estado ng "pangalan":
./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");
Ipinapakilala ang useReducer() hook
Sa nakaraang halimbawa, ginamit namin ang context API para pamahalaan at ibahagi ang aming estado pababa sa component tree, maaaring napansin mo na ginagamit pa rin namin ang useState bilang batayan ng aming logic, at ito ay halos ok dahil ang aming estado ay isang simpleng bagay pa rin. sa puntong ito ngunit kung papalawakin namin ang mga kakayahan ng aming daloy ng pagpapatotoo ay tiyak na kakailanganin naming mag-imbak ng higit pa sa email ng user na kasalukuyang naka-log in, at dito kami babalik sa mga limitasyon na dati naming pinuntahan patungkol sa ang paggamit ng useState na may kumplikadong estado, sa kabutihang palad, niresolba ng React ang isyung ito sa pamamagitan ng pagbibigay ng alternatibo sa useState para sa pamamahala ng kumplikadong estado: ipasok ang useReducer.
Ang useReducer ay maaaring isipin bilang isang pangkalahatang bersyon ng useState, nangangailangan ito ng dalawang parameter: isang reducer function at isang paunang estado.
Walang kawili-wiling tandaan para sa paunang estado, ang magic ay nangyayari sa loob ng reducer function: sinusuri nito ang uri ng aksyon na naganap, at depende sa aksyon na iyon, ang reducer ay tutukuyin kung anong mga update ang ilalapat sa estado at ibalik ang bagong halaga nito. .
Sa pagtingin sa code sa ibaba, ang reducer function ay may dalawang posibleng uri ng pagkilos:
-
"LOGIN": kung saan ang katayuan ng user ay maa-update sa mga bagong kredensyal ng user na ibinigay sa loob ng action payload.
-
"LOGOUT": kung saan ang katayuan ng user ay aalisin sa lokal na storage at ibabalik sa null.
Mahalagang tandaan na ang action object ay naglalaman ng parehong field ng uri na tumutukoy kung anong logic ang ilalapat at isang opsyonal na payload field upang magbigay ng data na kinakailangan para sa paglalapat ng logic na iyon.
Sa wakas, ibinabalik ng useReducer hook ang kasalukuyang estado at isang dispatch function na ginagamit namin para magpasa ng aksyon sa reducer.
Para sa natitirang bahagi ng lohika, ito ay magkapareho sa nakaraang halimbawa:
./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>
);
};
Pagpapadala ng mga aksyon sa halip na gamitin ang setState setter function – hal: setName –
Gaya ng kasasabi lang namin, ginagamit namin ang dispatch function para magpasa ng aksyon sa reducer, sa sumusunod na code, magsisimula kami ng LOGIN action at ibibigay namin ang email ng user bilang payload, ngayon ay maa-update ang status ng user at magti-trigger ang pagbabagong ito. isang muling pag-render ng lahat ng bahaging naka-subscribe sa estado ng user. Mahalagang ituro na ang isang muling pag-render ay ma-trigger lamang kung ang isang aktwal na pagbabago sa estado ay nangyari, walang muling pag-render kung ang reducer ay nagbabalik ng parehong nakaraang estado.
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 (...)};
Login ng User
Pro Tip
Pansinin kung paano nakaimbak ang object ng user na natatanggap namin pagkatapos ng matagumpay na pag-log in sa localStorage.
Custom na hook para sa Login
Ngayon na mayroon na kaming mahusay na pagkakahawak sa useReducer, maaari pa naming i-encapsulate ang aming logic sa pag-login at pag-logout sa sarili nilang magkahiwalay na custom na mga hook, sa pamamagitan ng isang tawag sa Login hook, maaari naming pangasiwaan ang isang API na tawag sa ruta ng pag-login, kunin ang bagong user mga kredensyal at iimbak ang mga ito sa lokal na imbakan, magpadala ng LOGIN na tawag upang i-update ang estado ng user, lahat habang nakikitungo sa paghawak ng error:
./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 };
};
Tandaan: para sa mas advanced na mga user ng React sa mga mambabasa, maaaring nagtataka ka kung bakit hindi namin ginamit ang lazy initialization feature ng useReducer para kunin ang mga kredensyal ng user, tumatanggap ang useReducer ng ikatlong opsyonal na parameter na tinatawag na init function, ginagamit ang function na ito kung sakaling kailangan naming maglapat ng ilang lohika bago namin makuha ang paunang halaga ng estado, ang dahilan kung bakit hindi namin pinili ito ay isang simpleng bagay ng paghihiwalay ng mga alalahanin, ang code sa paraang ito ay mas madaling maunawaan at bilang isang resulta ay mas simple upang mapanatili .
Pahina sa Pag-login
Ganito ang hitsura ng tuktok na bahagi ng aming Login page pagkatapos gamitin ang useLogin() hook upang i-extract ang login function, at i-invoke ang login function na may mga kredensyal na isinumite ng isang user:
// ... ... //
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 (...)
// ... ... //
Hindi rin namin pinapagana ang function na Isumite kapag isinumite ng user ang form:
<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>
At ang pag-render ng anumang mga error sa pagpapatotoo na natatanggap namin mula sa aming backend:
{
error && <span className="text-red-500 p-2">{error.message}</span>;
}
Pagpapanatili ng katayuan ng gumagamit
Maaaring nagtataka ka kung bakit kailangan naming iimbak ang object ng user sa localStorage, sa madaling salita, gusto naming panatilihing naka-sign in ang user hangga't hindi pa nag-expire ang token. Ang paggamit ng localStorage ay isang mahusay na paraan upang mag-imbak ng mga bit ng JSON, tulad ng sa aming halimbawa. Pansinin kung paano mabubura ang estado kung ire-refresh mo ang pahina pagkatapos mag-log in. Madali itong mareresolba gamit ang useEffect hook upang suriin kung mayroon kaming nakaimbak na object ng user sa localStorage, kung mayroon, awtomatiko kaming nag-log in sa user:
// ... ... //
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>
);
};
Mga custom na hook para sa Logout
Ang parehong bagay ay naaangkop sa Logout hook, dito kami ay nagpapadala ng isang LOGOUT na aksyon upang alisin ang kasalukuyang mga kredensyal ng user mula sa parehong estado at lokal na imbakan:
./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 };
};
User Logout
Upang mag-log out sa isang user, magdagdag tayo ng kaganapan sa pag-click sa button na Logout na makikita sa UserDropdown.jsx, at pangasiwaan ito nang naaayon:
./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>
// ... ... //
Pagprotekta sa Mga Ruta ng React
Ang huling hakbang sa pagpapatupad ng aming application ay ang paggamit ng pandaigdigang estado ng user upang kontrolin ang nabigasyon ng user, isang mabilis na paalala tungkol sa kung anong pag-uugali ang dapat naming makamit: sa simula ay binabati ang user ng pahina ng pag-log in, mula noon ang user ay makaka-access lamang sa home page pagkatapos ng matagumpay na pag-log in, ang user ay ire-redirect sa login page kapag nag-logout.
Nakamit namin ito sa tulong ng library ng react-router-dom sa pamamagitan ng pagtukoy sa 2 ruta: "/" at "/login", kinokontrol namin kung aling bahagi ang ire-render sa bawat ruta gamit ang global na auth state, ang pagpapatunay ng pagsusuri sa null ay kumakatawan sa isang hindi napatotohanang user at kabaliktaran:
./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;
Recap Diagram
Balutin
Sa artikulong ito sinubukan naming harapin ang simple ngunit napakakaraniwang kaso ng paggamit ng pagpapatupad ng pamamahala ng estado para sa isang daloy ng trabaho sa pagpapatotoo, na dumaraan sa iba't ibang mga diskarte, ang katwiran sa likod ng bawat isa at ang kanilang mga tradeoff. Ang pamamahala ng estado sa mga balangkas ng panig ng kliyente at sa React sa partikular ay isa sa mga pinakapinag-uusapang paksa sa komunidad ng frontend, dahil lang sa maaari nitong gawin o sirain ang pagganap at ang scalability ng iyong aplikasyon. Ang dami ng iba't ibang diskarte, pattern, library at tool na sumusubok na lutasin ang isyung ito ng pamamahala ng estado ay napakalaki, ang layunin namin ay bigyan ka ng matatag na pag-unawa sa mga kasanayan upang maipatupad mo ito sa sarili mong aplikasyon, para sa mas advanced mga diskarte at pattern ng pamamahala ng estado sa mas kumplikadong mga aplikasyon, tingnan ang aming susunod na artikulo kung saan tayo pupunta sa redux para sa nasusukat na pamamahala ng estado sa React.
Malapit na
ContextAPI vs Redux Toolkit
Ang Redux ay nagkaroon ng maraming taon ng pag-aampon sa komunidad bago ang Redux toolkit, sa katunayan, ang RTK ay ipinakilala bilang isang panimulang template upang mag-bootstrap ng redux state management sa mga bagong application (ang unang pangalan nito ay "redux-starter-kit" noong Oktubre 2019), bagaman ngayon, mayroong pangkalahatang pinagkasunduan sa pagitan ng mga tagapangasiwa ng Redux at ng komunidad na ang Redux toolkit ay ang wastong paraan upang gumana sa redux. Ang RTX ay nag-abstract ng maraming lohika ng Redux na ginagawang mas madaling gamitin, na may mas kaunting verbose, at hinihikayat ang mga developer na sundin ang mga pinakamahuhusay na kagawian, ang isang pangunahing pagkakaiba sa pagitan ng dalawa ay ang Redux ay binuo upang walang opinyon, na nagbibigay ng kaunting API at umaasang gagawin ng mga developer ang karamihan sa mabibigat na pag-angat sa pamamagitan ng pagsulat ng sarili nilang mga aklatan para sa mga karaniwang gawain at pagharap sa istruktura ng code, nagresulta ito sa mabagal na oras ng pag-develop at magulo na code, idinagdag ang Redux toolkit bilang dagdag na layer ng abstraction na pumipigil sa mga developer na mahulog sa mga karaniwang pitfalls nito, sumangguni sa opisyal na dokumentasyon para sa higit pang mga insight at ang pangangatwiran mula sa mga maintainer nito dito.