Начало
» Основни познания
»
Как да поправите грешката „Target container is not a DOM element“ в React 18
Как да поправите грешката „Target container is not a DOM element“ в React 18
Най-важното поправка е следната: уверете се, че стойността, която подавате на createRoot(), е реален DOM елемент, който вече съществува. В React 18 стандартната клиентска входна точка изглежда така:
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“. Собствената документация за отстраняване на неизправности на createRoot на React дефинира грешката точно в тези термини: стойността, подадена на createRoot, не е DOM възел.
Това ръководство започва с тази причина с висока вероятност, след което обхваща проблеми с времето на изпълнение, грешки при миграция към React 18, сървърно рендиране, портали, TypeScript и тестови среди, за да можете да спрете, когато достигнете случая, който съвпада с вашето приложение.
Стъпка 1: Докажете какво всъщност подавате на createRoot()
Преди да променяте конфигурацията, логвайте контейнера:
Ако конзолата изведе null, React не е частта, която е отказала първа. Браузърът не е намерил елемент с това ID в момента, в който кодът ви е изпълнен. Официалната секция за отстраняване на неизправности на React изброява несъответствие в ID-тата и изпълнението преди съществуването на DOM възела като чести причини.
Ако конзолата изведе нещо като <div id="root"></div>, тогава контейнерът съществува и трябва да преминете напред към проверките за React API, SSR, портали или среда по-долу.
Прилага се, когато: грешката се появява веднага по време на стартирането на приложението, особено в main.jsx, index.jsx или index.tsx.
Действие: не гадаете. Логвайте точната стойност, подадена на createRoot(). Ако е null, поправете защо DOM търсенето е неуспешно, преди да пипате кода на компонентите.
AI-генерирана илюстрация на грешката с контейнер в React 18 в конзолата на разработчика. Това не е екранна снимка от реално приложение или React DevTools.
Стъпка 2: Съвпаднете HTML ID-то с JavaScript търсенето
Най-простата причина в реалния свят е несъответствие между вашия HTML и вашия JavaScript.
Вашият HTML може да съдържа:
<div id="app"></div>
докато вашият React входен файл търси:
document.getElementById('root')
Тези имена трябва да съвпадат. Или променете разметката:
<div id="root"></div>
или променете търсенето:
const container = document.getElementById('app');
Текущата createRoot референция на React използва стандартния пример document.getElementById('root'), но root не е магическо задължително ID. Всеки реален DOM елемент може да се използва като коренов контейнер. Важното условие е елементът да съществува и търсенето да го върне.
Прилага се, когато: наскоро сте променили HTML шаблон, мигрирали сте от Create React App към Vite или друг бундлер, вградили сте React в съществуваща сървърно рендирана страница или сте преименували елемента за монтиране.
Действие: потърсете в проекта както id="root", така и getElementById('root'). Ако проектът умишлено използва друго ID, направете HTML и JavaScript да съвпадат.
AI-генерирана илюстрация на съвпадащ елемент за монтиране с id="root". Това е концептуален изглед на код редактор, а не екранна снимка на конкретен шаблон на рамка.
Стъпка 3: Използвайте React 18 root API в правилния ред
React 18 представи клиентското API createRoot. Официалното ръководство за надграждане до React 18 показва миграцията от по-стария модел ReactDOM.render към:
Изненадващо лесна грешка е да обърнете ролите на DOM контейнера и React компонента:
// Wrong
createRoot(<App />);
React изрично изброява това като друга честа причина за грешката „Target container is not a DOM element“. createRoot() получава DOM възела; root.render() получава React възела.
Друга грешка при миграция е да пренесете мислено подписа от React 17 и да се опитате да подадете контейнера на root.render():
Референцията на React за createRoot документира root.render(reactNode) като приемащ React възела, докато контейнерът принадлежи на createRoot(domNode).
TypeScript: не бъркайте non-null асерцията с поправка по време на изпълнение
Ръководството за надграждане до React 18 показва createRoot(container!) като форма за TypeScript. Възклицателният знак е асерция по време на компилиране: той казва на TypeScript, че вярвате, че стойността не е null. Той не създава липсващ HTML елемент по време на изпълнение.
По-безопасен модел, когато дебъгвате, е:
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, след копиране на входен файл от друг проект, или само в TypeScript компилации, където ! е добавен, за да заглуши предупреждение на компилатора.
Действие: проверете последователността на извикванията: DOM lookup → createRoot(container) → root.render(<App />). По време на дебъгване, предпочитайте явна проверка за null пред сляпо асертиране на container!.
AI-генерирана илюстрация на защитен модел за стартиране на React 18. Кодът е показан като концептуален пример, а не като уловен изход от реален проект.
Стъпка 4: Уверете се, че кодът за стартиране се изпълнява след като целевият елемент съществува
Едно ID може да е перфектно изписано и все пак да върне null, ако скриптът ви се изпълнява, преди браузърът да е парснал елемента. Документацията за отстраняване на неизправности на React конкретно предупреждава, че скрипт на бандъл не може да види DOM възли, които се появяват по-късно в HTML, когато изпълнението се случи твърде рано.
Това е основно релевантно за персонализирани HTML страници и по-стари настройки за вграждане. Често срещана безопасна подредба е да поставите елемента за монтиране преди скрипта:
Ако контролирате персонализиран скрипт, който може да се изпълни преди завършването на парсването, друга защитна опция е да изчакате 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();
}
Не добавяйте този wrapper автоматично към всеки React проект. Модерните бундлери и рамки обикновено управляват поставянето на входния скрипт и семантиката на зареждане вместо вас. Ако стандартно Vite, Next.js, Remix или генерирано от рамка приложение внезапно развие тази грешка, първо потърсете променен шаблон, ID за монтиране, персонализирана интеграция или код, изпълняващ се извън очакваната браузърна входна точка.
Прилага се, когато: същото ID съществува в крайния HTML, но търсенето все още е null по време на стартиране, особено в ръчно сглобена HTML страница, CMS шаблон, вграден уиджет или интеграция на скрипт на трета страна.
Действие: инспектирайте действителния изход на страницата и реда на изпълнение. Уверете се, че възелът за монтиране съществува преди кода, който извиква createRoot().
AI-генерирана илюстрация на guard за готовност на DOM за персонализиран React bootstrap. Това не е задължителен модел за всяко React 18 приложение; използвайте го само когато времето на стартиране наистина е проблемът.
Ако вашата страница е сървърно рендирана, използвайте hydrateRoot вместо
Има важно условие, при което валиден DOM елемент не е достатъчен, за да направи createRoot() правилното API. Ако контейнерът вече съдържа 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 />);
Причината е различна от грешката с целевия контейнер. createRoot() управлява клиентски рендиран корен и при първото си рендиране изчиства съществуващия HTML вътре в този корен. hydrateRoot() прикрепя React към HTML, който вече е бил произведен от React на сървъра. Официалната документация за createRoot изрично посочва това като капан при сървърно рендиране.
Прилага се, когато: имате сървърно рендиран React HTML, статично генериране, което излъчва React разметка, или рамка, която хидратира HTML в браузъра.
Действие: не „поправяйте“ SSR, като замените сървърната разметка с празен контейнер. Използвайте входната точка за хидратация на рамката или hydrateRoot() на React, където е подходящо.
Ако това е целеви елемент за модал или tooltip, може да ви трябва createPortal, а не друг root
Понякога разработчиците виждат липсващ контейнер, докато се опитват да рендират модал, tooltip, зона за toast или overlay извън основното дърво на приложението. Документацията на React казва, че когато искате JSX да се появи другаде в DOM, използвайте createPortal(), вместо да създавате друг root само за този детайлен UI.
import { createPortal } from 'react-dom';
function Modal({ children }) {
const modalRoot = document.getElementById('modal-root');
if (!modalRoot) return null;
return createPortal(children, modalRoot);
}
Официалната документация за createPortal казва, че целевият елемент на портала трябва вече да съществува. Така че порталите могат да породят свързан клас проблем с контейнера, ако modal-root липсва, но архитектурната поправка не е непременно „извикайте createRoot отново“.
Прилага се, когато: отказващият контейнер не е основният root на вашето приложение, а дестинация за overlay или възел, управляван извън нормалната DOM позиция на компонента.
Действие: запазете един нормален root на приложението, освен ако наистина не имате нужда от множество независими root-ове. За UI тип модал в рамките на същото React приложение, предпочитайте портал към съществуващ DOM възел.
Какво ако грешката се случва само в тестове?
Един тест може да се провали по същата фундаментална причина: очакваният контейнер никога не е бил вмъкнат в DOM-а на теста. Ако вашият тест ръчно извиква createRoot(document.getElementById('root')), уверете се, че настройката на теста всъщност създава този възел преди рендирането.
Въпреки това, много библиотеки за тестване на React управляват контейнерите вместо вас. Ако вече използвате помощника render() на тестова рамка, ръчното създаване на React root може да е ненужно и може да направи настройката на теста по-крехка.
Прилага се, когато: разработката работи в браузъра, но Jest, Vitest, JSDOM или друга тестова среда хвърля грешка с контейнера.
Действие: инспектирайте DOM настройката на теста, а не производствения index.html. Потвърдете, че възелът съществува в средата, където отказващият код всъщност се изпълнява.
Какво ако document е недостъпен?
Ако код, който извиква document.getElementById(), се изпълнява в сървър или друга не-браузърна среда, имате различен проблем с интеграцията. Клиентските API на React в react-dom/client са проектирани да рендират в браузърни DOM възли. Сървърното рендиране използва API от react-dom/server, и рамките нормално разделят сървърните и клиентските входни точки.
Прилага се, когато: грешката се появява по време на сървърно рендиране, Node стъпка за компилация или код, споделен между сървърни и браузърни бандли.
Действие: преместете създаването на root, което е само за браузъра, в клиентската входна точка. Ако използвате рамка, следвайте документираната граница клиент/сървър, вместо ръчно да извиквате createRoot() от споделен сървърен код.
Таблица за бърза диагностика
Какво виждате
Вероятна причина
Най-добър следващ контрол
console.log(container) е null
Несъответствие в ID или елементът все още не е наличен
Сравнете HTML ID-то и търсенето; инспектирайте времето на скрипта
HTML използва id="app", кодът търси root
Несъответствие в ID-то за монтиране
Направете и двете имена идентични
createRoot(<App />)
React елемент е подаден там, където се изисква DOM възел
Подайте DOM възела на createRoot, след което рендирайте <App />
Проектът все още използва ReactDOM.render след надграждане до React 18
Наследено клиентско API
Мигрирайте към createRoot, използвайки ръководството за надграждане до React 18
Контейнерът вече съдържа сървърно рендиран React HTML
Грешно клиентско API за инициализация
Използвайте hydrateRoot
Само целевият елемент за модал/tooltip се проваля
Липсващ целеви елемент на портал или ненужен допълнителен root
Използвайте createPortal със съществуващ DOM възел
Само тестовете се провалят
Тестовият DOM никога не е създал целевия елемент
Създайте контейнера в настройката на теста или използвайте рендера на тестовата библиотека
Финална верификация: потвърдете поправката, вместо да скриете грешката
След като направите промяна, верифицирайте пътя за стартиране в този ред:
Отворете страницата и инспектирайте браузърната конзола. Грешката с целевия контейнер трябва да е изчезнала.
Изпълнете console.log(document.getElementById('root')) и потвърдете, че извежда реален елемент, а не null.
Потвърдете, че импортирате createRoot от react-dom/client в React 18 клиентски рендирано приложение.
Потвърдете, че DOM елементът е подаден на createRoot() и React компонентът е подаден на root.render().
Ако страницата е рендирана от React на сървъра, потвърдете, че клиентът използва hydrateRoot() вместо това.
Ако отказващата дестинация е модал или tooltip, потвърдете, че целевият елемент на портала съществува преди извикването на createPortal().
Не считайте TypeScript non-null асерция, опционално веригосване или catch блок за поправка сама по себе си. Тези техники могат да заглушат пътя на грешката, без да осигурят DOM възела, от който React наистина се нуждае. Трайната поправка е да накарате структурата на страницата, API-то за инициализация и времето на изпълнение да съвпадат.
За нормално React 18 single-page приложение, най-краткият правилен ментален модел е: HTML-ът създава контейнера; JavaScript намира този контейнер; createRoot приема контейнера; root.render приема компонента. Веднъж щом тези четири части са в правилния ред, „Target container is not a DOM element“ обикновено изчезва по правилната причина.