element-ui開源至今已成爲前端在中後臺系統中最爲熱門的ui框架了。html
若是說Vue、React、Angular是前端三劍客,那麼element-ui能夠說在中後臺領域佔據半壁江山,github star數 43k之多。至今,它擁有了84個組件(Version 2.13.0)。前端
前兩行是空的,從第2行開始。vue
需求:因公司業務須要,常常有頁面中的表格須要多選(勾選),而後把勾選到的id組裝拼成字符串提交到後臺。git
解決方案:在element-ui官網看文檔,可以在table組件找到實現多選表格的辦法,在table組件中加一個type爲selection的列就好了。github
<el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
</el-table>
複製代碼
配合selection-change事件,能夠得到用戶選中的row組成的數組。element-ui
效果以下:數組
一切看起來都很完美,可是在實際運用中情況是千奇百出。數據結構
爲何這麼說?由於在公司的實際業務中,表格是分頁表格,每次切換頁碼,數據從新獲取,表格從新渲染,那麼第一個問題來了:用戶在頁碼爲1的表格選中的行,在切換頁碼以後,不見了。框架
分頁表格應該像下面這樣:函數
因而我又去element-ui官網翻看文檔,在table組件中找到了一個方法toggleRowSelection,此方法能夠切換表格中具體哪一行的選中狀態。
經過這個方法,咱們在獲取表格數據以後,立刻用此方法設置以前選中過的數據,這樣不就能夠在用戶切換的時候也把以前選中的行選中狀態渲染出來了嗎?
坑又立刻來了!!
由於經過selection-change事件獲取到了一個名爲selection的數組,裏面包含了用戶選中的行的信息。咱們把這個數組保存在一個變量中,用於用戶切換頁碼以後還能看見以前選中的行,然經過toggleRowSelection方法設置行的選中信息。
selection.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
複製代碼
這乍一看是沒有問題,可是在表格中,它竟然沒有勾選效果!!?
各類一度度娘,說是要在nextTick中去調用這個方法:
this.$nextTick(() => {
selection.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
});
複製代碼
嗯,沒報錯,打開頁面一看,嗯??怎麼仍是沒有選中!!!
內心一w個草尼瑪路過。。。。
在肯定ref名稱是否一致、selection中數據是否存在、調用方法是否觸發以後,我仍舊得不到我想要的結果。
玩個串串。。。
一陣冷靜事後,我決定了,打開element-ui源碼看一看table組件中是如何判斷選中的?
僅僅是table組件部分源碼。
首先看下table組件的結構
結構就是這樣,最外層的index.js用於導出這個table模塊,裏面的代碼也很是簡單,確定能看懂的。
// index.js
import ElTable from './src/table';
/* istanbul ignore next */
ElTable.install = function(Vue) {
Vue.component(ElTable.name, ElTable);
};
export default ElTable;
複製代碼
而後src裏面包含一個store文件夾和一些table的組件:body、column、footer、header、layout等,工具類文件util.js,配置文件config.js,and 一個dropdown(沒懂)、一個layout-observer(從名字上看是監聽layout的)、filter-panel(過濾用的)大概就這樣。
store文件夾裏面的代碼就是實現了一個只用於table組件中各組件數據交換的一個私有的Vuex。
按照個人需求,我只須要看部分關於selection的源碼。因此從佈局上,我能夠先從列從手,也就是table-column.js這文件。
但是看了下table-column.js裏邊確實是關於列的一些內容,可是從字面意思上沒找到selection部分的功能的代碼。
因此我暫且放棄從佈局上找,我直接從方法上找:toggleRowSelection。在這個table文件夾中用搜索大法,直接搜關鍵詞toggleRowSelection,在src/store/watcher.js中能夠找到以下:
// watcher.js 158行
toggleRowSelection(row, selected, emitChange = true) {
const changed = toggleRowStatus(this.states.selection, row, selected);
if (changed) {
const newSelection = (this.states.selection || []).slice();
// 調用 API 修改選中值,不觸發 select 事件
if (emitChange) {
this.table.$emit('select', newSelection, row);
}
this.table.$emit('selection-change', newSelection);
}
}
複製代碼
這個方法就是暴露在外部供咱們調用的,裏面第一行是主要信息,調用toggleRowStatus方法而後獲得changed值,而後把這個值emit出去。大概是這麼個過程,那麼就要從toggleRowStatus着手了。
注意第一行中的 this.states.selection將是後續的關鍵。
直接搜索關鍵詞,能夠找到這個方法是外部導出引用進來的。
import { getKeysMap, getRowIdentity, getColumnById, getColumnByKey, orderBy, toggleRowStatus } from '../util';
複製代碼
打開util.js文件,順利的找到了如下代碼:
export function toggleRowStatus(statusArr, row, newVal) {
let changed = false;
const index = statusArr.indexOf(row);
const included = index !== -1;
const addRow = () => {
statusArr.push(row);
changed = true;
};
const removeRow = () => {
statusArr.splice(index, 1);
changed = true;
};
if (typeof newVal === 'boolean') {
if (newVal && !included) {
addRow();
} else if (!newVal && included) {
removeRow();
}
} else {
if (included) {
removeRow();
} else {
addRow();
}
}
return changed;
}
複製代碼
解讀起來也不是很難,方法名字面意思:切換行的狀態。裏面有兩個方法,一個addRow,一個removeRow,都是字面意思。 主要實現功能:判斷下是不是新的值(newVal),若是不存在(!included)就add,反之remove。主要是index值的獲取,很簡單粗暴,直接用Array.prototype.indexOf去判斷,思考下,緣由是否是在這裏?
Array.prototype.indexOf():方法返回在數組中能夠找到一個給定元素的第一個索引,若是不存在,則返回-1。
可是,我仔細考慮了下,這裏好像並不影響。具體思考下:咱們初始化表格10條數據,此時table組件中用於存放選中row的數組selection這玩意一開始是空的,而後咱們調用toggleRowSelection主動設置被選中的row,這些row都被放進到了table組件中的selection中了(經過這個toggleRowStatus中addRow方法)。
已經放進去,爲何不渲染相應的狀態!!?
既然知道,table組件是經過selection存放被選中的row,那麼,就搜索selection吧。
獲得了78個結果,在7個文件中。獲得的結果太多了,咱們不想要這樣的結果。
而後進一步用全匹配搜索:
獲得了38個結果,在5個文件中。
縮小了一些範圍,可是仍是不少,也沒辦法了。一個一個文件找。
純按照Vscode給我搜出來的順序,第一個文件是config.js文件。
這個關鍵詞在config.js文件中出現了4次,能夠看到前面兩次匹配結果是一個樣式,並非咱們要的東西。
後面兩次就很值得看了。
// config.js 29行
// 這些選項不該該被覆蓋
export const cellForced = {
selection: {
renderHeader: function(h, { store }) {
return <el-checkbox
disabled={ store.states.data && store.states.data.length === 0 }
indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
nativeOn-click={ this.toggleAllSelection }
value={ this.isAllSelected } />;
},
renderCell: function(h, { row, column, store, $index }) {
return <el-checkbox
nativeOn-click={ (event) => event.stopPropagation() }
value={ store.isSelected(row) }
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
},
sortable: false,
resizable: false
}
// ...省略
}
複製代碼
只貼出有用的,從大的耳朵看,導出了一個模塊叫cellForced,雖然我不知道什麼意思。(四級沒過,砸砸輝)。 可是裏面兩個函數我可看懂了,看到了render關鍵詞,這不就是渲染的意思嘛,再往裏一看,媽呀,幸福!!裏面竟然有el-checkbox這個組件,這不就是多選模式下那一列嗎?(除此以外在table中別的地方不可能放玩意!)。
其實只有第四個關鍵詞出現的位置,在第34行纔是咱們想要的selection這玩意。
indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
分析一下: store.states.selection: 我纔是裏面裝有被選中row的數組集合。
其實接着看搜索結果第三個文件:watcher.js中,很明顯能找到它:
而且在第五個文件:table.vue中使用了mapStates去映射selection,也能夠找到它的影子:
而後這兩個文件不用管了,由於咱們找到了佈局的位置,回到config.js中:
// config.js 29行
// 這些選項不該該被覆蓋
export const cellForced = {
selection: {
renderHeader: function(h, { store }) {
return <el-checkbox
disabled={ store.states.data && store.states.data.length === 0 }
indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
nativeOn-click={ this.toggleAllSelection }
value={ this.isAllSelected } />;
},
renderCell: function(h, { row, column, store, $index }) {
return <el-checkbox
nativeOn-click={ (event) => event.stopPropagation() }
value={ store.isSelected(row) }
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
},
sortable: false,
resizable: false
}
// ...省略
}
複製代碼
一共使用了兩個渲染函數,一個渲染頭部,一個渲染格子,經過el-checkbox組件的屬性值咱們能夠判斷出在41行中:
value={ store.isSelected(row) }
複製代碼
這一行纔是渲染選中與否的關鍵所在。裏面邏輯簡單,就調用了一個方法名叫:isSelected,還告訴了咱們是store中的方法。
ok,找到store文件夾,搜索一下isSelected關鍵詞,在watcher.js中,咱們找到了它:
// watcher.js 120行
// 選擇
isSelected(row) {
const { selection = [] } = this.states;
return selection.indexOf(row) > -1;
},
複製代碼
裏邊的邏輯更是簡單的一匹,取出selection這個存有選中row的數組,而後返回row在selection中的位置是否大於-1。聯繫渲染函數中的內容,返回值爲true就渲染選中,反之不選中;
這裏十分的致命,爲何這麼說?
由於selection中確實存放了經過toggleRowSelection設置進來的row。可是在isSelected中形參row是從table組件中的props中的data傳遞過來的。
data又是從新請求接口得到的,因此在data中的row和selection中存放的row,它不是一個row。
這句話聽上去怎麼這麼繞,回到最基礎的,row是一個對象,它是一個引用類型,只要引用的地址不同,那麼你就不是你了。
雖然在數據結構和內容上,這兩個row都同樣,假設都是如下的玩意:
const row1 = { name: '1', id: 0, code: 110110, area: '北京市', street: '二環' }
const row2 = { name: '1', id: 0, code: 110110, area: '北京市', street: '二環' }
複製代碼
row1和row2他喵的不相等。
可是根據咱們的實際業務,row1和row2結構同樣,id同樣,這兩個玩意就是一個東西。
舉個更實際的例子:你二舅在村裏的瓦房裏出來,你認出來了是你二舅;你在北京東二環的某個小區裏看見你二舅從某個單元出來,你二舅他喵的不是你的二舅了。這太扯了!!
因此,意思就是說在table組件中源碼渲染的時候的判斷,太簡單了,沒有更深的判斷,只比較引用地址是否相同。
找到問題的根本所在,解決起來也是至關的容易。
以上辦法,1,2,3,4我都實現了,根據具體業務需求而變化。
深度比對,我也只是實現了一層。個人思路,首先對比key值數量,而後判斷你二舅的key給你大舅,這屬性是否存在,裏面的值是否相等。由於個人業務數據只有一層的屬性值。
後續更新,將放上解決方案的代碼,但願可以對你們有所幫助。