Перейти к основному содержимому

Компоненты

Что такое компонент

Компонент — переиспользуемый блок UI на React. В Docusaurus это функция или класс, возвращающая JSX; в статьях подключают через import в MDX или из swizzle-темы.

React-компоненты в it-knowledge-base — мост между ~3000 MDX-статей и интерактивом, поиском, подборками, PDF. Глобальной регистрации MDX-компонентов нет — каждая статья импортирует виджеты явно. Исключение — article chrome в theme/DocItem/Layout (PDF, hero, related автоматически).

Учебник — React, компоненты и JSX.


Три способа появления на странице

СпособПримерыГде настраивается
Import в MDXExternalPlayEmbed, CollectionHubСама статья .mdx
Theme layoutArticlePdfExport, TechArticleHerosrc/theme/DocItem/Layout/index.tsx
Pages / NavbarUniverseMap, DocSearchBarsrc/pages/index.js, NavbarItem/DocSearch

Связь между собойshared кирпичи, данные из src/data/, константы URL, клиентский движок поиска.


Импорт компонентов в MDX

---
title: Моя статья
description: "Краткое описание."
slug: /encyclopedia/example/1
---

import ExternalPlayEmbed from '@site/src/components/ExternalPlayEmbed';

# Заголовок

Текст параграфа.

<ExternalPlayEmbed example="about/data-types-play" title="Типы данных" />

Три правила при подключении в MDX.

  1. Пустая строка после --- frontmatter.
  2. Алиас @site/src/components/... (относительный путь из docs/ не резолвится).
  3. PascalCase в JSX — <ExternalPlayEmbed />.

Remark lazyMdxDemoImports на сборке заменит import на lazy-обёртку. В исходнике пишите обычный import — как в примере выше.

MDX и добавление нового виджета

  1. Файл в src/components/MyWidget.jsx (или .tsx).
  2. import в нужных .mdx.
  3. npm start — проверить Network (async chunk при скролле или клике).
  4. Тяжёлый интерактивplay.spirzen.ru + ExternalPlayEmbed для демо вне репозитория.

Как всё подключается — цепочка embed

ЭтапСлойДействие
1АвторПишет <ExternalPlayEmbed example="slug" /> в MDX
2remarkПереписывает import → lazyExternalEmbed(...)
3lazy-обёрткаEmbedClickGateclick-to-load
4Reactimport() грузит chunk ExternalPlayEmbed
5BrowserOnlyРендер только в браузере
6iframesrc="https://play.spirzen.ru/p/embed/slug/?theme=dark"
7play → parentpostMessage высоты; trusted origin
8DOMКонтейнер подстраивает высоту, опционально fullscreen

Подробнее об архитектуре — Архитектура, интерактив.


ExternalPlayEmbed — эталон embed

Назначение — встроить демо с play.spirzen.ru в iframe с авто-высотой, click-to-load, fullscreen.

Props

PropТипОписание
examplestringSlug демо → /p/embed/<slug>/
titlestringПодпись для a11y и UI
minHeightnumberМинимальная высота до postMessage (default 320)
autoLoadbooleanАвтоматическая загрузка iframe без клика
playPropsobjectQuery-параметры для play
embedDataobjectДанные в iframe через postMessage
srcstringПолный URL вместо exampleescape hatch

Схема интеграции

const url = new URL(buildPlayEmbedUrl(baseUrl, example));
url.searchParams.set('theme', theme);

baseUrlsiteConfig.customFields.playExamplesUrl (prod или localhost:4322). API URL в src/constants/playExamples.js.

Живой пример на этой странице

Компонент загружается. Это пример интеграции — сейчас мы запрашиваем данные из другой системы.

ExternalCodeEmbed

Аналог для code.spirzen.ru — пути /e/embed/<slug>/. Trusted origin для postMessage — src/constants/codeExamples.js.


import ExternalCodeEmbed from '@site/src/components/ExternalCodeEmbed';

<ExternalCodeEmbed example="python/hello-world" title="Hello World" />

Lazy-обёртка — как делать

Автор не пишет lazy вручную — remark подставляет обёртку при сборке.

lazyExternalEmbed (play/code)

// после remark в скомпилированном MDX

import __itLazyExternalEmbed from '@site/src/components/shared/lazyExternalEmbed';

const ExternalPlayEmbed = __itLazyExternalEmbed(
() => import('@site/src/components/ExternalPlayEmbed'),
{ kind: 'play' }
);

Как работает lazyExternalEmbed.js.

  1. До активации — только EmbedClickGate (нет iframe, нет внешних запросов).
  2. После клика — React.lazy + Suspense + autoLoad на внутренний компонент.
  3. Обёртка снаружи — BrowserOnly.

