notion-types 完全指南:Notion 核心数据结构的 TypeScript 类型定义深度解析

原创2026-10-14 12:08:071,964 阅读
文章标签:UI组件前端

notion-types 完全指南:Notion 核心数据结构的 TypeScript 类型定义深度解析

notion-types 是 react-notion-x 生态中负责描述 Notion 内部数据结构的类型包,只导出 TypeScript 类型、不包含任何运行时代码。本文将带你掌握它的安装方式、统一入口,并逐模块拆解 ID、Decoration、Block、Collection、CollectionView、Formula、RecordMap 等核心类型的结构与设计意图,最后结合仓库内 notion-client、notion-utils、react-notion-x 等包的真实消费方式,理解这些类型如何贯穿整个 Notion 渲染链路。

这个包解决什么问题

Notion 的前端页面数据并非普通 JSON,而是一套高度嵌套、带双重重包裹(double-nested)的"记录映射"结构:页面由块(block)构成,块通过 content 数组引用子块 ID,数据库(collection)通过 schema 描述列,视图(collection view)描述同一数据在不同形态(表格、看板、画廊、列表)下的呈现,富文本由 Decoration 数组描述样式。任何想要在 React 中准确还原 Notion 页面的工具,都必须先精确建模这套结构。

notion-types(packages/notion-types)正是为此而生:它的官方描述是 "TypeScript types for core Notion data structures"(Notion 核心数据结构的 TypeScript 类型)。它被设计为:

  • 纯类型包:This package only exports types,零运行时负担,sideEffects: false,可与任何打包器配合做摇树优化;
  • 全环境兼容:同时适用于 Node.js 与浏览器(在仓库 packages 总览 中被标注为 Universal 环境);
  • 生态基石:notion-client(抓取数据)、notion-utils(数据处理)、react-notion-x(渲染)、notion-compat(官方 API 兼容层)、notion-x-to-md(转 Markdown)全部以它作为类型基础。

安装与使用

在你的项目中安装:

npm install notion-types

它只导出类型,因此既可以用于服务端(如 notion-client 抓取页面后的数据建模),也可以安全地用于浏览器端(如 react-notion-x 渲染组件内部)。

统一命名空间导入即可访问全部类型:

import * as notion from 'notion-types'

包入口 src/index.ts 通过 8 个 export * 聚合了全部模块:

export * from './api'
export * from './block'
export * from './collection'
export * from './collection-view'
export * from './core'
export * from './formula'
export * from './maps'
export * from './user'

如果你只想按需导入,也可以使用具名导入,例如:

import { type Block, type Decoration, type ExtendedRecordMap } from 'notion-types'

从 package.json 可以看到,当前版本为 8.0.8,采用 ESM 与 types: ./src/index.ts 直接指向源码,构建产物由 tsdown 生成(tsdown.config.ts 中配置了 target: es2018、format: ['esm']、dts: true 并输出 build 目录)。注意其 engines 要求 node >= 18。

核心基础类型(core.ts)

core.ts 是整个包的"地基",其他所有模块都从这里导入基础类型。

ID 与 PropertyID

/** UUID */
export type ID = string

export type PropertyID = string

ID 是所有实体的统一标识符,本质是 UUID 字符串。PropertyID 用于标识数据库(collection)的列:它要么是类似 o;Os 的 4 字符哈希,要么是保留的特殊值 title。源码注释明确指出,可以像传统关系型数据库的主键一样理解 title 属性——它保证存在、是集合的"主索引"。

Color(Notion 支持的块颜色)

export type Color =
  | 'gray' | 'brown' | 'orange' | 'yellow' | 'teal'
  | 'blue' | 'purple' | 'pink' | 'red'
  | 'gray_background' | 'brown_background' | ...

前 9 个是文本色,后 9 个是对应的 *_background 背景色变体,与 Notion 编辑器中的调色板一一对应。它被用于块文本颜色、调用块(callout)背景、表格列颜色等场景。

PropertyType(数据库列类型)

export type PropertyType =
  | 'title' | 'text' | 'number' | 'select' | 'status'
  | 'multi_select' | 'auto_increment_id' | 'date' | 'person'
  | 'file' | 'checkbox' | 'url' | 'email' | 'phone_number'
  | 'formula' | 'relation'
  | 'created_time' | 'created_by'
  | 'last_edited_time' | 'last_edited_by'

涵盖 Notion 数据库的全部 20 种属性类型。注意 status 与 person 是较新加入的;auto_increment_id、created_*、last_edited_* 属于系统自动维护的列。

NumberFormat(数字格式)

