Το React είναι το βασικό πλαίσιο για τη δημιουργία δυναμικών εφαρμογών από την πλευρά του πελάτη για πολλούς προγραμματιστές. Η δυναμική φύση αυτών των εφαρμογών προέρχεται από την ευελιξία και την εκτεταμένη λίστα δυνατοτήτων και χαρακτηριστικών που είναι δυνατές από την πλευρά του πελάτη, γεγονός που επέτρεψε στους προγραμματιστές να δημιουργήσουν ολοκληρωμένες εφαρμογές που φορτώνουν στο πρόγραμμα περιήγησης μέσα σε λίγα δευτερόλεπτα, ένα κατόρθωμα που δεν ήταν δυνατό (ή πολύ δυσκίνητο) στις μέρες του στατικού ιστού.
Με αυτήν την επέκταση των δυνατοτήτων, ήρθε η έννοια της διαχείρισης της κατάστασης, καθώς αυξάνεται η πολυπλοκότητα στις εφαρμογές από την πλευρά του πελάτη, η ανάγκη διατήρησης της τοπικής κατάστασης γίνεται εμπόδιο από μόνη της, εάν δεν αντιμετωπιστεί σωστά και ληφθεί υπόψη η επεκτασιμότητα.
Αυτό το ζήτημα αντιμετωπίστηκε από πολλά πλαίσια, ακολουθώντας διαφορετικές προσεγγίσεις και εστιάζοντας σε διαφορετικά σύνολα υποπροβλημάτων, γι' αυτό είναι σημαντικό να έχουμε υψηλό επίπεδο κατανόησης του οικοσυστήματος του πλαισίου επιλογής για να αξιολογήσουμε τις ανάγκες κάθε εφαρμογής και να εφαρμόσουμε τη σωστή προσέγγιση σύμφωνα με αυτές μετρήσεις. Αυτό το άρθρο θα σας δώσει μια σύντομη επισκόπηση των κοινών ζητημάτων διαχείρισης κατάστασης και θα προσπαθήσει να εισαγάγει διαφορετικές προσεγγίσεις (useState, Context API) ως απάντηση σε αυτό. Αν και αυτό το άρθρο θα παρουσιάσει πολλαπλές λύσεις, θα επικεντρωθεί μόνο σε προκλήσεις σε μικρότερη κλίμακα, θα καλύψουμε πιο προχωρημένα θέματα σε προσεχή άρθρα.
Ροή εργασιών ελέγχου ταυτότητας
Μπορείτε να βρείτε τον κώδικα που παρουσιάζεται σε όλο το άρθρο εδώ.
Μπορείτε να αποκτήσετε πρόσβαση σε έναν ζωντανό σύνδεσμο προεπισκόπησης εδώ.
Εξετάστε την περίπτωση όπου υλοποιούμε τη διαδικασία ελέγχου ταυτότητας για μια εφαρμογή React.
Όπως φαίνεται στο παραπάνω GIF, θέλουμε να επιτρέψουμε στους χρήστες να συνδεθούν ή να εγγραφούν στην εφαρμογή μας χρησιμοποιώντας διαπιστευτήρια. Εάν δόθηκαν έγκυρα διαπιστευτήρια, ο χρήστης θα συνδεθεί, η εφαρμογή θα πλοηγηθεί αυτόματα στην αρχική σελίδα και ο χρήστης μπορεί να προχωρήσει στη χρήση της εφαρμογής.
Ομοίως, εάν ο χρήστης αποσυνδεθεί, οι πόροι της αρχικής σελίδας θα προστατεύονται πίσω από τη σύνδεση, η σελίδα σύνδεσης θα είναι η μόνη σελίδα στην οποία έχει πρόσβαση ο χρήστης.
Σκεπτόμενος αυτή τη ροή εργασίας από την άποψη της υλοποίησης, θα είχαμε ένα κύριο στοιχείο που ονομάζεται App, το στοιχείο App θα ανακατευθύνει τον χρήστη σε μία από τις δύο σελίδες: Αρχική σελίδα ή Σύνδεση, η τρέχουσα κατάσταση του χρήστη (συνδεμένος, αποσυνδεμένος) θα υπαγορεύσει ποια σελίδα στην οποία ανακατευθύνεται ο χρήστης, μια αλλαγή στην τρέχουσα κατάσταση του χρήστη (για παράδειγμα αλλαγή από συνδεδεμένο σε αποσυνδεμένο) θα πρέπει να ενεργοποιήσει μια άμεση ανακατεύθυνση στην αντίστοιχη σελίδα.
Όπως φαίνεται στην παραπάνω εικόνα, θέλουμε το στοιχείο της εφαρμογής να εξετάζει την τρέχουσα κατάσταση και να αποδίδει μόνο μία από τις δύο σελίδες – Αρχική σελίδα ή Σύνδεση – με βάση αυτήν την τρέχουσα κατάσταση.
Εάν ο χρήστης είναι μηδενικός, σημαίνει ότι δεν έχουμε πιστοποιημένο χρήστη, επομένως πλοηγούμαστε αυτόματα στη σελίδα σύνδεσης και προστατεύουμε την αρχική σελίδα χρησιμοποιώντας απόδοση υπό όρους. Εάν υπάρχει ο χρήστης, κάνουμε το αντίθετο.
Τώρα που καταλαβαίνουμε καλά τι πρέπει να εφαρμοστεί, ας διερευνήσουμε μερικές επιλογές, αλλά πρώτα ας ρυθμίσουμε το έργο React,
Το αποθετήριο έργου περιέχει ένα δείγμα εφαρμογής υποστήριξης που θα χρησιμοποιήσουμε για να εφαρμόσουμε την πλευρά του μπροστινού τμήματος (δεν θα ασχοληθούμε με αυτό, καθώς δεν είναι η κύρια εστίαση εδώ, αλλά ο κώδικας διατηρήθηκε σκόπιμα απλός, έτσι δεν θα δυσκολευτείτε με αυτόν )
Ξεκινάμε δημιουργώντας τις ακόλουθες σελίδες και στοιχεία:
-
Αρχική σελίδα
-
Σελίδα σύνδεσης
-
Επεξεργασία Σελίδα Χρήστη
-
Στοιχείο γραμμής πλοήγησης
-
UserDropdown Component
Μια εφαρμογή React με πολλές σελίδες χρειάζεται σωστή πλοήγηση, γι' αυτό μπορούμε να χρησιμοποιήσουμε το react-router-dom για να δημιουργήσουμε ένα παγκόσμιο πλαίσιο δρομολογητή προγράμματος περιήγησης και να καταχωρήσουμε διαφορετικές διαδρομές React.
yarn add react-router-dom
Γνωρίζουμε ότι πολλοί αναγνώστες προτιμούν να ακολουθούν μαζί με σεμινάρια, οπότε εδώ είναι το αρχικό πρότυπο για να σας ενημερώσουμε. Αυτός ο κλάδος εκκίνησης χρησιμοποιεί το DaisyUI για προκαθορισμένα στοιχεία TailwindCSS JSX. Περιλαμβάνει όλα τα στοιχεία, τις σελίδες και τον δρομολογητή που έχουν ήδη ρυθμιστεί. Εάν σκέφτεστε να ακολουθήσετε αυτό το σεμινάριο, να δημιουργήσετε μόνοι σας ολόκληρη τη ροή εξουσιοδότησης ακολουθώντας απλά βήματα, ξεκινήστε πρώτα διαχωρίζοντας το αποθετήριο. Αφού διαχωρίσετε το αποθετήριο, κλωνοποιήστε το και ξεκινήστε από το start-hereυποκατάστημα:
git clone git@github.com:<your-username>/fullstack-resourcify.git
Μόλις τραβήξετε τον κλάδο start-here:
-
Ανοίξτε το έργο με τον επεξεργαστή κώδικα που προτιμάτε
-
Αλλαγή καταλόγου σε frontend/
-
Εγκαταστήστε εξαρτήσεις: νήμα
-
Ξεκινήστε έναν διακομιστή ανάπτυξης: yarn dev·
Η προεπισκόπηση θα πρέπει να μοιάζει κάπως έτσι:
Το όνομα που αποδίδεται στο Navbar - επάνω δεξιά πλευρά - είναι μια μεταβλητή κατάστασης που ορίζεται στο κύριο στοιχείο της εφαρμογής. Η ίδια μεταβλητή μεταβιβάζεται και στο Navbar και στην Αρχική σελίδα. Η απλή φόρμα που χρησιμοποιείται παραπάνω ενημερώνει πραγματικά τη μεταβλητή κατάστασης "όνομα" από το στοιχείο EditPage.
Οι δύο προσεγγίσεις που παρουσιάζονται παρακάτω θα μπουν στις λεπτομέρειες της εφαρμογής:
Πρώτη προσέγγιση: useState
useState()
Το useState είναι ένα από τα πιο συχνά χρησιμοποιούμενα Hook React, σας επιτρέπει να δημιουργήσετε και να μεταλλάξετε κατάσταση σε ένα React Functional στοιχείο. Το στοιχείο useState έχει μια πραγματικά απλή υλοποίηση και είναι εύκολο στη χρήση: για να δημιουργήσετε μια νέα κατάσταση, πρέπει να καλέσετε useState με την αρχική τιμή της κατάστασης σας και το άγκιστρο useState θα επιστρέψει έναν πίνακα που περιέχει δύο μεταβλητές: η πρώτη είναι η κατάσταση μεταβλητή που μπορείτε να χρησιμοποιήσετε για να αναφέρετε την κατάστασή σας και η δεύτερη μια συνάρτηση που χρησιμοποιείτε για να αλλάξετε την τιμή της κατάστασης: αρκετά απλή.
Τι θα λέγατε να το δούμε στην πράξη; Το όνομα που αποδίδεται στο Navbar - επάνω δεξιά πλευρά - είναι μια μεταβλητή κατάστασης που ορίζεται στο κύριο στοιχείο της εφαρμογής. Η ίδια μεταβλητή μεταβιβάζεται και στο Navbar και στην Αρχική σελίδα. Η απλή φόρμα που χρησιμοποιείται παραπάνω ενημερώνει πραγματικά τη μεταβλητή κατάστασης "όνομα" από το στοιχείο EditPage. Η κατώτατη γραμμή είναι η εξής: το useState είναι ένα άγκιστρο πυρήνα που δέχεται μια αρχική κατάσταση ως παράμετρο και επιστρέφει δύο μεταβλητές με δύο τιμές, τη μεταβλητή κατάστασης που περιέχει την αρχική κατάσταση και μια συνάρτηση ρυθμιστή για την ίδια μεταβλητή κατάστασης.
Ας το αναλύσουμε και ας δούμε πώς εφαρμόστηκε αρχικά.
- Δημιουργία της μεταβλητής κατάστασης «όνομα»:
./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 (...)};
Στηρίγματα
Τα στηρίγματα είναι ένα από τα βασικά δομικά στοιχεία ενός στοιχείου react, εννοιολογικά, εάν σκεφτείτε ένα λειτουργικό στοιχείο React ως συνάρτηση Javascript, τότε τα στηρίγματα δεν είναι παρά οι παράμετροι συνάρτησης, ο συνδυασμός στηρίξεων και το άγκιστρο useState μπορεί να σας προσφέρει ένα σταθερό πλαίσιο για διαχείριση κατάστασης σε μια απλή εφαρμογή React.
Τα στηρίγματα React μεταβιβάζονται ως χαρακτηριστικά σε προσαρμοσμένα στοιχεία. Τα χαρακτηριστικά που μεταβιβάζονται ως props μπορούν να αποδομηθούν από το αντικείμενο 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>
Τα στηρίγματα μπορούν να γίνουν αποδεκτά και να χρησιμοποιηθούν μέσα σε ένα λειτουργικό στοιχείο παρόμοιο με ορίσματα κανονικής συνάρτησης. Επειδή το "όνομα" μεταβιβάζεται ως υποστήριγμα στο στοιχείο Home, μπορούμε να το αποδώσουμε στο ίδιο στοιχείο. Στο παρακάτω παράδειγμα, δεχόμαστε το περασμένο prop χρησιμοποιώντας τη σύνταξη αποδομής για να εξαγάγουμε την ιδιότητα ονόματος από το αντικείμενο 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>
... ...
Επαγγελματική Συμβουλή
Ανοίξτε την κονσόλα του προγράμματος περιήγησης και παρατηρήστε πώς αποδίδονται εκ νέου όλα τα στοιχεία που χρησιμοποιούν το υπόστρωμα "όνομα" όταν αλλάζει η κατάσταση. Κατά τον χειρισμό μιας μεταβλητής κατάστασης, το React θα αποθηκεύσει την επόμενη κατάσταση, θα αποδώσει ξανά το στοιχείο σας με τις νέες τιμές και θα ενημερώσει τη διεπαφή χρήστη.
use Κατάσταση μειονεκτήματα
Στηρίγματα-Γεωτρήσεις
Η διάτρηση στηρίγματος είναι ένας όρος που αναφέρεται σε μια ιεραρχία εξαρτημάτων όπου ένα σύνολο εξαρτημάτων χρειάζεται συγκεκριμένα στηρίγματα που παρέχονται από ένα μητρικό εξάρτημα, μια συνηθισμένη λύση που συνήθως χρησιμοποιεί ο άπειρος προγραμματιστής είναι να περάσει αυτά τα στηρίγματα σε ολόκληρη την αλυσίδα των εξαρτημάτων, το πρόβλημα με αυτό Η προσέγγιση είναι ότι μια αλλαγή σε οποιοδήποτε από αυτά τα στηρίγματα θα προκαλέσει την εκ νέου απόδοση ολόκληρης της αλυσίδας των στοιχείων, επιβραδύνοντας ουσιαστικά ολόκληρη την εφαρμογή ως αποτέλεσμα αυτών των περιττών αποδόσεων, των στοιχείων στη μέση της αλυσίδας που δεν απαιτούν αυτά τα στηρίγματα λειτουργούν ως μέσα για τη μεταφορά στηρίξεων.
Παράδειγμα:
-
Μια μεταβλητή κατάστασης ορίστηκε στο κύριο στοιχείο της εφαρμογής χρησιμοποιώντας το άγκιστρο useState().
-
Ένα υποστήριγμα ονόματος μεταβιβάστηκε στο στοιχείο Navbar
-
Το ίδιο υποστήριγμα έγινε αποδεκτό στο Navbar και πέρασε ως υποστήριγμα για άλλη μια φορά στο στοιχείο UserDropdown
-
Το UserDropdown είναι το τελευταίο θυγατρικό στοιχείο που δέχεται το 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>
</>
);
}
Φανταστείτε πόσο δύσκολο είναι να διατηρήσετε μια εφαρμογή React με διαφορετικά επίπεδα στοιχείων που περνούν και αποδίδουν την ίδια κατάσταση.
Αυξανόμενη πολυπλοκότητα και ποιότητα κώδικα
Με τη χρήση του useState και των props ως του μοναδικού μέσου διαχείρισης κατάστασης σε μια εφαρμογή react, η βάση κώδικα μπορεί γρήγορα να αναπτυχθεί σε πολυπλοκότητα, έχοντας να διαχειριστεί δεκάδες ή εκατοντάδες μεταβλητές κατάστασης, οι οποίες μπορεί να είναι διπλές η μία της άλλης, διάσπαρτες σε διαφορετικά αρχεία και Τα στοιχεία μπορεί να είναι αρκετά τρομακτικά, οποιαδήποτε αλλαγή σε μια δεδομένη μεταβλητή κατάστασης θα απαιτήσει προσεκτική εξέταση των εξαρτήσεων μεταξύ των στοιχείων για να αποφευχθεί τυχόν πρόσθετη απόδοση σε μια ήδη αργή εφαρμογή.
Δεύτερη προσέγγιση: Context API
Το Context API είναι η προσπάθεια της React να λύσει τα μειονεκτήματα της χρήσης μόνο των props και του useState για διαχείριση κατάστασης, ιδιαίτερα, το πλαίσιο API έρχεται ως απάντηση στο ζήτημα που αναφέρθηκε προηγουμένως σχετικά με την ανάγκη μεταβίβασης στηρίξεων σε ολόκληρο το δέντρο στοιχείων. Με τη χρήση του περιβάλλοντος, μπορείτε να ορίσετε μια κατάσταση για δεδομένα που θεωρείτε παγκόσμια και να έχετε πρόσβαση στην κατάστασή τους από οποιοδήποτε σημείο του δέντρου συστατικού: όχι πια γεώτρηση στήριξης.
Είναι σημαντικό να επισημάνουμε ότι το API περιβάλλοντος σχεδιάστηκε αρχικά για να λύσει το ζήτημα της παγκόσμιας κοινής χρήσης δεδομένων, δεδομένων όπως θέματα διεπαφής χρήστη, πληροφορίες ελέγχου ταυτότητας που είναι η περίπτωση χρήσης μας, γλώσσες και άλλα), για άλλους τύπους δεδομένων που πρέπει να κοινοποιηθούν μεταξύ περισσότερων από ένα συστατικών, αλλά δεν είναι απαραιτήτως καθολική για όλη την εφαρμογή, το περιβάλλον μπορεί να μην είναι η καλύτερη επιλογή, ανάλογα με την περίπτωση χρήσης, μπορείτε να εξετάσετε άλλες τεχνικές όπως σύνθεση στοιχείων που δεν εμπίπτει στο πεδίο εφαρμογής αυτού του άρθρου.
Μετάβαση στο Περιεχόμενο
Δημιουργία του περιβάλλοντος Auth
Το createContext είναι απλό, δημιουργεί μια νέα μεταβλητή περιβάλλοντος, λαμβάνει μία μόνο προαιρετική παράμετρο: την προεπιλεγμένη τιμή της μεταβλητής περιβάλλοντος.
Ας το δούμε αυτό στην πράξη, πρώτα δημιουργήστε έναν νέο φάκελο "contexts" και ένα νέο αρχείο μέσα σε αυτόν "Auth.jsx". Για να δημιουργήσουμε ένα νέο περιβάλλον, πρέπει να καλέσουμε τη συνάρτηση createContext(), να εκχωρήσουμε την επιστρεφόμενη τιμή σε μια νέα μεταβλητή Auth που θα εξαχθεί στη συνέχεια:
./src/contexts/Auth.jsx
import { createContext } from "react";
export const Auth = createContext();
Παρέχετε το πλαίσιο ελέγχου ταυτότητας
Τώρα πρέπει να εκθέσουμε τη μεταβλητή περιβάλλοντος "Auth" που δημιουργήσαμε προηγουμένως, για να το πετύχουμε αυτό χρησιμοποιούμε τον πάροχο περιβάλλοντος, ο πάροχος περιβάλλοντος είναι ένα στοιχείο που έχει ένα στοιχείο "αξία", μπορούμε να χρησιμοποιήσουμε αυτό το στηρίγμα για να μοιραστούμε μια τιμή - όνομα – κατά μήκος του δέντρου συστατικού, τυλίγοντας το δέντρο συστατικών με το στοιχείο παροχέα, κάνουμε αυτή την τιμή προσβάσιμη από οπουδήποτε μέσα στο δέντρο συστατικών, χωρίς να χρειάζεται να μεταβιβάσουμε αυτό το στήριγμα σε κάθε θυγατρικό στοιχείο ξεχωριστά.
Στο παράδειγμα ελέγχου ταυτότητας, χρειαζόμαστε μια μεταβλητή για να κρατήσει το αντικείμενο χρήστη και κάποιο είδος ρυθμιστή για να χειριστεί αυτή τη μεταβλητή κατάστασης, αυτή είναι μια τέλεια περίπτωση χρήσης για useState. Όταν χρησιμοποιείτε το Context, πρέπει να βεβαιωθείτε ότι ορίζετε τα δεδομένα που θέλετε να παρέχετε και ότι μεταβιβάζετε αυτά τα δεδομένα – χρήστη στο παράδειγμά μας – σε όλο το δέντρο συστατικών που είναι ένθετο μέσα, επομένως ορίστε έναν νέο χρήστη μεταβλητής κατάστασης μέσα στο στοιχείο παρόχου και Τέλος περνάμε τόσο τον χρήστη όσο και τον setUser μέσα σε έναν πίνακα ως την τιμή που θα εκθέσει ο πάροχος στο δέντρο συνιστωσών:
./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>;
};
Ένα άλλο πράγμα που μπορούμε να κάνουμε είναι να μετακινήσουμε τη μεταβλητή κατάστασης "όνομα" από το κύριο στοιχείο της εφαρμογής στο περιβάλλον Auth και να την εκθέσουμε σε ένθετα στοιχεία:
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>;
};
Τώρα το μόνο που απομένει είναι να τοποθετήσουμε την εφαρμογή μας στο ίδιο στοιχείο AuthProvider που μόλις εξάγαμε.
./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>
);
Επειδή αποδίδουμε το παιδικό στήριγμα μέσα στο Auth.Provider, όλα τα στοιχεία που είναι ένθετα μέσα στο στοιχείο AuthProvider μπορούν πλέον να καταναλώσουν το prop value που δώσαμε στον Auth.Provider. Μπορεί να φαίνεται μπερδεμένο, αλλά μόλις πειραματιστείτε με αυτό, δοκιμάστε να παρέχετε και να καταναλώνετε μια παγκόσμια κατάσταση - Context. Εξάλλου, μου είχε νόημα μόνο μετά από πειραματισμό με το Context API.
Καταναλώνοντας το πλαίσιο ελέγχου ταυτότητας
Το τελευταίο βήμα είναι απλό, χρησιμοποιούμε το άγκιστρο περιβάλλοντος "useContext" για να αποκτήσουμε πρόσβαση στην τιμή που παρέχει ο πάροχος περιβάλλοντος του "Auth", η οποία στην περίπτωσή μας είναι ο πίνακας που περιέχει το χρήστη και το setUser, στον παρακάτω κώδικα, μπορούμε να χρησιμοποιήστε το useContext για να καταναλώσετε το περιβάλλον Auth μέσα στο Navbar. Αυτό είναι δυνατό μόνο επειδή το Navbar είναι ένθετο μέσα στο στοιχείο App και δεδομένου ότι ο AuthProvider περιτυλίγεται γύρω από το στοιχείο App, το στήριγμα αξίας μπορεί να καταναλωθεί χρησιμοποιώντας μόνο το άγκιστρο useContext. Ένα άλλο εκπληκτικό εργαλείο που το React παρέχει εκ των υστέρων για τη διαχείριση οποιωνδήποτε δεδομένων είναι προσβάσιμα σε παγκόσμιο επίπεδο και επίσης τα οποία χειρίζονται οποιοδήποτε στοιχείο καταναλωτή.
./src/components/Navbar.jsx
export default function Navbar() {
const [name, setName] = useContext(Auth);
console.log(name)
return (...)};
Παρατηρήστε πώς δεν δεχόμαστε πλέον κανένα props στο λειτουργικό στοιχείο Navbar(). Χρησιμοποιούμε το useContext(Auth) για να καταναλώσουμε το περιβάλλον Auth, συλλαμβάνοντας τόσο το όνομα όσο και το setName. Αυτό σημαίνει επίσης ότι δεν χρειάζεται πλέον να περάσουμε το στηρίγμα στο Navbar:
./src/App.jsx
// ... //
return (
<BrowserRouter>
<div className="h-screen">
<Navbar/> // no need to pass prop anymore
// ... //
Ενημέρωση του πλαισίου ελέγχου ταυτότητας
Μπορούμε επίσης να χρησιμοποιήσουμε την παρεχόμενη συνάρτηση setName για να χειριστούμε τη μεταβλητή κατάστασης "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");
Παρουσιάζουμε το άγκιστρο useReducer().
Στο προηγούμενο παράδειγμα χρησιμοποιήσαμε το API περιβάλλοντος για να διαχειριστούμε και να μοιραστούμε την κατάστασή μας στο δέντρο συστατικών, ίσως έχετε παρατηρήσει ότι εξακολουθούμε να χρησιμοποιούμε το useState ως βάση της λογικής μας, και αυτό είναι ως επί το πλείστον εντάξει, καθώς η κατάσταση μας εξακολουθεί να είναι ένα απλό αντικείμενο σε αυτό το σημείο, αλλά εάν επεκτείναμε τις δυνατότητες της ροής ελέγχου ταυτότητας, θα χρειαστεί οπωσδήποτε να αποθηκεύσουμε περισσότερα από το email του χρήστη που είναι συνδεδεμένος αυτήν τη στιγμή, και εδώ θα επιστρέψουμε στους περιορισμούς που είχαμε προηγουμένως. η χρήση του useState με σύνθετη κατάσταση, ευτυχώς, το React επιλύει αυτό το ζήτημα παρέχοντας μια εναλλακτική λύση στο useState για τη διαχείριση σύνθετης κατάστασης: εισαγάγετε το useReducer.
Το useReducer μπορεί να θεωρηθεί ως μια γενικευμένη έκδοση του useState, χρειάζεται δύο παραμέτρους: μια λειτουργία μειωτήρα και μια αρχική κατάσταση.
Τίποτα ενδιαφέρον να σημειωθεί για την αρχική κατάσταση, η μαγεία συμβαίνει μέσα στη συνάρτηση μειωτήρα: ελέγχει για τον τύπο της ενέργειας που συνέβη και ανάλογα με αυτήν την ενέργεια, ο μειωτήρας θα καθορίσει ποιες ενημερώσεις θα εφαρμόσει στην κατάσταση και θα επιστρέψει τη νέα του τιμή .
Εξετάζοντας τον παρακάτω κώδικα, η συνάρτηση μειωτήρα έχει δύο πιθανούς τύπους ενεργειών:
-
"ΣΥΝΔΕΣΗ": σε αυτήν την περίπτωση η κατάσταση χρήστη θα ενημερωθεί με τα νέα διαπιστευτήρια χρήστη που παρέχονται μέσα στο ωφέλιμο φορτίο ενέργειας.
-
"LOGOUT": σε αυτήν την περίπτωση η κατάσταση χρήστη θα αφαιρεθεί από τον τοπικό χώρο αποθήκευσης και θα τεθεί ξανά σε null.
Είναι σημαντικό να σημειωθεί ότι το αντικείμενο δράσης περιέχει και ένα πεδίο τύπου που καθορίζει ποια λογική θα εφαρμοστεί και ένα προαιρετικό πεδίο ωφέλιμου φορτίου για την παροχή δεδομένων που είναι απαραίτητα για την εφαρμογή αυτής της λογικής.
Τέλος, το άγκιστρο useReducer επιστρέφει την τρέχουσα κατάσταση και μια συνάρτηση αποστολής που χρησιμοποιούμε για να μεταφέρουμε μια ενέργεια στον μειωτήρα.
Για την υπόλοιπη λογική, είναι πανομοιότυπο με το προηγούμενο παράδειγμα:
./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>
);
};
Αποστολή ενεργειών αντί για χρήση της συνάρτησης ρυθμιστή setState – π.χ.: setName –
Όπως μόλις αναφέραμε, χρησιμοποιούμε τη συνάρτηση αποστολής για να περάσουμε μια ενέργεια στον μειωτήρα, στον παρακάτω κώδικα, εκκινούμε μια ενέργεια LOGIN και παρέχουμε το email του χρήστη ως ωφέλιμο φορτίο, τώρα η κατάσταση χρήστη θα ενημερωθεί και αυτή η αλλαγή θα ενεργοποιηθεί μια εκ νέου απόδοση όλων των στοιχείων που έχουν εγγραφεί στην κατάσταση χρήστη. Είναι σημαντικό να επισημάνουμε ότι μια εκ νέου απόδοση θα ενεργοποιηθεί μόνο εάν συμβεί μια πραγματική αλλαγή στην κατάσταση, δεν θα γίνει εκ νέου απόδοση εάν ο μειωτής επιστρέψει την ίδια προηγούμενη κατάσταση.
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 (...)};
Σύνδεση χρήστη
Επαγγελματική Συμβουλή
Παρατηρήστε πώς το αντικείμενο χρήστη που λαμβάνουμε μετά από μια επιτυχημένη σύνδεση αποθηκεύεται τώρα στο localStorage.
Προσαρμοσμένο άγκιστρο για Σύνδεση
Τώρα που καταλαβαίνουμε καλά το useReducer, μπορούμε να ενσωματώσουμε περαιτέρω τη λογική σύνδεσης και αποσύνδεσής μας στα δικά τους ξεχωριστά προσαρμοσμένα άγκιστρα, με μία μόνο κλήση στο άγκιστρο σύνδεσης, μπορούμε να χειριστούμε μια κλήση API στη διαδρομή σύνδεσης, να ανακτήσουμε τον νέο χρήστη διαπιστευτήρια και αποθηκεύστε τα στον τοπικό αποθηκευτικό χώρο, στείλτε μια κλήση LOGIN για να ενημερώσετε την κατάσταση του χρήστη, όλα αυτά κατά την αντιμετώπιση σφαλμάτων:
./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 };
};
Σημείωση: για πιο προχωρημένους χρήστες του React μεταξύ των αναγνωστών, μπορεί να αναρωτιέστε γιατί δεν χρησιμοποιήσαμε τη λειτουργία lazy προετοιμασίας του useReducer για να ανακτήσουμε τα διαπιστευτήρια χρήστη, το useReducer δέχεται μια τρίτη προαιρετική παράμετρο που ονομάζεται συνάρτηση init, αυτή η συνάρτηση χρησιμοποιείται σε περίπτωση πρέπει να εφαρμόσουμε κάποια λογική για να μπορέσουμε να λάβουμε την αρχική τιμή της κατάστασης, ο λόγος που δεν επιλέξαμε αυτό είναι ένα απλό θέμα διαχωρισμού ανησυχιών, ο κώδικας με αυτόν τον τρόπο είναι πιο απλός στην κατανόηση και ως αποτέλεσμα πιο απλός στη διατήρηση .
Σελίδα σύνδεσης
Δείτε πώς φαίνεται το επάνω μέρος της σελίδας Σύνδεσής μας μετά τη χρήση του άγκιστρου useLogin() για την εξαγωγή της συνάρτησης σύνδεσης και την κλήση της συνάρτησης σύνδεσης με τα διαπιστευτήρια που υποβλήθηκαν από έναν χρήστη:
// ... ... //
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 (...)
// ... ... //
Απενεργοποιούμε επίσης τη λειτουργία Υποβολή όταν ο χρήστης υποβάλλει τη φόρμα:
<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>
Και την απόδοση τυχόν σφαλμάτων ελέγχου ταυτότητας που λαμβάνουμε από το backend μας:
{
error && <span className="text-red-500 p-2">{error.message}</span>;
}
Διατήρηση της κατάστασης χρήστη
Ίσως αναρωτιέστε γιατί πρέπει να αποθηκεύσουμε το αντικείμενο χρήστη στο localStorage, με απλά λόγια, θέλουμε να διατηρήσουμε τον χρήστη συνδεδεμένο όσο το διακριτικό δεν έχει λήξει. Η χρήση του localStorage είναι ένας πολύ καλός τρόπος αποθήκευσης bits του JSON, όπως στο παράδειγμά μας. Παρατηρήστε πώς εξαλείφεται η κατάσταση εάν ανανεώσετε τη σελίδα μετά τη σύνδεση. Αυτό μπορεί να επιλυθεί εύκολα χρησιμοποιώντας ένα άγκιστρο useEffect για να ελέγξετε αν έχουμε αποθηκευμένο αντικείμενο χρήστη στο localStorage, εάν υπάρχει, συνδέουμε τον χρήστη αυτόματα:
// ... ... //
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>
);
};
Προσαρμοσμένα άγκιστρα για Αποσύνδεση
Το ίδιο ισχύει και με το άγκιστρο Αποσύνδεσης, εδώ αποστέλλουμε μια ενέργεια LOGOUT για να αφαιρέσουμε τα τρέχοντα διαπιστευτήρια χρήστη τόσο από την κατάσταση όσο και από τον τοπικό χώρο αποθήκευσης:
./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 };
};
Αποσύνδεση χρήστη
Για να αποσυνδεθείτε από έναν χρήστη, ας προσθέσουμε ένα συμβάν κλικ στο κουμπί Αποσύνδεσης που βρίσκεται στο UserDropdown.jsx και ας το χειριστούμε ανάλογα:
./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>
// ... ... //
Προστασία οδών React
Το τελευταίο βήμα στην εφαρμογή της εφαρμογής μας είναι να αξιοποιήσουμε την παγκόσμια κατάσταση χρήστη για τον έλεγχο της πλοήγησης των χρηστών, μια γρήγορη υπενθύμιση σχετικά με τη συμπεριφορά που πρέπει να επιτύχουμε: αρχικά ο χρήστης καλωσορίζεται από τη σελίδα σύνδεσης, από εκείνο το σημείο ο χρήστης μπορεί να έχει πρόσβαση μόνο στην αρχική σελίδα μετά από μια επιτυχημένη σύνδεση, ομοίως ο χρήστης θα ανακατευθυνθεί στη σελίδα σύνδεσης κατά την αποσύνδεση.
Αυτό το επιτυγχάνουμε με τη βοήθεια της βιβλιοθήκης react-router-dom ορίζοντας 2 διαδρομές: "/" και "/login", ελέγχουμε ποιο στοιχείο θα αποδοθεί σε κάθε διαδρομή χρησιμοποιώντας την καθολική κατάσταση ταυτότητας, η αξιολόγηση auth σε null αντιπροσωπεύει έναν μη επαληθευμένο χρήστη και αντίστροφα:
./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;
Ανακεφαλαίωση του Διαγράμματος
Τύλιξε
Σε αυτό το άρθρο προσπαθήσαμε να αντιμετωπίσουμε την απλή αλλά πολύ συνηθισμένη περίπτωση εφαρμογής της διαχείρισης κατάστασης για μια ροή εργασιών ελέγχου ταυτότητας, περνώντας από τις διαφορετικές προσεγγίσεις, το σκεπτικό πίσω από κάθε μία και τις ανταλλαγές τους. Η διαχείριση κατάστασης στα πλαίσια του πελάτη και ειδικότερα στο React είναι ένα από τα πιο πολυσυζητημένα θέματα στην κοινότητα του frontend, απλώς και μόνο επειδή μπορεί είτε να βελτιώσει είτε να χαλάσει την απόδοση και την επεκτασιμότητα της εφαρμογής σας. Ο τεράστιος αριθμός των διαφορετικών τεχνικών, προτύπων, βιβλιοθηκών και εργαλείων που προσπαθούν να λύσουν αυτό το ζήτημα της κρατικής διαχείρισης είναι συντριπτικός, στόχος μας ήταν να σας δώσουμε μια σταθερή κατανόηση των πρακτικών, ώστε να μπορείτε να τις εφαρμόσετε στη δική σας εφαρμογή, για πιο προχωρημένους τεχνικές και μοτίβα διαχείρισης κατάστασης σε πιο σύνθετες εφαρμογές, ρίξτε μια ματιά στο επόμενο άρθρο μας όπου μπαίνουμε στο redux για επεκτάσιμη διαχείριση κατάστασης στο React.
Ερχομαι συντομα
ContextAPI εναντίον Redux Toolkit
Το Redux είχε πολλά χρόνια υιοθέτησης στην κοινότητα πριν από το Redux Toolkit, στην πραγματικότητα, το RTK εισήχθη ως πρότυπο εκκίνησης για την εκκίνηση της διαχείρισης κατάστασης redux σε νέες εφαρμογές (το αρχικό του όνομα ήταν "redux-starter-kit" τον Οκτώβριο του 2019), αν και Σήμερα, υπάρχει μια γενική συναίνεση μεταξύ των συντηρητών του Redux και της κοινότητας ότι το Redux Toolkit είναι ο έγκυρος τρόπος εργασίας με το redux. Το RTX αφαιρεί μεγάλο μέρος της λογικής του Redux που το κάνει πιο εύκολο στη χρήση, με πολύ λιγότερη περιεκτικότητα και ενθαρρύνει τους προγραμματιστές να ακολουθήστε τις βέλτιστες πρακτικές, μια βασική διαφορά μεταξύ των δύο είναι ότι το Redux κατασκευάστηκε για να μην έχει γνώμη, παρέχοντας ένα ελάχιστο API και περιμένοντας από τους προγραμματιστές να κάνουν το μεγαλύτερο μέρος της βαρύτητας γράφοντας τις δικές τους βιβλιοθήκες για κοινές εργασίες και αντιμετωπίζοντας τη δομή κώδικα, Αυτό είχε ως αποτέλεσμα αργό χρόνο ανάπτυξης και ακατάστατο κώδικα, το Redux toolkit προστέθηκε ως ένα επιπλέον επίπεδο αφαίρεσης που εμποδίζει τους προγραμματιστές να πέσουν στις κοινές του παγίδες, ανατρέξτε στην επίσημη τεκμηρίωση για περισσότερες πληροφορίες και το σκεπτικό από τους συντηρητές του εδώ.