- UI组件
- 前端
【免费下载链接】react-notion-x
Fast and accurate React renderer for Notion. TS batteries included. ⚡️
react-notion-x 是 Notion 的 React 渲染器,其最复杂、也最能体现渲染精度的部分,莫过于对 Notion 集合(Collection,即数据库)的渲染:一个集合页可以同时拥有表格、看板、画廊、列表等多种视图,每种视图下还有分组、封面、属性显示等大量细节。本文以仓库中维护集合渲染的 collections.md 设计笔记 为骨架,结合packages/react-notion-x/src/third-party/下的完整源码实现,系统讲解集合渲染的整体架构、四种视图的分发逻辑、卡片封面系统的实现原理,并对笔记中列出的 TODO 边界逐项做源码级解读。读完本文,你将能够理解 react-notion-x 中集合渲染的全链路设计,知道每个视图格式字段(如gallery_cover、table_properties)背后的代码行为,也能明确哪些能力当前尚未实现、在二次开发时应该从哪里入手。
集合渲染的整体架构
从 Block 到 Collection 的调用链
Notion 的数据模型里,collection_view/collection_view_page类型的 block 负责承载集合视图。在 react-notion-x 中,这条渲染链的入口位于 block.tsx:
- 当渲染到
collection_view_page(或parent_table === 'collection'的普通page)时,顶层会通过components.Collection渲染整个集合; - 当渲染到内嵌的
collection_view时(比如把数据库嵌入某个普通页面),同样走components.Collection; - 源码中还有一个"hack":在顶层遇到裸的
collection_view时,会先把它改写为collection_view_page再继续处理,以兼容直接访问原始集合视图 URL 的场景(见 block.tsx 中level === 0 && block.type === 'collection_view'的处理)。
Collection组件的实现在 collection.tsx,它根据 block 类型分流:普通数据库行页(page且parent_table === 'collection')渲染为行属性列表CollectionRow,而集合视图块则交给内部的CollectionViewBlock。
第三方模块的独立打包与 context 传递
值得注意的设计点是:third-party目录下的所有集合组件都放在 third-party/readme.md 所述的一个独立 bundle 中,目的是把大体积、可选的第三方依赖(如date-fns、format-number)与主包隔离,保证主 bundle 最小化。但这带来了一个副作用:独立 bundle 会各自调用React.createContext,导致useNotionContext在集合组件里拿到的是与主 bundle 不同的 context 实例。
为此,collection.tsx 在注释中明确说明:Collection组件直接接收ctx作为 props,再通过NotionContextProvider下发,确保所有子组件拿到正确一致的 context 值。这是理解集合渲染时必须知道的内部约定——Collection的签名是{ block, className, ctx },而主包通过components.Collection block={block} ctx={ctx}传入。
四种集合视图的分发与实现
CollectionView:视图类型的分发中枢
collection-view.tsx 是视图分发的核心:根据collectionView.type将渲染委托给四个具体组件:
collectionView.type | 渲染组件 | 说明 |
|---|---|---|
table | CollectionViewTable | 表格视图,含表头、单元格、tooltip |
gallery | CollectionViewGallery | 画廊视图,卡片网格 |
list | CollectionViewList | 列表视图,行内展示标题与属性 |
board | CollectionViewBoard | 看板视图,按分组列展示卡片 |
默认分支会console.warn('unsupported collection view', collectionView)并返回null。整个组件用React.memo包裹以降低重渲染开销。在 notion-types 的 collection-view.ts 中,CollectionViewType完整枚举为'table' | 'gallery' | 'list' | 'board' | 'calendar' | 'reducer'——即calendar(日历视图)在类型层面是存在的,但分发器里尚未实现,这正是 collections.md 中"Calendar View: TODO"对应的源码事实(后面会展开)。
视图切换与状态持久化
CollectionViewBlock(见 collection.tsx)负责视图级状态:
- 从
view_ids中取第一个作为默认视图; - 当前选中的视图 id 通过
useLocalStorage(block.id, { collectionViewId })持久化(localStorage 的键是 block id),因此用户切换视图后刷新页面仍会保持选择; - 当视图数量大于 1 且 context 中的
showCollectionViewDropdown为true(默认值即true,见 context.tsx 的默认配置)时,渲染视图 Tab 栏CollectionViewTabs,每个 Tab 由CollectionViewColumnDesc展示视图图标与名称,图标映射来自 icons/collection-view-icon.tsx(包括 calendar 图标,但图标有、渲染逻辑缺)。
这里就引出了 collections.md 中的 TODO:视图状态目前只存在 localStorage 中,不支持通过 URL 参数(?v=指定视图、?p=指定行)来分享或直达某个视图/某一行。也就是说,当前实现没有把"当前视图"和"当前行"编码进地址栏。
表格视图:列宽、换行、截断 tooltip 与加载更多
collection-view-table.tsx 是细节最丰富的实现:
- 分组支持:若
collectionView.format?.collection_group_by存在,则通过getCollectionGroups(见 collection-utils.ts)把数据拆成多个分组,用 HTML 原生<details>/<summary>结构渲染分组折叠(见 collection-group.tsx),隐藏的分组直接返回null; - 列定义:优先取
collectionView.format.table_properties中visible且 schema 存在的属性;否则回退为"title 列 + 除 title 外的全部 schema 属性"。列宽默认 title 280px、其他 200px,单列可通过p.width覆盖; - 换行控制:视图级默认
table_wrap ?? false(Notion 约定缺省不换行),单元格级p.wrap可单独覆盖;不换行的单元格带notion-table-cell-nowrapclass; - 截断 tooltip:table-cell-tooltip.tsx 在表格体上做事件委托,当鼠标悬停到被截断的 nowrap 单元格(或内部被截断的 select/multi-select 徽标)时,通过
createPortal在document.body上弹出深色 tooltip,且特意跳过 URL 单元格(URL 列有自己的缩写展示逻辑); - 首屏加载限制:读取
inline_collection_first_load_limit.limit,若数据量超过该值且未展开,则只渲染前 N 行并显示 "Load more" 按钮; - 宽度计算:表格与看板视图会依据
page_full_width等父页格式计算内容区宽度与左右 padding(详见CollectionViewBlock中的useMemo逻辑)。
画廊视图与列表视图
collection-view-gallery.tsx 渲染卡片网格:从collectionView.format解构出gallery_cover(默认{ type: 'none' })、gallery_cover_size(默认'medium')、gallery_cover_aspect(默认'cover'),同样支持inline_collection_first_load_limit与 "Load more"。
collection-view-list.tsx 渲染行式列表:每行由PageIcon+ title 属性 +list_properties中可见属性组成,整行用PageLink链接到mapPageUrl(block.id)。
看板视图与分组查询
collection-view-board.tsx 中,分组列数据来自collectionData.board_columns.results,每个分组的结果块通过results:${type}:${value}形式的键从collectionData中取出(键的拼装逻辑在 collection-utils.ts 的getCollectionGroups:uncategorized用于未分类值,checkbox 值归一化为'Yes',日期值通过date-fns格式化为'MMM d, yyyy hh:mm aa')。注意看板源码里有一处防御性判断:当集合处于 toggle 折叠内、board_columns没有 results 时,直接跳过该列渲染(源码注释 "no groupResults in the data when collection is in a toggle")。
CollectionCard 卡片与封面系统
卡片是画廊、看板两种视图共用的基础单元,实现在 collection-card.tsx。其封面(cover)逻辑对应 collections.md 中 "Card" 一节的 TODO 主题。CollectionCardCoverType在 notion-types 的 collection-view.ts 中定义为:
'page_cover' | 'page_content' | 'page_content_first' | 'property' | 'none' | 'file'CollectionCard实际实现了其中四种(file尚未实现,见 TODO),并按优先级处理:
page_content/page_content_first:在block.content中查找第一个image类型的子 block,取properties.source或format.display_source作为图片源,经mapImageUrl后渲染LazyImage;若找不到任何图片,则回退渲染notion-collection-card-cover-empty空占位(这正是 TODO 中"coverType: page_content若找不到图片则回退到页面内容的基础视图"的已实现部分);page_cover:取block.format.page_cover,同时用page_cover_position(默认 0.5)计算objectPosition,实现封面焦点对齐;property:读取指定属性:若是file类型属性则取第一个文件渲染为图片;否则直接用Property组件渲染该属性的文本值;none:不渲染封面。
封面定位的细节也在这里:coverPosition = (1 - page_cover_position) * 100,卡片自身焦点用card_cover_position(默认 0.5)。此外卡片还支持isLinkCollectionToUrlProperty:当 context 开启该选项且卡片上有可见的url类型属性时,整张卡片会变成一个指向该 URL 的链接(通过<form>+<input type="submit">的技巧避免嵌套<a>造成非法 DOM 结构),否则才链接到mapPageUrl(block.id)的页面地址。
Property 属性渲染:所有视图共用的单元格引擎
无论表格单元格、看板列头、卡片属性还是行属性列表,最终都落到 property.tsx 的Property/PropertyImpl(PropertyImplMemo做了React.memo优化)。它根据schema.type分发渲染:
text/date/relation/person/auto_increment_id:基于Text组件渲染;title:默认用PageLink链接到mapPageUrl(block.id)(linkToTitlePage可关闭,表格中由 context 的linkTableTitleProperties控制,默认true);select/multi_select:按 schema.options 匹配选项并渲染彩色徽标(color 由选项配置决定),multi_select 先按逗号拆分再逐个过滤;status:渲染带状态色圆点的徽标,未命中选项时用default-inferred颜色兜底;checkbox:data[0][0] === 'Yes'判定勾选;url:用new URL解析后做"域名 + 中段省略路径"的两段式展示(如/202...ternet),非法 URL 直接回退Text渲染;email/phone_number:分别以mailto:/tel:协议渲染链接;number:支持number_format全套货币/百分比/千分位格式(format-number库,含dollar、euro、yen、yuan、rupee等几十种);formula:调用 eval-formula.ts 求值,Date结果格式化为'MMM d, yyyy hh:mm aa',求值异常捕获后返回null;created_time/last_edited_time:基于 block 时间戳格式化展示;created_by/last_edited_by:目前仅占位(TODO 注释)。
所有渲染都通过components.propertyXxxValue包装,即用户可以覆写 context 中的components来自定义某类属性的渲染。行属性列表CollectionRow(见 collection-row.tsx)额外实现了属性可见性过滤(property_visibility中visibility === 'hide'的属性被剔除)与排序(优先按collection_page_properties顺序,否则按属性名按字母序)。
从 TODO 清单看已知边界与演进方向
collections.md 本质是一份集合渲染模块的设计笔记与 TODO 清单,逐条对照源码,可以清晰看到哪些已完成、哪些仍是边界:
- 行弹窗(row popup)与视图 URL 参数
?v=/?p=:未实现。当前视图切换状态只落在 localStorage(useLocalStorage(block.id, ...)),地址栏不参与;行级弹窗交互(Notion 原生点击行弹出的浮层)也尚未支持; - 看板/表格视图可滚动而非强制 100% 宽度:部分已实现。
CollectionViewBlock已按窗口宽度、page_full_width、maxNotionBodyWidth = 708计算出内容宽度与 padding(源码中有TODO: customize for mobile?的遗留注释),但移动端宽度策略仍是待细化项; - 测试隐藏各种属性:渲染侧的隐藏支持已存在——表格
table_properties过滤visible、看板p.hidden跳过列、行属性property_visibility剔除 hide;对应的系统化测试仍属 TODO; - 测试更高级的集合视图:
calendar、reducer等视图类型在类型定义(notion-types/src/collection-view.ts)中存在,但CollectionView分发器未实现,日历图标(collection-view-calendar.tsx)已就位; - URL hash block id 高亮目标块:未实现,当前
#hash不参与块级高亮; - iframe 资产懒加载时显示 spinner:属于资源加载体验类优化,未实现;
- 集合中部分 Notion 用户未预先加载:数据加载侧问题,
Property对person类型目前也只是文本渲染,未做用户头像等增强; - toggle 记住状态到 localStorage:未实现。值得注意 react-use.ts 中已内置
useLocalStorage(含序列化/反序列化、隐私模式异常兜底),但尚未用于 toggle 折叠状态; - 书签、图标及其他图片资产优雅处理加载错误:未实现。目前封面与文件属性走
LazyImage/GracefulImage(见 lazy-image.tsx、graceful-image.tsx),但统一的错误占位/回退策略仍是 TODO; - Card 的
coverType: files:类型已定义但未实现——CollectionCard的property分支只处理file类型属性,顶层filecoverType 分支空缺;page_content无图时的内容回退也只做到"空占位",距离"基础视图回退"(参考 Notion 官方示例页)仍有距离; - Calendar View:整体未实现,是分发器
default分支之外最明显的缺口。
如何在二次开发中扩展集合渲染
如果你需要在现有仓库基础上补齐上述能力(比如实现日历视图或?v=/?p=URL 状态),从源码结构看,切入点是清晰的:
- 新增视图:在 collection-view.tsx 的
switch中注册新case,并参考Board的分组/查询键约定实现一个CollectionViewCalendar组件; - URL 状态同步:在
CollectionViewBlock(collection.tsx)中把collectionState.collectionViewId与地址栏参数双向绑定,并沿用已有的useLocalStorage作为兜底; - 行弹窗:可基于
components.PageLink/modal.tsx已有的弹层能力实现行级预览浮层; - 封面回退:在 collection-card.tsx 的
page_content分支中,把"空占位"替换为基于页面正文标题/属性摘要的迷你内容卡片。
所有集合相关组件均位于 packages/react-notion-x/src/third-party/,类型定义在 notion-types/src/collection-view.ts,上下文默认配置(showCollectionViewDropdown、linkTableTitleProperties、isLinkCollectionToUrlProperty)在 context.tsx。官方全功能示例可参考仓库中的 examples/full 目录,其components/NotionPage.tsx展示了如何组装NotionRenderer并开启相关集合选项,是验证集合渲染行为最直接的可运行环境。
- UI组件
- 前端
【免费下载链接】react-notion-x
Fast and accurate React renderer for Notion. TS batteries included. ⚡️
相关推荐
React Notion X集合视图渲染原理:数据库表格、画廊、看板视图深度解析
React Notion X集合视图渲染原理:数据库表格、画廊、看板视图深度解析 React Notion X是一个快速准确的Notion渲染器,专为React
UI组件前端Z80-open-silicon开发者手册:TV80 Verilog核心深度剖析
Z80 open silicon开发者手册:TV80 Verilog核心深度剖析 Z80处理器作为经典8位微处理器的代表,在嵌入式系统和复古计算领域具有不可替代
UI组件前端react-notion-x 的 notion-x-to-md 集合数字格式化渲染实战:从 Notion 数据到 Markdown 表格
react notion x 的 notion x to md 集合数字格式化渲染实战:从 Notion 数据到 Markdown 表格 导读 本文围绕 not
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考