NumberFormat 定义了超过 40 种数字显示格式,包括通用格式(number_with_commas、percent、number)与各币种格式(dollar、euro、pound、yen、yuan、ruble 等)。这些值直接对应 Notion 属性配置中的"数字格式"选项。

Decoration(富文本装饰结构)

Decoration 是理解整个包的关键概念,它是 Notion 富文本的"元组编码"格式:每个文本片段由基础装饰和一组子装饰组成。

export type BoldFormat = ['b']
export type ItalicFormat = ['i']
export type StrikeFormat = ['s']
export type CodeFormat = ['c']
export type UnderlineFormat = ['_']
export type LinkFormat = ['a', string]
export type ColorFormat = ['h', Color]
export type UserFormat = ['u', string]
export type PageFormat = ['p', string]
export type InlineEquationFormat = ['e', string]
export type DateFormat = ['d', FormattedDate]
export type LinkMentionFormat = ['lm', string]
export type CustomEmojiFormat = ['ce', string]
// ...

export type BaseDecoration = [string]
export type AdditionalDecoration = [string, SubDecoration[]]
export type Decoration = BaseDecoration | AdditionalDecoration

每个格式都是一个元组:['b'] 表示加粗,['a', 'https://...'] 表示带目标的链接,['h', Color] 表示颜色,['e', latex] 表示内联公式,['d', FormattedDate] 表示内联日期(含 type: 'date' | 'daterange' | 'datetime' | 'datetimerange' 及 start_date、end_date、time_zone 等字段)。Decoration 本身是"基础文本 + 子装饰数组"或"仅有基础文本"的联合,Notion 页面上的每个文本块、标题、按钮文字都由 Decoration[] 描述。

这套元组结构在生态中被大量消费:notion-utils 的 get-text-content.ts 用它提取纯文本,notion-x-to-md 的 text.ts 与 property.ts 将它转换为 Markdown 的粗体/斜体/链接语法。

Block 类型体系(block.ts)

block.ts 定义了块类型枚举与每种块的结构化接口,是整个包体量最大的模块。

BlockType 枚举

export type BlockType =
  | 'page' | 'text' | 'bookmark' | 'bulleted_list' | 'numbered_list'
  | 'header' | 'sub_header' | 'sub_sub_header' | 'header_4'
  | 'quote' | 'equation' | 'to_do' | 'table_of_contents' | 'divider'
  | 'column_list' | 'column' | 'callout' | 'toggle'
  | 'image' | 'embed' | 'gist' | 'video' | 'figma' | 'typeform'
  | 'replit' | 'codepen' | 'excalidraw' | 'tweet' | 'maps' | 'pdf'
  | 'audio' | 'drive' | 'file' | 'code'
  | 'collection_view' | 'collection_view_page'
  | 'transclusion_container' | 'transclusion_reference' | 'alias'
  | 'table' | 'table_row' | 'external_object_instance'
  | 'breadcrumb' | 'button' | 'miro' | 'tab'

50 余种块类型,与仓库 readme.md 的 Supported Blocks 表格基本一一对应:transclusion_container / transclusion_reference 对应 Notion 的同步块(Sync Block),alias 对应页面链接别名,external_object_instance 对应外部对象实例,tab、button、miro、breadcrumb 是最新加入的块类型。

BaseBlock:所有块的公共基座

export interface BaseBlock {
  id: ID
  type: BlockType
  properties?: any
  format?: any
  content?: ID[]          // 子块 ID 数组,构成树形结构
  space_id?: ID
  parent_id: ID           // 父级 ID
  parent_table: 'space' | 'block' | 'table' | 'collection' | (string & {})
  version: number
  created_time: number
  last_edited_time: number
  alive: boolean
  created_by_table: string
  created_by_id: ID
  last_edited_by_table: string
  last_edited_by_id: ID
}

parent_table 允许块挂在 space、block、table、collection 之下,parent_id 对应其 ID;alive 标记块是否被删除。这个字段组合在 notion-client 中扮演重要角色——例如 notion-api.ts 的 fetchRelationPages 正是靠 parent_table === 'collection' 来识别数据库中的记录行并递归抓取关联页面。

三大派生基座

  • BaseTextBlock:文本类块(text、各标题、列表、引用、公式块等),properties.title 为 Decoration[],format.block_color 为 Color,且允许偶发携带子块 content;
  • BaseContentBlock:媒体/嵌入类块(image、video、embed、tweet、pdf、drive 等),properties 含 source(资源地址)、caption、alt_text,format 含 block_alignment(center | left | right)、block_width、block_height、display_source、block_full_width、block_page_width、block_aspect_ratio、block_preserve_scale 等布局参数,并带 file_ids 用于签名 URL 关联;
  • BasePageBlock:页面类块,format 含 page_full_width、page_small_text、page_cover_position、page_cover、page_icon、block_locked 等,并带 permissions: { role: Role; type: string }[] 权限数组(Role 定义在 core.ts 中,为 'editor' | 'reader' | 'none' | 'read_and_write')。

