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

在渲染时使用新的diff算法来减少Vue组件更新引起的Page.setData的实际更新量,达到提升页面性能的目的 #1165

Merged
merged 5 commits into from
Nov 12, 2018

Conversation

sexdevil
Copy link
Contributor

@sexdevil sexdevil commented Nov 6, 2018

  • 1、core/observer的set方法中,监听对象更新值时,用__keyPath:{key:true}方式把更新的key值绑定在vm数据对象上
  • 2、每次真实调用setData前,用mp/runtime/diffData.js进行检查优化大小,规则有
    • A.第一次vm渲染到小程序上,全量更新,并记下标志
    • B.第二次及以后再更新,检查_data对象,如果有__keyPath,跟据__keyPath更新,没有 __keyPath表示没有更新,不放到Page.setData
      • B1._data遇到Object类型,检查Object类型__keyPath,存在的话深度遍历所有属性递归检查更新,不存在__keyPath可能是this.obj = {}重新写对象值造成set没捕捉到,使用脏检查和页面数据对比更新
      • B2._data遇到Array类型,直接脏检查,避免数组不能记录__keyPath造成丢失
      • B3._props属性由于父组件传入不走set,所以遍历_props,值类型直接更新,对象类型深度遍历进入步骤B1,数组进入B2
      • B4.遍历上述后最终更新数据再加入_computedWatchers,_mpProps
      • B5.组件树上的节点都完成更新后调用Vue.nextTick钩子,清理所有对象上的__keyPath 为下次检查更新做准备
    • C.diff优化好data后,如果在Vue.config.devtool == true时打印500ms内的更新量方便使用者检查页面的更新情况
    • D.diff好的JSON中,删掉原来的$root.0={},只把必要的更新给Page.setData使用
  • 3、使用效果:减少了只改动一个根节点属性却造成整个组件树的数据都传递给 Page.setData 的情况,从而减少因为setData量大引起的真机尤其安卓卡顿的情况

What kind of change does this PR introduce? (check at least one)

  • Bugfix
  • Feature
  • Code style update
  • Refactor
  • Build-related changes
  • Other, please describe:

Does this PR introduce a breaking change? (check one)

  • Yes
  • No

If yes, please describe the impact and migration path for existing applications:

