Expo SDK 的 TSDoc 文档规范:为 Expo API 编写可自动生成的文档注释
本文基于 Expo 仓库中的技能文档 .claude/skills/expo-api-docs/SKILL.md 展开,系统讲解 Expo SDK 各 expo-* 包中 TypeScript API 的 TSDoc 注释规范:第三人称陈述式写法、@platform/@example/@deprecated/@default 等标签约定、代码示例与引用块格式,以及类型导出模式。读完本文后,你可以为任何 expo-* 模块写出能被文档生成系统(GenerateDocsAPIData + TypeDoc)正确提取并渲染成 API 参考页的注释,并理解底层提取管线的实际工作方式。
何时编写 TSDoc 注释
规范明确的第一原则是:写代码的同时写文档,而不是事后补("Document APIs as you write them, not as an afterthought")。以下场景必须使用这套约定:
- 实现会暴露公共 TypeScript API 的新功能;
- 在
packages/expo-*中添加或修改公共 API; - 为函数、类型、接口、常量或枚举补充文档;
- 添加平台特定注解(
@platform); - 在 docblock 中编写代码示例。
三条核心原则
- 第三人称陈述式(Third-person declarative) —— 描述函数"做什么",而不是命令式地写"去做什么"。例如用
Gets...、Returns...、Checks...,而不是Get...、Return...。 - 解释冰山(Explain the iceberg) —— 不仅要写参数和返回值,还要文档化失败模式、副作用、并发行为等"水面以下"的部分。
- 质量优于数量(Quality over quantity) —— 没有文档好过无用的文档。给一个
width属性写 "The width" 这种描述属于典型的"无用文档",规范明确将其列为反面案例。
函数文档:标准结构
一个符合规范的函数 docblock 的完整结构如下(此示例即规范文档给出的标准范例):
/**
* Gets the uptime since the last reboot of the device, in milliseconds.
* Android devices do not count time spent in deep sleep.
*
* @return A promise fulfilled with the milliseconds since last reboot.
*
* @example
* ```ts
* const uptime = await Device.getUptimeAsync();
* // 4371054
* ```
*
* @platform android
* @platform ios
*/
export async function getUptimeAsync(): Promise<number> {
关键要点:
- 第一句话:说明函数做什么;
- 后续句子:重要的行为、边界情况、平台差异;
- 单短语描述省略句尾句号;
- 多句描述使用句号。
在 Expo 仓库中可以找到与该范例完全对应的真实实现:packages/expo-device/src/Device.ts#L246-L262 中的 getUptimeAsync,其注释同样遵循"第一句功能描述 + 平台差异说明(Android 不计深度睡眠)+ @return + @example + 双 @platform 标签"的结构。
同文件中的 isRootedExperimentalAsync 则示范了"解释冰山"原则:注释中详细说明了为何 root/jailbreak 检测"不完全可靠"——Android 上通过查找 su 可执行文件路径实现、可能被反向工程绕过,iOS 上受 xCon 等闭源钩子方案影响,web 上恒返回 false。这就是规范要求的"文档化失败模式"。
参数文档与引用块说明
@param 的格式为 @param paramName 描述(首字母大写),参数描述中允许使用 Markdown(链接、强调、列表)、引用块(blockquote)和文档页链接:
/**
* Sets the sensor update interval.
*
* @param intervalMs Desired interval in milliseconds between sensor updates.
* > Starting from Android 12 (API level 31), the system has a 200Hz limit for each sensor updates.
* >
* > If you need an update interval less than 5ms, add `android.permission.HIGH_SAMPLING_RATE_SENSORS`
* > to **app.json** `permissions` field.
*/
setUpdateInterval(intervalMs: number): void {
这段示例展示了三个典型技巧:在 @param 内用多行 > 引用块补充平台限制(Android 12 起有 200Hz 采样上限)、说明例外情况的处理方式(申请 HIGH_SAMPLING_RATE_SENSORS 权限)、以及用 Markdown 链接指向官方配置文档页。
类型与接口文档:逐属性文档化
每个属性都要单独写注释,并"教会读者一些有用的东西"。反例是 "The width",正例是 "The width of the captured photo, measured in pixels":
export type GetImageOptions = {
/**
* The format of the clipboard image to be converted to.
*/
format: 'png' | 'jpeg';
/**
* Specify the quality of the returned image, between `0` and `1`.
* Applicable only when `format` is set to `jpeg`, ignored otherwise.
* @default 1
*/
jpegQuality?: number;
};
注意 @default 标签的用法:它不解析 Markdown,在渲染时被显示为行内代码(inline code),适合直接写 @default 1、@default 'png' 这类字面量。
支持的 TSDoc 标签速查表
以下是规范定义的完整标签列表,文档生成系统(TypeDoc)只会识别这些约定标签:
| 标签 | 用途 | 示例 |
|---|---|---|
@param |
参数描述 | @param options Configuration for the request |
@return / @returns |
返回值描述 | @return A promise fulfilled with the result |
@default |
默认值(无 Markdown,渲染为行内代码) | @default 1 |
@platform |
平台可用性(android, ios, web, expo) | @platform ios 11+ |
@example |
代码示例(置于描述最底部) | 见下文示例章节 |
@deprecated |
弃用提示(自动格式化为警告样式) | @deprecated Use newMethod() instead |
@experimental |
实验性 API 标签 | @experimental |
@hidden / @internal / @private |
从生成的文档中隐藏 | @hidden |
@header |
将方法归组到自定义标题下 | @header Scheduling |
@needsAudit |
标记待安全/API 审计(注释形式,非标签) | // @needsAudit |
@hideType |
隐藏常量上自动生成的 Type 提示块 | @hideType |
@platform 标签的四条规则:
- 全平台支持时不要使用
@platform—— 只在需要限制可用性时才添加; - 多平台支持时写多个
@platform标签(一行一个); - 可以指定最低版本:
@platform ios 11+; - 可用平台值为
android、ios、web、expo(指 Expo Go 应用)。
从源码结构看,这个约定不是口头规范:文档生成器 tools/src/commands/GenerateDocsAPIData.ts#L345-L355 在配置 TypeDoc 时显式注册了扩展 blockTags 列表,包括 @deprecated、@header、@hideType、@needsAudit、@platform 等,TypeDoc 才会把这些标签解析为结构化数据交给下游渲染。
代码示例:docblock 中的标准写法
示例必须用带语言标签的三反引号围栏包裹,整体放在 @example 标签下:
/**
* Checks device root/jailbreak status.
*
* @example
* ```ts
* const isRooted = await Device.isRootedExperimentalAsync();
* if (isRooted) {
* console.warn('Device may be compromised');
* }
* ```
*/
规范文档中所有 @example 都展示了同一模式:调用一行 + 注释形式给出预期输出(如 // 4371054)。这种"可运行代码 + 预期输出注释"的组合让读者既能复制运行,也能直接核对结果。
引用块:Note 与 warning
重要提示使用 > blockquote 表达,且两种格式有细微区别(注意 warning 是小写):
/**
* > **Note:** This method requires the `CAMERA` permission.
*
* > **warning** This method is experimental and not completely reliable.
*/
> **Note:**—— 信息性说明(大写 "Note" 带冒号);> **warning**—— 警示(小写 "warning");- 多行说明每行都要以
>开头,段落之间用空>分隔。
仓库实例:packages/expo-device/src/Device.ts#L286 的 isRootedExperimentalAsync 注释首行正是 > **warning** This method is experimental and is not completely reliable.,与规范逐字吻合。
常量与枚举文档
常量:直接描述取值含义与平台差异,如 isDevice 常量(packages/expo-device/src/Device.ts#L8-L12):
/**
* `true` if the app is running on a real device and `false` if running
* in a simulator or emulator. On web, this is always set to `true`.
*/
export const isDevice: boolean = ExpoDevice.isDevice;
枚举:枚举本身和个别枚举值都要文档化,个别值可挂 @platform:
/**
* Type used to define what type of data is stored in the clipboard.
*/
export enum ContentType {
PLAIN_TEXT = 'plain-text',
HTML = 'html',
IMAGE = 'image',
/**
* @platform iOS
*/
URL = 'url',
}
这类"枚举 + 逐值注释 + 值级 @platform"的模式在 packages/expo-clipboard/src/Clipboard.types.ts 等真实类型文件中可以见到(该文件同时大量使用 @default 与 @platform 标签,是规范落地的典型样本)。
返回值措辞:resolves to
@returns 标签中统一使用 "resolves to",遵循 MDN 的惯例:
- 首选:
@returns A promise that resolves to a CameraPhoto object. - 也可接受:
@returns A promise fulfilled with a CameraPhoto object.
在正文行文中,"resolves with" 同样可以接受(例如 "The promise resolves with the parsed result")。
类型导出模式:文档生成的硬性前提
关键(Critical)规则:类型必须从入口文件(entry point)导出,否则文档生成系统根本抓取不到。 有两种标准做法:
方式一:从类型文件直接 re-export
// index.ts or MainModule.ts
export {
type FileCreateOptions,
type DirectoryCreateOptions,
type FileHandle,
} from './Module.types';
方式二:import 后再导出
// Haptics.ts
import { NotificationFeedbackType, ImpactFeedbackStyle } from './Haptics.types';
// ... function implementations ...
export { NotificationFeedbackType, ImpactFeedbackStyle };
文档生成管线如何工作(源码级补充)
这条规则之所以是"硬性前提",可以从生成脚本 tools/src/commands/GenerateDocsAPIData.ts 的实现得到印证:
- 包到入口的映射表:脚本内部维护
PACKAGES_MAPPING(含expo-ui各组件的独立入口映射,如 L25-L120 的uiPackagesMapping),命令generate-docs-api-data(别名gdad,支持-p, --packageName与-s, --sdk参数)按该映射决定每个包的 TypeDocentryPoints。TypeDoc 只从入口提取公共导出符号——所以类型若不在入口导出,就永远不会出现在生成的 API 数据里; - TypeDoc 选项:脚本以
commentStyle: 'block'、jsDocCompatibility: false启动 TypeDoc(L340-L356),即只认/** ... */块注释,且必须使用上文列举的规范标签; - plugin 目录合并:若包内存在
plugin/src/index.ts及对应tsconfig.json,脚本会二次运行 TypeDoc 并把结果(标记_source: 'plugin')并入同一份 API 数据(L379-L404); - 内联文档注入:生成 JSON 后调用
applyDocsInline处理源码中的内联文档标记(DOCS_INLINE_TAG)(L406-L411); - 输出清洗:最终 JSON 会递归剔除
id、groups、kindString、originalName、files、sourceFileName等内部字段并压缩输出(MINIFY_JSON = true,L424-L434)。
该命令的行为契约还有对应的测试文件 tools/src/commands/GenerateDocsAPIData.test.ts 可供查阅,用于理解映射表的维护方式。
MDX 文档页中的用法示例写法
规范同时覆盖了官方文档站(.mdx 页面)中示例代码的写法,这些写法与 docblock 规范配套,共同支撑最终的 API 参考页:
1. 代码块格式 —— 必须带语言标签,展示"代码放在哪里"时加文件路径标签:
```ts app/(tabs)/index.tsx
import * as FileSystem from 'expo-file-system';
const content = await FileSystem.readAsStringAsync(uri);
```
2. 可交互 Snack 示例:
<SnackInline label="Basic file read" dependencies={['expo-file-system']}>
```tsx
import * as FileSystem from 'expo-file-system';
export default function App() {
// ...
}
```
3. 可折叠示例:
<Collapsible summary="Advanced usage with error handling">
```ts
try {
const result = await someAsyncOperation();
} catch (error) {
console.error('Operation failed:', error);
}
```
4. API 参考区块 —— 文档页末尾放置该组件,API 参考内容即从 TSDoc 注释自动生成:
<APISection packageName="expo-file-system" apiName="FileSystem" />
快速核对清单
要做(Do):
- 使用第三人称陈述式("Gets"、"Returns"、"Checks");
- 文档化参数/返回值之外的行为(失败模式、副作用、并发);
- 平台特定 API 使用
@platform标签; - 包含实用的
@example代码块; - 类型从入口文件导出。
不要做(Don't):
- 写无用描述(给 width 属性写 "The width");
- 使用命令式语气("Get the value");
- 对复杂行为跳过文档;
- 忘记 re-export 类型导致文档生成抓不到;
- 使用
@link标签(不受支持——应使用标准 Markdown 链接); - 在全平台支持时添加
@platform标签。
小结
这套规范的价值在于"注释即数据":TSDoc 注释不仅给人看,更是 tools/src/commands/GenerateDocsAPIData.ts 驱动 TypeDoc 提取结构化 API 数据的唯一来源。遵循第三人称陈述式、完整标签集(尤其是 @platform 与 @default)、docblock 代码示例格式,以及"类型必从入口导出"这一硬性前提,新写出的 expo-* API 就能零返工地进入官方 API 参考文档体系。完整规范原文见 .claude/skills/expo-api-docs/SKILL.md,真实注释范例可参照 packages/expo-device/src/Device.ts 与 packages/expo-clipboard/src/Clipboard.types.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00