代表性具体块

  • TodoBlock:properties.title + properties.checked: (['Yes'] | ['No'])[],用字符串元组表达勾选状态;
  • CodeBlock:properties.title、language、caption 三个 Decoration[];
  • TableBlock / TableRowBlock:TableBlock 通过 collection_id、view_ids、format.collection_pointer 指向一个隐藏数据库,format.table_block_column_order 定义列顺序、table_block_column_header / table_block_row_header 控制表头;TableRowBlock 的 properties 是 { [column: string]: Decoration[] } 的动态列映射;
  • SyncPointerBlock(transclusion_reference):format.copied_from_pointer 与 format.transclusion_reference_pointer 记录同步源;
  • PageLink(alias):format.alias_pointer.id 指向被引用的页面;
  • ButtonBlock:format.automation_id 关联自动化动作,format.block_color 控制颜色,是 Notion 按钮块(可打开 URL、触发 webhook)的类型建模;
  • GoogleDriveBlock:format.drive_status(authed、last_fetched)与 format.drive_properties(url、file_id、thumbnail、user_name、modified_time 等)完整还原 Drive 嵌入卡片。

Block 联合类型将所有具体块收拢为单一可判别联合(discriminated union),react-notion-x 的渲染核心 block.tsx 正是用 switch (block.type) 对 BlockType 的每个分支分发渲染,从 collection_view_page、page 到 text、image、code、to_do、transclusion_reference 等逐一处理。

数据库与属性(collection.ts)

collection.ts 建模 Notion 数据库(Database):

export interface SelectOption {
  id: ID
  color: Color
  value: string
}

export interface CollectionPropertySchema {
  name: string
  type: PropertyType
  options?: SelectOption[]   // select / multi_select 的候选项
  number_format?: NumberFormat
  formula?: Formula          // formula 列的公式定义
}

export interface CollectionPropertySchemaMap {
  [key: string]: CollectionPropertySchema
}

export interface Collection {
  id: ID
  version: number
  name: Decoration[]
  schema: CollectionPropertySchemaMap  // 列名哈希 -> 列定义
  icon: string
  parent_id: ID
  parent_table: string
  alive: boolean
  copied_from: string
  template_pages?: Array<ID>
  format?: {
    collection_page_properties?: Array<{ property: PropertyID; visible: boolean }>
    property_visibility?: Array<{ property: PropertyID; visibility: 'show' | 'hide' }>
    hide_linked_collection_name?: boolean
  }
}

schema 是"列哈希 → 列定义"的映射,键正是 core.ts 中的 PropertyID。format.collection_page_properties 控制数据库页面中列的显示顺序与可见性,property_visibility 控制在关联数据库视图中列的显隐。

在 notion-client 中,Collection 用于识别记录行的父级并解析关联属性——notion-api.ts 的 fetchRelationPages 通过 recordMap.collection[blockValue.parent_id] 拿到集合后,遍历 collection.schema 提取 relation 类型的列,再递归抓取关联页面块。

数据库视图(collection-view.ts)

collection-view.ts 描述 Notion 数据库的"视图"——同一份数据的不同展示形态:

export type CollectionViewType =
  | 'table' | 'gallery' | 'list' | 'board' | 'calendar' | 'reducer'
  • TableCollectionView:format.table_properties 数组(每列含 property、visible、width、wrap),page_sort 定义行排序;
  • GalleryCollectionView:format.gallery_cover(类型见下)、gallery_cover_size(small | medium | large)、gallery_cover_aspect(cover | contain)、gallery_properties;
  • BoardCollectionView:看板视图,board_cover / board_cover_size / board_cover_aspect,board_groups2 与 board_columns 描述分组列(含 property、hidden、value: { type: PropertyType; value: string });
  • ListCollectionView:list_properties 控制列表视图显示哪些列;
  • CalendarCollectionView:日历视图(源码标注 TODO,react-notion-x 的 Supported Blocks 表格中同样标注为 ❌ Missing,从源码结构看属于尚未完整建模/渲染的类型);
  • ReducerCollectionView:reducerResults.collection_group_results 承载分组聚合结果。

封面类型由 CollectionCardCover 表达:

export type CollectionCardCoverType =
  | 'page_cover' | 'page_content' | 'page_content_first'
  | 'property' | 'none' | 'file'

