收藏
评论

重渲染与自定义组件优化(下)官方


接下来我们看实践二实现wxs版本的stopwatch组件。

另一个组件stopwatch_wxs这个组件版本与stopwatch组件它实现了相同的方法,调用方式也是一样的,不同点在于我们wxs组件。这个版本的组件,它在wxml这个页面里边引入了一个wxs脚本。关于时间计算的逻辑,包括格式化等等这些全部从逻辑层移到了wxs脚本里面去,从这个组件的代码来看wxs组件的JS代码与我们原来的stopwatch组件,它的代码是一样的。

主要也是实现了三个方法start、stop和switch,并且它的wxss的样式代码相比原组件也没有一个变化。在组件的wxml代码里面引入了一个wxs脚本,JS这个代码层对wxs脚本的一个控制,它是通过一个叫做change:mode这样的一个属性去完成的。index.wxs这个脚本的这个内容我们可以看一下,稍后会看到,这个文件只能使用ES5的语法,所有ES6的语法都不能使用,并且这和我们在这个项目详情面板里边是否开启将JS变成ES5没有关系,你开不开启这个设置你都不可以使用,这样一个ES6语法。

wxs脚本它没有定时器,我们可以使用组件对象的requestAnimationFrame方法代替于原来的定时器方法,在这个组件对象上我们当然也可以使用setTimeout方法创建定制器,setInterval也可以,但显然在这个地方与帧频天然合拍的requestAnimationFrame方法,它更适合干这个工作,它能使我们这个数字的切换刷新的动画看起来更加的一个流畅。在测试的时候我们仍然可以打开Performance面板,使用wxs脚本实现了单次视图更新,它的消耗大概是在20毫秒左右,比原来的执行性能要稍好一些。


下面我们开始代码实践。

首先第一步我们需要去创建另外一个版本也就是wxs版本的这样的一个组件。这个组件在我们最终源码里面都有了,都已经实现了。我们看一下它的一个效果。首先我们看wxml这个标签代码,标签代码相比原来我们增加了第一个,增加了这个模块wxs这个模块的一个引入,然后在下面这个地方加了一个change:mode等于index.modeObserver。这个是为了实现一个属性监听,就是将我们对mode属性的一个变化,将这个事件然后传递给这样的一个方法 传递给它去驱动这个方法的一个执行。当我们加了这个属性以后,后面mode等于什么一个值我们必须也是要添加的,这个是必不可少的。

再看一下JS代码里面,首先这个地方,我们可以看到把它放在了data里面,因为要通过setData去改变它去触发我们视图上面属性它的一个改变,所以我们把这个地方把它放到data数据对象里面。然后start方法很简单,就是一个setData的调用,stop也是。switch方法没有变化,整个现在我们自定义组件对于wxs这个版本它的JS逻辑层代码已经达到了最简化,它没有一点多余的一个代码了。

那么这个组件的功能在哪里实现的,我们看一下。最重要的一个角色就是index.wxs脚本,这个脚本我们刚才提到了是在这个地方引入的对吧,引入以后然后我们看它里面干了什么事情。它有一个导出的方法在最下面,模块导出然后modeobserver这个方法干了什么事情,我们看一下。在这个地方它接收了一些参数,这些参数都是视图在调用它的时候负责传递给它的,这是它的新值就是mode的新值,因为这个监听的是mode属性,然后这是它的旧值,ownerInstance,这个是它本身组件它所属的实例,它属于哪个实例对象,这个它本身的一个实例。在这个里面我们需要去做判断,然后去看新值,判断如果它等于start,我们就调用这个start,如果它等于stop,我们就调用stop对吧。

start与stop怎么实现的?我们再接着往上面看,start里面这个地方有一个配置,这个配置其实传的其实就是ownerInstance。如果说我们组件在这个页面里面的话,它本身它的所有者其实就是一个page,它不是page也没有关系,因为我们用的它里面的这个方法是callMethod,调用它的方法还有requestAnimation调用它,只要这个方法可以调用就可以了,在这个里面我们再看一下干了一个什么事情。这个地方仍然会有一个convertTimeStampToString这样的一个时间格式化的方法,这个方法全是用ES5的这样一个语法写的。这个逻辑我们可以看到与我们原来的组件的逻辑,我们可以看一下是一样的,我们只是把它换了一个地方,然后拷贝在这个地方,同时将我们的let关键字改成var。为啥改成var?因为前面我们提到了,在wxs脚本里面你不可以使用ES6的语法,只能使用ES5对吧,然后这个地方就是为了格式化。

格式化以后在这个地方注意,我们有了一个小小的处理,为啥要处理?因为本身在wxs脚本里边去调callMethod的方法也是间接调setData,我们这个代码调用本身也是要通过Native层进行中转的。如果你这个方法调得太频繁的话显然它也会影响性能,所以我们这个地方做了一个限流,判断一下消失的时间是不是大于100毫秒,如果大于100毫秒然后允许它调用,如果没有那就先跳过去本次更新 让更新不太频繁。100毫秒间隔是可以的,因为我们人类对变化的一个感知时间大概是200毫秒,你可以感知它的一个变化100毫秒的话,基本上你对人类来讲是无感知的。

