記錄一次升級ant-design-vue的遇見的bug
使用版本:
"version": "2.5.2"
"ant-design-vue": "1.4.2",
vue模板內容
<template>
<div>
<a-table :columns="columns" :dataSource="data" :rowSelection="rowSelection" :locale="{emptyText:'sdfsd'}"/>
</div>
</template>
<script>
export default {
data() {
return {
data:[ {
key: 1,
address: 'New York No. 1 Lake Park',
}] ,
columns: [
{
title: 'Address',
dataIndex: 'address',
width: '100%',
key: 'address',
},
],
rowSelection:{
onChange: () => {},
onSelect: () => {},
onSelectAll: () => {},
}
};
},
};
</script>
chrome控制台顯示如下:

打開Sources看到是_traverse方法報錯
function _traverse (val, seen) {
var i, keys;
var isA = Array.isArray(val);
if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {
return
}
if (val.__ob__) {
var depId = val.__ob__.dep.id;
if (seen.has(depId)) {
return
}
seen.add(depId);
}
if (isA) {
i = val.length;
while (i--) { _traverse(val[i], seen); }
} else {
keys = Object.keys(val);
i = keys.length;
while (i--) { _traverse(val[keys[i]], seen); }
}
}
該方法存在於vue項目src/core/observer/traverse.js
中,
traverse 的邏輯也很簡單,它實際上就是對一個對象做深層遞歸遍歷,因為遍歷過程中就是對一個子對象的訪問,會觸發它們的 getter 過程,這樣就可以收集到依賴,也就是訂閱它們變化的 watcher
下面的代碼就會觸發traverse:
watch: {
a: {
deep: true, // deep屬性為true是關鍵
handler(newVal) {
console.log(newVal)
}
}
}
在 watcher 執行 get 求值的過程中有一段邏輯:
get() {
let value = this.getter.call(vm, vm)
// ...
if (this.deep) {
traverse(value)
}
}
在traverse中打斷點,打印出遞歸的traverse的參數name,發現異常:

如果深度遍歷一個vnode節點,每一個vnode都保存有父節點和子節點的引用,遍歷所有的vnode確實會導致棧溢出,但是怎么會遍歷到一個vnode節點呢?
再看前面遍歷到的一個對象,在ant-design-vue文檔中查到是渲染rowSelection用到的,於是在ant-desing-vue的源碼查到了 renderRowSelection
方法:
renderRowSelection: function renderRowSelection(prefixCls, locale) {
......
selectionColumn.title = selectionColumn.title
|| h(SelectionCheckboxAll, {
attrs: {
store: this.store,
locale: locale,
data: data,
getCheckboxPropsByItem: this.getCheckboxPropsByItem,
getRecordKey: this.getRecordKey,
disabled: checkboxAllDisabled,
prefixCls: prefixCls,
selections: rowSelection.selections,
hideDefaultSelections: rowSelection.hideDefaultSelections,
getPopupContainer: this.generatePopupContainerFunc()
},
on: {
'select': this.handleSelectRow
}
});
......
SelectionCheckboxAll
組件的locale屬性就包含了emptyText,filterReset等,找到了位置,調試一下在renderTable中發現如下代碼:
// locale至少是一個空對象
var locale = _extends({}, contextLocale, this.locale);
if (!locale || !locale.emptyText) {
mergedLocale.emptyText = renderEmpty(h, 'Table');
}
......
var columns = this.renderRowSelection(prefixCls, mergedLocale);
問題在於如果locale
,經過排查international的LocaleReceiver.js,發現locale
中有數據,又不存在emptyText字段,導致emptyText賦值為renderEmpty
方法返回的組件,渲染SelectionCheckboxAll組件的時候,對屬性遍歷就會報棧溢出。
解決方案比較簡單:給a-table組件加一個locale屬性,同時locale的屬性emptyText不為空。
之前的項目中為什么沒有報錯?
經過對比package.json和node_modules中的版本,發現舊項目ant-design-vue的版本是1.2.4,回退回去之后,問題消失了。
官網上為什么能夠正常使用呢?
自己下載了一個瀏覽器版本的vue和antd-vue,測試正常,懷疑是vue版本的問題,對比了traverse方法
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
多了 val instanceof VNode
這句判斷,問題就解決了。
看看vue升級記錄:
fix: do not traverse VNodes when regsitering dependencies
最終的解決方案:升級vue版本到2.6.x.
本文結束