前置知识: Markdown

Markdown 表格

9 min中级

GFM 表格语法:分隔行的设计原因、对齐约定与数字右对齐、单元格边界规则、表格与列表的选型决策、遮代码自检与 HTML 兜底。

认知导入(Layer 1 进阶层) 前置知识:003 段落与换行(表格是”块级元素”,行结构依赖换行)。 边界说明:表格是 GFM 扩展而非 CommonMark 核心,纯 CommonMark 渲染器会把它当普通文本;单元格内只能放行内元素,放不了列表和代码块。 强制练习:写一个三列表格,把分隔行少写一列,观察缺列的单元格如何被填空。

1. 基本语法:分隔行为什么是识别标志

表格由表头行 + 分隔行 + 数据行组成,单元格用竖线 | 分隔:

markdown
| 姓名 | 年龄 | 城市 |
| ---- | ---- | ---- |
| 张三 | 25   | 北京 |
| 李四 | 30   | 上海 |

渲染效果:

姓名年龄城市
张三25北京
李四30上海

构成规则(GFM 规范):

  • 第一行是表头;第二行是分隔行,每个单元格由至少一个短横线构成(---),它是表格的识别标志;
  • 两侧的竖线可以省略(姓名 | 年龄 的写法也合法),但为了源码可读性建议保留;
  • 表头单元格必填:分隔行上面必须有表头文字,只有分隔行的表格不成立;
  • 表格从空行或前一个块级元素结束后开始,表格写到第一个空行或下一个块级元素为止。

心智模型:表格是「行内元素的网格」。解析器的工作方式是:先用分隔行确认”这是一个表格”,再按竖线把每一行切成单元格,最后对每个单元格重新按行内语法解析——所以粗体、链接、代码都能用(见第 4 节),而列表、代码块这些块级结构进不来。

再问一层:为什么 GFM 要设计一个”分隔行”,而不是像 CSV 那样第一行直接是数据?因为 Markdown 的目标是源码本身可读:没有分隔行,人眼分不清”表头”和”数据”;有了分隔行,即使不经渲染,源码里的表格也是一张对齐的表。这是 Markdown “纯文本即排版”哲学在表格上的落点——分隔行是给渲染器的信号,更是给人类读者的排版。

2. 对齐控制:冒号是列的属性

分隔行中用冒号声明整列的对齐方式:

markdown
| 左对齐   | 居中对齐 |   右对齐 |
| :------- | :------: | -------: |
| 内容 1   |  内容 2  |   内容 3 |

渲染效果:

左对齐居中对齐右对齐
内容 1内容 2内容 3
  • :--- 左对齐(默认);
  • :---: 居中对齐;
  • ---: 右对齐。

约定俗成:文本列左对齐、数字列右对齐。右对齐数字不是审美偏好,是功能需求:位数不同的数字右对齐后个位对齐,量级一眼可比(1 200 与 98 000 000 左对齐时根本无法直接比大小)。表头源码里的空格只影响源码美观,不影响渲染与对齐——源码对齐交给 Prettier 这类格式化工具(第 9 节),手写时不必强迫症。

3. 列数不匹配的行为

数据行的单元格数不必与表头一致,GFM 规范有明确定义(这也是表格比”手写 HTML”友好的地方):

markdown
| A | B | C |
| - | - | - |
| 1 | 2 |
| 3 | 4 | 5 | 6 |
  • 数据行少列:缺的单元格按空单元格补齐(上面的第二行 C 列为空);
  • 数据行多列:多余单元格被丢弃(6 不会显示)。

设计原因同样在”源码可读”:解析器永远以表头的列数为准,宽容地吞掉手误,而不是让一行多打的竖线撑破整个表格。

4. 单元格内可以放什么

单元格按行内语法解析,行内元素全部可用:

markdown
| 项目 | 链接 | 代码 | 状态 |
| ---- | ---- | ---- | ---- |
| 文档 | [GitHub](https://github.com) | `npm run build` | **已完成** |

需要注意的边界:

  • 不能放块级元素:列表、代码块、标题、表格都无法放进单元格。想要”多行效果”只能用 <br>:
markdown
| 姓名 | 地址 |
| ---- | ---- |
| 张三 | 北京市朝阳区<br>建国路 88 号 |
  • 单元格里的 - 红色 不是列表:它只是以减号开头的字面文本,配合 <br> 排列仅能模拟视觉分行,没有列表语义。想表达”一个格子里多个并列项”且项目需要复杂格式(链接、多级)时,正确答案是换用列表而不是硬塞表格;
  • 竖线必须转义:内容含竖线时写成 \|(即使在代码跨度内也一样,详见 markdown/080-EscapeCharacter):
markdown
| 命令           | 说明     |
| -------------- | -------- |
| `grep \| file` | 管道操作 |
  • 任务复选框不渲染:| [x] | 在 GitHub 上显示为字面文本 [x],表格里表达状态请用文字。

5. 选型:什么时候该用表格,什么时候不该

表格是表达力最强也最容易被滥用的块级结构。做选择前先问一个问题:这些信息之间存在”两两交叉”的维度关系吗?

  • 有——“每行一个对象、每列一个属性”的矩阵关系(如”平台 × 支持情况”):用表格;
  • 没有,只是”术语 → 解释”的对照:用定义列表,它不需要对齐、不会被列宽挤爆;
  • 没有,只是”先后步骤”或”并列要点”:用列表(060 篇),表格的框线在这些场景纯属噪音;
  • 内容是”怎么做的”而非”有哪些”:用正文加代码块,硬压成表格会把操作步骤的因果顺序碾平。

一个实用的反向信号:如果你发现自己在单元格里频繁用 <br> 塞多行内容,多半说明选错了结构——表格擅长”广而浅”(列多、每格短),不擅长”深而长”(格内成段)。移动端会替你验证这一点:列一多,宽表格必然溢出(见 6.3)。

6. 复杂表格:HTML 兜底

GFM 表格做不到合并单元格与复杂表头。确有需要时退回 HTML 表格,注意 HTML 块前后各留一个空行(详见 markdown/230-HtmlEmbed):

html
<table>
  <thead>
    <tr>
      <th>模块</th>
      <th colspan="2">配置项</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td rowspan="2">数据库</td>
      <td>host</td>
      <td>localhost</td>
    </tr>
    <tr>
      <td>port</td>
      <td>5432</td>
    </tr>
  </tbody>
</table>

取舍提醒:HTML 表格维护成本高(diff 里一行动辄上百字符)、部分严格渲染环境会过滤属性,能用 GFM 表格就不要用 HTML。遇到”想合并单元格”的冲动,先回头检查第 5 节的选型问题——很多时候拆成两张小表比一张大表更可读。

7. 常见问题排查

7.1 表格没被识别

  • 分隔行缺失或写法错误(分隔行是识别标志,| --- | 至少一个横线);
  • 表头行与分隔行之间插入了其他内容;
  • 用了全角竖线 | 而非半角 |;
  • 分隔行列数与表头不一致(列错位由此产生)。

7.2 内容被竖线切断

内容含 | 时必须写 \|;URL 查询参数里罕见的竖线同样处理。

7.3 表格在移动端溢出

  • 减少列数,或把次要列合并为”备注”列;
  • 单元格内容尽量短,长说明移到表格外的正文;
  • 站点层面对宽表格启用横向滚动。

7.4 改了一列,diff 满屏红

源码手工对齐列宽后,加一个长内容会让整张表的每一行都被重新补空格——git diff 全红,review 时找不到真实改动。对策:交给 Prettier 统一格式化(见第 9 节),团队约定”表格格式化由工具负责,人只改内容”;或者干脆缩短该列的表头文案。

8. 动手实践:遮代码自检

先只读任务与提示,自己写完再对照参考实现。

任务一:写出满足以下全部渲染要求的表格源码——三列(方法 / 命令 / 说明);命令列右对齐;“管道”行的命令列内容是行内代码 cat a \| b(注意内容本身含竖线);“换行”行的说明列里分两行显示”第一行
第二行”。写完贴到支持 GFM 的渲染器(GitHub、本站预览均可)验证。

提示:两个坑都在”内容与语法的边界”上——单元格里的竖线要不要转义(回看第 4 节),以及代码跨度里 <br> 会不会被当成标签。

参考实现(先自己写完再看):

markdown
| 方法 | 命令          | 说明               |
| ---- | ------------: | ------------------ |
| 管道 | `cat a \| b`  | 两个命令串联执行   |
| 换行 | `echo`        | 第一行<br>第二行   |

要点自查:\| 转义在代码跨度内也必须写;<br> 写在普通单元格里(不在反引号内)才会渲染成换行;命令列分隔行写 -----:。

任务二:把下面这段散文化的”重试配置说明”改写成一张表格——

全局重试次数默认 3 次,可以通过 RETRY_TIMES 覆盖。超时时间默认 30 秒,环境变量是 TIMEOUT_MS,单位毫秒。两者都可以在配置文件里关掉,开关分别是 retry.enabled 与 timeout.enabled。

提示:先识别”每行一个对象、每列一个属性”的矩阵——三行(重试次数 / 超时时间 / 各自开关)其实是”配置项 × 默认值 / 环境变量 / 说明”的表格。

参考实现(先自己写完再看):

markdown
| 配置项     | 默认值 | 环境变量      | 配置文件开关    |
| ---------- | -----: | ------------- | --------------- |
| 重试次数   | 3      | `RETRY_TIMES` | `retry.enabled` |
| 超时时间   | 30 秒  | `TIMEOUT_MS`  | `timeout.enabled` |

要点自查:数字/默认值列右对齐;代码型内容用行内代码;四列都在移动端可承受的宽度内——原散文里”单位毫秒”这类细节可以留在表格后的正文里,不必硬塞成一列。

9. 工具与规范

  • 在线生成器(Tables Generator 等)可以可视化拖列、对齐后导出 Markdown;
  • 编辑器插件(VS Code 的 Markdown 表格增强、Prettier 的表格格式化)会自动对齐源码列宽——纯美观,不影响渲染;把表格格式化交给工具(如 Prettier),是避免第 7.4 节 diff 污染的治本方案;
  • markdownlint 的 MD055/MD056/MD058 等规则可约束表格风格(分隔行样式、列数一致、表格前后空行);CI 里跑 lint,比 review 时肉眼数竖线可靠。

10. 与之前和之后的知识的关系

  • 往前:段落与换行 决定了表格”一行一记录”的行结构;转义字符 的 \| 是表格里最高频的转义场景;
  • 横向:定义列表 接住”术语对照”这类伪表格需求;HTML 嵌入 是合并单元格的唯一出路;
  • 往后:规范文档写作 中,参数表、错误码表、兼容性表是表格最密集的产出场景,第 5 节的选型问题在那里会反复出现。

小结

  • 初学者要点:表格 = 表头行 + | --- | 分隔行 + 数据行;冒号控制对齐(文本左对齐、数字右对齐);单元格内换行用 <br>;竖线内容用 \| 转义。
  • 进阶注意:表格是 GFM 扩展,纯 CommonMark 渲染器不识别;分隔行既是给解析器的识别信号,也是”源码即可读表格”的设计落点;单元格只能装行内元素,列表/代码块放不进去(任务复选框在表格内也不渲染);少列补空、多列丢弃是规范行为;频繁 <br> 与”想合并单元格”都是选型错误的信号——先问”这些信息有矩阵关系吗”,再决定用表格、列表还是定义列表。