Skip to content

fix: 修复 heo 交互渲染与 Notion 请求队列异常#4051

Merged
tangly1024 merged 6 commits into
notionnext-org:mainfrom
88lin:fix/heo-stability-cleanups
May 18, 2026
Merged

fix: 修复 heo 交互渲染与 Notion 请求队列异常#4051
tangly1024 merged 6 commits into
notionnext-org:mainfrom
88lin:fix/heo-stability-cleanups

Conversation

@88lin
Copy link
Copy Markdown
Member

@88lin 88lin commented May 16, 2026

已知问题

  1. heo 主题部分交互存在稳定性隐患

    • Header 在 JSX 渲染阶段直接访问 document,服务端渲染阶段没有该全局对象,容易导致 SSR / hydration 风险。
    • 文章列表卡片的 className 三元表达式写在模板字符串外,实际会渲染出脏 class。
    • 加密文章密码错误提示通过 innerHTML 拼接,绕开 React 渲染流程;输入框 ref 未做空保护。
    • 搜索框输入法组合锁使用模块级变量,多实例之间会共享状态。
    • 侧边栏路由监听、搜索高亮、404 延迟跳转等 effect 存在闭包或依赖不完整问题。
  2. Notion 请求队列与 inflight 清理存在异步处理隐患

    • RateLimiter 中部分 async 调用未显式处理返回 Promise。
    • getNotionAPI 通过 finally 清理 inflight 时,rejected Promise 链可能产生未处理拒绝。

解决方案

  1. heo 主题交互稳定性修复

    • Header 中对 #post-bg 的检测移动到客户端 effect,并缓存到 ref / state。
    • useMemo 持有 throttled scroll handler,并在卸载时调用 cancel() 清理 pending 调用。
    • 修正文章卡片 className 模板字符串。
    • PostLock 错误提示改为 React state 渲染,并为 focus 增加空保护。
    • 将搜索框 composition lock 改为组件实例 useRef
    • 稳定侧边栏路由监听回调,补齐搜索高亮与 404 延迟跳转 effect 依赖,并清理定时器。
    • PostHeader 动态主题色改为 CSS 自定义属性,减少 styled-jsx 动态内容重建。
  2. Notion API 异步链路修复

    • 移除无意义的 async 包装,用 Promise.resolve().then(execute) 统一同步/异步错误传播。
    • 对 inflight 清理链增加 catch 兜底,避免清理链自身形成未处理拒绝。
    • RateLimiter 中的浮动 Promise 显式标记为 void,并收敛 any 类型。

改动收益

  1. 降低 heo 主题 SSR / hydration、DOM 直改和 hook stale closure 风险。
  2. 避免文章卡片渲染异常 className。
  3. 提升加密文章、搜索框、侧边栏和轮播交互的稳定性。
  4. 减少 Notion 请求队列在异常路径上的未处理 Promise 风险。

