# Документы и узлы

> Документ — это дерево компонентов, каждый из которых принимает свои данные как состояние и возвращает нужные ему узлы.

Source: https://docxcelerate.com/ru/docs/essentials/documents-and-nodes/

Документ — это шаблон плюс тип данных. Шаблон представляет собой дерево
**компонентов**; каждый возвращает **узлы**, а разрешение дерева на данных даёт
`DocumentModel` — чистый JSON, ничего не предполагающий о том, как всё это будет
отрисовано.

## Компонент принимает свои данные как состояние

```tsx
import { Paragraph, useState } from "docxcelerate/template";
import type { DocumentData } from "../types.ts";

export const Greeting: Paragraph = () => {
  const [state] = useState((data: DocumentData) => ({
    name: data.residentName,
  }));

  return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
};
```

`useState` — то место, через которое данные попадают в компонент, и
единственное. Всё, что идёт дальше, читает состояние. У этого правила есть
причина: раз каждая зависимость проходит через одно объявление, то, что нужно
компоненту, оказывается записанным, а не разбросанным по коду, который этим
пользуется.

Инициализатор — обычный TypeScript. Языка шаблонов нет, поэтому условия,
форматирование и импорты просто доступны.

`Paragraph` называет и элемент, и тип компонента — значение и тип могут делить
имя, — так что `const Greeting: Paragraph` говорит, что здесь получается, и
вернуть оттуда `<Section>` будет ошибкой компиляции.

## Данные могут приходить и как пропсы

Компоненту, которому дали нужное, не приходится тянуться за ним самому:

```tsx
export const Arrears: Paragraph<{ amount: number }> = ({ amount }) => {
  const { currency } = useFormat();

  return <Paragraph id="arrears">You owe {currency(amount)}.</Paragraph>;
};
```

Кто-то должен прочитать данные первым, поэтому компонент только на пропсах живёт
под родителем, который взял их в состояние. Пропсы — для компонентов, которые вы
хотите переиспользовать с разными значениями, состояние — для компонентов,
знающих, какому документу они принадлежат.

## Решения — это обычный `if`

```tsx
export const Balance: Paragraph = () => {
  const [state] = useState((data: DocumentData) => ({
    settled: data.balanceDue === 0,
  }));

  if (state.settled) {
    return <Paragraph id="settled">Nothing outstanding.</Paragraph>;
  }

  return <Paragraph id="arrears">A balance remains.</Paragraph>;
};
```

Дайте каждой ветке собственный id. Разрешённый документ тогда фиксирует, какая
из них досталась этому получателю, — а это и есть разница между документом,
который можно проверить, и документом, на который можно только посмотреть.

О ветвлении стоит знать одну вещь, прежде чем публиковать в движок: см.
[что меняется при публикации](/ru/docs/essentials/templates/#что-меняется-при-публикации).

## Идентификаторы

id — это то, как движок адресует узел и как артефакты сборки остаются
сравнимыми между запусками, поэтому считайте переименование ломающим изменением.

Его можно не указывать. Узел без id получает его из своего места, и именно это
избавляет ветки и циклы от необходимости придумывать имена. Чего нельзя — так
это использовать один и тот же дважды: два узла, претендующих на один id, — это
ошибка, сообщаемая с обеими позициями, а не гонка, в которой побеждает
последний.

## Разделы вкладываются, узлы — нет

`Section` — единственная вкладывающая конструкция. Её заголовок переходит в
структуру документа:

```tsx
<Section id="opening" title="Opening">
  <Greeting />
  <Offer />
</Section>
```

См. [Section](/ru/docs/nodes/section/) о том, что раздел может содержать и на
какую глубину.

## Хуки

Хуки — это то, чем компонент дотягивается до сборки вокруг себя:

| Хук | Что он даёт |
| --- | --- |
| `useState` | Данные, взятые один раз и сохранённые |
| `useShared` | Значение, оставленное компонентам, которые отрисуются позже |
| `useSetPrompts` | Промпты для отдаваемого узла — то, что делает его динамическим |
| `useSetPlaceholders` | Что предпросмотр показывает вместо сгенерированного содержимого |
| `usePlaceholderData` | Подставные имена, даты и цифры для предпросмотра |
| `useFormat` | Валюты, даты, списки и множественные формы с учётом локали |
| `useAvailableTokens` | Бюджет токенов, выделенный этой сборкой |
| `useDeriver` | Выполняет зарегистрированный деривер прямо сейчас |

Они выполняются в порядке вызова, поэтому каждый хук должен быть достигнут до
первого `await` и до любого ветвления — то же правило, что и в React, и по той
же причине. Вызвать хук после `await` — ошибка, которая об этом и сообщает,
вместо того чтобы молча прицепиться к следующему отрисованному компоненту.

## Сборка документа

```ts
import { buildDocument } from "docxcelerate";

const doc = await buildDocument(documentTemplate, data);
```

Результат — `DocumentModel`: `schemaVersion`, `id`, `title` и массив `nodes`. В
нём нет ни стилей, ни вёрстки — их позже применяет тот рендерер, который его
потребляет.

Для проекта документа, объявленного через `defineDocumentProject`, лучше брать
`buildProjectPreviewDocument`: он применяет стиль проекта и разрешает
динамические узлы в их заполнители:

```ts
import { buildProjectPreviewDocument } from "docxcelerate";

const doc = await buildProjectPreviewDocument(project);
```

Именно это вызывает приложение предпросмотра, так что собранное в коде совпадает
с тем, что вы видели в браузере.
