Weex 表面上是一个客户端技术,但实际上它串联起了从本地开发环境到云端部署和分发的整个链路。
开发者首先可以在本地像撰写 web 页面一样撰写一个 app 的页面,然后编译成一段 JavaScript 代码,形成 Weex 的一个 JS bundle;
在云端,开发者可以把生成的 JS bundle 部署上去,然后通过网络请求或预下发的方式传递到用户的移动应用客户端;
在移动应用客户端里,WeexSDK 会准备好一个 JavaScript 引擎,并且在用户打开一个 Weex 页面时执行相应的 JS bundle,并在执行过程中产生各种命令发送到 native 端进行的界面渲染或数据存储、网络通信、调用设备功能、用户交互响应等移动应用的场景实践;
同时,如果用户没有安装移动应用,他仍然可以在浏览器里打开一个相同的 web 页面,这个页面是使用相同的页面源代码,通过浏览器里的 JavaScript 引擎运行起来的。
在列表变得特别长的时候,尤其是列表包含了大量图文视频的时候,即使做了懒加载优化了渲染效率,超长列表内存的占用量也很难优化。
如果能复用列表中相似的模板结构,由数据来驱动原生组件的渲染,节点离屏之后将模板回收,将会大幅优化长列表的渲染性能和内存使用量。
图中的 JSFM 为 JS Framework 的简写。
下面以 Vue.js 为例,详细介绍一下常规组件的渲染过程。
如上图所示,前端框架中的 Template
不再需要数据,而是直接展开成一种纯静态的模板结构,结构中包含了模板渲染逻辑,格式仍然是 VNode
。然后经过 JS Framework 转换成 Weex 支持的 Element
,其中也包含了模板的原生渲染指令。客户端解析出可复用的模板结构,由数据驱动模板渲染,这个模板结构和前端组件中的定义是一致的。
这个过程除了要把模板发给客户端,还得带上模板的渲染逻辑,告诉客户端如何根据数据来渲染模板。为了描述这些渲染逻辑,就得设计一套面向原生渲染引擎的模板渲染指令。
当列表向下滚动时,回收掉上方不在屏幕内的模板,并不销毁而是将其中的数据清空。当列表下方需要渲染新的数据时,会取出回收的空模板,注入数据渲染出真实节点,然后追加到列表下方。列表向上滚动时的原理是一样的,为了保障列表滚动的流畅,也会渲染屏幕上下方扩展区域内的节点。
无论真实的数据有多少条,真实渲染的只有可滚动区域内的节点,这样不仅可以加快首屏的渲染速度,内存的占用量也不会随着列表长度大幅增长。
想让客户端只根据模板和数据就能渲染出来节点,看起来只有函数式组件才可以做到,也就是要求组件必须是不含内部状态的,然而实际应用中绝大多数组件都含有内部状态的,只做到这一步是远远不够的。
对于包含了状态的组件,渲染过程就比较复杂了,因为组件内部状态的处理逻辑(data
,watch
, computed
)都在前端中,然而模板和数据都已经发给客户端处理了,所以需要经过多个回合的通信来解决状态同步问题。详细处理过程可以参考 Implementation.md#渲染过程
为了实现可复用的原生组件,在前端框架中引入了这两个概念:
VNode
节点,也不监听事件,内部状态更新时不会触发渲染而是数据改动发给客户端。在渲染的过程中,如果发现某个组件用在了 <recycle-list>
里,就不再走之前的处理逻辑,而是创建一个 Virtual Component Template,并且不初始化任何状态(data
,watch
, computed
)、不绑定生命周期,但是会初始化自定义事件的功能。渲染组件时不执行 render
函数,而是执行定制的 @render
函数生成带有原生渲染指令的模板结构,这个结构将一次性发给客户端,后续不会再修改。
在创建 Virtual Component Template 时,会监听客户端原生组件的 create
生命周期钩子,当客户端派发了 create
的时候,才会真正的开始创建只含状态不含节点的 Virtual Component。虚拟组件模板只有一份,但是从同一份模板创建出的 Virtual Component 会有多个,与客户端发送的 create
钩子的次数有关,与数据有关。另外,由于事件是绑定在节点上的,原生 UI 捕获到的事件只会派发给 Virtual Component Template,然后再找到相应的 Virtual Component 并以其为作用域执行事件处理函数。
Virtual Component 内部只管理数据,即使数据有变动也不会触发渲染,而是调用特殊接口向客户端更新组件的内部状态,由客户端根据新数据更新组件的 UI。在创建 Virtual Component 时,会监听客户端原生组件的 attach
、detach
、 update
、 syncState
生命周期,生命周期的派发有客户端来控制,语义和前端框架略有差异,具体细节可以参考列表的渲染过程和更新过程。
生命周期对应表如下:
Native | Vue | Rax | |
---|---|---|---|
create | beforeCreate | constructor | |
* | create | created | - |
attach | beforeMount | componentWillMount | |
* | attach | mounted | componentDidMount |
- | - | componentWillReceiveProps | |
- | - | shouldComponentUpdate | |
update | beforeUpdate | componentWillUpdate | |
* | update | updated | componentDidUpdate |
* | detach | beforeDestroy | componentWillUnmount |
detach | destroyed | - |