printer:印刷器
把文档树渲染成排版成品的核心模块:Printer(概念与渲染器的登记处)、
PrinterComponent(执行两阶段渲染的组件)、PrinterRenderer(渲染器协议),
以及预处理产物的类型定义。预处理机制的原理见教程第 4 章和第 6 章。
Printer
Printer 是输出一侧的入口,同时也是全部概念定义的保管者(编辑器通过它查询概念)。 和 EditorCore 一样,应用启动时构造一次、全程共用。四个构造参数在教程第 3 章出现过:
new Printer(
first_class_concepts: FirstClassConcept[],
second_class_concepts: SecondClassConcept[],
renderers: RendererDict, // {group: {一级概念名: PrinterRenderer}, ...}
default_renderers: DefaultRendererDict, // 每种节点类型一个默认渲染器
)
它的方法可以分成四类:动态注册(add_ 系列,用于运行时追加概念和渲染器)、
概念与渲染器的查询(get_ 系列)、参数加工(process_parameters),
以及预处理的入口(preprocess,通常由印刷器组件自动调用,很少手动触发):
| 方法 | 说明 |
|---|---|
add_first_concept(x) / add_second_concept(x) / add_renderer(type, name, renderer) | 构造之后动态注册概念和渲染器 |
get_first_concept(type, name) / get_second_concept(type, name) | 按名字查询概念 |
get_node_first_concept(node) / get_node_second_concept(node) | 从节点出发解析它的概念 |
get_renderer(type, name?) / get_node_renderer(node) | 查询渲染器,查不到时返回默认渲染器 |
process_parameters(node) | 把节点参数加工成处理后的形式:套用二级概念的重写、求值函数型参数、剥掉类型包装 |
preprocess({root, init_env?}) | 对整棵树做预处理,返回四件产物 [环境, 各节点上下文, 各节点参数, 缓存] |
preprocess_init() | 异步初始化:调用各渲染器的 init(比如预加载数据),返回 Promise |
PrinterComponent
印刷器的 React 组件(类组件),负责执行预处理和渲染。
通常通过它的包装 DefaultPrinterComponent 使用(见默认实现那页)。
interface PrinterComponentProps {
printer: Printer
root: AbstractNode
init_env?: Env // 预处理的初始环境,一般不需要
onUpdateCache?: (cache: PrinterCache) => void // 每次预处理后把缓存交出来
}
| 实例方法 | 说明 |
|---|---|
scroll_to(path) / scroll_to_idx(idx) | 滚动到指定节点,「点击引用跳转到目标」用它实现 |
get_ref(path) / get_ref_from_idx(idx) / bind_ref(path) | 取得或绑定节点对应的 DOM 元素 |
preprocess(params?) / update() | 手动触发预处理、手动刷新 |
PrinterRenderer:渲染器协议
印刷渲染器是 PrinterRenderer 类的实例。它把一个渲染器拆成几个阶段函数:
new PrinterRenderer<NodeType>({
init?: () => Promise<void>, // 一次性的异步准备工作
enter?: PrinterEnterFunction<NodeType>, // 预处理进入节点时调用
exit?: PrinterExitFunction<NodeType>, // 预处理离开节点时调用
renderer: PrinterRenderFunction<NodeType>, // 渲染阶段的 React 组件
renderer_as_property?: PrinterRenderFunction<NodeType>, // 仅抽象节点:作为属性渲染时用
})
各阶段函数的签名:
// 预处理阶段,按文档顺序对每个节点调用。直接原地修改 env 和 context。
type PrinterEnterFunction<NT> = (
node: Readonly<NT>, path: Readonly<number[]>,
parameters: Readonly<ProcessedParameterList>,
env_draft: Env, context_draft: Context,
) => void
// 离开节点时调用。返回二元组:写入缓存的内容,以及是否已处理完成
//(返回 false 表示需要再跑一轮预处理迭代)。
type PrinterExitFunction<NT> = (同上述参数) => [PrinterCacheItem, boolean]
// 渲染阶段:一个普通的 React 组件。
interface PrinterRenderFunctionProps<NT> {
node: NT
context: Context // 预处理为该节点留下的结论
parameters: {[key: string]: any} // 处理后的参数
children?: React.ReactNode // 子节点的渲染结果,务必输出
}
直接手写 enter 和 exit 属于底层用法。日常开发用
默认实现的工厂函数配合 contexter:
工厂会把 contexter 的钩子自动接到这两个位置上(见教程第 6 章)。
预处理产物的类型
预处理返回的四件产物(环境、各节点上下文、各节点参数、缓存)各有自己的类型名。 写渲染器和 contexter 时会反复见到它们,值得对照教程第 6 章记一遍:
| 类型 | 说明 |
|---|---|
Env | {[key]: any}。预处理过程中的全局环境,各 contexter 用自己的 key 在其中各占一块。 |
Context | {[key]: any}。预处理留给单个节点的结论,渲染阶段该节点可读。 |
ProcessedParameterList | {[key]: any}。处理后的参数,值是普通值。 |
PrinterCache / PrinterCacheItem | 按节点 idx 索引的缓存表。exit 的返回值汇集于此,全局可查,引用名等「给别人看的信息」放这里。 |
RendererDict | {group: {名: PrinterRenderer}, inline: ..., structure: ..., support: ..., abstract: ...} |
DefaultRendererDict | 七种节点类型各一个默认 PrinterRenderer |
渲染上下文 hooks
印刷渲染函数内部可以使用一组 hooks 访问整个印刷现场的信息
(由 PrinterGlobalInfo 这个 React Context 提供):
| Hook | 说明 |
|---|---|
usePrinter() / usePrinterGlobalInfo() | 印刷器全局信息 |
usePrinterRoot() | 正在被渲染的根节点 |
usePrinterComponent() | PrinterComponent 实例,可调用 scroll_to_idx 等方法 |
usePrinterAllEnv() / usePrinterAllContexts() / usePrinterAllParameters() / usePrinterAllCaches() | 预处理的全部产物。实现引用显示时最常用的是 caches:按目标 idx 查出引用名。 |