The PR fulfills these requirements:

  • It's submitted to the dev branch for v2.x (or to a previous version branch), not the master branch
  • When resolving a specific issue, it's referenced in the PR's title (e.g. fix #xxx[,#xxx], where "xxx" is the issue number)
  • All tests are passing
  • New/updated tests are included

If adding a new feature, the PR's description includes:

  • A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

Other information:

1、core/observer的set方法中,监听对象更新值时,用__keyPath:{key:true}方式把更新的key值绑定在vm数据对象上
2、每次真实调用setData前,用mp/runtime/diffData.js进行检查优化大小,规则有
   A.第一次vm渲染到小程序上,全量更新,并记下标志
   B.第二次及以后再更新,检查_data对象,如果有__keyPath,跟据__keyPath更新,没有
   __keyPath表示没有更新,不放到Page.setData
   B1._data遇到Object类型,检查Object类型__keyPath,存在的话深度遍历所有属性
   递归检查更新,不存在__keyPath可能是this.obj = {}重新写对象值造成set没捕捉到,
   使用脏检查和页面数据对比更新
   B2._data遇到Array类型,直接脏检查,避免数组不能记录__keyPath造成丢失
   B3._props属性由于父组件传入不走set,所以遍历_props,值类型直接更新,对象类型
   深度遍历进入步骤B1,数组进入B2
   B4.遍历上述后最终更新数据再加入_computedWatchers,_mpProps
   B5.组件树上的节点都完成更新后调用Vue.nextTick钩子,清理所有对象上的__keyPath
   为下次检查更新做准备
   C.diff优化好data后,如果在Vue.config.devtool == true时打印500ms内的更新量
   方便使用者检查页面的更新情况
   D.diff好的JSON中,删掉原来的$root.0={},只把必要的更新给Page.setData使用
3、使用效果:减少了只改动一个根节点属性却造成整个组件树的数据都传递给
   Page.setData的情况,从而减少因为setData量大引起的真机尤其安卓卡顿的情况
@DreamPWJ
Copy link

DreamPWJ commented Nov 7, 2018

希望尽快验证合并 如果mpvue项目组没有太多能力拓展新功能 希望有时间审核一下PR 如果都没有 ....

@zxzhgk
Copy link

zxzhgk commented Nov 7, 2018

急需这个~~

@yejinjian
Copy link

非常给力

@zhangjun050754
Copy link

使用报错。。。。
Setting data field "$root.0,6.text" to undefined is invalid.
console.error @ VM369:1
x @ WAService.js:1
(anonymous) @ WAService.js:1
value @ WAService.js:1
(anonymous) @ index.js:5607
later @ index.js:5578
(anonymous) @ WAService.js:1
(anonymous) @ WAService.js:1
setTimeout (async)
setTimeout @ WAService.js:1
(anonymous) @ index.js:5599
updateDataToMP @ index.js:5636
patch @ index.js:4957
Vue._update @ index.js:2078
updateComponent @ index.js:2186
get @ index.js:2516
Watcher @ index.js:2505
mountComponent @ index.js:2190
(anonymous) @ index.js:5798
initMP @ index.js:5133
global.webpackJsonp.Vue$3.$mount @ index.js:5797
init @ index.js:3053
createComponent @ index.js:4469
createElm @ index.js:4430
createChildren @ index.js:4540
createElm @ index.js:4445
createChildren @ index.js:4540
createElm @ index.js:4445
createChildren @ index.js:4540
createElm @ index.js:4445
patch @ index.js:4876
patch @ index.js:4956
Vue._update @ index.js:2078
updateComponent @ index.js:2186
get @ index.js:2516
Watcher @ index.js:2505
mountComponent @ index.js:2190
(anonymous) @ index.js:5798
onReady @ index.js:5268
(anonymous) @ WAService.js:1
(anonymous) @ WAService.js:1
(anonymous) @ WAService.js:1
(anonymous) @ WAService.js:1
(anonymous) @ WAService.js:1
n @ WAService.js:1
e @ appservice?t=1541771681456:2176
r.registerCallback.t @ appservice?t=1541771681456:2176
l.forEach.t @ appservice?t=1541771681456:2176
(anonymous) @ appservice?t=1541771681456:2176
s.onmessage @ appservice?t=1541771681456:2176
VM369:1 jsEnginScriptError
Converting circular structure to JSON
TypeError: Converting circular structure to JSON

@zhangjun050754
Copy link

报错,麻烦看下什么情况
jsEnginScriptError Converting circular structure to JSON TypeError: Converting circular structure to JSON

@sexdevil
Copy link
Contributor Author

发下引发错误的mpvue页面源码吧

@sexdevil
Copy link
Contributor Author

Setting data field "$root.0,6.text" to undefined is invalid. this.text = undefined会引发小程序错误,框架已更新,对于这种情况不更新视图
jsEnginScriptError Converting circular structure to JSON TypeError: Converting circular structure to JSON 这种情况没有复现 请提供下用例代码

@anchengjian anchengjian changed the base branch from master to develop November 10, 2018 11:10
@sexdevil
Copy link
Contributor Author

jsEnginScriptError Converting circular structure to JSON TypeError: Converting circular structure to JSON 这种情况判定是由于this.a= undefined引发,最新commit已兼容处理

@zhangjun050754
Copy link

zhangjun050754 commented Nov 11, 2018

依然没有解决,应该是使用Vuex 有问题
VM18845:1 jsEnginScriptError
Converting circular structure to JSON
TypeError: Converting circular structure to JSON

<template>
  <div class="counter-warp">
    <p>Vuex counter:{{ count }}</p>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
    <a href="/pages/index/main" class="home">去往首页</a>
  </div>
</template>

<script>
// Use Vuex
import store from './store'

export default {
  computed: {
    count () {
      return store.state.count
    }
  },
  methods: {
    increment () {
      store.commit('increment')
    },
    decrement () {
      store.commit('decrement')
    }
  }
}
</script>
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: (state) => {
      const obj = state
      obj.count += 1
    },
    decrement: (state) => {
      const obj = state
      obj.count -= 1
    }
  }
})

export default store

@sexdevil
Copy link
Contributor Author

VM18845:1 jsEnginScriptError
Converting circular structure to JSON
TypeError: Converting circular structure to JSON
问题已更新解决,__computedWatcher更新时赋值成了watcher对象,应该用watcher['value']传递,否则循环引用,已更新

@sexdevil
Copy link
Contributor Author

Converting circular structure to JSON我这里已fix提交,感谢反馈

@hucq hucq merged commit 68a158d into Meituan-Dianping:develop Nov 12, 2018
@luojinghui
Copy link

这个已经合并了么???什么时候可以拉到最新代码呢?

@zxzhgk
Copy link

zxzhgk commented Nov 16, 2018

