62. Dynamic Import
## JavaScript: Мозги. Dynamic Import
В этом уроке мы изучим `dynamic import`, мощный инструмент JavaScript, позволяющий загружать модули по требованию, а не сразу при загрузке страницы. Это помогает оптимизировать производительность и уменьшить первоначальное время загрузки.
### Что такое Dynamic Import?
Обычный `import` (статический импорт) выполняется в начале скрипта и загружает все необходимые модули сразу. `Dynamic import`, с другой стороны, позволяет загружать модули асинхронно и только тогда, когда они действительно нужны. Он возвращает промис, который разрешается экспортированным значением модуля.
### Пример кода
Предположим, у нас есть модуль `module.js`:
```javascript// module.jsexport function greet(name) { return `Привет, ${name}!`;}Теперь мы можем использовать dynamic import для загрузки этого модуля:
async function loadModule() { try { const module = await import('./module.js'); const message = module.greet('Мир'); console.log(message); // Выведет: Привет, Мир! } catch (error) { console.error('Ошибка загрузки модуля:', error); }}
loadModule();В этом примере import('./module.js') возвращает промис, который разрешается экспортированными значениями module.js. Мы используем await для ожидания разрешения промиса и затем вызываем функцию greet из загруженного модуля. Важно обернуть все в try...catch для обработки возможных ошибок при загрузке модуля.
Условная загрузка
Заголовок раздела «Условная загрузка»Dynamic import особенно полезен для условной загрузки модулей:
async function loadAnalytics() { if (userIsAuthenticated()) { // Представим, что есть функция проверки аутентификации try { const analytics = await import('./analytics.js'); analytics.trackPageView(); // Предположим, что у модуля есть функция trackPageView } catch (error) { console.error('Ошибка загрузки аналитики:', error); } } else { console.log('Пользователь не аутентифицирован, аналитика не загружается.'); }}
loadAnalytics();В этом примере модуль analytics.js загружается только в том случае, если пользователь аутентифицирован. Это позволяет избежать ненужной загрузки кода для неавторизованных пользователей.
Жизненный пример
Заголовок раздела «Жизненный пример»Dynamic import широко используется в современных веб-приложениях и фреймворках:
-
Code Splitting (Разделение кода): Многие фреймворки, такие как React, Vue и Angular, используют dynamic import для разделения кода на небольшие чанки, которые загружаются по требованию. Это улучшает производительность, особенно для больших приложений. Например, в React можно использовать
React.lazyиSuspenseдля ленивой загрузки компонентов. -
Маршрутизация: Dynamic import позволяет загружать компоненты, связанные с определенными маршрутами, только тогда, когда пользователь переходит на этот маршрут.
-
Плагины и расширения: Приложения могут использовать dynamic import для загрузки плагинов или расширений по требованию, позволяя пользователям добавлять функциональность без необходимости перезагружать все приложение.
Ключевые моменты
Заголовок раздела «Ключевые моменты»Dynamic importвозвращает промис.- Позволяет загружать модули асинхронно и по требованию.
- Идеально подходит для code splitting, условной загрузки и плагинов.
- Улучшает производительность и уменьшает первоначальное время загрузки.
- Не забывайте про обработку ошибок с помощью
try...catch.
## Интерактивный пример
<Playground client:load template="static" files={{ "/index.html": `<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <style> body { background: #1e1e2e; color: #cdd6f4; font-family: sans-serif; padding: 20px; margin: 0; } h2 { color: #89b4fa; margin-top: 20px; } button { background: #89b4fa; color: #1e1e2e; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; margin: 4px; font-size: 14px; } button:hover { background: #74c7ec; } button:disabled { background: #45475a; color: #6c7086; cursor: default; } .box { background: #313244; border-radius: 8px; padding: 16px; margin: 10px 0; } .output { background: #181825; border-radius: 6px; padding: 12px; font-family: monospace; font-size: 13px; min-height: 60px; color: #a6e3a1; white-space: pre-wrap; } .loading { color: #f9e2af; } .error { color: #f38ba8; } .step { background: #45475a; border-radius: 4px; padding: 4px 8px; font-size: 12px; margin: 2px 0; display: block; } .step.done { background: #1e3a2f; color: #a6e3a1; } .step.active { background: #3a3000; color: #f9e2af; } </style></head><body> <h2>Симуляция динамического import()</h2> <div class="box"> <p style="color:#bac2de;font-size:13px;margin:0 0 10px"> В браузере <code style="background:#181825;padding:2px 6px;border-radius:3px">import()</code> возвращает Promise. Здесь мы симулируем загрузку модуля с задержкой. </p> <button id="loadBtn" onclick="loadModule()">📦 Загрузить модуль</button> <button onclick="reset()">Сбросить</button> <div id="steps" style="margin-top:10px"></div> <div class="output" id="out" style="margin-top:8px">Нажмите кнопку для загрузки модуля</div> </div>
<h2>Условная загрузка (по авторизации)</h2> <div class="box"> <button onclick="loadAdmin(true)">Загрузить как admin</button> <button onclick="loadAdmin(false)">Загрузить как гость</button> <div class="output" id="adminOut">Нажмите кнопку</div> </div>
<h2>import() с обработкой ошибок</h2> <div class="box"> <button onclick="loadWithError()">Загрузить несуществующий модуль</button> <div class="output" id="errorOut">Нажмите кнопку</div> </div>
<script> // Симуляция динамического импорта function simulateDynamicImport(moduleName, delay = 800) { return new Promise((resolve, reject) => { const modules = { 'math': { add: (a,b) => a+b, multiply: (a,b) => a*b, PI: 3.14159 }, 'utils': { formatDate: (d) => d.toLocaleDateString('ru'), capitalize: (s) => s[0].toUpperCase()+s.slice(1) }, 'admin': { getUsers: () => ['Alice','Bob','Charlie'], deleteUser: (id) => 'Deleted: '+id }, }; setTimeout(() => { if (modules[moduleName]) resolve(modules[moduleName]); else reject(new Error('Module "' + moduleName + '" not found')); }, delay); }); }
function addStep(text, status) { const s = document.createElement('span'); s.className = 'step ' + (status || ''); s.textContent = text; document.getElementById('steps').appendChild(s); return s; }
async function loadModule() { document.getElementById('loadBtn').disabled = true; document.getElementById('steps').innerHTML = ''; document.getElementById('out').textContent = '';
const s1 = addStep('1. import("./math.js") → Promise {pending}', 'active'); document.getElementById('out').textContent = 'Загрузка модуля...';
await new Promise(r => setTimeout(r, 400)); s1.className = 'step done'; s1.textContent = '1. import("./math.js") → Promise {pending} ✓'; const s2 = addStep('2. Скачивание и парсинг модуля...', 'active');
const mod = await simulateDynamicImport('math'); s2.className = 'step done'; s2.textContent = '2. Модуль загружен ✓'; addStep('3. Promise {fulfilled} → модуль готов', 'done');
const lines = [ 'const mod = await import("./math.js");', '', 'mod.add(5, 3) => ' + mod.add(5, 3), 'mod.multiply(4, 7) => ' + mod.multiply(4, 7), 'mod.PI => ' + mod.PI, '', '// Модуль загружен только по запросу — не при старте!', ]; document.getElementById('out').textContent = lines.join('\\n'); }
async function loadAdmin(isAdmin) { document.getElementById('adminOut').textContent = 'Проверяем права доступа...'; await new Promise(r => setTimeout(r, 300));
if (isAdmin) { const mod = await simulateDynamicImport('admin', 500); document.getElementById('adminOut').textContent = 'if (isAdmin) {\\n const { getUsers } = await import("./admin.js");\\n}\\n\\n' + 'getUsers() => [' + mod.getUsers().join(', ') + ']\\n' + 'deleteUser(1) => ' + mod.deleteUser(1); } else { document.getElementById('adminOut').textContent = 'Гость: admin-модуль НЕ загружен.\\n\\n// import("./admin.js") не вызван —\\n// код не скачан, bandwidth сохранён ✅'; } }
async function loadWithError() { document.getElementById('errorOut').textContent = 'Попытка загрузки...'; try { await simulateDynamicImport('nonexistent', 400); } catch(err) { document.getElementById('errorOut').textContent = 'try {\\n await import("./nonexistent.js");\\n} catch(err) {\\n // Поймано!\\n}\\n\\n' + '❌ Ошибка: ' + err.message + '\\n\\nВсегда оборачивайте import() в try/catch!'; document.getElementById('errorOut').style.color = '#f38ba8'; setTimeout(() => document.getElementById('errorOut').style.color = '', 2000); } }
function reset() { document.getElementById('loadBtn').disabled = false; document.getElementById('steps').innerHTML = ''; document.getElementById('out').textContent = 'Нажмите кнопку для загрузки модуля'; } </script></body></html>`, }}/>