白羽
白羽
  • 发布:2022-09-23 03:07
  • 更新:2024-07-02 20:37
  • 阅读:4138

【分享】renderjs使用经验-欢迎补充

分类:uni-app
wxs

时隔一年,我重新使用renderjs,却发现很多知识已经忘了。然而由于renderjs的学习资料少之又少,重新学习,消耗了我大量的时间和精力。为避免再次忘掉,同时也为了丰富renderjs资料,方便诸位同行,我决定写一些笔记。

本文纯属个人摸索,如有错漏,还请诸位多多指正!


一、renderjs基本概念及资料

二、视图层和逻辑层的伪分离

  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,请注意:

  1. 目前仅支持内联使用。
  2. 不要直接引用大型类库,推荐通过动态创建 script 方式引用。
  3. 可以使用 vue 组件的生命周期(不支持 beforeDestroy、destroyed、beforeUnmount、unmounted),不可以使用 App、Page 的生命周期
  4. 视图层和逻辑层通讯方式与 WXS 一致,另外可以通过 this.$ownerInstance 获取当前组件的 ComponentDescriptor 实例。
  5. 注意逻辑层给数据时最好一次性给到渲染层,而不是不停从逻辑层向渲染层发消息,那样还是会产生逻辑层和视图层的多次通信,还是会卡
  6. 观测更新的数据在视图层可以直接访问到。
  7. APP 端视图层的页面引用资源的路径相对于根目录计算,例如:./static/test.js。
  8. APP 端可以使用 dom、bom API,不可直接访问逻辑层数据,不可以使用 uni 相关接口(如:uni.request)
  9. H5 端逻辑层和视图层实际运行在同一个环境中,相当于使用 mixin 方式,可以直接访问逻辑层数据。
    ...未完,待续
9 关注 分享
BoredApe 写意山水画 萧 3***@qq.com z***@126.com 狂人的芝士 1***@qq.com HRK_01 7***@qq.com

要回复文章请先登录注册

1***@qq.com

1***@qq.com

renderjs是不是不支持vue3?
2024-07-02 20:37
HRK_01

HRK_01

回复 h***@163.com :
这段代码是监听逻辑层属性更改,更改时触发视图层里面的updateEcharts的方法(更改后的值也同时带过去
2024-04-01 17:12
h***@163.com

h***@163.com

回复 1***@qq.com :
:prop="option" :change:prop="echarts.updateEcharts"
我也不明白这个,找遍了整个文档没有找到详细解释的地方,这段代码到底是什么意思?
2024-03-31 15:25
HRK_01

HRK_01

点赞,文章写的通俗易懂
2023-12-29 16:51
1***@qq.com

1***@qq.com

写的很好
2023-12-15 18:01
aak12345

aak12345

https://uniapp.dcloud.net.cn/api/window/communication.html uni主动发消息给iframe,可以在renderjs里面添加uni.$on,然后在uni里面uni.$emit
2023-11-08 19:19
ouemng

ouemng

为什么我只要加上renderjs的代码,真机运行时就会报错$t.setAttribute is not a function at uni-jsframework.js:31:5744。求解啊
2023-08-04 18:21
1***@qq.com

1***@qq.com

官网的文档一直没写prop跟 change:prop这个配置,我觉得这个很重要
2023-05-10 10:46
7***@qq.com

7***@qq.com

在 renderjs 如果一个对象越来越大,会影响在 Android 中动画渲染的效果吗?
2023-02-23 17:54
taiful

taiful

写的太好了,如果我想用这种机制来做一个多线程可行吗?求微信,我的是13128901052
2023-02-22 11:12