news 2026/9/15 10:45:10

TinaCMS MDX 多模板对象字段实战:用 `_template` 驱动块级组件数据建模与无损往返

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TinaCMS MDX 多模板对象字段实战:用 `_template` 驱动块级组件数据建模与无损往返

TinaCMS MDX 多模板对象字段实战:用_template驱动块级组件数据建模与无损往返

【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo 🦙 ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms

在 TinaCMS 的富文本编辑体验中,rich-text字段不仅支持 Markdown 排版,还允许通过模板(templates)把结构化的 React 组件直接嵌入文档正文。本文以仓库 mdx-block-object-list-template 测试用例 为切入点,深入讲解「对象列表 + 模板」(object list with templates)这一数据建模方式:如何在块级组件(如<Action>)的属性中携带一个由_template字段指定具体形态的对象,以及 MDX 的解析(parse)与序列化(stringify)如何做到无损往返。读完本文,你将掌握在 TinaCMS 中定义多形态嵌套对象字段、书写对应 MDX 语法、并通过源码与测试验证其行为的方法。

一、测试夹具全景:一个最小而完整的多模板对象用例

该测试用例位于 packages/@tinacms/mdx/src/next/tests/mdx-block-object-list-template/,共包含 5 个文件,构成一条完整的「输入 MDX → 字段定义 → AST → 输出 MDX → 测试断言」链路:

文件作用
in.md待解析的 MDX 输入,定义两个<Action>
field.ts对应rich-text字段的 schema,声明模板与对象子模板
node.jsonparseMDX解析后期望的 AST 快照
out.mdserializeMDX序列化后期望的 MDX 快照(与in.md完全一致)
index.test.tsvitest 测试:解析匹配 node.json,序列化匹配 out.md

其中in.md的内容是(共 17 行,包含两个块级组件与一行普通文本):

<Action action={{ _template: "popup", title: "Say hello", description: "This is a description" }} /> And another template <Action action={{ _template: "link", title: "Say hello", url: "http://example.com" }} />

可以看到,<Action>组件的action属性是一个JSX 表达式属性{{ ... }}花括号包裹的对象字面量),对象内通过_template键声明自身形态:第一个是popup,携带titledescription;第二个是link,携带titleurl。这正是「多模板对象」在 MDX 中的书面表达方式。

二、字段 Schema 详解:rich-text 模板中的嵌套对象模板

要让上面的 MDX 被正确解析,必须在rich-text字段上声明对应的 schema。完整定义见 field.ts:

import { RichTextField } from '@tinacms/schema-tools'; export const field: RichTextField = { name: 'body', type: 'rich-text', parser: { type: 'mdx' }, templates: [ { name: 'Action', label: 'Action', fields: [ { type: 'object', name: 'action', templates: [ { label: 'Popup', name: 'popup', fields: [ { type: 'string', name: 'title' }, { type: 'string', name: 'descrption' }, ], }, { label: 'Link', name: 'link', fields: [ { type: 'string', name: 'title' }, { type: 'string', name: 'url' }, ], }, ], }, ], }, ], };

逐层拆解这个 schema 的嵌套结构:

  1. 顶层templates:声明富文本中可用的块级模板(block templates)。name: 'Action'与 MDX 中的组件名<Action>一一对应,label: 'Action'用于编辑器中展示。这里parser: { type: 'mdx' }明确告诉 TinaCMS 使用 MDX 解析器而非普通 Markdown。
  2. 模板的fieldsAction模板拥有一个字段action,其类型为object——即「对象字段」。
  3. 对象的templatesaction字段内部再次声明templates,包含popuplink两个子模板,每个子模板都有自己的fields。这正是「对象列表 + 模板」的核心:同一个对象字段可以在两种(或多种)形态之间切换,形态由数据中的_template键决定。

子模板字段的数据类型(string)、字段名(titledescrptionurl)与 MDX 对象字面量中的键一一对应。需要留意的是,popup 子模板中字段名写的是descrption(拼写与 MDX 中的description不一致),这属于测试用例本身遗留的拼写细节,不影响机制理解——实际项目中应保证 schema 字段名与 MDX 键完全一致,数据才能被正确映射。

与「纯对象列表字段」的区别

在 tests 目录下还有一个对比用例 mdx-block-object-list-field。两者命名相似,机制不同:

  • object list field:对象字段不带templates,而是扁平地声明多个fields,形态固定;
  • object list template(本文主题):对象字段带templates,即多模板对象(polymorphic object),形态可随_template切换。

需要多形态复用(如按钮既是弹窗触发器又是外链)时,选择后者;形态固定时,选择前者即可。

三、解析产物:AST 中_template的落点

node.json记录了parseMDX的期望输出。核心结构如下(节选第一个<Action>):

{ "type": "mdxJsxFlowElement", "name": "Action", "children": [{ "type": "text", "text": "" }], "props": { "action": { "_template": "popup", "title": "Say hello", "description": "This is a description" } } }

解读这份 AST:

  • <Action>被解析为mdxJsxFlowElement节点,name保留组件名Action
  • JSX 属性被收集进props对象,action的值就是 MDX 中{{ ... }}内的对象字面量;
  • _template原样保留在props.action内部,与titledescription平级,不单独抽离。后续的编辑会话正是靠读取这个键来实例化对应的子模板表单;
  • 组件若有子内容会被递归处理后放入props.children,本用例无子内容,因此children仅含一个空文本节点。

