Κατάσταση χειρισμού στο React: Τέσσερις αμετάβλητες προσεγγίσεις που πρέπει να ληφθούν υπόψη

Ίσως το πιο κοινό σημείο σύγχυσης στο React σήμερα: κατάσταση.

Φανταστείτε ότι έχετε μια φόρμα για την επεξεργασία ενός χρήστη. Είναι σύνηθες να δημιουργείτε ένα χειριστή μίας αλλαγής για να χειρίζεστε αλλαγές σε όλα τα πεδία φόρμας. Μπορεί να μοιάζει κάπως έτσι:

updateState(event) { const {name, value} = event.target; let user = this.state.user; // this is a reference, not a copy... user[name] = value; // so this mutates state ? return this.setState({user}); }

Η ανησυχία είναι στη γραμμή 4. Η γραμμή 4 ουσιαστικά μεταλλάσσει την κατάσταση επειδή η μεταβλητή χρήστη είναι μια αναφορά στην κατάσταση. Η κατάσταση αντιδράσεων πρέπει να αντιμετωπίζεται ως αμετάβλητη.

Από τα έγγραφα React:

Ποτέ μην κάνετε this.stateάμεση μετάλλαξη , καθώς η κλήση setState()μετά μπορεί να αντικαταστήσει τη μετάλλαξη που κάνατε. Αντιμετωπίστε this.stateσαν να ήταν αμετάβλητο.

Γιατί;

  1. Οι παρτίδες setState λειτουργούν πίσω από τα παρασκήνια. Αυτό σημαίνει ότι μια χειροκίνητη μετάλλαξη κατάστασης μπορεί να παρακαμφθεί κατά την επεξεργασία του setState.
  2. Εάν δηλώσετε τη μέθοδο shouldComponentUpdate, δεν μπορείτε να χρησιμοποιήσετε έναν έλεγχο ισοτιμίας === μέσα, επειδή η αναφορά αντικειμένου δεν θα αλλάξει . Έτσι, η παραπάνω προσέγγιση έχει επίσης πιθανή επίπτωση στην απόδοση.

Κατώτατη γραμμή : Το παραπάνω παράδειγμα λειτουργεί συχνά εντάξει, αλλά για να αποφευχθούν ακραίες περιπτώσεις, αντιμετωπίζετε την κατάσταση ως αμετάβλητη.

Εδώ είναι τέσσερις τρόποι για να αντιμετωπίζετε την κατάσταση ως αμετάβλητη:

Προσέγγιση # 1: Object.assign

Το Object.assign δημιουργεί ένα αντίγραφο ενός αντικειμένου. Η πρώτη παράμετρος είναι ο στόχος και, στη συνέχεια, καθορίζετε μία ή περισσότερες παραμέτρους για ιδιότητες στις οποίες θέλετε να αντιμετωπίσετε. Έτσι, η διόρθωση του παραπάνω παραδείγματος συνεπάγεται μια απλή αλλαγή στη γραμμή 3:

updateState(event) { const {name, value} = event.target; let user = Object.assign({}, this.state.user); user[name] = value; return this.setState({user}); }

Στη γραμμή 3, λέω "Δημιουργήστε ένα νέο κενό αντικείμενο και προσθέστε όλες τις ιδιότητες στο this.state.user σε αυτό." Αυτό δημιουργεί ένα ξεχωριστό αντίγραφο του αντικειμένου χρήστη που είναι αποθηκευμένο σε κατάσταση. Τώρα είμαι ασφαλής να μεταλλάξω το αντικείμενο χρήστη στη γραμμή 4 - είναι ένα εντελώς ξεχωριστό αντικείμενο από το αντικείμενο στην κατάσταση.

Φροντίστε να συμπληρώσετε το Object.assign επειδή δεν υποστηρίζεται στο IE και δεν μεταφέρεται από το Babel. Τέσσερις επιλογές που πρέπει να λάβετε υπόψη:

  1. αντικείμενο-εκχώρηση
  2. Τα έγγραφα MDN
  3. Babel Polyfill
  4. Polyfill.io

Προσέγγιση # 2: Διάδοση αντικειμένων

Η εξάπλωση αντικειμένων είναι επί του παρόντος ένα χαρακτηριστικό του σταδίου 3 και μπορεί να μεταφερθεί από το Babel. Αυτή η προσέγγιση είναι πιο περιεκτική:

updateState(event) { const {name, value} = event.target; let user = {...this.state.user, [name]: value}; this.setState({user}); }

Στη γραμμή 3 λέω "Χρησιμοποιήστε όλες τις ιδιότητες στο this.state.user για να δημιουργήσετε ένα νέο αντικείμενο και, στη συνέχεια, ορίστε την ιδιότητα που αντιπροσωπεύεται από το [όνομα] σε μια νέα τιμή που μεταβιβάστηκε στο event.target.value". Επομένως, αυτή η προσέγγιση λειτουργεί παρόμοια με την προσέγγιση Object.assign, αλλά έχει δύο οφέλη:

  1. Δεν απαιτείται polyfill, καθώς το Babel μπορεί να μετακινηθεί
  2. Πιο περιεκτική

Μπορείτε ακόμη και να χρησιμοποιήσετε καταστρεπτικό και ευθυγραμμισμένο για να το κάνετε αυτό με ένα επένδυση:

updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); }

Καταστρέφω το συμβάν στην υπογραφή της μεθόδου για να λάβω μια αναφορά στο event.target. Στη συνέχεια, δηλώνω ότι η κατάσταση θα πρέπει να οριστεί σε ένα αντίγραφο του this.state.user με τη σχετική ιδιότητα να έχει μια νέα τιμή. Μου αρέσει πόσο σύντομο είναι αυτό. Αυτή τη στιγμή είναι η αγαπημένη μου προσέγγιση για τη διαχείριση γραφής αλλαγών ;

Αυτές οι δύο προσεγγίσεις παραπάνω είναι οι πιο συνηθισμένοι και απλοί τρόποι χειρισμού της αμετάβλητης κατάστασης. Θέλετε περισσότερη δύναμη; Δείτε τις άλλες δύο επιλογές παρακάτω.

Προσέγγιση # 3: Βοηθός αμετάβλητης

Το Immutability-helper είναι μια εύχρηστη βιβλιοθήκη για τη μετάλλαξη ενός αντιγράφου δεδομένων χωρίς αλλαγή της πηγής. Αυτή η βιβλιοθήκη προτείνεται στα έγγραφα του React.

// Import at the top: import update from 'immutability-helper'; updateState({target}) { let user = update(this.state.user, {$merge: {[target.name]: target.value}}); this.setState({user}); }

Στη γραμμή 5, καλώ την συγχώνευση, η οποία είναι μία από τις πολλές εντολές που παρέχονται από το immutability-helper. Μοιάζει πολύ με το Object.assign, το μεταδίδω ως αντικείμενο-στόχο ως την πρώτη παράμετρο και μετά καθορίζω την ιδιότητα στην οποία θα ήθελα να συγχωνευτώ.

Υπάρχουν πολλά περισσότερα στον αμετάβλητο βοηθό από αυτό. Χρησιμοποιεί μια σύνταξη εμπνευσμένη από τη γλώσσα ερωτημάτων του MongoDB και προσφέρει μια ποικιλία ισχυρών τρόπων εργασίας με αμετάβλητα δεδομένα.

Προσέγγιση # 4: Immutable.js

Θέλετε να επιβάλλετε μέσω προγραμματισμού το αμετάβλητο; Σκεφτείτε το immutable.js. Αυτή η βιβλιοθήκη παρέχει αμετάβλητες δομές δεδομένων.

Ακολουθεί ένα παράδειγμα, χρησιμοποιώντας έναν αμετάβλητο χάρτη:

 // At top, import immutable import { Map } from 'immutable'; // Later, in constructor... this.state = { // Create an immutable map in state using immutable.js user: Map({ firstName: 'Cory', lastName: 'House'}) }; updateState({target}) { // this line returns a new user object assuming an immutable map is stored in state. let user = this.state.user.set(target.name, target.value); this.setState({user}); }

Υπάρχουν τρία βασικά βήματα παραπάνω:

  1. Εισαγωγή αμετάβλητη.
  2. Ορίστε την κατάσταση σε έναν αμετάβλητο χάρτη στον κατασκευαστή
  3. Χρησιμοποιήστε τη μέθοδο καθορισμού στο πρόγραμμα χειρισμού αλλαγών για να δημιουργήσετε ένα νέο αντίγραφο του χρήστη.

Η ομορφιά του immutable.js: Εάν προσπαθείτε να μεταλλάξετε την κατάσταση απευθείας, θα αποτύχει . Με τις άλλες προσεγγίσεις παραπάνω, είναι εύκολο να ξεχάσετε και το React δεν θα σας προειδοποιήσει όταν μεταλλάξετε την κατάσταση απευθείας.

Τα μειονεκτήματα του αμετάβλητου;

  1. Bloat . Το Immutable.js προσθέτει 57K ελαχιστοποιημένο στο πακέτο σας. Λαμβάνοντας υπόψη τις βιβλιοθήκες όπως το Preact μπορεί να αντικαταστήσει το React μόνο σε 3K, είναι δύσκολο να γίνει αποδεκτό.
  2. Σύνταξη . Πρέπει να αναφέρετε ιδιότητες αντικειμένων μέσω συμβολοσειρών και μεθόδων κλήσεων αντί απευθείας. Προτιμώ το user.name από το user.get ('name').
  3. YATTL (Ακόμα άλλο πράγμα που πρέπει να μάθετε) - Όποιος συμμετέχει στην ομάδα σας πρέπει να μάθει ακόμη ένα άλλο API για τη λήψη και ρύθμιση δεδομένων, καθώς και ένα νέο σύνολο τύπων δεδομένων.

Μερικές άλλες ενδιαφέρουσες εναλλακτικές λύσεις που πρέπει να λάβετε υπόψη:

  • απρόσκοπτη-αμετάβλητη
  • αντιδράστε-αντιγράψτε-γράψτε

Προειδοποίηση: Προσέξτε για ένθετα αντικείμενα!

Οι επιλογές # 1 και # 2 παραπάνω (Object.assign και Object spread) κάνουν μόνο έναν ρηχό κλώνο. Έτσι, εάν το αντικείμενο σας περιέχει ένθετα αντικείμενα, αυτά τα ένθετα αντικείμενα θα αντιγραφούν με αναφορά αντί για τιμή . Έτσι, εάν αλλάξετε το ένθετο αντικείμενο, θα μεταλλάξετε το αρχικό αντικείμενο. ;

Να είστε χειρουργικοί για αυτό που κλωνοποιείτε. Μην κλωνοποιείτε όλα τα πράγματα. Κλωνοποιήστε τα αντικείμενα που έχουν αλλάξει. Το αμετάβλητο-βοηθό (αναφέρεται παραπάνω) το καθιστά εύκολο. Όπως και εναλλακτικές λύσεις όπως immer, updeep ή μια μεγάλη λίστα εναλλακτικών.

Μπορεί να μπείτε στον πειρασμό να αναζητήσετε εργαλεία βαθιάς συγχώνευσης, όπως το deep clone ή το lodash.merge, αλλά αποφύγετε την τυφλή κλωνοποίηση .

Να γιατί:

  1. Η βαθιά κλωνοποίηση είναι ακριβή.
  2. Η βαθιά κλωνοποίηση είναι συνήθως σπατάλη (αντ 'αυτού, μόνο η κλωνοποίηση όσων έχει αλλάξει)
  3. Η βαθιά κλωνοποίηση προκαλεί περιττές αποδόσεις δεδομένου ότι η React πιστεύει ότι όλα έχουν αλλάξει όταν στην πραγματικότητα ίσως έχει αλλάξει μόνο ένα συγκεκριμένο θυγατρικό αντικείμενο.

Ευχαριστώ τον Dan Abramov για τις προτάσεις που ανέφερα παραπάνω:

Δεν πιστεύω ότι το cloneDeep () είναι καλή πρόταση. Μπορεί να είναι πολύ ακριβό. Αντιγράψτε μόνο τα μέρη που άλλαξαν. Βιβλιοθήκες όπως το immutability-helper (//t.co/YadMmpiOO8), updeep (//t.co/P0MzD19hcD) ή immer (//t.co/VyRa6Cd4IP) βοηθούν σε αυτό.

- Dan Abramov (@dan_abramov) 23 Απριλίου 2018

Τελική συμβουλή: Σκεφτείτε να χρησιμοποιήσετε το λειτουργικό setState

Μια άλλη ρυτίδα μπορεί να σας δαγκώσει:

Το setState () δεν μεταλλάσσει αμέσως αυτό το. αλλά δημιουργεί μια εκκρεμή κατάσταση μετάβασης. Η πρόσβαση στο this.state μετά την κλήση αυτής της μεθόδου μπορεί ενδεχομένως να επιστρέψει την υπάρχουσα τιμή.

Δεδομένου ότι οι κλήσεις setState είναι παρτίδες, κωδικός όπως αυτός οδηγεί σε σφάλμα:

updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); doSomething(this.state.user) // Uh oh, setState merely schedules a state change, so this.state.user may still have old value }

Εάν θέλετε να εκτελέσετε κώδικα μετά την ολοκλήρωση μιας κλήσης setState, χρησιμοποιήστε τη φόρμα επανάκλησης του setState:

updateState({target}) { this.setState((prevState) => { const updatedUser = {...prevState.user, [target.name]: target.value}; // use previous value in state to build new state... return { user: updatedUser }; // And what I return here will be set as the new state }, () => this.doSomething(this.state.user); // Now I can safely utilize the new state I've created to call other funcs... ); }

Η λήψη μου

Θαυμάζω την απλότητα και το μικρό βάρος της επιλογής # 2 παραπάνω: Διαστολή αντικειμένων. Δεν απαιτεί polyfill ή ξεχωριστή βιβλιοθήκη, μπορώ να δηλώσω έναν χειριστή αλλαγών σε μία γραμμή και μπορώ να κάνω χειρουργική επέμβαση για το τι έχει αλλάξει. ; Εργάζεστε με δομές ένθετων αντικειμένων; Προτιμώ αυτήν την περίοδο Immer.

Έχετε άλλους τρόπους που θέλετε να χειριστείτε την κατάσταση στο React; Παρακαλώ κάντε κλικ μέσα από τα σχόλια!

Ψάχνετε περισσότερα για το React; ⚛

Έχω γράψει πολλά μαθήματα React και JavaScript στο Pluralsight (δωρεάν δοκιμή). Η τελευταία μου δημοσίευση "Δημιουργία επαναχρησιμοποιήσιμων στοιχείων αντιδράσεων" μόλις δημοσιεύτηκε! ;

Ο Cory House είναι ο συγγραφέας πολλών μαθημάτων σε JavaScript, React, clean code, .NET και πολλά άλλα στο Pluralsight. Είναι κύριος σύμβουλος στο reactjsconsulting.com, αρχιτέκτονας λογισμικού στο VinSolutions, Microsoft MVP και εκπαιδεύει προγραμματιστές λογισμικού διεθνώς για πρακτικές λογισμικού όπως η ανάπτυξη front-end και καθαρή κωδικοποίηση. Ο Cory κάνει tweets σχετικά με τη JavaScript και την ανάπτυξη front-end στο Twitter ως @housecor.