1. 《从零开始搭建一个多端 IM》贰:页面导航模式设计- Navigator 导航
  2. 创建于 2017-02-24 00:18:32

具体实现参照:UiLibrary/Navigator

标示图
image.png

下面讲讲实现过程中思考过的几个问题

路由模式

Q: 每个视图是否需要像 Web 一样有一个路由表?
这是从用户使用习惯的一个考虑。观察传统的 Web 网页,很少有真正意义上的返回概念,大概是因为在 PC 时代,屏幕足够大,能够容纳足够多的导航信息,用户基本都是点哪进哪,导致用户的浏览轨迹没有一个稳定的路径, 所以开发去关心上一页是什么状态,也便没有了什么意义。这种情况下,有一个涵盖所有页面的路由表,能够快速的在页面之间进行切换,能够极大提升开发效率,简化开发流程。
回到移动端 App 的导航模式,它类似 树状结构,一个功能线类似一个树枝,切换到一个功能需要先回到主干,再进入到另一个树枝,不会纵横交错。
这里有一个使用细节,在 App 中,你返回上一个页面的时候,上个页面的状态是被保留,为了实现这个特性,应用会保留一个导航栈,导航栈的大小多少会影响到应用的性能,这也侧面导致 App 的导航不能无限延伸。
rn 生来还是为了做移动端 App 的,还是要遵循移动端用户习惯,反应到技术实现上,一个全局的路由表并没有很大意义,既然是导航栈,那么 push/pop 装卸视图,会更加契合。

TabBar 和 Navigator 的关系

TabBarNavigator 的第一个路由,位于栈底,只要导航栈在,那么 TabBar 就永远不会被销毁,就可以保留各种组件状态,比如滑动位置、输入状态。
对于 Navigator 来说,一个 TabBar 就是第一个 Main Scene 罢了,和其他 Main Scene 视图没有区别。
TabBar 由于本身又实现了多个横向的导航,自带缓存机制。

基于此,一个经典的 TabBar + Navigator 的导航模式就出来了。

route 配置上改放哪些属性

NavigationCardStack 把导航栈的数据结构开放给了开发者,通过改变导航栈,进而触发视图变更,基础定义如下:

{
    index: 0, // 代表当前的显示的是哪个路由。
    // 路由视图的配置信息
    routes: [
        {
            key: '0', // 路由唯一键值
            title: '' // 如果自定义 NavigationHeader 的 renderTitleComponent 属性,
            // 默认控件会去取 title 的属性值
        }
    ]
}

自定义导航组件的设计中,在每个 Main Scene 内部的组件,会在组件的 props 属性上添加 navigator 实例对象,用于访问当前所处的 Navigator 组件实例。
通过该实例,可以设置导航栏状态等,比如我们在该实例上开放了一个 setRenderRightCompoent 方法,用于设置 RenderRightCompoent
这个时候,我们碰到一个问题,RightComponent 属性究竟需不需要放到 route 配置上去,例如

{
    key: '1',
    title: 'blabla',
    RenderRightCompoent: function () {}
}

由于 routes 定义绑定在 state 上,更新 RenderRightCompoent,会触发新一轮的 render,这样可以保证最新的 RightCompoent 会被渲染出来。
虽然这里的关键点是把发生变更的属性反应到 state 上,但是,把 RenderRightCompoent 放置到路由上,数据结构上回更加清晰。
无需担心 route 变量各处传递,会占用更多内存,因为对于对象,这只是个引用而已。
所以这里的原则是,需要实时更新的相关联组件,放到 route,放到 state 里面去。
无需实时更新的组件,比如主视图的堆栈,可以考虑单独维护一个数组,脱离 state,以提高性能。

这里还有个 NavigationCardStack 渲染时机的小知识,renderScene 方法是比 header 渲染先执行的,基于此,我们可以在 Main Scene 内组件的 componentWillMount 方法上,设置导航栏属性,以此减少重复渲染。

组件之间交互问题。

在示例项目中,RightComponent,是一个保存按钮,Man Scene 里面有一个输入框,只有当输入框有值的时候,保存按钮为可用状态。
示例的实现是返回了一个 RightComponentref 引用,通过调用应用的 setState 来更新按钮状态。

还有一种实现方式是,利用事件来传递状态,不做展开,因为光 EventEmitter 模块选型就可以单独写一大篇文章。

更高级的封装实现,可以考虑 mobxredux 等数据流管理架构。

视图渲染频率控制

可以考虑利用静态容器,来控制渲染频率,当前组件的控制策略是,只在组件状态从(unActive -> Active)时渲染。
👇 是我的回答,关于重复渲染的问题
Component rendered twice when using NavigationExperimental (NavigationCardStack)