ReactNative 的组件架构设计

2015年12月21日 346点热度 0人点赞 0条评论

【编者按】本篇作者 @cnsnake11,写的是 react native 的架构设计,如果你用 react 来开发 web 程序,本篇文章只能仅供参考,问题都没有在 web 上去考虑过。

本篇较长,前面是目前 flux 开源框架的一些分析,后面是架构设计过程。您可以直奔主题。

用 RN 最大的难题是设计思想的转变,以前的设计方法论已经不太适用了。而 RN 仅仅提供了 view 的框架,构建完整 app 的架构并没有直接提供。

考虑目前遇到的如下问题,希望架构给出解决方案。

  1. 交互:如何解决组件间通信【父子、子父、兄弟等,特别是跨层or反向数据流动等】;用state还是接口操作组件;

  2. 职责:组件状态放哪,业务逻辑放哪,数据放哪,因为太灵活了,怎么做都可以实现功能,但是怎么做才是最好的,才是最正确的呢?


todo 一个问题:由于 react 是面向状态编程,相当于 react 的组件只关注数据的最终状态,数据是怎么产生的并不关心,但是某些场景下,数据如何产生的是会影响到组件的一些行为的【比如一个新增行要求有动画效果,查询出的行就不需要等】,这在 RN 中很难描述。。。。。

RN 架构就是为解决上述问题提供的指导和方法论,是通盘考虑整个开发、测试、运维的状况,做出的考虑最全面的抉择,或者为抉择提供依据。

目前为 react 服务的架构也有一些了,如 Flux,Reflux,Redux,Relay,Marty。

Flux
flux 是官方提供的架构,目的是分层解耦,职责划分清晰,谁负责干啥很明确。具体描述可以参考官方文档,这里不详述。

  1. action 封装请求

  2. dispatcher 注册处理器、分发请求

  3. store 是处理器,处理业务逻辑,保存数据

  4. view 根据 store 提供的数据进行展现;接受用户的输入并发出 action 请求。


图片
数据流动:
Action-> Dispatcher -> Store -> Component

但我觉得解耦的太细了,干一个事,要做太多太多的额外工作了。

光注册监听动作就 2 次,一次是 store 注册到 dispatcher,一次是 view 注册到 store 中。

而且,注册到dispatcher的监听应该都不叫注册,架构完全没有提供任何封装,直接暴露一个统一的回调方法,里面自行 if else 路由不同的 store。
Reflux
结构上与 flux 架构基本一致,去掉了 flux 的一些冗余操作【比如没有了 dispatcher】,架构更加简洁和紧凑,用到了一些约定大于配置的理念。

基本上将 flux 的架构冗余都简化了,可以说是 flux 的去冗余提升版,但是没有本质的变化。
╔═════════╗       ╔════════╗       ╔═════════════════╗
║ Actions ║──────>║ Stores ║──────>║ View Components ║
╚═════════╝       ╚════════╝       ╚═════════════════╝
     ^                                      │
     └──────────────────────────────────────┘
  1. 更容易的监听。listenables 和约定以 on 开头的方法。等。

  2. 去掉了 dispatcher。

  3. action 可以进行 aop 编程。

  4. 去掉了 waitfor。store 可以监听 store。

  5. component 提供了一系列 mixin,方便注册\卸载到 store 的监听和与 store 交互等。

Redux
社区内比较受推崇,因为用起来相对比较简单

图片
特性:

  1. 分层设计,职责清晰。

  2. 要求 store reducer 都是页面单例,易于管理。

  3. action 为请求 dto 对象,是请求类型,请求数据的载体。

  4. reducer 是处理请求的方法。不允许有状态,必须是纯方法。必须严格遵守输入输出,中间不允许有异步调用。不允许对state直接进行修改,要想修改必须返回新对象。

  5. store

  6. 维持应用的 state;

  7. 提供 getState() 方法获取 state;

  8. 提供 dispatch(action) 方法分发请求来更新 state;门面模式,要求所有的请求满足统一的格式【可以进行路由、监控、日志等】,统一的调用方式。

  9. 通过 subscribe(listener) 注册监听器监听state的变化。

  10. 官方文档写的较为详细,从设计到开发都有,比flux要好

痛处如下,看能否接受或者解决:

  1. redux 的原则 1:state 不能被修改。

  • 其实这个用 react 的 state 也会有同样的问题,最好把 state设计的没有冗余,尽量少出这种情况

  • 解决方案:参考官方:因为我们不能直接修改却要更新数组中指定的一项数据,这里需要先把前面和后面都切开。如果经常需要这类的操作,可以选择使用帮助类 React.addons.update,updeep,或者使用原生支持深度更新的库 Immutable。最后,时刻谨记永远不要在克隆 state 前修改它。