具体改动

  1. lib/db/notion/RateLimiter.ts

    • 移除未使用的 path import。
    • 将队列类型从 any 收敛为 unknown
    • 对递归 enqueue、processQueue 调用显式使用 void
  2. lib/db/notion/getNotionAPI.js

    • 去掉无意义的 async 包装。
    • 保留原始调用方 Promise,同时用 catch/finally 链清理 inflight。
  3. pages/[prefix]/index.js

    • 补齐文章锁自动解锁与目录生成 effect 的关键依赖。
  4. themes/heo/components/*themes/heo/index.js

    • 修复 Header、BlogPostCard、PostLock、SearchInput、SideBarDrawer、Swipe、PostHeader 和搜索/404 相关 effect 的稳定性问题。

测试确认

  • yarn install --frozen-lockfile 成功
  • yarn lint --file lib/db/notion/RateLimiter.ts --file lib/db/notion/getNotionAPI.js --file pages/[prefix]/index.js --file themes/heo/components/BlogPostCard.js --file themes/heo/components/Header.js --file themes/heo/components/PostHeader.js --file themes/heo/components/PostLock.js --file themes/heo/components/SearchInput.js --file themes/heo/components/SideBarDrawer.js --file themes/heo/components/Swipe.js --file themes/heo/index.js 通过,0 warning / 0 error
  • yarn type-check 通过
  • yarn test --runInBand 通过,12 个测试套件 / 84 个测试全部通过

补充说明:yarn lint 全量检查当前会被 upstream main 既有 lint error 阻断,失败点包括 pages/api/cache.jslib/cache/vercel_cache.jslib/site/**lib/utils/** 等未在本 PR 修改的文件;本 PR 改动文件的 targeted lint 已通过。

@netlify
Copy link
Copy Markdown

netlify Bot commented May 16, 2026

Deploy Preview for notionnext-netlify ready!

Name Link
🔨 Latest commit e02732b
🔍 Latest deploy log https://app.netlify.com/projects/notionnext-netlify/deploys/6a09e3079439d80008524af0
😎 Deploy Preview https://deploy-preview-4051--notionnext-netlify.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@88lin 88lin force-pushed the fix/heo-stability-cleanups branch from 08852b6 to d0740f7 Compare May 16, 2026 09:05
修复内容:
- 修复 heo Header 渲染阶段直接访问 document 的 SSR / hydration 风险,并用 useMemo 持有 throttle 实例,清理监听时同步 cancel pending 调用。
- 修复 heo 文章卡片 className 模板字符串三元表达式写错位的问题,避免渲染脏 class。
- 将加密文章密码错误提示从 innerHTML 拼接改为 React 状态渲染,并为输入框 focus 增加空保护。
- 将搜索输入框输入法组合锁从模块级变量改为组件实例 ref,避免多个实例共享状态。
- 稳定 heo 侧边栏路由监听回调,并优化 Swipe 轮播定时器,避免索引变化时反复重建 interval。
- 修复 heo 搜索高亮与 404 延迟跳转 effect 的依赖和定时器清理。
- 收敛 Notion API inflight Promise 清理逻辑,避免 rejected Promise 触发未处理拒绝;同步修复 RateLimiter 中浮动 Promise 与 any 类型。
验证:
- yarn next lint --file <本次改动文件> 通过,0 warning / 0 error。
- yarn type-check 通过。
- yarn test --runInBand 通过,12 个测试套件 / 84 个测试全部通过。
@88lin 88lin force-pushed the fix/heo-stability-cleanups branch from d0740f7 to fe1882e Compare May 16, 2026 09:11
@88lin 88lin changed the title fix(heo): 修复主题交互与请求队列稳定性 fix: 修复 heo 交互渲染与 Notion 请求队列异常 May 16, 2026
@88lin
Copy link
Copy Markdown
Member Author

88lin commented May 16, 2026

PR #4051 代码审查报告

  • 标题fix: 修复 heo 交互渲染与 Notion 请求队列异常
  • 分支fix/heo-stability-cleanups -> main
  • 改动量:11 个文件,+147 / -89
  • 审查日期:2026-05-16

一、审查结论

本 PR 的改动方向是合理的,主要修复 heo 主题中的渲染、交互与 Hook 依赖问题,并收敛 Notion API 请求队列中的 Promise 清理风险。审查后未发现需要阻塞合并的问题。

建议结论:

  • 可以保留并提交上游 PR:改动集中在已有问题点,未引入明显的架构性风险。
  • 无需回退现有 11 个文件的改动:每个文件都有明确收益,且与本次修复目标相关。

验证结果:

  • yarn next lint --file <本次改动文件>:通过,0 warning / 0 error。
  • yarn type-check:通过。
  • yarn test --runInBand:通过,12 个测试套件 / 84 个测试全部通过。

二、改动总览

文件 主要修改 审查结论
lib/db/notion/RateLimiter.ts 收窄 any 类型,显式处理浮动 Promise 有益,建议保留
lib/db/notion/getNotionAPI.js 移除多余 async,修复 inflight 清理链的 rejected Promise 风险 有益,建议保留
pages/[prefix]/index.js 补全加密文章与目录生成相关 effect 依赖 有益,建议保留
themes/heo/components/BlogPostCard.js 修复 className 三元表达式写错位 明确 bug 修复,建议保留
themes/heo/components/Header.js 避免渲染阶段访问 document,稳定 throttle 实例并清理监听 有益,建议保留
themes/heo/components/PostHeader.js 用 CSS 自定义属性承载文章头图主题色 有益,建议保留
themes/heo/components/PostLock.js 用 React 状态替代 innerHTML,补 ref 空保护 有益,建议保留
themes/heo/components/SearchInput.js 模块级输入法锁改为实例级 useRef 明确修复多实例污染,建议保留
themes/heo/components/SideBarDrawer.js useCallback 稳定抽屉开关回调 有益,建议保留
themes/heo/components/Swipe.js 改为函数式 setState,避免轮播定时器反复重建 明确修复闭包问题,建议保留
themes/heo/index.js 清理定时器,补全依赖,修复非标准 DOM 属性 有益,建议保留

三、重点审查意见

3.1 Header SSR / Hydration 风险

原实现会在 JSX 渲染阶段直接调用 document.querySelector('#post-bg')。在 Next.js 的 SSR 场景下,服务端没有 document,这类访问容易导致 SSR 报错或 hydration 行为不一致。

PR 将 DOM 查询移动到 useEffect 中,并通过 hasPostBg 状态参与渲染。这个方向正确:服务端渲染不再访问浏览器 API,客户端挂载后再根据实际 DOM 状态更新导航栏占位和文字颜色。

throttle 也从 useCallback 调整为 useMemo 持有实例,并在卸载时调用 scrollTrigger.cancel()。这比只缓存回调引用更符合 lodash throttle 的使用方式,可以避免 pending 回调在组件卸载后继续执行。

结论:该改动是本 PR 的核心修复之一,建议保留。

3.2 Notion API inflight Promise 清理

getNotionAPI.js 原先直接调用 promise.finally(() => inflight.delete(key))。如果原始请求 reject,finally 返回的新 Promise 也会 reject;这个新 Promise 如果没有被消费,就可能触发未处理拒绝。

PR 改为:

promise
  .catch(() => {})
  .finally(() => globalStore.inflight.delete(key))

这里的空 catch 只用于消费清理链上的 rejected Promise,不会吞掉返回给调用方的原始 promise。也就是说,调用方仍然可以收到原始错误,inflight 清理链本身也不会制造额外的 unhandled rejection。

结论:该改动有实际稳定性收益,建议保留。

3.3 PostLock 的 innerHTML 替换

原实现通过 innerHTML 拼接错误提示。当前错误文案来自 locale,实际攻击面较小,但这种写法绕过 React 的渲染模型,也会让 React 无法稳定接管这段 DOM。

PR 改为用 showError 状态渲染错误提示,并通过 requestAnimationFrame 重启抖动动画。这个实现更符合 React 组件状态模型,也避免了后续文案来源变化时产生注入风险。

结论:改动合理,建议保留。

3.4 输入法组合锁的作用域

SearchInput.js 原先使用模块级 let lock = false。这会让同一页面的多个搜索框实例共享输入法组合状态;在 SSR 进程中,模块级变量还可能跨请求共享。

PR 改为组件实例级 useRef(false),每个搜索框实例独立维护组合输入状态。这是明确的正确性修复。

结论:改动必要,建议保留。

3.5 Hook 依赖与定时器清理

本 PR 多处补全了 effect 依赖,并为 setTimeout / setInterval 补充清理逻辑:

  • pages/[prefix]/index.js:路由或文章变化后重新处理密码与目录。
  • themes/heo/index.js:搜索高亮和 404 延迟跳转在依赖变化或卸载时清理旧定时器。
  • themes/heo/components/Swipe.js:函数式 setState 避免闭包读到旧索引。
  • themes/heo/components/SideBarDrawer.js:抽屉关闭逻辑随 onOpen / onClose 更新。

这些改动都属于 React Hook 生命周期的稳定性修复,范围合理。

结论:建议保留。


四、非阻塞观察

以下问题不建议作为当前 PR 的阻塞项,也不建议为了它们继续扩大本 PR diff,但后续可以单独清理。

4.1 RateLimiter 内部 inflight 的 key 语义仍不完整

enqueue(key, requestFunc) 会接收业务 key,但 processQueue() 内部实际加入 this.inflight 的是 crypto.randomUUID(),不是传入的业务 key。这意味着 RateLimiter 自身的 inflight.has(key) 不能真正按请求 key 去重。

不过,getNotionAPI.js 外层已经用 globalStore.inflight 按请求 key 做了去重,本 PR 没有扩大这个历史问题。因此当前 PR 可以通过;如果后续要继续清理,可以考虑:

  • 移除 RateLimiter 内部的 inflight 去重逻辑,只保留队列限流职责。
  • 或将 processQueue() 内部的随机 key 改为 enqueue() 传入的业务 key。

4.2 Header.js 仍有两个滚动监听

当前 Header.js 中导航样式切换与 activeIndex 切换仍分别注册滚动监听。PR 已经修复了 SSR 访问和 throttle 清理问题,但没有合并这两条滚动逻辑。

这不是本 PR 引入的问题,也不影响当前修复成立。后续如需进一步优化,可以把导航样式和菜单索引更新合并到同一个滚动处理流程中,减少监听器数量。


五、最终建议

本 PR 可以作为一次独立的稳定性修复提交给上游。它覆盖的修复点明确,diff 与问题范围匹配,没有发现必须回退的改动。

建议保持当前 PR 范围,不再继续追加非阻塞优化。这样更利于上游审查:本次 PR 只解决 heo 主题交互渲染、Hook 生命周期和 Notion 请求队列清理问题,后续结构性简化可以另开 PR。

88lin added a commit to 88lin/NotionNext that referenced this pull request May 16, 2026
修复内容:
- 修复 heo Header 渲染阶段直接访问 document 的 SSR / hydration 风险,将文章背景检测移动到客户端 effect 中处理。
- 使用 useMemo 持有 Header 的 throttle 滚动处理实例,并在卸载时 cancel pending 调用,避免残留回调。
- 修复 heo 文章卡片 className 三元表达式写错位的问题,避免渲染无效 class。
- 将加密文章密码错误提示从 innerHTML 拼接改为 React 状态渲染,并为输入框 focus 增加空保护。
- 将搜索输入框输入法组合锁从模块级变量改为组件实例 ref,避免多实例或 SSR 多请求间共享状态。
- 稳定 heo 侧边栏路由监听回调,避免 effect 捕获旧的开关回调。
- 优化 Swipe 轮播定时器,改用函数式 setState,避免每次索引变化都重建 interval。
- 修复搜索高亮、文章目录生成和 404 延迟跳转相关 effect 的依赖与清理逻辑。
- 收敛 Notion API inflight Promise 清理链,避免 rejected Promise 产生额外的未处理拒绝。
- 收窄 RateLimiter 中的 any 类型,并显式标记不需要 await 的异步队列调用。

影响范围:
- heo 主题 Header、文章卡片、文章锁、搜索框、侧边栏、轮播和搜索页交互。
- Notion API 请求队列、请求去重和限流相关逻辑。

说明:
- 本次提交主要是稳定性与 React 生命周期修复,不改变既有功能入口。
- 当前暂存区改动与 PR notionnext-org#4051 的修复目标一致,但本地 fork 基底与上游代码存在少量差异,因此以本地暂存区内容为准。
@88lin 88lin requested a review from tangly1024 as a code owner May 17, 2026 05:15
@tangly1024 tangly1024 merged commit befa7f5 into notionnext-org:main May 18, 2026
9 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants