簡潔
最近使用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;
只找到打包後的代碼,有人知道具體源碼在哪個部分可以評論下