2. 单一的庞大的 reducer 的拆分

  • 这块设计也不好做,会让人疑惑

  • 官方给的 demo 中直接按 state 的内容区分,我觉得这样做不好,如果后期有跨内容的情况,就比较奇怪了。官方给的 combineReducers 方案,也只是减少代码量,本质没有变化,state 还是拆分处理,路由还是业务逻辑自己来做。

  • 解决方案:还是处理一整个 state,可以按照约定写 reducer 类而不是方法,类里按照actionType 建方法,架构自动路由并调用。

  • 以前做 java 架构,路由一定是架构来调用的,目前感觉各大 flux 框架都是解决问题不彻底。

3. 官方建议设计模式:顶层容器组件才对redux有依赖,组件间通过 props 来传递数据。按照这样设计还是没有解决组件间交互和数据传递的问题。官方 react 设计建议:react 的设计建议:http://camsong.github.io/redux-in-chinese/docs/basics/UsageWithReact.htm

4. 使用 connect 将 state 绑定到 component。此处有些黑盒了。

5. 异步 action 用来请求服务端数据,利用middleware增强 createStore 的 dispatch 后即支持。

Relay 以及 Marty 此处暂时未作研究。
结论
开源架构封装的简单的 flux 会产生较多的冗余代码。

开源架构封装的复杂的 redux,其和 RN 绑定封装了一些东西,是一个黑盒,不易理解和维护。

介于上述两者之间的开源架构 reflux,文档较上述 2 个少,不知道其可持续性如何。如果一定要用开源架构的话,我觉得他稍加封装是一个较为推荐的选择。

不是特复杂的程序【一般 spa 的程序会更复杂一些,而 RN 并不是 spa】,这些概念只会增加你的开发难度,并且对后面维护的人要求更高。

我们继续头脑风暴,继续抽象总结一下flux系列框架, flux系列框架干了什么,没干什么,针对开篇提出的问题。

  1. 【解决职责】flux系列框架都做到了解耦,分层,谁该干什么就干什么,不许干别的,让代码读起来更有预测性和一致性,方便维护

  2. 【解决通信】继续解耦,flux系列框架采用事件机制解决各层之间通信,采用props传递解决各组件之间通信。

事件系统是关键

flux 系列架构解决通信问题的方法是使用事件系统,事件系统中的回调函数是业务逻辑,redux 是【store action reducer】,flux 是【action dispacher store】。
我们真的需要事件系统吗?

事件系统的好处:

  1. 一个事件可以注册多个回调函数

  2. 各回调函数间没有耦合。

关于 1

需要注册多个的这种情况并不多见,不信你去翻看你已经写好的代码,是不是大部分都是注册一个。

关于 2

解耦确实很彻底,但是当我需要控制执行顺序,需要等 a 执行完在执行 b,怎么办?ok 你可以先注册 a 在注册 b 啊。那a要是一个 fetch 或 ajax 操作呢?这时候只能乖乖的在 a 的请求结束回调函数中进行调用 b 了。又变成 a 依赖 b了。当然,你可以继续 dispatch(b),这就没有耦合了。但是你要知道注册一个事件是要有成本的,要写 action,而且这种 dispatch 的方式,真的不太适合人类的阅读,dispatch 一下,下一步都有谁来执行都不知道,这哪有直接调用来的爽快。

好吧说到这,最后的结论也出来了,不使用开源架构,借助其好的思想,替换其事件系统为面向对象结构,自行封装架构。
架构设计
再次强调:目前仅考虑如何应用于 react native

先扣题,针对开篇问题的解决方案如下

交互

  1. 组件对外发布:组件对外只允许使用props来暴露功能,不允许使用接口及其它一切方式

  2. 父子组件间:组件的子组件通过父组件传递的接口来与父组件通信

  3. 兄弟组件间:

  • 方案 1:假设 a 要调用 b,参考第一条的话,其实就是 a 要改变 b 的 props,那么 a 只要改 b 的 props 的来源即可,b 的 props 的来源一般就是根组件的 state。那么根组件就要有组织和协调的能力。

  • 方案 2:利用事件机制,基本同 flux 架构。略复杂,且我们并不需要事件的特性,本架构设计不推荐。

职责


  1. root- 存放 state,组织子 view 组件,组织业务逻辑对象等

  2. 子 view 组件-根据t his.props 渲染 view。

  3. 业务逻辑对象-提供业务逻辑方法