export interface CollectionCardCover {
  type: CollectionCardCoverType
  property?: PropertyID  // type 为 'property' 时指向具体列
}

这些类型直接驱动 react-notion-x 的 collection-card.tsx 等第三方组件对数据库卡片的渲染。

公式类型(formula.ts)

formula.ts 完整建模 Notion 公式的抽象语法树:

  • FormulaType:constant | property | operator | function | symbol;
  • FormulaValueType:string | number | boolean | date 及常量类型 'e' | 'false' | 'true' | 'pi';
  • FormulaResultType:text | number | boolean | date | checkbox,即公式求值结果的展示类别;
  • FormulaOperatorType:算术运算符(-、*、%、/、+)与比较运算符(!=、<=、==、>、<、>=);
  • FormulaFunctionType:约 60 个内置函数,分四组:
    • 逻辑:and、or、not、if、empty、equal、unequal、larger、smaller 等;
    • 数值:abs、add、ceil、floor、round、sqrt、pow、mod、log10、log2、exp、max、min 等;
    • 文本:concat、contains、format、join、length、replace、replaceAll、slice、test;
    • 日期时间:now、dateAdd、dateBetween、dateSubtract、formatDate、fromTimestamp、year、month、day、hour、minute、start、end、timestamp。

每种公式节点都有对应接口:ConstantFormula(含 value 与 value_type)、PropertyFormula(通过 id: PropertyID 与 name 引用列)、SymbolFormula、FunctionFormula(name + args: Array<Formula>)、OperatorFormula(operator + name + args)。Formula 即这五者的递归联合。

这套 AST 有两个实际求值器:notion-x-to-md 的 eval-formula.ts 在转 Markdown 时求值公式属性,react-notion-x 的 third-party/eval-formula.ts 在渲染属性时求值,二者都直接消费 FormulaFunctionType 的分支。

用户类型(user.ts)

user.ts 建模 Notion 用户:

export interface User {
  id: ID
  version: number
  email: string
  given_name: string
  family_name: string
  profile_photo: string
  onboarding_completed: boolean
  mobile_onboarding_completed: boolean
}

用户数据在 RecordMap.notion_user 中按 ID 索引,供 person 属性、created_by / last_edited_by 以及 @提及(UserFormat)等场景解析显示名与头像。

聚合映射与 RecordMap(maps.ts)

maps.ts 是整个包"灵魂"所在——它定义 Notion 页面数据的最终形态。

NotionMap 与双重嵌套

export type NotionMapBox<T> =
  | { role: Role; value: T }
  | {
      // some blocks are double-nested...
      role: Role
      value: { role: Role; value: T }
    }

export interface NotionMap<T> {
  [key: string]: NotionMapBox<T>
}

export type BlockMap = NotionMap<Block>
export type UserMap = NotionMap<User>
export type CollectionMap = NotionMap<Collection>
export type CollectionViewMap = NotionMap<CollectionView>

NotionMapBox<T> 的注释 "some blocks are double-nested" 记录了 Notion API 的一个真实坑:部分块的 value 内部还嵌套一层 { role, value }。这正是 notion-utils 中 get-block-value.ts 存在的原因——它递归"拆箱"(unbox),直到拿到真正的 Block 值,且源码注释明确关联到 issue #682。所有上层代码都应通过 getBlockValue 访问块数据,而不是直接假定 value 是一层。

RecordMap 与 ExtendedRecordMap

export interface RecordMap {
  block: BlockMap
  collection?: CollectionMap
  collection_view?: CollectionViewMap
  notion_user?: UserMap
}

export interface ExtendedRecordMap extends RecordMap {
  collection: CollectionMap
  collection_view: CollectionViewMap
  notion_user: UserMap

  collection_query: {
    [collectionId: string]: {
      [collectionViewId: string]: CollectionQueryResult
    }
  }
  signed_urls: {
    [blockIdOrAssetUrl: string]: string
  }
  preview_images?: PreviewImageMap
  custom_emojis?: CustomEmojiMap
}

源码注释特别强调:ExtendedRecordMap 并非 Notion 原生类型,而是 react-notion-x 为方便使用而扩展的便捷类型。它在 RecordMap 基础上补齐了四个附加映射:

  • collection_query:集合视图的查询结果缓存,CollectionQueryResult 内含 total、blockIds、aggregationResults(按 PropertyType 分组的聚合)、看板视图专用的 groupResults、分页游标 collection_group_results(blockIds + hasMore)等;
  • signed_urls:原始资源 URL → 签名 URL 的映射(键兼容块 ID 与资源 URL,保证向后兼容),是 PDF、文件、音频等受保护资源渲染的关键;
  • preview_images:PreviewImageMap,url → { originalWidth, originalHeight, dataURIBase64 },支撑 LQIP 低清占位图;
  • custom_emojis:CustomEmojiMap,emojiId → url,支撑自定义表情。