尝试了一下,picker 组件还是存在拖动后瞬间回到顶部的问题,是我用的方式不对么, 和#639 解决的是同一个问题么

@825553819
Copy link

1、core/observer的set方法中,监听对象更新值时,用__keyPath:{key:true}方式把更新的key值绑定在vm数据对象上
2、每次真实调用setData前,用mp/runtime/diffData.js进行检查优化大小,规则有
A.第一次vm渲染到小程序上,全量更新,并记下标志
B.第二次及以后再更新,检查_data对象,如果有__keyPath,跟据__keyPath更新,没有
__keyPath表示没有更新,不放到Page.setData
B1._data遇到Object类型,检查Object类型__keyPath,存在的话深度遍历所有属性
递归检查更新,不存在__keyPath可能是this.obj = {}重新写对象值造成set没捕捉到,
使用脏检查和页面数据对比更新
B2._data遇到Array类型,直接脏检查,避免数组不能记录__keyPath造成丢失
B3._props属性由于父组件传入不走set,所以遍历_props,值类型直接更新,对象类型
深度遍历进入步骤B1,数组进入B2
B4.遍历上述后最终更新数据再加入_computedWatchers,_mpProps
B5.组件树上的节点都完成更新后调用Vue.nextTick钩子,清理所有对象上的__keyPath
为下次检查更新做准备
C.diff优化好data后,如果在Vue.config.devtool == true时打印500ms内的更新量
方便使用者检查页面的更新情况
D.diff好的JSON中,删掉原来的$root.0={},只把必要的更新给Page.setData使用
3、使用效果:减少了只改动一个根节点属性却造成整个组件树的数据都传递给
Page.setData的情况,从而减少因为setData量大引起的真机尤其安卓卡顿的情况

What kind of change does this PR introduce? (check at least one)

  • Bugfix
  • Feature
  • Code style update
  • Refactor
  • Build-related changes
  • Other, please describe:

Does this PR introduce a breaking change? (check one)

  • Yes
  • No

If yes, please describe the impact and migration path for existing applications:

