时隔一年,我重新使用renderjs,却发现很多知识已经忘了。然而由于renderjs的学习资料少之又少,重新学习,消耗了我大量的时间和精力。为避免再次忘掉,同时也为了丰富renderjs资料,方便诸位同行,我决定写一些笔记。
本文纯属个人摸索,如有错漏,还请诸位多多指正!
一、renderjs基本概念及资料
-
学习renderjs的几份相关资料
1)renderjs简介
2)谜之wxs,uni-app如何用它大幅提升性能
3)小程序专题/wxs
4)wxs规范及wxs响应事件 -
与renderjs相关的几个示例
1)通过renderjs,在app和h5端使用完整的 echarts
2)uni-swipe-action滑动操作组件
二、视图层和逻辑层的伪分离
uniapp在APP-vue端、APP-nvue、小程序端、H5端……等所有端,都遵循了视图层和逻辑层分离的设计原则。
例如,下面的html模板隶属于视图层,而模板上绑定的响应数据、事件方法,以及<script>标签中的代码(示例1中未标出),都隶属于逻辑层。
示例1
<!--html模版,属于视图层;模板上绑定的响应数据、事件、方法,属于逻辑层-->
<template>
<text @tap="showCursor" class="my-text">
{{text1}}<text v-if="show === false" class="cursor">光标</text>{{text2}}
</text>
</template>
<script>
/*这里的所有代码,都属于逻辑层*/
export default {
data() {
return {
text1: 'left',
text2: 'right'
}
},
medthod: {
showCursor() {
......
}
}
}
</script>
当用户点击视图层时,如果视图层模板绑定了事件方法(例如showCursor方法),则视图层会通过某种机制,触发逻辑层的showCursor方法,执行逻辑层相关代码——这是视图层调用逻辑层代码的一种机制。而当逻辑层代码运行时,如果代码改变了同属逻辑层且对视图层有影响的那些响应数据 ,那么逻辑层也会通过某种机制,触发视图层的渲染行为,渲染相关视图——这是逻辑层代码控制视图层渲染行为的一种机制。这两种机制在大多数情况下都运行良好,保证和实现了逻辑层与视图层相互通信和解耦,在一定程度上解放了开发者。
不过,这样的解藕并不彻底,因为视图层和逻辑层依然运行于同一个webview下的同一个线程,它们依然在共享着内存,能够直接互访对方的数据(参考示例2或示例3)(虽然共享内存并不是什么坏事,反而还拥有高性能、速度快的优点。)
示例2
methods: {
showCursor() {
this.show = true; //更改逻辑层的响应数据,Vue将在内部保存更改,以便在随后的恰当时刻触发视图层的渲染行为,从而将<text class="cursor">光标</text>插入到{{text1}}和{{text2}}之间(参见示例1的html模板)
conlose.log(this.$el.innerHTML); //改变this.show后,立即查看视图层的渲染效果,发现Vue尚未将内部保存的更改送至试图层,因此<text class="cursor">光标</text>尚未载入到视图层(在这里,逻辑层的代码已经在调用视图层的$el和innerHTML了,这说明逻辑层和视图层是处于同一线程的、可以共享内存的)
this.$nextTick(()=> {
//当执行到这里的时候,vue已经将内部保存的更改送至了视图层,触发了视图层的渲染行为,并完成了视图层的渲染。也即,this.show=true引起的视图层渲染已经完完
conlose.log(this.$el.innerHTML); //由于视图层渲染完毕,所以此时查看innerHTML,会发现<text class="cursor">光标</text>已经插入到视图层
});
}
示例3
methods: {
async showCursor() {
this.show = true; //更改逻辑层的响应数据,Vue将在内部保存更改,以便在随后的恰当时刻触发视图层的渲染行为,从而将<text class="cursor">光标</text>插入到{{text1}}和{{text2}}之间(参见示例1的html模板)
conlose.log(this.$el.innerHTML); //改变this.show后,立即查看视图层的渲染效果,发现Vue尚未将内部保存的更改送至试图层,因此<text class="cursor">光标</text>尚未载入到视图层(在这里,逻辑层的代码已经在调用视图层的$el和innerHTML了,这说明逻辑层和视图层是处于同一线程的、可以共享内存的)
await this.$nextTick(); //等待Vue会将内部保存的更改送到视图层,在待视图层渲染完毕后,才从这里往下执行
conlose.log(this.$el.innerHTML); //由于视图层渲染完毕,所以此时查看innerHTML,会发现<text class="cursor">光标</text>已经插入到视图层
});
}
坏就坏在视图层和逻辑层同属一个线程,它们会相互影响、相互制约:当视图层的渲染量很大时,会阻塞逻辑层代码的运行;而当逻辑层的业务很繁忙时,又会阻塞视图层的渲染。这就容易引起卡顿。因而,在H5手机端,单线程不适用于交互复杂、渲染实时、业务复杂的场景,只能做一些简单的展示。(不过,在性能经常过剩PC端,单线程也能做很多事了,此处不表。)
三、真正的视图层和逻辑层分离
实际上,uniapp只有H5端是视图层和逻辑层工作于同一个线程中的,APP-VUE端、APP-NVUE端,以及小程序端,都是视图层和逻辑层分别工作于不同的两个线程中的。
这是因为,标准浏览器下的H5手机端向来拉胯,大家已经无语到习惯了,一般不指望在H5手机端设计复杂的业务和交互,基本上只做纯展示。而H5的PC端则由于CPU和GPU的强大处理能力,通常足够胜任各种交互复杂、渲染及时、业务复杂的场景。所以,H5的手机端和PC端都不需要双线程(PC端的确不需要,手机端是由于无奈)。
但是APP和小程序不一样啊,CPU和GPU弱鸡(相对于PC端)的同时,人们还对它们抱有巨大的期望,试图在它们身上实现复杂业务和交互。于是,为了追求高性能,APP端和小程序端都打开了两个线程——一个专用于视图渲染,叫渲染线程;一个专用于逻辑业务的处理,叫逻辑线程。这样带来的好处是:即使视图层的渲染量很大,且逻辑层业务也很繁忙,依然不太担心出现性能问题,两个线程各管各的就行了。这也就是为什么APP端和小程序端的性能上限比手机版H5端的性能上限高的原因。
不过,问题也随之而来,由于两个线程是相互独立的,视图层和逻辑层之间的数据关联遭到了割裂,不能再共享内存,不能再互访对方的数据。于是,在APP端和小程序端,前面的示例2和示例3,逻辑层不能再直接使用this.$el和this.$el.innerHTML了,因为$el和innerHTML隶属于视图层,它们在逻辑层中并不存在。
更大的问题在于,由于不能共享内存,视图层和逻辑层之间的数据必须经历(内部)序列化、反序列化和跨线程传送才能完成交换,交换效率十分低下,至少比单线程慢十倍。这就导致类似于touchmove视图跟手渲染这样原本在单线程上很容易实现的需求,在双线程上反而很难实现,总是出视觉上的卡顿现角。
如何解决这个问题呢?我们不妨这样思考:双线程中的渲染线程,它本来就是一个webview;而webview本来就拥有自己的视图层和js环境。既如此,那么我们只要把这个js环境放出来,视为视图层的私有“逻辑层”,那不就可以利用这个私有的逻辑层,在渲染线程内,独立处理touchmove视图跟手渲染这样的渲染任务,从而避免与逻辑线程进行跨线程的数据交换了嘛!
嗯,这个视图层私有的逻辑层,就叫做renderjs吧!
于是乎,APP端和小程序端,都拥有两个线程,一个是专用于视图层渲染的,叫渲染线程;另一个是专用于逻辑层业务的,叫逻辑线程。其中,渲染线程,既拥有自己的视图层,也拥有自己的私有逻辑层,叫renderjs层。
微信:renderjs层太强了,有点怕,得限制一下,让它整一下class和style得了,其它的不要碰。限制之后,就叫wxs吧!
某宝:俺也一样,不过,叫SJS吧!
百度:俺也一样,叫Filter……要不,也叫SJS吧!
WEEK、APP-NUE:我的渲染线程没有js环境,怎么办怎么办?要不,弄一个bingdingx糊弄糊弄吧?!
APP-VUE:虽然已经无敌,但为了兼容wxs,俺也把自己包装一下吧,让自己拥有和wxs一样的接口!wxs有的俺都有,wxs没有的俺也有!
PC端和H5端:虽然俺只有一个线程,但是为了和APP端兼容,俺也可以包装一个自己,假装拥有renderjs层!
四、renderjs详解
下面用代码框架展示视图层、renderjs层、逻辑层三者的关系,以及渲染线程、逻辑线程二者的关系。
示例4 一个标准的双线程模板
<template>
<text class="aaa"><!--这里的模板本身,隶属于渲染线程,可称为渲染线程的视图层,简称视图层。其上绑定的showCursor、text1、text2、show等,则隶属于逻辑线程(称为逻辑层)-->
<text class="bbb" @tap="showCursor">
{{text1}}<text class="cursor" v-if="show === false">光标</text>{{text2}}
</text>
</text>
</template>
<script module="text" lang="renderjs">
//这里的整个script块及代码,全部隶属于渲染线程,可称为渲染线程的逻辑层,或简称为renderjs层。
//它与上面视图层的关系,是同一线程(渲染线程)内的视图层和逻辑层之间的关系,因此它与视图层共享内存,在APP-VUE和Web端可以直接调用视图层的$el、innerHTML等属性和window、document、navigator等浏览器的js API,但在小程序端会功能受限(具体用法参见wxs、SJS等)。
//它与下面逻辑线程的关系,是两个不同线程之间的关系。
//由于渲染线程和逻辑线程二者并未共享内存,所以当前代码块(隶属于渲染线程的私有逻辑层,即renderjs层)不能直接调用隶属于另一个线程(逻辑线程)的showCursor、text1、text2、show等数据。
</script>
<script>
//这里的整个<script>块、代码,以及上面模板中绑定的showCursor、text1、text2、show等,全部隶属于逻辑线程,可称为逻辑层(逻辑线程只有逻辑层)
//在本模块中,可以随意访问showCursor、text1、text2、show等属性或方法,但是显然不能直接访问另一线程中的视图层和逻辑层数据
//也就是,既不能直接访问html模板中的$el、innerHTML等属性,也不能直接访问renderjs、wxs、SJS模块中的数据。而只能通过执行当前模块中的showCursor方法,或更改当前模块中的text1、text2、show属性,间接地、异步地影响视图层的渲染效果。
</script>
对于renderjs,请注意:
- 目前仅支持内联使用。
- 不要直接引用大型类库,推荐通过动态创建 script 方式引用。
- 可以使用 vue 组件的生命周期(不支持 beforeDestroy、destroyed、beforeUnmount、unmounted),不可以使用 App、Page 的生命周期
- 视图层和逻辑层通讯方式与 WXS 一致,另外可以通过 this.$ownerInstance 获取当前组件的 ComponentDescriptor 实例。
- 注意逻辑层给数据时最好一次性给到渲染层,而不是不停从逻辑层向渲染层发消息,那样还是会产生逻辑层和视图层的多次通信,还是会卡
- 观测更新的数据在视图层可以直接访问到。
- APP 端视图层的页面引用资源的路径相对于根目录计算,例如:./static/test.js。
- APP 端可以使用 dom、bom API,不可直接访问逻辑层数据,不可以使用 uni 相关接口(如:uni.request)
- H5 端逻辑层和视图层实际运行在同一个环境中,相当于使用 mixin 方式,可以直接访问逻辑层数据。
...未完,待续
16 个评论
要回复文章请先登录或注册
斜陽
8***@qq.com
8***@qq.com
dennyjiang
1***@qq.com
HRK_01
h***@163.com
HRK_01
1***@qq.com
aak12345