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

Основы

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

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

Документ — это шаблон плюс тип данных. Шаблон представляет собой дерево компонентов; каждый возвращает узлы, а разрешение дерева на данных даёт 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