这个地方会有一个mode等于start的判断,如果是它还等于start这个状态,组件这个状态的话我们继续用requestAnimationFrame去启动本身上面的调用它本身是一个递归调用。只有当mode等于stop的时候,它才会停止调用 不再会调用了,这是它的一个调用方法。这个地方我们再多说一下,这地方有一个是什么 requestAnimationFrame这个方法本身是由我们渲染的时候,视图它本身帧频去驱动的,就是视图层渲染一次,它触发一次这个事件,然后我们通过这个方法去绑定的这些回调执行,它也会再跟着再执行一次,就这样一种方式。

它可以最大程度的跟我们这个视图的渲染进行合拍,如果是你不用这种方式,你用定时器的话,定时器写在逻辑层里面很可能跟我们这个视图渲染它不是合拍的,视图渲染了比如说你每秒渲染30帧,然后JS定时器你要求它每秒更新60帧,它跟不上。它跟不上的时候视图就会卡顿。所以这个地方我们用requestAnimationFrame这个方法,这是我们优先要使用的一个方法。前面我们提到了在这个里面,我们不可以使用setInterval setTimeout,本身在wxs脚本里面是不能使用的。但是我们在我们传进来的对象上比如说像ownerInstance,我们在这个上面它其实是有定时器方法的,你明白吗?它是有定时器方法的,就是我们可以在这个上面去调用定时器方法,有这个选项可以选择。但是就如我们刚才所说它性能其实不如requestAnimationFrame,所以我们在这个地方还是选择这个方法。

下面我们再看stop,其实它什么事都没干,为什么?正常情况下我们在这个地方要干一个清扫的工作,就是我们要调cancelAnimationFrame,但这个cancelAnimationFrame这个方法在这个对象上面它是不存在的。你把这个配置换成这个也是一样的,本身这个方法在这个上面目前是不存在的,以后可能会有,如果以后有的话,这个地方我们要做一个清扫工作,但目前它没有,所以这个地方这个方法什么也没有做这样的一种方式。

还有一个地方我们需要说明一下,就是这个mode,这个mode我们可以看一下我们是在哪里定义的,是本身在这个模块代码里面对吧。在这个地方进行定义的,它相当于什么,它其实相当于是一个模块变量。这个模块当它在我们的页面里面 在这个地方,当它在这个地方引入的时候,其实它已经有了一个模块的实例。明白了吗?它已经有了一个实例,当它有了一个实例以后,里面的这些变量它其实都是有状态的,都可以自己持有自己的一个状态,持有自己一种状态。所以在这个地方我们可以拿这个mode然后去做临时的一个状态的储存,然后这个地方可以去判断。

如果我们不用这种方式还有另外一种方式可以用是什么方式,就是通过instance.getDataset然后调用它的mode,调用它的mode去判断是否等于start,这个取它是从哪取的,它其实从我们这个组件。我们看一下传递过来的事件是这个,因为这个事件是从这传的 ,instance它其实等于谁?它其实等于这个view对不对,我们取它的dataset取到哪里,取到这个地方看到没有?这个地方有一个data-mode,等于mode对不对。如果是我们按照我们后面提到的这个方式去取,取到的信息其实是它。当然这个地方我们要有一个对比,在这个模块里边让它自己持有这个状态,与我们从这个视图上去取这个状态,两种方式进行对比。

我们下面的这种方式它的效率会更高,因为它本身自己就把这个状态给它做了很好的一个判断,你不需要去麻烦别人再去取那个状态了,你多了一层调用效率又会降低,这是关于这个脚本的一个说明说得稍微多一点,因为所有的代码都是在这里面实现的。

这个工作做完以后,接下来我们要做什么?因为我们组件的向外暴露的方法都是一致的,行为也是一致的,所以我们只需要在我们组件引入的在这个地方加了一个wxs,只需要加这个以后组件引用就变成从原来引入这个组件,然后变成了引入这个组件,这个组件名我们不变,组件名我们仍然可以按原来的方式进行使用,现在代码终于改完了。然后单击编译,我们看一下它的实际运行效果。选择我们首页然后运行首页 ,已经渲染出来了,然后单击 然后它运行,我们看到也在快速地进行切换,现在我们要对比,要打开Performance面板,可以让它先停下来把我们原来的给它清除一下,重新单击录制,然后单击开始,已经有了 然后停止 不需要太长,因为我们只需要测一段时间,只要它有执行就可以了,看一下 仍然是有问题的。因为我们的优化看来是无止境的,而我们对比一下看看跟原来相比是不是有所改善,你比如这个我们看到它渲染任务大概是54.30对不对,然后它里边这里面有个setData,然后这个地方也是大概57.40,这个是66 稍微长一点66,这个地方是61,然后这个大概是26,这个里面我看一下,它里面其实也是有setData的 每次都不一样。

