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

对于设置了唯一key的组件, 更新数据后会把旧数据重新更新一遍 #1642

Open
hereisfun opened this issue Jun 19, 2019 · 1 comment

Comments

@hereisfun
Copy link

[扼要问题描述]

mpvue 版本号:

[mpvue@2.0.5]

最小化复现代码:

[建议提供最小化可运行的代码:附件或文本代码]

index.vue

<template>
  <view @click="add" key='container'>
    <t-text :content="'t-text count: ' + count" :key="count" />
    <text>\n real count: {{count}}</text>
  </view>
</template>

<script>
import TText from "./t-text"
export default {
  components: {
    TText,
  },
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    add() {
      this.count++
    },
  }
}
</script>

t-text.vue:

<template>
  <text>{{content}}</text>
</template>

<script>
export default {
  props: ['content']
}
</script>

问题复现步骤:

  1. 点击view

观察到的表现:

  1. 第一行(组件)的数字先从0变成1,再瞬间变回0
  2. 第二行(原生text)的数据从0变成1
@hereisfun
Copy link
Author

经排查,推测是mpvue源码的问题

下面的代码,表示patch的时候会调用一遍小程序的setData

function patch () {
  corePatch.apply(this, arguments);
  this.$updateDataToMP();
}

// install platform patch function
Vue$3.prototype.__patch__ = patch;

而vue在销毁组件vnode时,也是调用__patch__来销毁的:

// call the last hook...
vm._isDestroyed = true;
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, 'destroyed');

也就是说,如果触发了组件的销毁,而原本该位置的组件节点仍然存在时,会错误的调用一次$updateDataToMP, 将旧数据塞回去。

举例说明:

页面中有A, B, 更新后变成只有C(A, B, C都是同一个组件,key值唯一)
A, B -> C
此时因为key不同,sameVnode的判定为false,在updateChildren时会首先生成C, 然后destroy A, B
也就是:
create C: this.setData({$root.0_0_0: C})
destroy A和B: this.setData({$root.0_0_0: A, $root.0_0_1: B})
因为0_0_0位置上的元素还在,所以旧数据就被重新更新上去了。

规避方法:

  1. 免改源码方法:
    不使用唯一key。你可以不用key或者用索引作为key。这样updateChildren时就会走patchVnode分支,不用destroy组件节点。
    尽管vue官方推荐使用唯一key,但不使用key时,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法来更新节点。另外,因为小程序没有DOM操作,节点的更新本质也是通过setData来做的,所以不用key应该也不会带来太多性能问题

  2. 改源码:
    思路就是在destroy的时候不要调用updateDataToMP, 观察发现被销毁的vm会被标记为vm._isDestroyed = true, 因此在updateDataToMP中加上条件判断即可:

function updateDataToMP () {
  var page = getPage(this);
  if (!page) {
    return
  }

  // 对于被销毁的节点,不更新data
  if (this._isDestroyed) {
    return
  }

  var data = formatVmData(this);
  diffData(this, data);
  throttleSetData(page.setData.bind(page), data);
}

该修改已提PR: #1643

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