useCallback — βασικές έννοιες, το hook memoization συναρτήσεων στο React

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-07-04 Χρόνος ανάγνωσης: 9 λεπ

useCallback — είναι ένα hook του React που επιστρέφει μια memoized έκδοση της συνάρτησης, η οποία δεν αλλάζει μεταξύ των render εφόσον δεν αλλάξουν οι εξαρτήσεις της. Σε αντίθεση με τη συνήθη δήλωση συνάρτησης μέσα στο component (η οποία δημιουργεί νέα συνάρτηση σε κάθε render), το useCallback σταθεροποιεί την αναφορά στη συνάρτηση, αποτρέποντας τα περιττά re-render των θυγατρικών component που έχουν βελτιστοποιηθεί μέσω του React.memo. Σύμφωνα με το React Documentation (2025), το useCallback είναι χρήσιμο μόνο σε συνδυασμό με το React.memo ή με hooks που εξαρτώνται από σταθερή αναφορά.

Βασικά σημεία

  • useCallback — hook για memoization συνάρτησης, που διατηρεί την αναφορά μεταξύ των render.
  • React.memo — το useCallback είναι αποτελεσματικό μόνο σε συνδυασμό με το React.memo για την αποτροπή re-render.
  • Εξαρτήσεις — η συνάρτηση αναδημιουργείται μόνο όταν αλλάξουν οι καθορισμένες τιμές στον πίνακα.
  • Σταθερή αναφορά — το useCallback εγγυάται ότι η αναφορά στη συνάρτηση δεν θα αλλάξει χωρίς λόγο.
  • Μην το καταχράσαι — το υπερβολικό useCallback χωρίς React.memo επιδεινώνει την απόδοση.

Τι είναι το useCallback στο React

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 θα επιβραδύνει απλώς την εφαρμογή. Η βελτιστοποίηση πρέπει να είναι μετρήσιμη, όχι διαισθητική.

jsx
import { useCallback } from 'react';

function Parent() {
    const [count, setCount] = useState(0);

    // Σταθερή αναφορά — η ίδια συνάρτηση έως ότου αλλάξουν τα deps
    const handleClick = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return <Child onClick={handleClick} />;
}

Πώς λειτουργεί το memoization συναρτήσεων

Το 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 και απόδοση

Υπάρχει διαδεδομένος μύθος ότι το useCallback βελτιώνει αυτόματα την απόδοση. Στην πραγματικότητα, μεμονωμένα (χωρίς React.memo) το useCallback επιβραδύνει ελαφρώς την εφαρμογή λόγω του κόστους σύγκρισης εξαρτήσεων. Το hook αποφέρει πραγματικό όφελος μόνο σε τρία σενάρια: αποτροπή re-render των component React.memo, σταθερότητα των callback στο useEffect και μεταβίβαση σε custom hooks που εξαρτώνται από reference equality.

Ο κανόνας είναι απλός: έως ότου εντοπίσεις πρόβλημα απόδοσης μέσω του React DevTools Profiler — μην χρησιμοποιείς το useCallback. Η ομάδα του React έχει τονίσει επανειλημμένα ότι η πρόωρη βελτιστοποίηση είναι η ρίζα του κακού. Πρώτα γράψε καθαρό κώδικα χωρίς memoization, μέτρησε, βρες το σημείο συμφόρησης στον profiler και μόνο τότε πρόσθεσε το useCallback εκεί που πραγματικά χρειάζεται.

jsx
// Μετρήσιμη βελτιστοποίηση: το 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 είναι φθηνότερο από τη σύγκριση εξαρτήσεων.

Πότε να χρησιμοποιείς το useCallback

Πρώτο σενάριο — React.memo. Αν το θυγατρικό component είναι τυλιγμένο σε React.memo και δέχεται συνάρτηση callback ως prop, χωρίς useCallback το θυγατρικό component θα επανασχεδιάζεται σε κάθε render του γονέα, ακόμα κι αν τα δικά του δεδομένα δεν άλλαξαν. Το useCallback σταθεροποιεί την αναφορά και το React.memo μπορεί να παραλείψει σωστά το re-render.

Δεύτερο σενάριο — useEffect με callback στις εξαρτήσεις. Αν η συνάρτηση μεταβιβάζεται στον πίνακα εξαρτήσεων του useEffect, κάθε νέα αναφορά θα επανεκκινεί το effect. Το useCallback εγγυάται ότι η αναφορά είναι σταθερή και το effect εκτελείται μόνο όταν αλλάξουν τα πραγματικά δεδομένα, όχι σε κάθε render. Αυτό είναι ιδιαίτερα σημαντικό για εγγραφές (subscriptions) και αιτήματα.

  • React.memo children — αποτρέπει το re-render των memoized θυγατρικών component κατά τη μεταβίβαση callback.
  • useEffect dependencies — σταθεροποιεί τη συνάρτηση στον πίνακα εξαρτήσεων του effect, αποτρέποντας περιττές επανεκκινήσεις.
  • Custom hooks — αν το hook δέχεται callback και εξαρτάται από το reference equality του, το useCallback είναι υποχρεωτικό.
  • Context value — αν μεταβιβάζεται συνάρτηση στο value του context, το useMemo με useCallback σταθεροποιεί την αναφορά.
jsx
// 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 vs useMemo

Η βασική διαφορά μεταξύ 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 αντικειμένων
jsx
// Αυτά είναι ισοδύναμα:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);

Συνήθη λάθη με το useCallback

