导航栏闪烁问题排查与修复

这次导航栏闪烁包含两个独立问题:首次进入页面时的位置跳动,以及接口返回后导航栏重新闪烁。

首次进入时的位置闪烁

沉浸式导航栏需要通过 Bridge 获取客户端的状态栏高度。这个过程是异步的,而页面首次渲染时高度默认为 0。

页面会先按错误高度显示导航栏,等 Bridge 返回真实高度后再更新位置,视觉上就会向下跳一下。

修复方式是等待和缓存:

const [statusBarPaddingRem, setStatusBarPaddingRem] = React.useState(
  () => cachedStatusBarPaddingRem ?? 0,
)

const [statusBarReady, setStatusBarReady] = React.useState(
  () => !inApp || cachedStatusBarPaddingRem !== null,
)

首次没有缓存时,先等待 Bridge 返回高度,再显示导航栏,同时保留 300ms 兜底,避免导航栏一直不出现。

拿到有效高度后会更新缓存。后续页面跳转或返回时,导航栏直接使用最近一次高度,不再从 0 开始。每次进入页面仍会重新获取高度,新旧高度不同时更新缓存,相同时 React 会跳过无意义的状态更新。

等待状态栏高度并复用缓存,避免导航栏位置跳动

接口返回后再次闪烁

为了让网络错误页隐藏客服入口,PageLayout 开始根据页面传入的 rightActions 判断右侧按钮是否展示。

部分页面的右侧按钮和客服链接依赖接口数据。接口返回后,导航配置发生变化,原来的导航栏 Effect 会重新执行。

React 会先执行旧 Effect 的 cleanup,恢复原生导航栏并清空右侧按钮,然后再执行新的 Effect,重新设置导航栏。这个短暂的切换过程造成了闪烁。

接口返回引起的 React 重新渲染只是触发条件,真正造成闪烁的是导航栏 Effect 中的 Bridge 操作被重复执行。

拆分导航栏职责

修复时将原来的导航栏逻辑拆成两个独立的 useLayoutEffect:

// 导航栏生命周期
useLayoutEffect(() => {
  // 显示、隐藏导航栏,注册返回事件
}, [effectiveMode, hideNav, nativeTitle])

// 右侧按钮配置
useLayoutEffect(() => {
  setNavBarRightView(rightView)
}, [activeNativeRightView, effectiveMode, hideNav])

第一个 Effect 只负责导航栏初始化和清理,第二个 Effect 只负责更新右侧按钮。

这样接口返回后,客服入口的显示状态和跳转链接仍然可以更新,但不会重新执行整个导航栏生命周期,也不会再次切换原生导航栏。

同时,将右侧按钮是否展示转换成稳定的布尔值,避免只因 rightActions 数组引用变化就产生额外影响。

将导航栏生命周期与右侧按钮更新拆开

修复结果

最终对应两条修复链路:

  • 首次进入或返回页面时,等待状态栏高度并复用缓存,解决导航栏位置跳动。
  • 接口返回或切换 Tab 时,只更新右侧按钮,不重新初始化导航栏,解决导航栏闪烁。

评论