lazyDemo / lazyDemoInView (остальные)

const Foo = __itLazyDemoInView(() => import('@site/src/components/Foo'));
ОбёрткаКогда
lazyExternalEmbediframe play/code — отдельный chunk, сериализация через embedLoadQueue
lazyDemoInViewОстальные @site/src/components/*chunk при попадании в viewport
lazyDemoВ theme layout — без viewport, сразу lazy при монтировании layout

Не импортируйте shared/ из MDX — remark оборачивает только корень components/.

Создать свою lazy-обёртку (редко)

Паттерн как в lazyDemo.js.


import React, {lazy, Suspense} from 'react';

export default function lazyMyWidget(importFn) {
const Lazy = lazy(importFn);
return function Wrapped(props) {
return (
<Suspense fallback={<div role="status">Загрузка…</div>}>
<Lazy {...props} />
</Suspense>
);
};
}

Для MDX достаточно положить файл в components/lazyMdxDemoImports подхватит автоматически.


shared/ — кирпичи инфраструктуры

МодульРоль
lazyDemo.jsReact.lazy + Suspense
lazyDemoInView.jsLazy + IntersectionObserver
lazyExternalEmbed.jsОчередь iframe, BrowserOnly
EmbedClickGate.jsxUI gate "Запустить демо"
embedLoadQueue.jsСериализация — max 2 iframe одновременно
useEmbedViewport.jsСтабильный src, высота, in-view mount
embedScrollLock.jsБлокировка скролла в fullscreen
deferredIdle.jsidlerequestIdleCallback
demoFallback.jsxСкелетоны загрузки

Кирпич — мелкий модуль без публичного MDX-API; обёртка в корне components/ — то, что импортируют статьи.


click-to-load и autoLoad

РежимКогдаСеть при открытии статьи
click-to-load (default)Embed play/code через lazyExternalEmbedНет запросов к play/code до клика
autoLoadProp на ExternalPlayEmbed после активации gateiframe грузится сразу после клика на gate
lazyDemoInViewОбычные виджетыПодгрузка chunk при скролле к блоку

Автоматическая загрузка всего iframe без gate перегружает страницу с десятками демо — в проде gate обязателен; autoLoad={true} только для осознанных случаев.


postMessage, API и trusted origin

Контракт spirzen ↔ play/code — postMessage + URL slug, без отдельного REST на каждую статью.

СообщениеНаправлениеСмысл
it-play-embed-heightplay → parentВысота для DOM
it-play-themeparent → playСинхронизация light/dark
it-play-embed-dataparent → playПроизвольный payload
it-play-fullscreenplay → parentРежим на весь экран

Trusted origin — whitelist в PLAY_TRUSTED_ORIGINS / CODE_EXAMPLES_TRUSTED_ORIGINS; чужой event.origin игнорируется.

Query-параметры?theme=light|dark, доп. ключи через playProps в URL.


BrowserOnly — подробно

BrowserOnly — компонент Docusaurus (@docusaurus/BrowserOnly), который рендерит детей только на клиенте, после гидратации. На сервере (SSR) при docusaurus build React генерирует HTML без кода, требующего window, document, localStorage, iframe.

Зачем нужен

Проблема без BrowserOnlyПример
window is not definedpostMessage, IntersectionObserver
Расхождение SSR/клиентСлучайные id, размеры viewport
Лишняя работа на сервереDocSearch, PDF export

Docusaurus — SSG + гидратация. Компонент с прямым доступом к window в теле функции упадёт при prerender.

Два способа использования

1. Fallback на сервере

<BrowserOnly fallback={<div role="status">Загрузка…</div>}>
{() => <ExternalPlayEmbedInner {...props} />}
</BrowserOnly>

На SSR и до гидратации показывается fallback (скелетон, пустой null). После mount в браузере вызывается функция-ребёнок () => <Inner />.

2. Children как render prop

Дети должны быть функцией () => ReactNode, не готовым JSX. Иначе код с window выполнится при импорте модуля на сервере.

Где используется в проекте

КомпонентFallback
ExternalPlayEmbed / ExternalCodeEmbedСкелетон с role="status"
lazyExternalEmbedСообщение "Подготовка embed…"
ArticlePdfExportnull
DocSearchBarУпрощённая кнопка без модалки
LabTrainersHub, RandomArticledemoLoadingFallback
DeveloperExamPlayТекст загрузки экзамена

BrowserOnly vs useEffect

ПодходКогда
BrowserOnlyВесь компонент бесполезен без DOM (embed, поиск, PDF)
useEffectЧасть логики клиентская (TechArticleHero вставляет иконку в h1 после mount)
typeof window !== 'undefined'В clientModules, отдельно от MDX-компонентов

TechArticleHero без BrowserOnly — при SSR возвращает null, в useEffect ищет h1 в DOM и монтирует createRoot.

a11y с BrowserOnly

Fallback должен иметь role="status" или aria-live="polite", чтобы скринридер не молчал при подгрузке тяжёлого блока.


Theme layout — без MDX

const ArticlePdfExport = lazyDemo(() => import('@site/src/components/ArticlePdfExport'));
const TechArticleHero = lazyDemo(() => import('@site/src/components/TechArticleHero'));

В layout после контента — <ArticlePdfExport />, <TechArticleHero />, <ArticleRelated />, <ArticleSeeAlso />.

TechArticleHero props из MDX не принимает — читает useDoc(), techArticlePages.js, вставляет иконку у h1 через DOM.

articleMetaEnhancement.ts после idle делает теги кликабельными → /tags/....


Хабы, каталоги, виджеты

CollectionHub — каталог подборки


import CollectionHub from '@site/src/components/CollectionHub';

<CollectionHub label="Первые шаги" />

sidebarCollections.js + collectionDocTitles.json → полный список статей по label.

GettingStartedPaths, LabTrainersHub

Хабы маршрутов и тренажёров — карточки, ссылки на /lab/, play-демо.

RandomChecklistItem / RandomQuestionFromArticle

Парсинг списков "Проверь себя" из DOM (articleExtract.js), случайность одного пункта.

DocSearch

Не в MDX. Цепочка — RootDocSearchProvider → navbar → Ctrl+K → docSearchEngine.js + индекс.

DesignThemePicker

TSX в navbar — стилизация через applyItDesign.


Толстые и тонкие компоненты

ТипПримерыГде логика
ТонкиеCollectionHub, SpirzenOnlineToolLinkРендер данных
ТолстыеExternalPlayEmbed, LabTrainersHubiframe, postMessage, state, оптимизация
ВынесенныеWebGL, эмуляция, длинный кодplay.spirzen.ru / code, в статье только embed

Виджеты — самодостаточный блок в тексте (демо, случайный вопрос, хаб). Интерактив с WebGL или тяжёлой эмуляцией — отдельный сервис play, не src/components/.


Стилизация и глобальные классы

ПодходКогда
CSS module (*.module.css)Изолированный UI компонента — предпочтительно
Глобальные классыСистема статей — .callout, .article-tags, .wiki-link в custom.css
Infima --ifm-*Согласование с темой

Стилизация компонентов не должна ломать типографику .theme-doc-markdown.


Бандл, chunks и оптимизация

docusaurus.config.jssplitChunks.

  • vendor-react, vendor-docusaurus, vendor-prism
  • itEmbed — async ExternalPlayEmbed / ExternalCodeEmbed
  • itDemoAsync — остальные компоненты до ~200 KB
  • itMermaid — диаграммы

Перегруз страницы — десятки синхронных демо; лечится gate + lazyDemoInView + embedLoadQueue. Оптимизацияленивая подгрузка, idle enhancement, prefetch limit в clientModules.


Как создать новый компонент — чеклист

СценарийРешение
WebGL, эмулятор, тяжёлый UIplay.spirzen.ru + ExternalPlayEmbed
Виджет в 1–2 статьяхsrc/components/MyWidget.jsx
Блок на всех статьяхDocItem/Layout + lazyDemo
Данные + список ссылокdata/*.json + тонкий компонент
// src/components/MyWidget.jsx

import React from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
import styles from './MyWidget.module.css';

/** @param {{ label: string }} props */
export default function MyWidget({ label }) {
return (
<BrowserOnly fallback={<span>{label}</span>}>
{() => <div className={styles.root}>{label}</div>}
</BrowserOnly>
);
}
npm start
npm run build

Частые ошибки

ОшибкаСимптомИсправление
Нет пустой строки после frontmatterMDX compile errorПустая строка после ---
@theme вместо @siteModule not foundПроектные компоненты — @site/src/...
window is not definedПадение SSRBrowserOnly или useEffect
iframe пустой локальноplay/code не запущеныlocalhost:4321/4322 или env
postMessage высота 0Неверный originTRUSTED_ORIGINS в constants
slug не в play404 в iframedocs:demo-registry, синхрон slug
import из shared/ в MDXНет lazy-обёрткиТолько корень components/

Реестр демо

info/demo-registry.md (docs:demo-registry). Slug в example="..." должен существовать на play.


Шпаргалка импортов


import ExternalPlayEmbed from '@site/src/components/ExternalPlayEmbed';
import ExternalCodeEmbed from '@site/src/components/ExternalCodeEmbed';
import CollectionHub from '@site/src/components/CollectionHub';
import LabTrainersHub from '@site/src/components/LabTrainersHub';
import GettingStartedPaths from '@site/src/components/GettingStartedPaths';
import RandomChecklistItem from '@site/src/components/RandomChecklistItem';
import RandomQuestionFromArticle from '@site/src/components/RandomQuestionFromArticle';
import DeveloperExamPlay from '@site/src/components/DeveloperExamPlay';
import DocCardList from '@theme/DocCardList';

DocCardList — компонент темы; remark не оборачивает @theme/* в lazy.


Глоссарий

Компонент

Переиспользуемый блок UI на React.

React-компоненты

Функции/классы с JSX в src/components/ и src/theme/.

Мост

Связь MDX-контента с интерактивом, данными и темой.

Интерактив

Исполняемые демо, тренажёры, play/code embeds.

Импорт компонентов

import X from '@site/src/components/X' в MDX.

Связь между собой

Компоненты → shared → data/constants → внешние сервисы.

embed

Встраивание play/code через iframe.

Эталон

ExternalPlayEmbed — образец для новых embed.

Демо

Интерактивный пример в статье.

iframe

Вложенный документ play/code.

play.spirzen.ru

Сервис интерактива, /p/embed/<slug>/.

lazy-обёртка

lazyExternalEmbed, lazyDemoInView — отложенный import.

props

Входные параметры JSX (example, title, …).

click-to-load

Загрузка embed только после клика (EmbedClickGate).

Автоматическая загрузка

autoLoad — iframe сразу после gate.

a11y

Доступность — role, aria-label, клавиатура. CSS a11y.

postMessage

API обмена сообщениями с iframe.

API

Контракт URL/postMessage между spirzen и play/code.

Подгрузка

Async загрузка chunk или iframe.

Запросы

HTTP к play/code — только после активации gate.

query-параметры

?theme=dark и playProps в URL iframe.

URL

Адрес embed, полной страницы play, статьи.

escape hatch

Prop src — полный URL вместо example.

trusted origin

Whitelist доменов для postMessage.

Кирпич

Мелкий модуль в shared/.

shared

src/components/shared/ — инфраструктура embed/lazy.

Обёртка

HOC вокруг lazy/import (lazyDemo, lazyExternalEmbed).

import

Статический в MDX; динамический import() в lazy.

lazyDemo

React.lazy + Suspense для theme layout.

Сериализация

embedLoadQueue — не более 2 iframe параллельно.

Блокировка скролла

embedScrollLock при fullscreen embed.

Suspense

React — показ fallback пока lazy chunk грузится.

autoLoad

Prop — пропустить внутренний gate после внешнего клика.

gate

EmbedClickGate — UI до загрузки iframe.

MDX

Markdown + JSX; точка вставки компонентов.

idle

scheduleIdleWork — отложенный DOM enhancement.

DOM

Дерево страницы в браузере.

Хаб

Агрегатор ссылок (CollectionHub, LabTrainersHub).

Каталог

Список демо или статей с метаданными.

label

Подпись подборки или кнопки.

Рендер

Отрисовка React в DOM.

Парсинг

Разбор DOM/markdown (RandomQuestion, articleExtract).

Случайность

RandomArticle, RandomChecklistItem — pick из набора.

Индекс

doc-search-index, demo-registry.

TS и TSX

TypeScript-компоненты (DesignThemePicker, theme).

WebGL

3D в браузере — на play.spirzen.ru, вне энциклопедии.

Эмуляция

Эмуляторы CPU/сети — play.spirzen.ru.

Тренажёры

Интерактивные упражнения лаборатории.

Виджеты

Вставляемые блоки в тексте статьи.

Толстые и тонкие компоненты

Много логики vs тонкий рендер данных.

BrowserOnly

Docusaurus — рендер только на клиенте, защита SSR.

Глобальные классы

.callout, .wiki-link в custom.css.

SSR

Server-Side Rendering при docusaurus build.

Синхронизация

theme/colorMode ↔ iframe через postMessage и query.

Стилизация компонентов

CSS modules + Infima variables.

Бандл

Собранный JS; splitChunks в config.

Перегруз

Слишком много sync iframe/chunks на одной странице.

Оптимизация

lazy, gate, queue, idle, viewport.

chunk

Отдельный JS-файл async import.

viewport

Видимая область; IntersectionObserver.


Связь с другими главами

Полезные статьи энциклопедии

Содержание