简洁
最近使用uniapp开发微信小程序,某一个页面需要做成可配置化,因此会出现直接在父组件修改传递到子组件的prop,但是会出现这种情况,假设新传递的prop对象为newObj,旧传递的prop对象为oldObj,如果Object.keys(newObj).length > Object.keys(oldObj).length,newObj中会带有oldObj的属性,值为null。
例子
father.vue
<template>
<view>
<view>
{{ curCategory }}
</view>
<view>
<childTry :config="config"
@gotoPre="gotoPrePage()"
@gotoNext="gotoNextPage()">
</childTry>
</view>
</view>
</template>
<script>
import childTry from './childTry.vue'
export default {
components: {
childTry
},
data() {
return {
pages: ['tab1', 'tab2', 'tab3'],
curCategory: 'tab1',
//**********//
config: {},
tabConfig1: {
'tabConfig1-1': {
},
},
tabConfig2: {
'tabConfig2-1': {
},
'tabConfig2-2': {
},
'tabConfig2-3': {
},
},
tabConfig3: {
'tabConfig3-1': {
},
'tabConfig3-2': {
},
'tabConfig3-3': {
}
}
}
},
watch: {
curCategory(newVal) {
if(newVal === 'tab1') {
this.config = this.tabConfig1;
console.log('father Config', this.config);
return;
}
if(newVal === 'tab2') {
this.config = this.tabConfig2;
console.log('father Config', this.config);
return;
}
if(newVal === 'tab3') {
this.config = this.tabConfig3;
console.log('father Config', this.config);
return;
}
}
},
computed:{
curPage:function() {
const index = this.pages.indexOf(this.curCategory)
return index + 1
}
},
methods: {
gotoPrePage() {
// this.setLocalStorage(category,answers)
const index = this.pages.indexOf(this.curCategory)
this.curCategory = this.pages[index-1 < 0 ? index : index-1]
},
gotoNextPage() {
const index = this.pages.indexOf(this.curCategory)
this.curCategory = this.pages[index+1]
},
}
}
</script>
childVue
<template>
<view>
<view>
<view class="previous-btn" @click="gotoPre">上一步</view>
<view class="next-btn" @click="submit">下一步</view>
</view>
</view>
</template>
<script>
export default {
name: "try",
props:["config"],
data() {
return {
}
},
watch: {
async category() {
// this.initAns = await this.getAnswers()
// if(this.initAns)
// this.initAnswers()
},
config: {
deep: true,
handler(newVal){
console.log('childNewVal',newVal)
console.log('childOldVal',oldVal)
}
}
},
methods:{
gotoPre() {
// this.formatterSubques()
this.$emit('gotoPre')
},
submit() {
this.answers = [];
// this.formatterQues()
// if(this.formatterSubques()) {
// }
this.$emit('gotoNext')
}
}
}
</script>
步骤
从tab2跳到tab3时
总结:oldvAL的属性跑到newVal的属性中去了。
当我们把父组件的tabConfig3的对象属性的数量修改成一个
tabConfig1: {
'tabConfig1-1': {
},
},
tabConfig2: {
'tabConfig2-1': {
},
'tabConfig2-2': {
},
'tabConfig2-3': {
},
},
tabConfig3: {
'tabConfig3-1': {
},
}
测试结果,当从tab3到tab2时
可以看到,oldVal的属性跑到newVal的属性中去了
原因
当父组件传递子组件的prop为一个对象时,在父组件修改对象,假设新的porp为newObj,旧的prop为oldObj,如果Object.keys(newObj).length > Object.keys(oldObj).length,会将oldObj存在的属性,newObj不存在的属性,赋予newObj。
父组件修改传入子组件的prop,会触发render更新,进而触发pach打补丁,pach代码中有一段是diff函数,会使oldObj的属性附加到newObj的属性中。
导致错误的打包源代码如下
/packages/vue-cli-plugin-uni/packages/mp-vue/dist/mp.runtime.esm.js的文件中
// pach函数中
// data为newData,mpData为oldData,比较新旧data的差异,进行差异更新
var diffData = this.$shouldDiffData === false ? data : diff(data, mpData);
// diff函数
function diff(current, pre) {
var result = {};
syncKeys(current, pre);
_diff(current, pre, '', result);
return result
}
// syncKeys函数,进行递归差异更新
function syncKeys(current, pre) {
if (current === pre) { return }
var rootCurrentType = type(current);
var rootPreType = type(pre);
if (rootCurrentType == OBJECTTYPE && rootPreType == OBJECTTYPE) {
// 错误原因在于此,当新的对象的keys长度大于旧对象的keys长度时,会进行将旧对象属性赋予新对象
if(Object.keys(current).length >= Object.keys(pre).length){
for (var key in pre) {
var currentValue = current[key];
// 如果newObj的keys数量大于oldObj的keys数量,则会将oldObj的属性赋予newObj,并且值为null
if (currentValue === undefined) {
current[key] = null;
} else {
syncKeys(currentValue, pre[key]);
}
}
}
} else if (rootCurrentType == ARRAYTYPE && rootPreType == ARRAYTYPE) {
if (current.length >= pre.length) {
pre.forEach(function (item, index) {
syncKeys(current[index], item);
});
}
}
}
上述打包后的代码中,会将新旧data进行比较,但是不知道为什么要把旧对象的key值添加到新对象中,有点坑。
在组件中设置$shouldDiffData字段为false可以规避这个bug,同时不使用其差异算法
this.$shouldDiffData = false;
只找到打包后的代码,有人知道具体源码在哪个部分可以评论下