以下内容根据演讲现场视频以及PDF整理而成。
1.RAX
我们首先谈一谈什么是 RAX。RAX 其实是一个基于 React 写法的跨容器的 JS 框架。RAX 首先是基于 React 写法的框架,所以如果大家会使用 React,那么就能够很快地上手使用 RAX;另外一点就是 RAX 框架是跨容器的,如果之前的项目是使用 React 写的,为了使用 Weex 的功能,使用 RAX 框架进行迁移是非常方便的。
RAX 有很多的优势,比如在 gzip 之后,其大小比 React 小了四倍,并且在服务端的渲染方面 RAX 也有非常好的表现。在双十一和双十二促销过程中,RAX 都起到了基础和核心的作用。其实之前总谈到天猫双十一的数据,其实在淘宝双十二会场中也达到了1600个以上的页面,数量如此多的页面其实基本都是使用Rax进行渲染的,并且在双促中,淘宝都是以 RAX 为核心来构建上层的标签和 API 以及 RAX 的 UI 组件、上层的大促模块和大促页面的。
2.RAX 对于大促的基础支持
接下来简单介绍一下 RAX 对于大促的基础支持。其实除了像天猫双十一、淘宝双十二以及现在的年货节等大促活动以外,还有很多像爱逛街等淘宝使用的很多业务以及独立应用都是使用Rax框架搭建的,但是我们今天主要分享的是 RAX 在双促中的应用场景。
另外一部分是基础的标签。在整个 Weex 的组件里面其实有 div、scroller、list 等等这样的一系列标签,对应到 RAX 这边也有一套相应的解决方案,比如 div 对应过来是 View,scroller 对应过来就是 scrollView,其实在这里很多的概念是引用了 React Native 的概念,所以如果大家之前的项目是使用 React Native 进行开发的话,就能很方便地享受 Weex 的一些能力。
之后分享一下从标签到整个上层的页面的构建过程。下面这张图中最下层的 Compontents 是我们所提供的最基础的标签,比如 Slider、View、Video,这些都可以映射地理解为 HTML5 基础标签级别最细粒度的标签。
在基础标签集合之上分为了两块最主要的部分,一部分是 RAXUI,也就是组件支持,而另一部分就是页面渲染,这两部分将支持整个大促中的统一的应用解决方案。RAXUI 提供了对于图片的播放、视频的加载以及横向的导航切换等通用的解决方案,能够帮助我们快速地完成业务模块。而对于另一部分页面渲染,也就是 rax-page 来说,它所做的事情主要是组织 RAXUI 的组件和模块来达到页面滚动的容器、渲染优化以及完成渲染逻辑,并且实现页面的秒开以及在页面渲染过程中的优化。
在这一层之上就是大促中使用到的模块以及页面所需要的一些组成部分。淘宝的大促页面大致区分了以下的概念:业务上的公用模块以及滚动容器内的一些模块,也就是 APP 级别的模块,而像电梯的内容以及橱窗的内容等,这些都是位于一个通用容器内的,则属于Page 级别的模块。
从基础标签,到通用组件再到上层业务模块,是淘宝大促页面构成的整体思路。
3.淘宝双促中的页面渲染逻辑
接下来来与大家分享淘宝双促的一些实践,主要分享一下淘宝双促中的页面渲染逻辑。
在整个 rax-page,也就是淘宝渲染核心的组件里面包含了如下图所示的这样的几个主要的组成部分。
接下来是模块的组织,也就是页面模块这一层。对于这一层而言,在淘宝的每一个斑马模块的外层,都会进行包裹,也就是实现了 Module 的逻辑,其里面将会处理模块级别的埋点,并且对于大促的细粒度的拆分等进行优化,很多模块级别的处理将会放在这一层实现。 Link 是一个业务级别的埋点,之前的分享到了天猫里面的很多的埋点是自己拼的,而淘宝这边则提供了业务级别的埋点标签,也就是 Link 标签,这个标签所做的就是通过自动化的方式帮助用户实现埋点,也就是将原本用户需要手动实现的埋点做成标签进行实现。
下面还有事件通信,如果大家开发过 React Native 或者 React 的话,就会很容易理解。RAX 框架里面的事件通信是通过使用的 React 里面的 Context 上下文实现的,对页面的每一层进行绑定之后,业务下面的每一个模块、模块与模块之间以及模块与整个页面之间都可以进行事件的消息收发。这个机制在淘宝的双促中也有很多的应用,包括下拉刷新以及页面内弹层等等。
以上这些标签都是组成页面核心渲染逻辑的部分,有了这些标签接下来就可以使用它们对接整个斑马平台,并且使用渲染逻辑对于模块进行渲染。
在模块组织方面,则主要分为了两种类型:滚动容器内的模块以及页面级通用的模块。这个里面形象到页面上,其实就是 Block 是一个滚动的容器,在这个滚动容器的内部可以包含很多个业务模块,比如橱窗、slider 等以及一些其他在浏览页面时需要进行滚动的部分。而另外一部分像通用导航头,以及比如需要在页面顶部或者底部显示的消息,也就是需要不随着页面滚动而发生动作的模块,所以像通用导航头等这样的模块属于更上一个层级,所以将像通用导航头这样的模块都放在了容器外面。在最右边的图可以很好地看出,通用容器其实是页面中的一个部分,而通用导航头却位于 Block 外部。
我们使用双十二大促的一个页面作为例子进行说明。在如下图所示的这个页面中,最上面的是一个通用的电梯,这个通用电梯使用了 tabheader 组件,它所做的事情就是使得导航头吸附在页面顶部,不会随着页面的滚动而滚动,并且可以个根据不同电梯的跳转而跳转到不同的楼层。页面底端的抢红包和返回页面顶部等也是一样的不随着页面变化的,所有的这些都会定义为通用的模块。而这些通用的模块将会放在页面的滚动容器外面,然后像里面的橱窗就是在滚动容器内部的。总而言之,APP 级别的模块不会随着页面而滚动, Page 级别的模块是包裹在页面里面的,需要跟随页面滚动,这两个概念就是构成双促会场大促模块的基本概念。
接下来分享两块比较核心的部分就是双十一以及双十二大促时,在 H5 页面以及 Weex 页面的渲染方面使用的不同的渲染策略。我们都知道使用 Weex 开发所期望的是通过一次编码能够实现在不同容器内获得一致的体验,而在真实的运行过程中,在 H5 页面和 Weex 页面中运行时还是或多或少存在一些差异的。
我们首先简单介绍一下 H5 页面的渲染,后面将会谈到的 Weex 与 H5 渲染的差异是比较大的。先跟大家解释一个概念,就是在 Weex 中,一个 View 的容器或者一个 div 容器其实是有一个 appear 实现的。当页面滚动到这样的页面的一个可视区域的时候,View 容器将会触发其 appear,同样的,我们在 H5 页面中也模拟了这样概念,以此来使得两端具有一致的开发体验。当某一个模块出现在可视区域内触发 appear 事件的时候,我们会将此模块进行渲染,因为在用户刚刚打开这个页面的时候不会将全部的模块进行渲染,而是做了首屏的切分,只有首屏的模块才会在用户刚打开页面的时候进行渲染,非首屏的模块在此时将会是一个高度已经撑开的占位容器。
而对于 Weex 而言,则会是非常不同的一套逻辑。之前的分享里面有谈到在天猫里面做了首屏的拦截,在触发底部的 loadmore 事件的时候,会将所有的非首屏模块一次性地加载到页面上,而在淘宝这边所采用的的策略则是与天猫不太一样的。因为用户期望在首屏点击电梯跳转到页面指定位置的时候,需要将该位置的所有可视区域内的模块全部展示出来。在 Weex 中的渲染策略依然是首屏全部渲染完成之后,对于非首屏模块的进行一次性地加载。而这个一次性加载也分为了两个部分,首先是将能够直接拿到数据的斑马模块一次性地进行渲染,比如图中的 Module3、Module4 等,在这个过程中,如果遇到某个模块的数据需要自己请求或者需要额外的一些请求逻辑的话,这个模块会被等待掉。等待的时间大概会有几百毫秒,我们之前测试的数据是在比较低端的安卓机器上面大概需要八百毫秒左右,这种情况下,在一些比较不好的机型也不会产生太差的体验的。最终对于用户而言,可以非常快速地看到首屏,即使是飞快地向下滚动也能看到所有的模块都已经出现了,但是这样做的同时就是需要前端同学与自己的后端同学进行约定和配合,将页面加载的性能与服务器进行权衡,这就是 Weex 页面在双促中的渲染逻辑。
接下来分享页面内元素的组织。我们与斑马进行对接或者在应用已经有了渲染逻辑的情况下,接下来进行一些模块的开发可能就需要有一些更加细粒度的元素。而对于模块开发者的影响可能就是需要使用 React。
下图是一个双十一的长列表模块的最简单的代码示例,在静态属性里面定义了很多自己模块需要的东西,比方说 getModuleRowHeight() 方法返回了一个经过计算后的模块的真实高度,有了这样的一个真实高度以后就能够在打开真实页面的时候将整个页面撑开。还有很多相关的约定的东西加上我们开发者习惯的 React 的写法就使得很容易进行 RAX 开发。
讲到标签和组件,还是需要拿刚才的那个例子来说明。最上面的 Header:精选好店、腔调好店等这些东西其实是由最基础的最细粒度的 Weex 标签提供的,Weex 里面的 div、scroller 以及文本图片这样的东西组成下拉的电梯,这样的组件经过 RAXUI 的封装会变成tabheader 提供给使用者。
页面中已经有了很多细粒度的标签,通过这些细粒度的标签可以很容易地开发出业务的模块。像图中这样的比如是一个会场的页面,上部是 slider 的图像滑动,然后使用 tabheader 做电梯可以跳转到指定位置,然后最下边是一个长列表,这样的整个页面方案其实是用自己的一套组件可以进行实现的,以至于对于不同的业务场景,作为横向的 tab 滑动功能以及视频播放功能或者通过 tabbar 导航切换长列表,都是在现有的组件体系中完成的。
接下来分享组件与双促中经常使用的模块之间的关系。tabheader 是用来实现所有的电梯以及底部的 bar 等有关 tab 切换的功能的;slider 可以用来做轮播;使用 grid 可以实现多列布局等等。
在整个大促的过程中,有了这一层最基本的标签,通用的 RAXUI 的组件,我们在实现大促里面所需要的模块的时候其实只是进行了简单的业务封装。而双十一和双十二促销的时候就是需要在此基础之上对组件进行优化。比如对于 grid,之前为了减少嵌套,为了实现多列布局时会在一个容器中放置几个 View 实现多列,在双促里面需要标签嵌套的层级不能过多,为了达到这样的效果,其实需要将中间的层级去掉,使用 cell 的方式进行实现。再比如 picture,在 Weex 里面,是不需要关心图片的懒加载的,但是在 H5 里面直接使用原生的标签可能会出现很多问题。picture 里面做了很多事情,其中一个是基于滚动容器的事件封装,就是在滚动的过程中去加载图片数据,这样整个数据量在图片方面的减小也是非常可观的。对于视频播放而言,在 Weex 中有 video 标签专门负责,但是其提供的能力非常有限,所以需要一套更为通用的方案来兼容 Weex 和 H5,player 标签就是进行了更重一级的封装。有了这样的一套组件,就能够更加方便地实现双促的模块。
接下来分享一个在双促中为淘宝带来很大性能受益的例子。下图中最左边的那条线是包含两三百个坑位的长列表,放大后就是右边的一行两列的长列表的图,这样的长列表是由 List 和 cell 来实现的。为了使得这样的一个无限的长列表有更好的加载顺序,首先采用的方案是让自己的 Block 滚动容器使用 List 标签,里面的每一个模块、每一行都是用 cell 标签,但是在像斑马或者一些搭建的应用中很容易写一个外层的 div,里面包含了10行数据。这样的事情放在 List 列表中其实是没有进行细粒度拆分的,所以不会在处于非可视区域时进行回收。因此这样的情况在淘宝如此长的列表场景下是无法接受的,所以我们做的一件事情就是使得这样的页面可以被细粒度地进行拆分。
最后分享一个刚刚在代码段中看到的 repeat 类型的模块,repeat 类型的模块所表示的是当前这个模块可以被重复地渲染很多次,而且每一行都是一模一样的,可以将类似这样的模块,每一行都单独进行拆分来实现更好的渲染。整个的渲染逻辑是:在开始时,渲染容器是一个空的 cell,这个空的cell是高度真实撑开,所以可以方便与电梯跳转到真实位置。当 cell 经过电梯跳转到可视区域的时候,会触发自己的 appear 事件,此时 appear 事件将会触发真实的数据并获取到请求回来,这样数据拿到之后将会通知给上一层也就是页面渲染层。对于页面渲染层而言,我们刚刚讲到了有做页面通信的东西,之后会将这一部分数据拿给页面上层进行拆分,上层将每条数据拆分成单独模块,做成 cell。这样,在整个页面加载的过程中,被回收的效果是非常明显的。
其实除了大促之外,还有很多淘宝的业务,包括商家、搜索以及周围的业务都在使用RAX 。而且 RAX 目前已经开源了,它也是一套比较成熟的方案,大家感兴趣的可以访问 RAX 的 Github 了解更多信息( https://github.com/alibaba/rax)。