The PR fulfills these requirements:

  • It's submitted to the dev branch for v2.x (or to a previous version branch), not the master branch
  • When resolving a specific issue, it's referenced in the PR's title (e.g. fix #xxx[,#xxx], where "xxx" is the issue number)
  • All tests are passing
  • New/updated tests are included

If adding a new feature, the PR's description includes:

  • A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

Other information:
我拉取完最新代码之后,试了一下发现一个新问题。 比如, 在data里面初始化一个数据 如arr=[1,2,3],
页面渲染这个数组。 通过事件更新数组或者加入新的元素,页面数据不会更新。调试mpvue源码之后发现传入的page.setData 的数据没有出现修改的那个数组。最后在compareAndSetDeepData 方法里面发现 更新数据是通过代码 oldData === null || JSON.stringify(oldData) !== JSON.stringify(newData) 来判断。如果数据是引用类型 那么在我更新完数据之后 vm.$root.$mp.page.data 里面的数据也会改变 ,那么在检查是否需要setData的时候 oldData 和newData 就会永远相同, 因此新修改的数据就会不符合更新的要求。 这些仅是我个人分析所得,也不知道说的是否正确,希望mpvue的作者能试一下我说的情况。

@sexdevil
Copy link
Contributor Author

sexdevil commented Dec 2, 2018 via email

@825553819
Copy link

怎么更新的?arr.push(1)? ----- 原始邮件 ----- 发件人:825553819 notifications@github.com 收件人:Meituan-Dianping/mpvue mpvue@noreply.github.com 抄送人:toukang super00778@sina.com, Author author@noreply.github.com 主题:Re:_[Meituan-Dianping/mpvue]在渲染时使用新的diff算法来减少Vue组件更新引起的Page.setData的实际更新量,达到提升页面性能的目的(#1165) 日期:2018年12月01日 17点02分 1、core/observer的set方法中,监听对象更新值时,用__keyPath:{key:true}方式把更新的key值绑定在vm数据对象上 2、每次真实调用setData前,用mp/runtime/diffData.js进行检查优化大小,规则有 A.第一次vm渲染到小程序上,全量更新,并记下标志 B.第二次及以后再更新,检查_data对象,如果有__keyPath,跟据__keyPath更新,没有

__keyPath表示没有更新,不放到Page.setData B1._data遇到Object类型,检查Object类型__keyPath,存在的话深度遍历所有属性 递归检查更新,不存在__keyPath可能是this.obj = {}重新写对象值造成set没捕捉到, 使用脏检查和页面数据对比更新 B2._data遇到Array类型,直接脏检查,避免数组不能记录__keyPath造成丢失 B3._props属性由于父组件传入不走set,所以遍历_props,值类型直接更新,对象类型 深度遍历进入步骤B1,数组进入B2 B4.遍历上述后最终更新数据再加入_computedWatchers,_mpProps B5.组件树上的节点都完成更新后调用Vue.nextTick钩子,清理所有对象上的__keyPath 为下次检查更新做准备 C.diff优化好data后,如果在Vue.config.devtool == true时打印500ms内的更新量 方便使用者检查页面的更新情况 D.diff好的JSON中,删掉原来的$root.0={},只把必要的更新给Page.setData使用 3、使用效果:减少了只改动一个根节点属性却造成整个组件树的数据都传递给 Page.setData的情况,从而减少因为setData量大引起的真机尤其安卓卡顿的情况 What kind of change does this PR introduce? (check at least one) Bugfix Feature Code style update Refactor Build-related changes Other, please describe: Does this PR introduce a breaking change? (check one) Yes No If yes, please describe the impact and migration path for existing applications: The PR fulfills these requirements: It's submitted to the dev branch for v2.x (or to a previous version branch), not the master branch When resolving a specific issue, it's referenced in the PR's title (e.g. fix #xxx[,#xxx], where "xxx" is the issue number) All tests are passing New/updated tests are included If adding a new feature, the PR's description includes: A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it) Other information: 我拉取完最新代码之后,试了一下发现一个新问题。 比如, 在data里面初始化一个数据 如arr=[1,2,3], 页面渲染这个数组。 通过事件更新数组或者加入新的元素,页面数据不会更新。调试mpvue源码之后发现传入的page.setData 的数据没有出现修改的那个数组。最后在compareAndSetDeepData 方法里面发现 更新数据是通过代码 oldData === null || JSON.stringify(oldData) !== JSON.stringify(newData) 来判断。如果数据是引用类型 那么在我更新完数据之后 vm.$root.$mp.page.data 里面的数据也会改变 ,那么在检查是否需要setData的时候 oldData 和newData 就会永远相同, 因此新修改的数据就会不符合更新的要求。 这些仅是我个人分析所得,也不知道说的是否正确,希望mpvue的作者能试一下我说的情况。 — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

下面是我的测试代码


{{item}}

<input type="button" value="add1" @click="add"/>
<input type="button" value="update2" @click="update"/>

<script> /*先点击add 在点击更新按钮 更新的数据没有显示在页面上 或者先更新 在add 新的数据也没有渲染到页面上 */ export default { data(){ return { arr:[1,2,3,4] } }, methods:{ add(){ this.arr.push(5); console.log(this.arr) }, update(){ this.$set(this.arr, 2, 99); console.log(this.arr) } } } </script>

mpvue 引入是develope 分支中的 packages/mpvue/index.js

@825553819
Copy link

代码没有贴全 在来一次
<template>
<div>
<div v-for="(item, index) in arr" :key="index">{{item}}</div>
<input type="button" value="add1" @click="add"/>
<input type="button" value="update2" @click="update"/>
</div>

</template>
<script>
/*先点击add 在点击更新按钮 更新的数据没有显示在页面上 或者先更新 在add 新的数据也没有渲染到页面上 */
export default {
data(){
return {
arr:[1,2,3,4]
}
},
methods:{
add(){
this.arr.push(5);
console.log(this.arr)
},
update(){
this.$set(this.arr, 2, 99);
console.log(this.arr)
}
}
}
</script>

@sexdevil
Copy link
Contributor Author

sexdevil commented Dec 3, 2018

感谢反馈~之前

代码没有贴全 在来一次
<template>
<div>
<div v-for="(item, index) in arr" :key="index">{{item}}</div>
<input type="button" value="add1" @click="add"/>
<input type="button" value="update2" @click="update"/>
</div>

</template>
<script>
/*先点击add 在点击更新按钮 更新的数据没有显示在页面上 或者先更新 在add 新的数据也没有渲染到页面上 */
export default {
data(){
return {
arr:[1,2,3,4]
}
},
methods:{
add(){
this.arr.push(5);
console.log(this.arr)
},
update(){
this.$set(this.arr, 2, 99);
console.log(this.arr)
}
}
}
</script>

感谢反馈~之前测试用例没有写直接操作this上的数组变量�,问题定位很专业 👍 ,个人分支已更新成数组强制更新视图了,https://github.com/sexdevil/mpvue ,测试OK后我们会合并dev分支

@825553819
Copy link

感谢反馈~之前

代码没有贴全 在来一次
<template>
<div>
<div v-for="(item, index) in arr" :key="index">{{item}}</div>
<input type="button" value="add1" @click="add"/>
<input type="button" value="update2" @click="update"/>
</div>
</template>
<script>
/*先点击add 在点击更新按钮 更新的数据没有显示在页面上 或者先更新 在add 新的数据也没有渲染到页面上 */
export default {
data(){
return {
arr:[1,2,3,4]
}
},
methods:{
add(){
this.arr.push(5);
console.log(this.arr)
},
update(){
this.$set(this.arr, 2, 99);
console.log(this.arr)
}
}
}
</script>

感谢反馈~之前测试用例没有写直接操作this上的数组变量�,问题定位很专业 ,个人分支已更新成数组强制更新视图了,https://github.com/sexdevil/mpvue ,测试OK后我们会合并dev分支

按照您的修改我更新的代码,上面操作数组数据的问题解决了。个人感觉如果数组强制更新,不知道会不会带来较多额外的性能消耗。测试发现修改其他定义的变量(跟上面的arr是平级变量,比如 name: ‘aa’, 将name改成其他值)数组arr也会被传入到setDate方法里面。如果页面中arr中的数据结构复杂且数据较多或者页面中有多个数组的变量,不需要更新时也传入到setDate方法里面,性能上不知道会怎样,尤其是在页面滚动事件中修改变量的情况。

@sexdevil
Copy link
Contributor Author

sexdevil commented Dec 4, 2018

这个得后续再找其他方法优化了 目前就是数组这里和之前版本一样性能。影响的大头还是组件树整体更新的问题,这次feature先解决这个

@qgd1987
Copy link

qgd1987 commented Dec 14, 2018

TypeError: Cannot read property 'dep' of undefined
at minifyDeepData (index.js:5408)
at index.js:5467
at Array.forEach ()
at diffData (index.js:5463)
at VueComponent.updateDataToMP [as $updateDataToMP] (index.js:5642)
at VueComponent.patch [as patch] (index.js:4960)
at VueComponent.Vue._update (index.js:2090)
at VueComponent.updateComponent (index.js:2188)
at Watcher.get (index.js:2518)
at Watcher.run (index.js:2596) "$root.0,0-2,0-1" "match" {match_id: "335725", match_name: "塞尔超", league_id: "198", show_flag: 1, match_type: "4", …} {$root.0,0-2,0-1: {…}, $root.0,0-2,0-1.match.is_follow: 1}

为什么会报这种错误

@sexdevil
Copy link
Contributor Author

请贴上复现的代码

@bravelincy
Copy link

Setting data field "$root.0,1-4.studioId" to undefined is invalid.

某个计算属性的值是undefined时,就会提示这个错误,代码如下便可复现:

computed: {
  studioId () {
    return undefined // 某个表达式被计算出来undefined
  }
}

@sexdevil
Copy link
Contributor Author

sexdevil commented Dec 29, 2018 via email

@sexdevil
Copy link
Contributor Author

Setting data field "$root.0,1-4.studioId" to undefined is invalid.

某个计算属性的值是undefined时,就会提示这个错误,代码如下便可复现:

computed: {
  studioId () {
    return undefined // 某个表达式被计算出来undefined
  }
}

正常,computed现在会被翻译成 this.setData({$root.0,1-4.studioId:undefined});
这种写法腾讯会认为非法,文档有写。真机现象就是不渲染不处理。
如果框架兜底写成this.setData({$root.0,1-4.studioId:''})的话会忽略报警,让开发找不到问题,正确方式就是不要传undefined值,业务bug

@bravelincy
Copy link

Setting data field "$root.0,1-4.studioId" to undefined is invalid.

某个计算属性的值是undefined时,就会提示这个错误,代码如下便可复现:

computed: {
  studioId () {
    return undefined // 某个表达式被计算出来undefined
  }
}

正常,computed现在会被翻译成 this.setData({$root.0,1-4.studioId:undefined});
这种写法腾讯会认为非法,文档有写。真机现象就是不渲染不处理。
如果框架兜底写成this.setData({$root.0,1-4.studioId:''})的话会忽略报警,让开发找不到问题,正确方式就是不要传undefined值,业务bug

但是之前的mpvue版本没有这个问题啊。

@qfgyd2004
Copy link

diff没有任何改善,前后更新的数据量是一样的

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

Successfully merging this pull request may close these issues.