Το Hook Βελτιστοποίησης Απόδοσης useCallback στο React
Σε αυτό το μάθημα θα εξετάσουμε το ακόλουθο
hook για βελτιστοποίηση απόδοσης
useCallback.
Το hook useCallback είναι παρόμοιο με το API useMemo,
η διαφορά έγκειται στο ότι το πρώτο
κρυφά μνημονεύει τιμή μεταξύ των στιγμών επανεμφάνισης
οθόνης, ενώ το δεύτερο - μια συνάρτηση callback.
Αυτό μας επιτρέπει να μην επανεκκινούμε δαπανηρές σε πόρους
συναρτήσεις, όταν αυτό δεν απαιτείται και μπορεί
να χρησιμοποιηθεί κατά τη
μετάδοση συνάρτησης
σε θυγατρικά components.
Ας το μελετήσουμε πιο αναλυτικά με ένα παράδειγμα.
Αρχικά, ας δημιουργήσουμε ένα component App
και ας ορίσουμε σε αυτό μια state μεταβλητή num:
const [num, setNum] = useState(0);
Ας έχουμε ένα κουμπί, upon click
στο οποίο η num αυξάνεται
κατά 1, και μια παράγραφο, στην οποία
θα εμφανίζουμε την τιμή της num:
return (
<div>
<button onClick={() => setNum(num + 1)}>click</button>
<p>clicks: {num}</p>
</div>
);
Τώρα, ας υποθέσουμε ότι στο
App εμφανίζεται επίσης κάποια λίστα
με στοιχεία, την οποία θα συμπληρώνουμε
πατώντας ένα άλλο κουμπί. Για την αποθήκευση
στοιχείων αυτής της λίστας, θα ορίσουμε
state μεταβλητή items:
const [items, setItems] = useState([]);
Και στη συνέχεια θα γράψουμε τη συνάρτηση addItem
για την προσθήκη τους:
function addItem() {
setItems([...items, 'new item']);
}
Τώρα ας γράψουμε τον κώδικα για την εμφάνιση
στοιχείων της λίστας και ας τον μεταφέρουμε σε ένα θυγατρικό
component Items, το οποίο ως
props θα λαμβάνει τον πίνακα στοιχείων
και τη συνάρτηση για την προσθήκη τους. Ας μην ξεχάσουμε
να προσθέσουμε έξοδο στην κονσόλα, για να βλέπουμε
πότε το Items μας θα
επανασχεδιάζεται:
function Items({ items, addItem }) {
const result = items.map((item, index) => {
return <p key={index}>{item}</p>;
});
console.log('Items render');
return (
<div>
<h3>Our items</h3>
{result}
<button onClick={addItem}>add item</button>
</div>
);
}
export default Items;
Ας τοποθετήσουμε το Items στο τέλος του component
App και ας του μεταδώσουμε τον πίνακα
items και τη συνάρτηση για προσθήκη
στοιχείων addItem:
return (
<>
<div>
<button onClick={() => setNum(num + 1)}>click</button>
<p>clicks: {num}</p>
<br />
</div>
<Items items={items} addItem={addItem} />
</>
);
Και τώρα ας πατήσουμε τα κουμπιά
και ας βεβαιωθούμε ότι η num αυξάνεται και
νέα στοιχεία προστίθενται στη λίστα.
Ανοίγοντας την κονσόλα, θα δούμε ότι
η λίστα μας επανασχεδιάζεται κάθε
φορά, ακόμα και αν κλικάρουμε στο κουμπί,
που αυξάνει την num.
Αν έχουμε μια μικρή λίστα, όλα
είναι εντάξει, αλλά αν υποτίθεται ότι θα
είναι ογκώδης και υπάρχουν πολλά ακόμα πράγματα εκεί;
Κανένα πρόβλημα - θα πείτε, αφού στο προηγούμενο
μάθημα εξετάσαμε το API memo,
για να αποφεύγουμε ακριβώς τις μη απαραίτητες επανασχεδιάσεις
components.
Οπότε ας τυλίξουμε το component μας
Items στο memo και όλα καλά.
Παρεμπιπτόντως, αυτό μπορεί να γίνει απευθείας
κατά την εξαγωγή του Items:
export default memo(Items);
Ας μην ξεχάσουμε να εισάγουμε το memo:
import { memo } from 'react';
Και τώρα ας ανοίξουμε την κονσόλα και ας πατήσουμε
τα κουμπιά. Όλες οι προσπάθειες μάταια!
Μεμονοποιήσαμε το component, αλλά upon click
στο κουμπί 'click' το component
Items συνεχίζει να
επανασχεδιάζεται κάθε φορά.
Το θέμα είναι ότι όταν το γονεϊκό
component επανασχεδιάζεται, οι συναρτήσεις του
δημιουργούνται εκ νέου - αυτό αφορά και τη δική μας
συνάρτηση addItem, την οποία μεταδίδουμε στο
Items.
Ακριβώς σε αυτή τη στιγμή θα μας βοηθήσει το hook
useCallback. Ας το εφαρμόσουμε.
Αρχικά, ας το εισάγουμε στο
App:
import { useCallback } from 'react';
Στη συνέχεια, ας μετατρέψουμε την απλή δήλωση συνάρτησης
addItem σε
Function Expression, θα ορίσουμε ως
πρώτη παράμετρο για το useCallback
τη συνάρτησή μας ως callback. Δεύτερη
παράμετρο, σε αγκύλες, θα ορίσουμε
τις εξαρτήσεις - όλες τις αντιδραστικές μεταβλητές,
που συμμετέχουν στη συνάρτηση, στην περίπτωσή μας
αυτός είναι ο πίνακας items:
const addItem = useCallback(() => {
setItems(() => [...items, 'New item']);
}, [items]);
Έτοιμο! Έτσι, κρυφά μνημονεύσαμε (cache)
τη συνάρτηση. Πατάμε ξανά τα κουμπιά και
βλέπουμε ότι τώρα upon click στο κουμπί
'click' το θυγατρικό μας component δεν
επανασχεδιάζεται.
Δημιουργήστε ένα component App, τοποθετήστε
σε αυτό μια παράγραφο με κείμενο. Ορίστε
state με αρχική τιμή 'text'
και εμφανίστε την στην παράγραφο. Upon click
στην παράγραφο, ας προστίθεται στο τέλος του κειμένου
ένα θαυμαστικό.
Δημιουργήστε ένα θυγατρικό component Products,
στο οποίο θα έχετε ένα κουμπί για προσθήκη
νέου προϊόντος. Τοποθετήστε το στο App.
Στο γονεϊκό component, δημιουργήστε state
με πίνακα προϊόντων και συνάρτηση προσθήκης
νέου προϊόντος. Μεταδώστε τα στο
θυγατρικό ως props, εμφανίστε σε αυτό
τον πίνακα που μεταδόθηκε ως λίστα ul.
Στο Products εμφανίστε στην κονσόλα κείμενο
'products render'.
Τυλίξτε το Products σε memo.
Κάντε click στην παράγραφο και στο κουμπί. Βεβαιωθείτε,
ότι upon click στην παράγραφο το θυγατρικό component
επανασχεδιάζεται παρ' όλα αυτά.
Κρυφά μνημονεύστε (cache) τη συνάρτηση για προσθήκη
προϊόντων, τυλίγοντάς την στο hook useCallback.
Κάντε click στην παράγραφο και στο κουμπί. Βεβαιωθείτε,
ότι upon click στην παράγραφο, το θυγατρικό component
δεν επανασχεδιάζεται πλέον.