童鞋们好,国庆节已经结束。大家是否已经把学习与工作模式完全切回来了呢?
欢迎关注公众号:大前端早读
之前一篇文章我们了解了一下什么是快应用,现在我们带大家来看看快应用开发时,需要做到的一些优化方式。如果对快应用开发并不熟悉的童鞋,可以先去学一学快应用的一般开发方式。
快应用是一种新的应用形态,以往的手机端应用主要有两种方式:网页、原生应用;网页无需安装,却体验不是很好;原生应用体验流畅,却需要从应用商店下载安装,难以一步直达用户;快应用的出现,就是希望能够让用户无需下载安装,并且还能流畅的体验应用内容。
以下主要针对布局组件和js模块的技巧优化进行说明.由于编写快应用时所用到的标签最终由设备从底层渲染为对应的Native组件,所以合理使用布局组件和设计业务代码一定程度上影响着性能和用户体验。
text: 展示文本使用text组件,且子组件只能为a和span,文本溢出等属性均适用内联属性lines等设置.
block: 在遍历数组类型的数据时,最外层容器尽可能的使用block组件,因为它在最终渲染时不会产生对应的Native组件减少冗余的页面结构同时配合for、if、show等指令能更为灵活的进行列表条件渲染.
list: 在开发一些常列表或者屏幕滚动效果时,一般会使用div进行循环便利,然而DOM结构复杂时,滚动页面会出现卡顿现象,因为Native无法复用div组件实现的列表元素,针对需要良好列表滚动体验的情形推荐使用list组件代替div实现场列表布局,Native会复用type属性的list-item(作为复用的自定义字符串标识),设置相同type属性的list-item是优化列表滚动性能的关键.
同时需要注意以下几点:
list-item内不能嵌套list
list-item的type属性为必填属性
list-item内部需谨慎使用if/for指令,因为相同list-item的DOM结构必须完全相同,使用if或for指令会造成DOM结构的差异
当遇到类似xxx cannot be cast to xxx at …list的错误时,需要检查list-item是否存在如下情形:
是否设置了type属性. 解决方案: 设置type属性
内部使用了if指令时. 解决方案:使用show指令替代或设置不同的type属性
设置为相同的type属性时,但DOM结构不同. 解决方案:使用不同的type属性
了解list组件特点后,当DOM结构复杂时为了得到流畅的列表滚动体验,可以从以下几点进行性能优化:
精简DOM层级: 即减少DOM树的层级和分支上的DOM节点数.层级越少、数量越少,布局和渲染就会越快.所以在开发中尽量避免容易的容器组件包裹和层级嵌套.
复用list-item: 即列表中相同的DOM结构设置为同一type属性的list-item,这是优化列表滚动体验的关键.
细粒度划分list-item: 即列表中相同DOM结构划分为尽可能小的列表元素,而非将复杂的结构放在同一个当中.
关闭scrollpage: 此属性默认关闭,标志是否将顶部页面中非list的元素随list一起滚动.开启会降低list的渲染性能,所以在开启的情况下推荐先尝试将顶部页面中非list的元素,作为一种或多种type属性的list-item,移入list中,从而达到关闭scrollpage提高渲染性能的目的.
list-item懒加载: 此懒加载方案跟传统web中类似,即在可视区域内加载部分数据,并提前fetch请求足够的列表数据保存在内存变量中,当list滚动到底部时再从列表变量中提取另一部分数据来渲染列表,且当列表变量中数据不足时检查列表变量中是否具有足够数据,有则直接在列表变量中取否则提前fetch请求数据填充列表变量.
这样达到每次请求与页面渲染的数据量不一致,减少首屏渲染占用的JS执行时间,减少渲染后续list-item的等待时间.
注意: 避免在viewModel的数据属性中定义列表变量,避免数据属性对变量的变化监听而导致触发set/get数据驱动的定义造成不必要的性能消耗.
tabs: 直接使用tabs组件默认会加载所有标签页面的内容,会大致JS线程连续忙于渲染而无法响应用户点击事件等,造成体验困扰.可以让页签内容在用户点击时延迟渲染而不是整个页面初始化时渲染,这项功能可以通过if指令有条件的渲染来达到减少加载时间提升页面性能.
合理使用后台选择器: 过多的使用后台选择器,也会在节点配置上带来性能损耗,尤其是当一个节点满足多个选择时.
优化建议:
避免使用组件名称(tag 标签名称)作为后代选择的最后一项匹配规则,如: .doc-page #shop text { ... };否则每个 text 组件渲染时都会遍历匹配一次
减少后代选择的层级数量,层级越深,单次匹配耗时越长,如:.class1 .class2 .class3 .class4 .class5 .class6 { … }
后代选择中最后一条匹配规则的定义名称尽量唯一,如:.doc-page #shop .shop-item .shop-name-full { ... }
函数共享: 快应用与传统H5页面显著的不同点是快应用多页面共享同一个V8 Context,而H5通常是一个页面一个.页面无法通讯.
跨应用框架中可以在页面ViewModel中通过获取app.ux中导出的数据及方法.
const appDef = this.$app.$def
整合常用JS库到app.ux中并暴露给每个页面使用,可以避免每个页面在打包时对JS的重复定义,减少冗余代码.
简化ViewModel的数据: 对于属性public、protected、private主要承担数据的定义与改造功能,会对赋值的数据中每个属性进行递归式的定义.
因此属性个数的定义越少越好,尤其是数组类型数据,建议过滤不需要用到的对象属性使数据结构尽可能的扁平化.
错误处理: 当对一个值为null或undefined的变量取值就会报错.
解决方案:
1. 使用&&短路逻辑运算的执行顺序来规避错误.
a && a.x && a.x.o
2. 在ViewModel上增加函数方法(推荐).
JSON.parse()解析出错: 在遇到服务器端权限校验失败等问题时,会返回类似503的HTML页面,此时JSON解析肯定就会失败.
解决方案:
1. 在每个JSON.parse()代码执行处进行try-catch包围,处理出错情况
2. 在app.ux中提前使用try-catch包围(推荐)
回调函数中引用ViewModel数据报错:
例如: 用户打开 PageA,然后点击链接打开 PageB,PageB 中执行接口方法(如 fetch 请求),然后立即返回到 PageA;此时接口的回调函数返回,但 PageB 已经出栈销毁,此时,执行开发者传递的回调函数报错 这是由于,回调函数中访问了一些ViewModel的数据等,而这些ViewModel的数据属性已经伴随着页面销毁而删除了,所以引起报错.
报错信息为:undefined:217: TypeError: Cannot read property \'xx\' of null
解决方案:
1. 在回调函数执行之前,通过ViewModel对象的$valid判断页面状态
2. 在Function.prototype上定义方法,关联到每个回调函数绑定ViewModel实例(推荐)
堆栈溢出问题:
通过$element(id)获取到内容并赋值给成员变量,可能会引发堆栈溢出(RangeError: Maximum call stack size exceeded)从而导致程序崩溃,同时成员变量发生变化时,即会引发堆栈溢出报错问题.
这是因为赋值为VM属性,会触发数据驱动变化,导致内部出现异常循环,从而引发堆栈溢出报错.
解决方案: 避免$element(id)获取到的内容直接赋值给成员变量,而是赋值给局部变量或全局变量的形式.
针对页面布局组件的使用来看,尽量减少无用冗余DOM结构和层级嵌套.对于列表和多页面切换展示尽可能的懒加载.
业务模块主要通过良好的结构优化尽可能的代码复用减少页面数量以较少rpk包的体积,同时提升项目可维护性,保证页面渲染性能,减少不必要的代码耦合.