Αρχική
» ΒΑΣΙΚΕΣ ΓΝΩΣΕΙΣ
»
Πώς να διορθώσετε το σφάλμα "Target Container Is Not a DOM Element" στο React 18
Πώς να διορθώσετε το σφάλμα "Target Container Is Not a DOM Element" στο React 18
Η πιο σημαντική διόρθωση είναι η εξής: βεβαιωθείτε ότι η τιμή που περνάτε στο createRoot() είναι ένα πραγματικό στοιχείο DOM που υπάρχει ήδη. Στο React 18, το κανονικό σημείο εισόδου του πελάτη (client entry point) μοιάζει ως εξής:
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
if (!container) {
throw new Error('Root element not found');
}
const root = createRoot(container);
root.render(<App />);
Αν το document.getElementById('root') επιστρέφει null, ή αν περάσετε κατά λάθος ένα στοιχείο React όπως το <App /> στο createRoot(), το React δεν μπορεί να δημιουργήσει μια ρίζα και μπορεί να αναφέρει το σφάλμα “Target container is not a DOM element”. Η δική της τεκμηρίωση επίλυσης προβλημάτων του React για το createRoot ορίζει το σφάλμα ακριβώς με αυτούς τους όρους: η τιμή που περνάτε στο createRoot δεν είναι κόμβος DOM.
Αυτός ο οδηγός ξεκινά με αυτή την αιτία υψηλής πιθανότητας, καλύπτοντας στη συνέχεια προβλήματα χρονισμού, λάθη μετανάστευσης στο React 18, απόδοση διακομιστή (server rendering), portals, TypeScript και περιβάλλοντα δοκιμών, ώστε να μπορείτε να σταματήσετε μόλις φτάσετε στην περίπτωση που ταιριάζει με την εφαρμογή σας.
Βήμα 1: Αποδείξτε τι περνάτε πραγματικά στο createRoot()
Πριν αλλάξετε τη διαμόρφωση, καταγράψτε το container:
Αν η κονσόλα εκτυπώνει null, το React δεν είναι το μέρος που απέτυχε πρώτο. Ο browser δεν βρήκε ένα στοιχείο με αυτό το ID τη στιγμή που εκτελέστηκε ο κώδικάς σας. Η επίσημη ενότητα επίλυσης προβλημάτων του React αναφέρει την ασυμφωνία ID και την εκτέλεση πριν υπάρξει ο κόμβος DOM ως κοινούς λόγους.
Αν η κονσόλα εκτυπώνει κάτι σαν <div id="root"></div>, τότε το container υπάρχει και πρέπει να προχωρήσετε στις παρακάτω ελέγχους για το API του React, το SSR, τα portals ή το περιβάλλον.
Ισχύει όταν: το σφάλμα εμφανίζεται αμέσως κατά την εκκίνηση της εφαρμογής, ιδιαίτερα στα αρχεία main.jsx, index.jsx ή index.tsx.
Ενέργεια: μην μαντεύετε. Καταγράψτε την ακριβή τιμή που περνάτε στο createRoot(). Αν είναι null, διορθώστε γιατί απέτυχε η αναζήτηση στο DOM πριν αγγίξετε τον κώδικα των component.
Εικόνα που δημιουργήθηκε από AI του σφάλματος container του React 18 σε μια κονσόλα προγραμματιστή. Δεν είναι στιγμιότυπο οθόνης από πραγματική εφαρμογή ή React DevTools.
Βήμα 2: Αντιστοιχίστε το ID του HTML με την αναζήτηση JavaScript
Η πιο απλή αιτία στον πραγματικό κόσμο είναι μια ασυμφωνία μεταξύ του HTML και του JavaScript σας.
Το HTML σας μπορεί να περιέχει:
<div id="app"></div>
ενώ το αρχείο εισόδου React σας ζητά:
document.getElementById('root')
Αυτά τα ονόματα πρέπει να ταιριάζουν. Είτε αλλάξτε το markup:
<div id="root"></div>
είτε αλλάξτε την αναζήτηση:
const container = document.getElementById('app');
Η τρέχουσα αναφορά createRoot του React χρησιμοποιεί το τυπικό παράδειγμα document.getElementById('root'), αλλά το root δεν είναι ένα μαγικό υποχρεωτικό ID. Οποιοδήποτε πραγματικό στοιχείο DOM μπορεί να χρησιμοποιηθεί ως container ρίζας. Η σημαντική προϋπόθεση είναι ότι το στοιχείο υπάρχει και ότι η αναζήτηση το επιστρέφει.
Ισχύει όταν: αλλάξατε πρόσφατα ένα πρότυπο HTML, μεταναστεύσατε από Create React App σε Vite ή άλλο bundler, ενσωματώσατε το React σε μια υπάρχουσα σελίδα με server rendering, ή μετονομάσατε το στοιχείο προσάρτησης (mount element).
Ενέργεια: αναζητήστε στο έργο τόσο το id="root" όσο και το getElementById('root'). Αν το έργο χρησιμοποιεί σκόπιμα ένα άλλο ID, κάντε το HTML και το JavaScript να συμφωνούν.
Εικόνα που δημιουργήθηκε από AI ενός στοιχείου προσάρτησης με id="root" που ταιριάζει. Είναι μια εννοιολογική προβολή επεξεργαστή κώδικα, όχι στιγμιότυπο οθόνης ενός συγκεκριμένου προτύπου framework.
Βήμα 3: Χρησιμοποιήστε το API ρίζας του React 18 στη σωστή σειρά
Το React 18 εισήγαγε το API πελάτη createRoot. Ο επίσημος οδηγός αναβάθμισης React 18 δείχνει τη μετανάστευση από το παλαιότερο μοτίβο ReactDOM.render στο:
Ένα εκπληκτικά εύκολο λάθος είναι να αντιστρέψετε τους ρόλους του container DOM και του component React:
// Λάθος
createRoot(<App />);
Το React αναφέρει ρητά αυτό ως μια άλλη κοινή αιτία του σφάλματος “Target container is not a DOM element”. Το createRoot() λαμβάνει τον κόμβο DOM. Το root.render() λαμβάνει τον κόμβο React.
Ένα άλλο λάθος μετανάστευσης είναι να κρατήσετε νοερά την υπογραφή του React 17 και να προσπαθήσετε να περάσετε το container στο root.render():
Η αναφορά createRoot του React τεκμηριώνει το root.render(reactNode) ως λαμβάνον τον κόμβο React, ενώ το container ανήκει στο createRoot(domNode).
TypeScript: μην συγχέετε την μη-μηδενική δήλωση (non-null assertion) με μια διόρθωση χρόνου εκτέλεσης
Ο οδηγός αναβάθμισης React 18 δείχνει το createRoot(container!) ως μορφή TypeScript. Το θαυμαστικό είναι μια δήλωση χρόνου μεταγλώττισης: λέει στο TypeScript ότι πιστεύετε ότι η τιμή δεν είναι null. Δεν δημιουργεί ένα λείπον στοιχείο HTML κατά τον χρόνο εκτέλεσης.
Ένα ασφαλέστερο μοτίβο όταν κάνετε debugging είναι:
const container = document.getElementById('root');
if (container === null) {
throw new Error('Expected #root to exist');
}
createRoot(container).render(<App />);
Αυτό παράγει ένα πιο χρήσιμο σφάλμα συγκεκριμένο για την εφαρμογή, αν το HTML και το JavaScript αποκλίνουν.
Ισχύει όταν: το πρόβλημα εμφανίστηκε κατά τη μετανάστευση από React 17 σε React 18, μετά την αντιγραφή ενός αρχείου εισόδου από άλλο έργο, ή μόνο σε builds TypeScript όπου προστέθηκε το ! για να σιωπήσει μια προειδοποίηση του μεταγλωττιστή.
Ενέργεια: επαληθεύστε τη σειρά κλήσεων: αναζήτηση DOM → createRoot(container) → root.render(<App />). Κατά το debugging, προτιμήστε έναν ρητό έλεγχο null αντί να δηλώνετε τυφλά container!.
Εικόνα που δημιουργήθηκε από AI ενός αμυντικού μοτίβου εκκίνησης React 18. Ο κώδικας εμφανίζεται ως εννοιολογικό παράδειγμα, όχι ως καταγεγραμμένη έξοδος από πραγματικό έργο.
Βήμα 4: Βεβαιωθείτε ότι ο κώδικας εκκίνησης εκτελείται αφού υπάρξει το στοιχείο-στόχος
Ένα ID μπορεί να είναι τέλεια γραμμένο και να επιστρέφει παρ' όλα αυτά null αν το σενάριό σας εκτελείται πριν ο browser έχει αναλύσει το στοιχείο. Τα έγγραφα επίλυσης προβλημάτων του React προειδοποιούν συγκεκριμένα ότι ένα bundle script δεν μπορεί να δει κόμβους DOM που εμφανίζονται αργότερα στο HTML όταν η εκτέλεση συμβαίνει πολύ νωρίς.
Αυτό είναι κυρίως σχετικό με προσαρμοσμένες σελίδες HTML και παλαιότερες ρυθμίσεις ενσωμάτωσης. Μια κοινή ασφαλής διάταξη είναι να τοποθετήσετε το στοιχείο προσάρτησης πριν το script:
Αν ελέγχετε ένα προσαρμοσμένο script που μπορεί να εκτελεστεί πριν ολοκληρωθεί η ανάλυση, μια άλλη αμυντική επιλογή είναι να περιμένετε το DOMContentLoaded:
function start() {
const container = document.getElementById('root');
if (!container) throw new Error('Root element not found');
createRoot(container).render(<App />);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
Μην προσθέσετε αυτόν τον περιτυλιγμένο κώδικα αυτόματα σε κάθε έργο React. Οι σύγχρονοι bundlers και frameworks συνήθως διαχειρίζονται τη θέση του script εισόδου και τη σημασιολογία φόρτωσης για εσάς. Αν ένα stock app Vite, Next.js, Remix ή framework-παραγόμενο αναπτύξει ξαφνικά αυτό το σφάλμα, ψάξτε πρώτα για ένα αλλαγμένο πρότυπο, ID προσάρτησης, προσαρμοσμένη ενσωμάτωση ή κώδικα που εκτελείται εκτός του αναμενόμενου σημείου εισόδου browser.
Ισχύει όταν: το ίδιο ID υπάρχει στο τελικό HTML αλλά η αναζήτηση είναι ακόμα null κατά την εκκίνηση, ιδιαίτερα σε μια χειροκίνητα συναρμολογημένη σελίδα HTML, πρότυπο CMS, ενσωμάτωση widget ή ενσωμάτωση script τρίτων.
Ενέργεια: επιθεωρήστε την πραγματική πηγή της σελίδας και τη σειρά εκτέλεσης. Βεβαιωθείτε ότι ο κόμβος προσάρτησης υπάρχει πριν τον κώδικα που καλεί το createRoot().
Εικόνα που δημιουργήθηκε από AI μιας φρουράς ετοιμότητας DOM για μια προσαρμοσμένη εκκίνηση React. Δεν είναι ένα υποχρεωτικό μοτίβο για κάθε εφαρμογή React 18. Χρησιμοποιήστε το μόνο όταν ο χρονισμός εκκίνησης είναι πραγματικά το πρόβλημα.
Αν η σελίδα σας αποδίδεται από διακομιστή, χρησιμοποιήστε το hydrateRoot αντί
Υπάρχει μια σημαντική προϋπόθεση όπου ένα έγκυρο στοιχείο DOM δεν είναι αρκετό για να κάνει το createRoot() το σωστό API. Αν το container περιέχει ήδη HTML παραγόμενο από το React στον διακομιστή ή κατά τον χρόνο build, η τεκμηρίωση του React λέει να χρησιμοποιήσετε το hydrateRoot() αντί του createRoot().
import { hydrateRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
if (!container) {
throw new Error('Root element not found');
}
hydrateRoot(container, <App />);
Ο λόγος είναι διαφορετικός από το σφάλμα target-container. Το createRoot() διαχειρίζεται μια ρίζα αποδομένη από τον πελάτη και, στην πρώτη απόδοσή του, καθαρίζει το υπάρχον HTML μέσα σε αυτή τη ρίζα. Το hydrateRoot() συνδέει το React σε HTML που είχε ήδη παραχθεί από το React στον διακομιστή. Η επίσημη τεκμηρίωση createRoot το αναφέρει ρητά ως παγίδα απόδοσης διακομιστή.
Ισχύει όταν: έχετε React HTML αποδομένο από διακομιστή, στατική παραγωγή που εκπέμπει React markup, ή ένα framework που ενυδατώνει (hydrates) το HTML στον browser.
Ενέργεια: μην "διορθώσετε" το SSR αντικαθιστώντας το markup του διακομιστή με ένα άδειο container. Χρησιμοποιήστε το σημείο εισόδου ενυδάτωσης του framework ή το hydrateRoot() του React ως κατάλληλο.
Αν αυτός είναι ο στόχος ενός modal ή tooltip, μπορεί να χρειαστείτε createPortal—όχι άλλη ρίζα
Μερικές φορές οι προγραμματιστές βλέπουν ένα λείπον container ενώ προσπαθούν να αποδώσουν ένα modal, tooltip, περιοχή toast ή overlay έξω από το κύριο δέντρο της εφαρμογής. Τα έγγραφα του React λένε ότι όταν θέλετε το JSX να εμφανίζεται αλλού στο DOM, χρησιμοποιήστε το createPortal() αντί να δημιουργήσετε άλλη μια ρίζα μόνο για αυτό το UI παιδί.
import { createPortal } from 'react-dom';
function Modal({ children }) {
const modalRoot = document.getElementById('modal-root');
if (!modalRoot) return null;
return createPortal(children, modalRoot);
}
Η επίσημη τεκμηρίωση createPortal λέει ότι ο στόχος του portal πρέπει ήδη να υπάρχει. Έτσι, τα portals μπορούν να παράγουν μια σχετική κατηγορία προβλήματος container αν λείπει το modal-root, αλλά η αρχιτεκτονική διόρθωση δεν είναι απαραίτητα "καλέστε ξανά το createRoot".
Ισχύει όταν: το αποτυγχάνον container δεν είναι η κύρια ρίζα της εφαρμογής σας αλλά ένας προορισμός overlay ή ένας κόμβος που διαχειρίζεται εκτός της κανονικής θέσης DOM του component.
Ενέργεια: κρατήστε μία κανονική ρίζα εφαρμογής εκτός αν χρειάζεστε πραγματικά πολλές ανεξάρτητες ρίζες. Για UI τύπου modal εντός της ίδιας εφαρμογής React, προτιμήστε ένα portal σε έναν υπάρχοντα κόμβο DOM.
Τι γίνεται αν το σφάλμα συμβαίνει μόνο σε δοκιμές;
Μια δοκιμή μπορεί να αποτύχει για τον ίδιο θεμελιώδη λόγο: το αναμενόμενο container δεν εισήχθη ποτέ στο DOM δοκιμής. Αν η δοκιμή σας καλεί χειροκίνητα το createRoot(document.getElementById('root')), βεβαιωθείτε ότι η ρύθμιση της δοκιμής δημιουργεί πραγματικά αυτόν τον κόμβο πριν την απόδοση.
Ωστόσο, πολλές βιβλιοθήκες δοκιμών React διαχειρίζονται τα containers για εσάς. Αν χρησιμοποιείτε ήδη ένα helper render() ενός framework δοκιμών, η χειροκίνητη δημιουργία μιας ρίζας React μπορεί να είναι περιττή και μπορεί να κάνει τη ρύθμιση της δοκιμής πιο εύθραυστη.
Ισχύει όταν: η ανάπτυξη λειτουργεί στον browser αλλά το Jest, Vitest, JSDOM ή άλλο περιβάλλον δοκιμών ρίχνει το σφάλμα container.
Ενέργεια: επιθεωρήστε τη ρύθμιση DOM της δοκιμής, όχι το παραγωγικό index.html. Επιβεβαιώστε ότι ο κόμβος υπάρχει μέσα στο περιβάλλον όπου εκτελείται πραγματικά ο αποτυγχάνων κώδικας.
Τι γίνεται αν το document δεν είναι διαθέσιμο;
Αν κώδικας που καλεί το document.getElementById() εκτελείται σε διακομιστή ή άλλο περιβάλλον μη-browser, έχετε ένα διαφορετικό πρόβλημα ενσωμάτωσης. Τα APIs πελάτη του React στο react-dom/client είναι σχεδιασμένα να αποδίδουν σε κόμβους DOM browser. Η απόδοση διακομιστή χρησιμοποιεί APIs από το react-dom/server, και τα frameworks συνήθως χωρίζουν τα σημεία εισόδου διακομιστή και πελάτη.
Ισχύει όταν: το σφάλμα εμφανίζεται κατά την απόδοση διακομιστή (SSR), ένα βήμα build Node, ή κώδικα κοινόχρηστο μεταξύ bundles διακομιστή και browser.
Ενέργεια: μετακινήστε τη δημιουργία ρίζας μόνο για browser στο σημείο εισόδου του πελάτη. Αν χρησιμοποιείτε ένα framework, ακολουθήστε το τεκμηριωμένο όριο πελάτη/διακομιστή του αντί να καλείτε χειροκίνητα το createRoot() από κοινόχρηστο κώδικα διακομιστή.
Πίνακας γρήγορης διάγνωσης
Τι βλέπετε
Πιθανή αιτία
Καλύτερος επόμενος έλεγχος
Το console.log(container) είναι null
Ασυμφωνία ID ή στοιχείο που δεν υπάρχει ακόμα
Συγκρίνετε το ID του HTML και την αναζήτηση. Επιθεωρήστε τον χρονισμό του script
Το HTML χρησιμοποιεί id="app", ο κώδικας ρωτά για root
Ασυμφωνία ID προσάρτησης
Κάντε και τα δύο ονόματα πανομοιότυπα
createRoot(<App />)
Στοιχείο React περάστηκε όπου απαιτείται κόμβος DOM
Περάστε τον κόμβο DOM στο createRoot, στη συνέχεια αποδώστε το <App />
Το έργο χρησιμοποιεί ακόμα ReactDOM.render μετά από αναβάθμιση React 18
Παλαιό API πελάτη
Μεταναστεύστε στο createRoot χρησιμοποιώντας τον οδηγό αναβάθμισης React 18
Το container περιέχει ήδη React HTML αποδομένο από διακομιστή
Λανθασμένο API αρχικοποίησης πελάτη
Χρησιμοποιήστε το hydrateRoot
Μόνο ο στόχος modal/tooltip αποτυγχάνει
Λείπει ο στόχος portal ή περιττή επιπλέον ρίζα
Χρησιμοποιήστε το createPortal με έναν υπάρχοντα κόμβο DOM
Μόνο οι δοκιμές αποτυγχάνουν
Το DOM δοκιμής δεν δημιούργησε ποτέ το στοιχείο-στόχο
Δημιουργήστε το container στη ρύθμιση δοκιμής ή χρησιμοποιήστε τον renderer της βιβλιοθήκης δοκιμών
Τελική επαλήθευση: επιβεβαιώστε τη διόρθωση αντί να κρύψετε το σφάλμα
Μετά την πραγματοποίηση μιας αλλαγής, επαληθεύστε τη διαδρομή εκκίνησης με αυτή τη σειρά:
Ανοίξτε τη σελίδα και επιθεωρήστε την κονσόλα του browser. Το σφάλμα target-container πρέπει να έχει φύγει.
Εκτελέστε console.log(document.getElementById('root')) και επιβεβαιώστε ότι εκτυπώνει ένα πραγματικό στοιχείο, όχι null.
Επιβεβαιώστε ότι εισάγετε το createRoot από το react-dom/client σε μια εφαρμογή React 18 αποδομένη από πελάτη.
Επιβεβαιώστε ότι το στοιχείο DOM περνά στο createRoot() και το component React περνά στο root.render().
Αν η σελίδα αποδόθηκε από το React στον διακομιστή, επιβεβαιώστε ότι ο πελάτης χρησιμοποιεί το hydrateRoot() αντί.
Αν ο αποτυγχάνων προορισμός είναι ένα modal ή tooltip, επιβεβαιώστε ότι ο στόχος του portal υπάρχει πριν καλέσετε το createPortal().
Μην θεωρείτε μια μη-μηδενική δήλωση TypeScript, προαιρετική αλυσίδα (optional chaining) ή ένα catch block ως τη διόρθωση από μόνα τους. Αυτές οι τεχνικές μπορούν να σιωπήσουν ένα μονοπάτι σφάλματος χωρίς να παρέχουν τον κόμβο DOM που χρειάζεται πραγματικά το React. Η διαρκής διόρθωση είναι να κάνετε τη δομή της σελίδας, το API αρχικοποίησης και τον χρονισμό εκτέλεσης να συμφωνούν.
Για μια κανονική εφαρμογή single-page React 18, το συντομότερο σωστό νοητικό μοντέλο είναι: το HTML δημιουργεί το container. Η JavaScript βρίσκει αυτό το container. Το createRoot λαμβάνει το container. Το root.render λαμβάνει το component. Μόλις αυτά τα τέσσερα κομμάτια είναι στη σωστή σειρά, το "Target container is not a DOM element" συνήθως εξαφανίζεται για τον σωστό λόγο.