Το πιο συνηθισμένο λάθος — το ανούσιο τύλιγμα όλων των συναρτήσεων σε useCallback χωρίς React.memo στα θυγατρικά component. Αν το θυγατρικό component δεν είναι τυλιγμένο σε React.memo, θα κάνει re-render σε κάθε render του γονέα, ανεξάρτητα από το αν αλλάζει ή όχι η αναφορά του callback. Το useCallback χωρίς React.memo — κόστος χωρίς όφελος.

  • useCallback χωρίς React.memo — το θυγατρικό component κάνει ούτως ή άλλως re-render· η βελτιστοποίηση είναι άνευ νοήματος.
  • Ξεχασμένες εξαρτήσεις — αν μέσα στο callback χρησιμοποιείται μεταβλητή που δεν αναφέρεται στα deps, το callback περιέχει stale closure.
  • Υπερβολικό memoization — τύλιγμα κάθε συνάρτησης, συμπεριλαμβανομένων των τετριμμένων onClick με console.log.
  • Αντικείμενα και πίνακες στις εξαρτήσεις — η νέα αναφορά σε κάθε render θεωρείται αλλαγή, το memoization είναι άχρηστο.
jsx
// ❌ Άχρηστο: χωρίς 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;

Όχι. Το useCallback έχει νόημα μόνο σε τρεις περιπτώσεις: το θυγατρικό component είναι τυλιγμένο σε React.memo, η συνάρτηση χρησιμοποιείται στον πίνακα εξαρτήσεων του useEffect ή η συνάρτηση μεταβιβάζεται σε custom hook που εξαρτάται από reference equality. Στις υπόλοιπες περιπτώσεις το useCallback προσθέτει overhead χωρίς όφελος. Η ομάδα του React συνιστά πρώτα να γράφετε χωρίς βελτιστοποιήσεις και να τις προσθέτετε βάσει των αποτελεσμάτων profiling.

Τι είναι πιο γρήγορο — useCallback ή νέα συνάρτηση σε κάθε render;

Για απλά component — η νέα συνάρτηση σε κάθε render είναι ελαφρώς πιο γρήγορη, καθώς το useCallback ξοδεύει πόρους στη σύγκριση εξαρτήσεων και στην εκχώρηση πίνακα. Για component με βαθιά δέντρα React.memo — το useCallback κερδίζει, αποτρέποντας το re-render χιλιάδων θυγατρικών στοιχείων. Μέτρησε και σύγκρινε, μην μαντεύεις — χρησιμοποίησε το React DevTools Profiler για αντικειμενική αξιολόγηση.

Μπορώ να χρησιμοποιήσω το useCallback με async συναρτήσεις;

Ναι, το useCallback λειτουργεί με async συναρτήσεις ακριβώς όπως και με τις σύγχρονες. Το hook κάνει memoize την ίδια τη συνάρτηση, ενώ το αποτέλεσμα (Promise) επιστρέφεται κάθε φορά κατά την κλήση. Η ασύγχρονη συνάρτηση μέσα στο useCallback είναι συνηθισμένο μοτίβο για σταθερά callback φόρτωσης δεδομένων που χρησιμοποιούνται στο useEffect: const fetchData = useCallback(async (id) => {...}, []).

Πώς να διορθώσω το πρόβλημα με το useCallback και το React.memo;

Χρησιμοποίησε το React DevTools Profiler — θα δείξει ποια component κάνουν re-render και γιατί. Για προγραμματικό έλεγχο πρόσθεσε console.log ή useWhyDidYouUpdate — μια βιβλιοθήκη που καταγράφει την αιτία του re-render. Οι κύριες αιτίες: άλλαξε ένα prop (συμπεριλαμβανομένης της αναφοράς callback), άλλαξε το state ή το context. Αν το useCallback δεν βοηθά — έλεγξε ότι όλες οι εξαρτήσεις αναφέρονται σωστά.

Πώς να μεταβιβάσω το useCallback σε θυγατρικό component χωρίς React.memo;

Ακόμα και χωρίς React.memo, το useCallback μπορεί να είναι χρήσιμο σε συνδυασμό με useMemo για το value του context. Αν μεταβιβάζεις αντικείμενο με συναρτήσεις στο Context.Provider, τύλιξε τη δημιουργία του αντικειμένου σε useMemo και κάθε συνάρτηση σε useCallback. Αυτό θα αποτρέψει το re-render όλων των καταναλωτών του context όταν αλλάξει μία από τις συναρτήσεις. Αλλά για άμεση μεταβίβαση callback σε props χωρίς React.memo, το useCallback δεν προσφέρει όφελος.

Συμπεράσματα

  • useCallback — hook για memoization συνάρτησης, που επιστρέφει σταθερή αναφορά έως την αλλαγή των εξαρτήσεων.
  • React.memo synergy — το useCallback είναι αποτελεσματικό μόνο σε συνδυασμό με React.memo στα θυγατρικά component.
  • Σύγκριση εξαρτήσεων — μέσω Object.is· τα αντικείμενα/πίνακες στα deps χαλάνε το memoization.
  • Μέτρησε, μην μαντεύεις — πρόσθεσε το useCallback μόνο αφού εντοπίσεις σημείο συμφόρησης μέσω profiler.
  • Stable closure — συμπεριέλαβε όλες τις χρησιμοποιούμενες μεταβλητές στον πίνακα εξαρτήσεων, αλλιώς — stale closure.
  • useCallback vs useMemo — το useCallback κάνει memoize τη συνάρτηση, το useMemo — το αποτέλεσμα υπολογισμού.
  • Πλεονασμός — πάνω από το 90% των περιπτώσεων χρήσης του useCallback σε πραγματικά έργα είναι πρόωρη βελτιστοποίηση.

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης