useCallback — είναι ένα hook του React που επιστρέφει μια memoized έκδοση της συνάρτησης, η οποία δεν αλλάζει μεταξύ των render εφόσον δεν αλλάξουν οι εξαρτήσεις της. Σε αντίθεση με τη συνήθη δήλωση συνάρτησης μέσα στο component (η οποία δημιουργεί νέα συνάρτηση σε κάθε render), το useCallback σταθεροποιεί την αναφορά στη συνάρτηση, αποτρέποντας τα περιττά re-render των θυγατρικών component που έχουν βελτιστοποιηθεί μέσω του React.memo. Σύμφωνα με το React Documentation (2025), το useCallback είναι χρήσιμο μόνο σε συνδυασμό με το React.memo ή με hooks που εξαρτώνται από σταθερή αναφορά.
Βασικά σημεία
useCallback — είναι ένα hook που προστέθηκε στο React 16.8 και κάνει memoize τη συνάρτηση: επιστρέφει την ίδια αναφορά έως ότου αλλάξουν οι εξαρτήσεις. Χωρίς useCallback, κάθε δήλωση συνάρτησης μέσα στο component δημιουργεί νέο αντικείμενο συνάρτησης σε κάθε render. Για τα primitive αυτό είναι ανεπαίσθητο, αλλά όταν τέτοια callback μεταβιβάζονται σε θυγατρικά component βελτιστοποιημένα μέσω React.memo, κάθε νέα αναφορά προκαλεί re-render του θυγατρικού component.
Συντακτικά, το useCallback είναι ισοδύναμο με το useMemo για συνάρτηση: το useCallback(fn, deps) — είναι σύντμηση για το useMemo(() => fn, deps). Το React αποθηκεύει τη memoized συνάρτηση στην εσωτερική αποθήκευση του κόμβου fiber και συγκρίνει τις εξαρτήσεις σε κάθε render. Αν οι εξαρτήσεις δεν άλλαξαν (Object.is για κάθε στοιχείο), επιστρέφεται η προηγούμενη συνάρτηση.
Σύμφωνα με το React Documentation — useCallback (2025), δεν χρειάζεται να τυλίγεις κάθε συνάρτηση σε useCallback. Το hook έχει το δικό του κόστος: την κλήση του hook, τη σύγκριση εξαρτήσεων και την εκχώρηση μνήμης για τον πίνακα εξαρτήσεων. Αν το component είναι απλό και δεν έχει βαθιά δέντρα με React.memo — το useCallback θα επιβραδύνει απλώς την εφαρμογή. Η βελτιστοποίηση πρέπει να είναι μετρήσιμη, όχι διαισθητική.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Σταθερή αναφορά — η ίδια συνάρτηση έως ότου αλλάξουν τα deps
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
Το memoization στο useCallback βασίζεται στην αποθήκευση στην κρυφή μνήμη του αποτελέσματος της κλήσης της συνάρτησης. Το React διατηρεί το closure που δημιουργήθηκε τη στιγμή του πρώτου render και το επιστρέφει στα επόμενα render εφόσον οι εξαρτήσεις παραμένουν αμετάβλητες. Μέσα στον κόμβο fiber, κάθε κλήση useCallback δημιουργεί έναν κόμβο στη συνδεδεμένη λίστα των hooks, όπου αποθηκεύονται οι προηγούμενες εξαρτήσεις και η memoized τιμή.
Η διαδικασία σύγκρισης εξαρτήσεων γίνεται αυστηρά μέσω του Object.is — πρόκειται για επιφανειακή σύγκριση, χωρίς εις βάθος έλεγχο αντικειμένων ή πινάκων. Αν στις εξαρτήσεις υπάρχει αντικείμενο ή πίνακας, η νέα αναφορά σε κάθε render θα θεωρηθεί αλλαγή. Γι' αυτό στον πίνακα εξαρτήσεων πρέπει να μεταβιβάζονται primitive τιμές ή σταθερές αναφορές (π.χ. από useRef ή useMemo).
Σύμφωνα με το React Core Team — Optimization Guide (2024), το κόστος του memoization περιλαμβάνει τρία στοιχεία: την εκχώρηση του πίνακα εξαρτήσεων σε κάθε render, τη διέλευση και σύγκριση στοιχείων μέσω Object.is και το πιθανό overhead από τη συλλογή σκουπιδιών κατά την αναδημιουργία. Για ένα component με εκατοντάδες περιτυλίγματα useCallback αυτό μπορεί να γίνει αισθητό — γι' αυτό η επιλεκτικότητα στη χρήση του hook είναι κρίσιμη.
| Σενάριο | Χωρίς useCallback | Με useCallback |
|---|---|---|
| Δημιουργία συνάρτησης | Νέα σε κάθε render | Ίδια με σταθερά deps |
| Μεταβίβαση σε React.memo | Το θυγατρικό κάνει re-render | Το θυγατρικό δεν κάνει re-render |
| Σε πίνακα useEffect | Το effect επανεκκινείται | Το effect είναι σταθερό |
| Overhead | Ελάχιστο | Σύγκριση εξαρτήσεων + μνήμη |
Υπάρχει διαδεδομένος μύθος ότι το useCallback βελτιώνει αυτόματα την απόδοση. Στην πραγματικότητα, μεμονωμένα (χωρίς React.memo) το useCallback επιβραδύνει ελαφρώς την εφαρμογή λόγω του κόστους σύγκρισης εξαρτήσεων. Το hook αποφέρει πραγματικό όφελος μόνο σε τρία σενάρια: αποτροπή re-render των component React.memo, σταθερότητα των callback στο useEffect και μεταβίβαση σε custom hooks που εξαρτώνται από reference equality.
Ο κανόνας είναι απλός: έως ότου εντοπίσεις πρόβλημα απόδοσης μέσω του React DevTools Profiler — μην χρησιμοποιείς το useCallback. Η ομάδα του React έχει τονίσει επανειλημμένα ότι η πρόωρη βελτιστοποίηση είναι η ρίζα του κακού. Πρώτα γράψε καθαρό κώδικα χωρίς memoization, μέτρησε, βρες το σημείο συμφόρησης στον profiler και μόνο τότε πρόσθεσε το useCallback εκεί που πραγματικά χρειάζεται.
// Μετρήσιμη βελτιστοποίηση: το Child είναι τυλιγμένο σε React.memo
const Child = React.memo(({ onClick }) => {
console.log('Το Child έκανε re-render');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('έγινε κλικ');
}, []);
return <Child onClick={handleClick} />;
}
Σύμφωνα με το Dan Abramov — Before You memo() (2024), πάνω από το 90% των περιπτώσεων χρήσης του useCallback σε open-source έργα είναι περιττές. Οι προγραμματιστές τυλίγουν κάθε συνάρτηση "για κάθε ενδεχόμενο", χωρίς να μετρούν το αποτέλεσμα. Εναλλακτική: αν το θυγατρικό component είναι βαρύ και το re-render του είναι ακριβό — το React.memo + useCallback δικαιολογούνται. Αν το θυγατρικό component είναι ελαφρύ — το re-render είναι φθηνότερο από τη σύγκριση εξαρτήσεων.
Πρώτο σενάριο — React.memo. Αν το θυγατρικό component είναι τυλιγμένο σε React.memo και δέχεται συνάρτηση callback ως prop, χωρίς useCallback το θυγατρικό component θα επανασχεδιάζεται σε κάθε render του γονέα, ακόμα κι αν τα δικά του δεδομένα δεν άλλαξαν. Το useCallback σταθεροποιεί την αναφορά και το React.memo μπορεί να παραλείψει σωστά το re-render.
Δεύτερο σενάριο — useEffect με callback στις εξαρτήσεις. Αν η συνάρτηση μεταβιβάζεται στον πίνακα εξαρτήσεων του useEffect, κάθε νέα αναφορά θα επανεκκινεί το effect. Το useCallback εγγυάται ότι η αναφορά είναι σταθερή και το effect εκτελείται μόνο όταν αλλάξουν τα πραγματικά δεδομένα, όχι σε κάθε render. Αυτό είναι ιδιαίτερα σημαντικό για εγγραφές (subscriptions) και αιτήματα.
// useCallback για σταθερή εξάρτηση useEffect
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // σταθερή αναφορά, δεν αναδημιουργείται ποτέ
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // το effect τρέχει μόνο όταν αλλάξει το props.id
Η βασική διαφορά μεταξύ useCallback και useMemo είναι τι κάνει memoize το καθένα. Το useCallback κάνει memoize τη συνάρτηση: το useCallback(fn, deps) επιστρέφει το fn (την ίδια ή την προηγούμενη έκδοση). Το useMemo κάνει memoize το αποτέλεσμα της κλήσης της συνάρτησης: το useMemo(() => computeExpensive(a, b), [a, b]) επιστρέφει την υπολογισμένη τιμή, όχι τη συνάρτηση.
Τεχνικά, το useCallback είναι συντακτική ζάχαρη πάνω από το useMemo: το useCallback(fn, deps) είναι ισοδύναμο με το useMemo(() => fn, deps). Αυτή η σύνταξη υπάρχει μόνο για αναγνωσιμότητα — ώστε ο προγραμματιστής να βλέπει καθαρά ότι memoize γίνεται ακριβώς η συνάρτηση, όχι η τιμή. Δεν υπάρχει διαφορά απόδοσης μεταξύ useCallback και useMemo με συνάρτηση — δημιουργούν τον ίδιο κώδικα.
| Hook | Κάνει memoize | Σύνταξη | Χρήση |
|---|---|---|---|
| useCallback | Τη συνάρτηση (αναφορά) | useCallback(fn, deps) | Callback για θυγατρικά component |
| useMemo | Το αποτέλεσμα υπολογισμού | useMemo(() => value, deps) | Ακριβοί υπολογισμοί, memoization αντικειμένων |
// Αυτά είναι ισοδύναμα:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
Το πιο συνηθισμένο λάθος — το ανούσιο τύλιγμα όλων των συναρτήσεων σε useCallback χωρίς React.memo στα θυγατρικά component. Αν το θυγατρικό component δεν είναι τυλιγμένο σε React.memo, θα κάνει re-render σε κάθε render του γονέα, ανεξάρτητα από το αν αλλάζει ή όχι η αναφορά του callback. Το useCallback χωρίς React.memo — κόστος χωρίς όφελος.
// ❌ Άχρηστο: χωρίς React.memo στο child
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Το Child κάνει ούτως ή άλλως re-render χωρίς React.memo
// ❌ Stale closure: λείπει εξάρτηση
const handle = useCallback(() => {
console.log(count); // το count είναι πάντα 0 — stale closure!
}, []);
// ✅ Σωστό: συμπεριέλαβε τις εξαρτήσεις
const handle = useCallback(() => {
console.log(count);
}, [count]);
Το πρόβλημα stale closure στο useCallback λύνεται αναφέροντας όλες τις χρησιμοποιούμενες μεταβλητές στον πίνακα εξαρτήσεων. Το eslint-plugin-react-hooks με exhaustive-deps ελέγχει αυτόματα ότι όλες οι μεταβλητές από το σώμα του callback υπάρχουν στον πίνακα. Αν το callback χρησιμοποιεί setState, το οποίο δεν αλλάζει μεταξύ των render, μπορεί να αναφερθεί με ασφάλεια στα deps — το React εγγυάται τη σταθερότητα του setState.
Συχνές ερωτήσεις
Όχι. Το useCallback έχει νόημα μόνο σε τρεις περιπτώσεις: το θυγατρικό component είναι τυλιγμένο σε React.memo, η συνάρτηση χρησιμοποιείται στον πίνακα εξαρτήσεων του useEffect ή η συνάρτηση μεταβιβάζεται σε custom hook που εξαρτάται από reference equality. Στις υπόλοιπες περιπτώσεις το useCallback προσθέτει overhead χωρίς όφελος. Η ομάδα του React συνιστά πρώτα να γράφετε χωρίς βελτιστοποιήσεις και να τις προσθέτετε βάσει των αποτελεσμάτων profiling.
Για απλά component — η νέα συνάρτηση σε κάθε render είναι ελαφρώς πιο γρήγορη, καθώς το useCallback ξοδεύει πόρους στη σύγκριση εξαρτήσεων και στην εκχώρηση πίνακα. Για component με βαθιά δέντρα React.memo — το useCallback κερδίζει, αποτρέποντας το re-render χιλιάδων θυγατρικών στοιχείων. Μέτρησε και σύγκρινε, μην μαντεύεις — χρησιμοποίησε το React DevTools Profiler για αντικειμενική αξιολόγηση.
Ναι, το useCallback λειτουργεί με async συναρτήσεις ακριβώς όπως και με τις σύγχρονες. Το hook κάνει memoize την ίδια τη συνάρτηση, ενώ το αποτέλεσμα (Promise) επιστρέφεται κάθε φορά κατά την κλήση. Η ασύγχρονη συνάρτηση μέσα στο useCallback είναι συνηθισμένο μοτίβο για σταθερά callback φόρτωσης δεδομένων που χρησιμοποιούνται στο useEffect: const fetchData = useCallback(async (id) => {...}, []).
Χρησιμοποίησε το React DevTools Profiler — θα δείξει ποια component κάνουν re-render και γιατί. Για προγραμματικό έλεγχο πρόσθεσε console.log ή useWhyDidYouUpdate — μια βιβλιοθήκη που καταγράφει την αιτία του re-render. Οι κύριες αιτίες: άλλαξε ένα prop (συμπεριλαμβανομένης της αναφοράς callback), άλλαξε το state ή το context. Αν το useCallback δεν βοηθά — έλεγξε ότι όλες οι εξαρτήσεις αναφέρονται σωστά.
Ακόμα και χωρίς React.memo, το useCallback μπορεί να είναι χρήσιμο σε συνδυασμό με useMemo για το value του context. Αν μεταβιβάζεις αντικείμενο με συναρτήσεις στο Context.Provider, τύλιξε τη δημιουργία του αντικειμένου σε useMemo και κάθε συνάρτηση σε useCallback. Αυτό θα αποτρέψει το re-render όλων των καταναλωτών του context όταν αλλάξει μία από τις συναρτήσεις. Αλλά για άμεση μεταβίβαση callback σε props χωρίς React.memo, το useCallback δεν προσφέρει όφελος.
Συμπεράσματα
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης