Основы
Документы и узлы
Документ — это дерево компонентов, каждый из которых принимает свои данные как состояние и возвращает нужные ему узлы.
Документ — это шаблон плюс тип данных. Шаблон представляет собой дерево
компонентов; каждый возвращает узлы, а разрешение дерева на данных даёт
DocumentModel — чистый JSON, ничего не предполагающий о том, как всё это будет
отрисовано.
Компонент принимает свои данные как состояние
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> будет ошибкой компиляции.
Данные могут приходить и как пропсы
Компоненту, которому дали нужное, не приходится тянуться за ним самому:
export const Arrears: Paragraph<{ amount: number }> = ({ amount }) => {
const { currency } = useFormat();
return <Paragraph id="arrears">You owe {currency(amount)}.</Paragraph>;
};
Кто-то должен прочитать данные первым, поэтому компонент только на пропсах живёт под родителем, который взял их в состояние. Пропсы — для компонентов, которые вы хотите переиспользовать с разными значениями, состояние — для компонентов, знающих, какому документу они принадлежат.
Решения — это обычный if
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. Разрешённый документ тогда фиксирует, какая из них досталась этому получателю, — а это и есть разница между документом, который можно проверить, и документом, на который можно только посмотреть.
О ветвлении стоит знать одну вещь, прежде чем публиковать в движок: см. что меняется при публикации.
Идентификаторы
id — это то, как движок адресует узел и как артефакты сборки остаются сравнимыми между запусками, поэтому считайте переименование ломающим изменением.
Его можно не указывать. Узел без id получает его из своего места, и именно это избавляет ветки и циклы от необходимости придумывать имена. Чего нельзя — так это использовать один и тот же дважды: два узла, претендующих на один id, — это ошибка, сообщаемая с обеими позициями, а не гонка, в которой побеждает последний.
Разделы вкладываются, узлы — нет
Section — единственная вкладывающая конструкция. Её заголовок переходит в
структуру документа:
<Section id="opening" title="Opening">
<Greeting />
<Offer />
</Section>
См. Section о том, что раздел может содержать и на какую глубину.
Хуки
Хуки — это то, чем компонент дотягивается до сборки вокруг себя:
| Хук | Что он даёт |
|---|---|
useState | Данные, взятые один раз и сохранённые |
useShared | Значение, оставленное компонентам, которые отрисуются позже |
useSetPrompts | Промпты для отдаваемого узла — то, что делает его динамическим |
useSetPlaceholders | Что предпросмотр показывает вместо сгенерированного содержимого |
usePlaceholderData | Подставные имена, даты и цифры для предпросмотра |
useFormat | Валюты, даты, списки и множественные формы с учётом локали |
useAvailableTokens | Бюджет токенов, выделенный этой сборкой |
useDeriver | Выполняет зарегистрированный деривер прямо сейчас |
Они выполняются в порядке вызова, поэтому каждый хук должен быть достигнут до
первого await и до любого ветвления — то же правило, что и в React, и по той
же причине. Вызвать хук после await — ошибка, которая об этом и сообщает,
вместо того чтобы молча прицепиться к следующему отрисованному компоненту.
Сборка документа
import { buildDocument } from "docxcelerate";
const doc = await buildDocument(documentTemplate, data);
Результат — DocumentModel: schemaVersion, id, title и массив nodes. В
нём нет ни стилей, ни вёрстки — их позже применяет тот рендерер, который его
потребляет.
Для проекта документа, объявленного через defineDocumentProject, лучше брать
buildProjectPreviewDocument: он применяет стиль проекта и разрешает
динамические узлы в их заполнители:
import { buildProjectPreviewDocument } from "docxcelerate";
const doc = await buildProjectPreviewDocument(project);
Именно это вызывает приложение предпросмотра, так что собранное в коде совпадает с тем, что вы видели в браузере.
Изменить эту страницу на GitHub ↗ Читать эту страницу в Markdown