Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

小程序setData解析&优化方案&坑 #1

Open
hustdhq opened this issue Jan 23, 2018 · 0 comments
Open

小程序setData解析&优化方案&坑 #1

hustdhq opened this issue Jan 23, 2018 · 0 comments

Comments

@hustdhq
Copy link
Owner

hustdhq commented Jan 23, 2018

一、工作原理

小程序采用了mvvm框架,但与react、vue不同的是,小程序的vm层并不会为我们自动更新视图,而是需要我们手动调用setData方法来通知view层进行更新。

以下是官方描述的小程序mvvm框架工作原理:

小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

总结起来就是以下两点:

  • 小程序view层和js逻辑运行在不同的环境中,无法直接共享数据,需要通过桥协议进行通信(例如开发者手动调用setData、触发事件等)
  • 数据到达视图层并不是实时的

这样做的优点在于,为小程序提供了多个view对应同一个model的能力(类似于SPA),同时逻辑层不会阻碍view层的渲染,提升用户体验。

缺点也很明显:

  • view层和model层通信效率较低
  • view层无法直接调用业务js逻辑,需要用事件触发
  • js无法直接控制DOM

微信小程序团队后续推出了wxs,即运行在WebView层的js,用于提升view和model之间的通信效率。目前我对wxs了解不深,今后可能会对其进行调研

二、优化

setData方法是小程序提供的model层与view层通信的方法之一,也是我们开发小程序时最常用的一种

针对上述通信效率较低的缺点,微信提供了对setData的优化建议:

常见的 setData 操作错误

1. 频繁的去 setData

在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:
Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;
渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;

2. 每次 setData 都传递大量新数据

由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程,

3. 后台态页面进行 setData

当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。

总而言之两句话:次数越少越好、体积越小越好

三、实际场景

但在实际的开发过程中,我们不可避免地会遇到setData的性能问题,以下是我遇到的一些实际场景,以及对应的解决方案

3.1 设置数组/对象中动态元素的值

setData的基础用法很简单

this.setData({
    foo: 'value'
})

如果此时foo是个数组/对象,而我只想修改其中一个元素,也很简单

// bad
foo[5] = 'value5'
bar.name = 'Jack'
this.setData({
    foo: this.data.foo,
    bar: this.data.bar
})
  
// good
this.setData({
    'foo[5]': 'value5',
    'bar.name': 'Jack'
})

如果我们需要给下标为变量的元素赋值,可以利用ES6语法中的属性名表达式:

let foo = 'name'
  
// bad
bar[foo] = 'Jack'
this.setData({
    bar: this.data.bar
})
  
// error,在小程序中这样做会报错
this.setData({
    bar[`${foo}`]: 'Jack'
})
  
//good
let _name = `bar.${foo}`
this.setData({
    [name]: 'Jack'
})
  
//error,注意取对象的属性时要用“.”,用中括号会报错(可能是小程序的bug)
let _name = `bar['${foo}']`
this.setData({
    [name]: 'Jack'
})

总之,我们尽量只更新需要修改的元素,而不要对整个数组/对象进行更新。因为即使小程序内部对setData有优化机制,不会重复渲染已有的视图,但也会把资源消耗在新旧数据的diff过程上

3.2 对数组、对象进行合并(多见于分页场景)

举个栗子,列表页中每一页有20件商品,用户下拉到页面底部时,会去后端请求下一页的20件商品,并push到当前列表中

我们仍然需要秉持之前的原则:尽量只更新需要修改的元素,而不要对整个数组/对象进行更新。方案如下:

数组:

let newData = [1,2,3,4,5]
  
// bad
this.setData({
    oldData: this.data.oldData.concat(newData)
})
  
// good
let _obj = {}
for(let i = 0; i < newData.length; i ++) {
    _obj[`oldData[${i + this.data.oldData.length}]`] = newData[i]
}
this.setData(_obj)

对象:

let newData = {a: 1, b: 2, c: 3}
  
// bad
this.setData({
    oldData: Object.assign(this.data.oldData, newData)
})
  
// good
let _obj = {}
for(let key in newData) {
    _obj[`oldData.${key}`] = newData.key
}
this.setData(_obj)

以上优化方案可以封装成公用方法

四、坑

4.1 每次setData的数据不能超过1M

当setData的数据大小超过1M时,会报如下错误:
image

解决方案:数据量超过1M的情况还是比较少的。我们需要在设计接口时提前考虑该因素,对有可能超出大小限制的数据进行分拆。

4.2 setData的回调失效

要注意,setData对model层的数据更新是同步的,但setData到view更新的这段过程是异步的。

如果我们需要在视图更新完成后做一些操作,就需要用到setData的callback,例如:

this.setData({
    foo: bar
}, () => {
    console.log('视图已更新完成!')
})

但如果页面引入了自定义组件时,该回调函数不会被执行。这是小程序目前已知的一个bug,在基础库1.6.4中复现。目前为止没有看到微信官方正面回复何时修复该bug。建议暂时不要使用该回调函数。

五、参考文档

微信官方setData优化建议

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant