Weex 长列表的复用方案 <recycle-list>

分类:手机开发| 发布:camnprbubuol| 查看: | 发表时间:2018/3/27

 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,其中也包含了模板的原生渲染指令。客户端解析出可复用的模板结构,由数据驱动模板渲染,这个模板结构和前端组件中的定义是一致的。

这个过程除了要把模板发给客户端,还得带上模板的渲染逻辑,告诉客户端如何根据数据来渲染模板。为了描述这些渲染逻辑,就得设计一套面向原生渲染引擎的模板渲染指令。

当列表向下滚动时,回收掉上方不在屏幕内的模板,并不销毁而是将其中的数据清空。当列表下方需要渲染新的数据时,会取出回收的空模板,注入数据渲染出真实节点,然后追加到列表下方。列表向上滚动时的原理是一样的,为了保障列表滚动的流畅,也会渲染屏幕上下方扩展区域内的节点。

无论真实的数据有多少条,真实渲染的只有可滚动区域内的节点,这样不仅可以加快首屏的渲染速度,内存的占用量也不会随着列表长度大幅增长。

使用 Virtual Component 管理组件状态

想让客户端只根据模板和数据就能渲染出来节点,看起来只有函数式组件才可以做到,也就是要求组件必须是不含内部状态的,然而实际应用中绝大多数组件都含有内部状态的,只做到这一步是远远不够的。

对于包含了状态的组件,渲染过程就比较复杂了,因为组件内部状态的处理逻辑(data,watchcomputed)都在前端中,然而模板和数据都已经发给客户端处理了,所以需要经过多个回合的通信来解决状态同步问题。详细处理过程可以参考 Implementation.md#渲染过程

为了实现可复用的原生组件,在前端框架中引入了这两个概念:

  • Virtual Component Template: 虚拟组件模板。包含了带有原生模板指令的节点,监听节点上的事件,但是不包含任何状态,不会触发更新,也没有生命周期。整个过程中只会渲染一次,目的是将模板的结构发给客户端。
  • Virtual Component: 虚拟组件。包含内部状态和生命周期,但是并不渲染,不执行 render 函数也没有 VNode 节点,也不监听事件,内部状态更新时不会触发渲染而是数据改动发给客户端。

virtual component

在渲染的过程中,如果发现某个组件用在了 <recycle-list> 里,就不再走之前的处理逻辑,而是创建一个 Virtual Component Template,并且不初始化任何状态(data,watchcomputed)、不绑定生命周期,但是会初始化自定义事件的功能。渲染组件时不执行 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 -
365据说看到好文章不转的人,服务器容易宕机
原创文章如转载,请注明:转载自郑州网建-前端开发 http://camnpr.com/
本文链接:http://camnpr.com/mobile-dev/2292.html