RecordMap 与 ExtendedRecordMap 是生态的"数据契约":

  • notion-client 的 notion-api.ts 中 getPage() 返回 Promise<notion.ExtendedRecordMap>,并在抓取后主动补齐 recordMap.collection、recordMap.collection_view、recordMap.notion_user、collection_query、signed_urls 等映射;
  • notion-utils 的 merge-record-maps.ts 对两份 ExtendedRecordMap 按 block、collection、collection_view、notion_user、collection_query、signed_urls、preview_images 七个键逐一合并,用于增量抓取场景;
  • react-notion-x 的 renderer.tsx 与 context.tsx 直接以 recordMap: ExtendedRecordMap 作为 NotionRenderer 的 props 与 NotionContext 的核心字段,渲染时从 recordMap.block 取出根块;
  • notion-compat 的 notion-compat-api.ts 作为官方 API 的兼容层,其 getPage() 同样返回 notion.ExtendedRecordMap,说明该契约同时覆盖了官方与私有两套 API 数据源。

其余辅助类型

  • PageChunk:recordMap + cursor.stack,对应分页抓取的原始 chunk 结构;
  • CollectionInstance:recordMap + result: CollectionQueryResult,一个集合实例的快照;
  • PageMap:{ [pageId: string]: ExtendedRecordMap | null },多页面站点(如博客列表)的页面缓存容器;
  • PropertyMap:{ [key: string]: Decoration[] },块的 properties 的通用形态。

API 请求/响应类型(api.ts)

api.ts 描述 notion-client 与 Notion 交互时用到的请求/响应形态:

export interface SearchParams {
  ancestorId: string
  query: string
  filters?: {
    isDeletedOnly: boolean
    excludeTemplates: boolean
    isNavigableOnly: boolean
    requireEditPermissions: boolean
  }
  limit?: number
  searchSessionId?: string
}

export interface SearchResults {
  recordMap: RecordMap
  results: SearchResult[]
  total: number
}

export interface SearchResult {
  id: string
  isNavigable: boolean
  score: number
  highlight: { pathText: string; text: string }
}

export interface APIError {
  errorId: string
  name: string
  message: string
}

export interface RecordValues<T> {
  results: T[]
}

SearchParams / SearchResults / SearchResult 直接对应 notion-client 的搜索接口与 react-notion-x 内置的搜索弹窗(search-dialog.tsx);react-notion-x 的 types.ts 中 SearchNotionFn = (params: types.SearchParams) => Promise<types.SearchResults> 的签名正是从这里来的。

生态中的真实消费链路

把上述类型串联起来,可以看到一条完整的数据流:notion-client 通过 getPage() 返回 ExtendedRecordMap → notion-utils 用 getBlockValue() 拆箱、getPageContentBlockIds() 枚举内容块、mergeRecordMaps() 合并增量数据 → react-notion-x 的 NotionRenderer 接收 ExtendedRecordMap,按 BlockType 分发到各块组件,数据库场景再由 CollectionViewType 分发到 table/gallery/board/list 等第三方视图组件 → notion-x-to-md 消费 Decoration、Formula、Collection 生成 Markdown;notion-compat 则将官方 API 数据转换为同一套 ExtendedRecordMap 契约。整条链路上,类型定义只此一份,这正是 notion-types 作为生态基石的体现。

小结

notion-types 虽然只导出类型,却是整个 react-notion-x 生态最底层、最核心的"数据字典":core.ts 定义了 ID、颜色、属性类型与富文本 Decoration 元组结构;block.ts 用判别联合精确建模 50 余种块;collection.ts / collection-view.ts 建模数据库与其多形态视图;formula.ts 提供公式 AST;maps.ts 给出 RecordMap / ExtendedRecordMap 这一贯穿抓取、处理、渲染全链路的数据契约。无论你是要在自己的 Next.js 站点中渲染 Notion 页面、把 Notion 转成 Markdown 供 LLM 使用,还是想深度理解 Notion 内部数据结构,从 notion-types 入手都是最高效的路径——安装一行命令,收益是整个 Notion 数据模型的完整类型化视图。

许可证

notion-types 采用 MIT 协议(见 package.json 与仓库根 license),作者为 Travis Fischer,可自由用于商业与非商业项目。

登录后查看全文
react-notion-x