第二个<Action>_template: "link")的 AST 结构完全相同,只是props.action变为{ "_template": "link", "title": "Say hello", "url": "http://example.com" }。两节点之间的普通文本And another template则被解析为独立的p段落节点——说明模板块与 Markdown 文本可以在同一文档中自由混排

四、底层实现:JSX 属性如何变成props对象

解析管线由 parse/index.ts 的parseMDX入口驱动:先fromMarkdown产出 mdast 树,再用compact合并相邻同类型节点,最后交给postProcessor。其中关键的一步位于 parse/post-processing.ts 的addPropsToMdxFlow

node.attributes.forEach((attribute) => { if (attribute.type === 'mdxJsxAttribute') { props[attribute.name] = attribute.value; } else { throw new Error('HANDLE mdxJsxExpressionAttribute'); } });

这段代码遍历 JSX 节点的attributes,把mdxJsxAttribute类型的属性按「属性名 → 值」收进propsaction={{ ... }}这类表达式属性经 mdast-util-mdx-jsx 解析后,其值已经是求值后的对象字面量,因此props.action直接就是{ _template, title, ... }这样的普通对象,_template键无需特殊处理即自然落入其中。处理完成后node.attributes被删除、node.props被挂载,children被替换为空文本节点,最终交给remarkToSlate转成 TinaCMS 编辑器使用的数据结构。

五、无损往返:parse → stringify 的闭环验证

「多模板对象」机制能够安全落地,离不开解析与序列化的对称性。序列化入口在 stringify/index.ts 的stringifyMDX:经过preProcess预处理、normalizeMarkWhitespace规整空白后,由toTinaMarkdown依据同一份fieldschema 重新生成 MDX。因为 schema 与输入一致,props.action里的_template会在输出时被原样写回{{ _template: "..." }}对象字面量。

测试用例 index.test.ts 正是验证这一闭环:

it('matches input', () => { const tree = parseMDX(input, field, (v) => v); expect(util.print(tree)).toMatchFile(util.nodePath(__dirname)); const string = serializeMDX(tree, field, (v) => v); expect(string).toMatchFile(util.mdPath(__dirname)); });

测试断言了两件事:

  1. parseMDX的输出与 node.json 快照一致(其中util.print通过removePosition剔除position定位信息,保证快照不随行列号抖动,见 tests/util.ts);
  2. serializeMDX的输出与 out.md 快照一致,而 out.md 与 in.md 逐字相同。

也就是说,同一份 MDX 经「解析 → 序列化」往返后内容不变_template: "popup"_template: "link"两种形态都能被稳定地还原,这是多模板对象字段可用于生产内容编辑的前提。

六、在自己的 TinaCMS 项目中落地

参考本用例,在 TinaCMS 项目中配置「对象列表 + 模板」字段的步骤为:

  1. rich-text字段(如body)的templates中声明块级模板(如Action),name与页面组件名保持一致;
  2. 在模板fields中加入type: 'object'的字段(如action),在其内部声明多个子模板(popuplink),并为每个子模板配置独立的fields
  3. 在内容 MDX 中按<Action action={{ _template: "popup", ... }} />的语法书写,对象键与子模板字段名对齐;
  4. 运行parseMDX/serializeMDX(见 parse/index.ts 与 stringify/index.ts),或直接参考本用例的 index.test.ts 建立快照测试,验证往返一致性。

其余可交叉验证的用例还包括 mdx-basic-nested-objects、mdx-block-object-list-field 与 mdx-block-scalar-fields,分别覆盖嵌套对象、固定形态对象字段与标量属性等相邻场景,可作为进阶阅读材料。

小结

通过 mdx-block-object-list-template 这一个最小用例,我们完整走通了 TinaCMS「多模板对象」机制的三个层面:schema 层(object 字段嵌套 templates 声明多形态)、语法层(MDX 中_template键指定形态)、管线层(parse 将 JSX 属性归入props、stringify 原样还原,测试保证无损往返)。掌握这套模式,你就能在富文本正文中安全地嵌入结构可变、可编辑的复杂组件,让内容建模既有 Markdown 的自由度,又有组件数据的严谨性。

【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo 🦙 ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 10:44:55

酵母展示技术:生物医药研发的高效筛选工具

1. 酵母展示技术&#xff1a;从实验室工具到生物医药革命2009年&#xff0c;一家名为Adimab的生物技术公司凭借酵母展示技术平台&#xff0c;在短短18个月内完成了传统方法需要5年才能实现的抗体药物开发周期。这个案例彻底改变了生物医药行业对高通量筛选的认知——酵母不再只…

作者头像 李华
网站建设 2026/9/15 10:44:41

Python复合材料层合板性能分析:从刚度矩阵到失效判据全流程

简介&#xff1a;这份Python代码包面向复合材料力学方向的学生与工程师&#xff0c;围绕经典层压理论&#xff08;CLT&#xff09;实现复合层定义、层压板铺层、应力应变分布计算与失效准则判定&#xff0c;覆盖杨氏模量、刚度矩阵、强度校核等核心环节&#xff0c;适合课程设计…

作者头像 李华
网站建设 2026/9/15 10:44:35

ATtiny1616事件系统驱动TCB输入捕获实现精确频率测量

简介&#xff1a;面向ATtiny1616频率测量与输入捕获应用的单片机及嵌入式开发者&#xff0c;这份资源将官方数据手册与一套可编译的Atmel Studio工程集成在一起&#xff0c;帮助解决事件触发中断、定时器计数值读取及频率反推等实现问题。工程通过PWM模块生成方波信号&#xff…

作者头像 李华