这个地方它性能我们可以做个小小的测试,它主要是由什么样的一个代码然后引发的,我们可以改一个地方 改哪里,你觉得我们改哪里可以让性能稍微变好一点,改我们的这个地方对不对。因为它现在性能其实主要是在这个地方受影响的,我们只需要在这个地方将这个阈值改一下,我们改成200 改成200之后再做一个测试,再看一下表现 把这个清掉,然后再单击开始 然后录制,它本身面板的开启也是需要CPU的,所以它开启的时候我们可以明显感到它有一个卡顿,好,停止,现在这个测试我们看一下跟我们刚才的操作相比,明显可以感到我这个界面没有刚才卡顿了对不对,比刚才好了很多,这个地方已经没有红三角了,红三角的显示与刚才相比已经少了很多,看我们刚才这些东西已经没有了对不对,我们可以看到里面的方法,这个地方这有一个 很多都是匿名函数的一个回调,我们再看这个地方有setData对不对,setData大概是浪费了6.98毫秒,它整体上这个时间 我们渲染这个时间大概是消耗了到36.40,刚才我们看到大概是60毫秒,现在我们把它 间隔100改成200以后,消耗大概是变到了36毫秒,基本上效率提升了一半。

这和我们的预想也是一致的,因为我们将本身这个地方将100改成200的时候,从理论上来讲它这个性能应该会提升100对吧,这个从侧面也说明这个地方它其实就是影响我们性能的主要的一个关键点。从理论上讲也是因为我们所有的这些其他的一个代码,它都是这个wxs代码对吧,只有这一个地方我们是用到了JS的逻辑层计算这个结果,然后要通过这样一种方式传给JS逻辑层,由JS逻辑层再去更新这样视图里面的数据,本质上我们这种优化把这个组件变成wxs这样的一个版本,变了以后 其实只有我们上面的这些计算代码是用新的脚本语言,就是wxs脚本去执行的,而渲染数据的传递还是原来的那种方式并且它还绕了一个弯对不对,所以从这两种组件的表现来看,我们新的组件它在渲染的时候

它性能比原来的组件会稍微好那么一点点,但是好的也不算太多,因为本质上它还是受限于callMethod以及setData这个方法的一个限制,它有限制在这个地方,我们这个演示就说到这里。

最后我们总结一下,小程序的视图更新有重渲染机制,当这个逻辑层代码通过setData方法

改变视图数据的时候它会触发新的wxml节点树的一个生成以及新旧节点树的一个差异比较将数据密集更新的功能组件化可以显著提升我们视图渲染的运行时效率。此外wxs脚本,它由于不需要底层的一个中转,也就是通过evalulateJavaScript方法的一个帮助,它可以绕过逻辑层直接操作这个视图层的一个组件,使用它辅助完成这个组件的一个开发也可以大大提升我们这个视图渲染的一个效率,在wxs脚本与逻辑层需要相互调用的地方我们也有办法进行处理 我们举个例子,例如在wxs脚本里面可以通过ownerInstance的callMethod方法去调用这个页面上的方法。例如刚才我们看到的对setData这个方法的一个调用反过来逻辑层怎么样去调用wxs脚本里面的方法,我们可以通过视图层上绑定一个名称,为changexxx这样的一个特别属性,触发对wxs脚本的里面的方法的一个调用。

这节课我们涉及到的文档如屏幕上所示,这节课我们就讲到这里。


这节课我们主要学习了如何将数据更新频繁的功能区域进行组件化以及如何使用wxs脚本辅助完成组件的一个自定义以提升运行时的渲染性能。

下节课我们学习代码按需注入与初始渲染缓存,这里有个问题请你思考一下:在传统动态的网页开发里面,可以将HTML这个页面在这个服务器端缓存下来甚至写成静态的HTML文件以此来加快下一次用户访问,同一个页面的加载速度在这个小程序开发里面有类似的技术也可以实现这样的一个效果,你知道是哪个技术吗?

下节课我们一起来深入探讨一下这个问题。


点击查看开放文档:

最后一次编辑于  2022-07-13
赞 5
收藏

4 个评论

  • 汪继涛
    汪继涛
    2022-07-26

    讲的很好,给大佬点赞

    2022-07-26
    赞同 3
    回复
  • 七分
    七分
    2022-06-08

    厉害

    2022-06-08
    赞同 3
    回复
  • 🤠
    🤠
    2022-08-01

    其实这么看,感觉这一节和上一节的方法几乎没区别啊,这一节主要是做了节流所以看起来性能好一点,但是如果把节流放在上一节,也会有一样的效果。

    2022-08-01
    赞同 2
    回复 1
    • D10NG
      D10NG
      2022-08-22
      我还以为有绕过setData的方法
      2022-08-22
      回复
  • Zhao ZW
    Zhao ZW
    05-03

    总结:

    wxml->js(逻辑)->setData渲染 “这样渲染效率不行”

    wxml->wxs(逻辑,100毫秒发送一次)->js层->setData渲染 在性能面板中找到运行较快的一段说: “看效率提高啦”。

    自欺欺人!

    05-03
    赞同
    回复
登录 后发表内容

小程序性能优化实践

课程标签