根据以上推导,我将其命名为面向对象的 ReactNative 架构设计,它与 flux 系列架构的最大的不同之处在于,用业务逻辑对象来代替了【store action dispatcher】or【store reducer】的事件系统。业务逻辑对象就是一组对象,用面向对象的设计理念设计出的n个对象,其负责处理整个页面的业务逻辑。

以上为推导过程,干货才开始。。。。

面向对象的 ReactNative 组件\页面架构设计

一个独立完整的组件\页面一般由以下元素构成:

1. root 组件,1 个,
  • 负责初始化 state

  • 负责提供对外 props列表

  • 负责组合子 view 组件形成页面效果

  • 负责注册业务逻辑对象提供的业务逻辑方法

  • 负责管理业务逻辑对象

2. view 子组件,0-n 个,
  • 根据 props 进行视图的渲染

3. 业务逻辑对象,0-n个,
  • 提供业务逻辑方法

root 组件


root 组件由以下元素组成:

  1. props- 公有属性

  2. state-RN 体系的状态,必须使用 Immutable 对象

  3. 私有属性

  4. 业务逻辑对象的引用 - 在 componentWillMount 中初始化

  5. 私有方法 - 以下划线开头,内部使用 or 传递给子组件使用

  6. 公有方法【不推荐】,子组件和外部组件都可以用,但不推荐用公有方法来对外发布功能,破坏了面向状态编程,尽可能的使用 props 来发布功能


图片

子 view 组件

子 view 组件中包含:

1. props-公有属性
2. 私有属性-如果你不能理解下面的要求,建议没有,统一放在父组件上
  • 绝对不允许和父组件的属性 or 状态有冗余。无论是显性冗余还是计算结果冗余,除非你能确定结算是性能的瓶颈。

  • 此属性只有自己会用,父组件和兄弟组件不会使用,如果你不确定这点,请把这个组件放到父组件上,方便组件间通信

3. 私有方法-仅作为渲染 view 的使用,不许有业务逻辑
4. 公有方法【不推荐,理由同 root 组件】
业务逻辑对象

业务逻辑对象由以下元素组成:

  1. root组件对象引用-this.root

  2. 构造器-初始化root对象,初始化私有属性

  3. 私有属性

  4. 公有方法-对外提供业务逻辑

  5. 私有方法-以下划线开头,内部使用

ps1:通用型组件只要求尽量满足上述架构设计

通用型组件一般为不包含任何业务的纯技术组件,具有高复用价值、高定制性、通常不能直接使用需要代码定制等特点。

可以说是一个系统的各个基础零件,比如一个蒙板效果,或者一个模态弹出框。

架构的最终目的是保证系统整体结构良好,代码质量良好,易于维护。一般编写通用型组件的人也是经验较为丰富的工程师,代码质量会有保证。而且,作为零件的通用组件的使用场景和生命周期都和普通组件\页面不同,所以,仅要求通用组件编写尽量满足架构设计即可。

ps2:view 子组件复用问题

抛出一个问题,设计的过程中,子组件是否需要复用?子组件是否需要复用会影响到组件设计。

  1. 需复用,只暴露 props,可以内部自行管理 state【尽量避免除非业务需要】

  2. 不需复用,只暴露 props,内部无 state【因为不会单独使用,不需要 setState 来触发渲染】

其实, 一般按照不需复用的情况设计,除非复用很明确,但这时候应该抽出去,变成独立的组件存在就可以了,所以这个问题是不存在的。
按场景分析、验证架构设计
触发 view 改变的场景【即需要 setState 的场景】

1. 回调函数触发【异步】
  • 组件生命周期事件中 or 用户操作触发 启动请求服务器

  • 组件生命周期事件中 or 用户操作触发,启动定时任务 or 注册了其它回调函数【比如交互管理器的动画结束事件】

2. 用户操作触发
  • view 的直接改变【同步】

  • 仅仅注册回调函数【异步,参考上面一条】

完整 demo 代码
此 demo 仿照 redux 提供的 todolist demo 编写。

redux demo 地址:http://camsong.github.io/redux-in-chinese/docs/basics/ExampleTodoList.html

demo截图:

图片

todolist 页面:

本段代码较长,阅读体验不佳,欢迎小伙伴到原文中进行阅读。


-EOF-



阅读原文 get 完整版 demo 代码。

专业的开发者技术社区

多样化线上知识交流

丰富线下活动和给力工作机会

图片

37320ReactNative 的组件架构设计

这个人很懒,什么都没留下

文章评论