序文
日々のプロジェクト開発では、表格跨页多选
要件が発生することがよくありますが、次に、例を使用してel-table
、この要件を段階的に実現していきます。
実践的な開発
オンライン体験
https://codesandbox.io/s/priceless-mcclintock-4cp7x3?file=/src/App.vue
通常版
この部分はいくつかのキーコードを書き込むだけなので、せっかちなYanzuは直接読み取ることができます性能进阶版
- まず、選択した配列を初期化する必要があります
checkedRows
this.checkedRows = []
- 選択をトリガーするときは、現在の行のデータを取得する必要があります
push
。checkedRows
それ以外の場合は、対応する行を削除する必要があります。
<el-table ref="multipleTable" @select="handleSelectChange">
handleSelectChange (val, row) {
const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id)
if (checkedIndex > -1) {
// 选中剔除
this.checkedRows.splice(checkedIndex, 1)
} else {
// 未选中压入
this.checkedRows.push(row)
}
}
- ページ変更実装時のエコーロジック
this.data.forEach(row=>{
const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id)
if(checkedIndex>-1) this.$refs.multipleTable.toggleRowSelection(row,true)
})
エフェクトのプレビュー
この時の効果を見てみましょう
完全なコード
<template>
<div>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@select="handleSelectChange"
@select-all="handleSelectAllChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
label="日期"
width="120"
prop="date"
/>
<el-table-column
prop="name"
label="姓名"
width="120"
/>
</el-table>
<el-pagination
background
:current-page.sync="currentPage"
layout="prev, pager, next"
:total="1000"
@current-change="currentChange"
/>
</div>
</template>
<script>
export default {
data () {
return {
currentPage: 1,
checkedRows: [],
pageSize: 10,
totalData: Array.from({
length: 1000 }, (_, index) => {
return {
date: '2016-05-03',
id: index,
name: '王小虎' + index
}
})
}
},
computed: {
tableData () {
const {
currentPage, totalData, pageSize } = this
return totalData.slice((currentPage - 1) * pageSize, currentPage * pageSize)
}
},
methods: {
currentChange (page) {
this.currentPage = page
this.tableData.forEach(row => {
const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id)
if (checkedIndex > -1) this.$refs.multipleTable.toggleRowSelection(row, true)
})
},
handleSelectChange (val, row) {
const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id)
if (checkedIndex > -1) {
this.checkedRows.splice(checkedIndex, 1)
} else {
this.checkedRows.push(row)
}
},
handleSelectAllChange (val) {
this.tableData.forEach(row => {
this.handleSelectChange(null, row)
})
}
}
}
</script>
パフォーマンスアドバンストエディション
性能欠陥分析
優秀なヤンの祖先、あなたは上記のコードのパフォーマンス上の欠陥を見つけるはずです
1.複雑さのサイクルhandleSelectChange
を実行する必要があるO(n)
2.currentChange
エコー ロジック内にはO(n^2)
複雑さのループがあります。
シーン内のチェックされた行数が10000
行に達すると、各ページに100
項目が表示されると想像してください。
その場合、クリックしてページを変更するたびに最悪のループを実行する必要があり10000 * 100
、これは恐ろしいことです...
データ構造を再設計する
実際、配列として設計する必要はなくcheckedRows
、 1 つとして設計できるmap
ため、値の読み取りに必要なのはO(1)
複雑さだけです。
1. 変身checkedRows
this.crossPageMap = new Map()
2. 選択ロジックを変更します ( 核心代码
)
handleSelectChange (val, row) {
// 实现了 O(n) 到 O(1) 的提升
const checked = this.crossPageMap.has(row.id)
if (checked) {
this.crossPageMap.delete(row.id)
} else {
this.crossPageMap.set(row.id, row)
}
}
3. ページ変更エコー ロジックを変更する
currentChange (page) {
this.currentPage = page
// 实现了 O(n^2) 到 O(n) 的提升
this.tableData.forEach(row => {
const checked = this.crossPageMap.has(row.id)
if (checked) this.$refs.multipleTable.toggleRowSelection(row, true)
})
}
完全なコード
<template>
<div>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%;height:500px"
@select="handleSelectChange"
@select-all="handleSelectAllChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
label="日期"
width="120"
prop="date"
/>
<el-table-column
prop="name"
label="姓名"
width="120"
/>
</el-table>
<el-pagination
background
:current-page.sync="currentPage"
layout="prev, pager, next"
:total="1000"
@current-change="currentChange"
/>
</div>
</template>
<script>
export default {
data () {
return {
currentPage: 1,
crossPageMap: new Map(),
pageSize: 10,
totalData: Array.from({
length: 1000 }, (_, index) => {
return {
date: '2016-05-03',
id: index,
name: '王小虎' + index
}
})
}
},
computed: {
tableData () {
const {
currentPage, totalData, pageSize } = this
return totalData.slice((currentPage - 1) * pageSize, currentPage * pageSize)
}
},
methods: {
currentChange (page) {
this.currentPage = page
this.tableData.forEach(row => {
const checked = this.crossPageMap.has(row.id)
if (checked) this.$refs.multipleTable.toggleRowSelection(row, true)
})
},
handleSelectChange (val, row) {
const checked = this.crossPageMap.has(row.id)
if (checked) {
this.crossPageMap.delete(row.id)
} else {
this.crossPageMap.set(row.id, row)
}
},
handleSelectAllChange (val) {
this.tableData.forEach(row => {
this.handleSelectChange(null, row)
})
}
}
}
</script>
抽象的なビジネス ロジック
上記は完全なビジネス コード部分ですが、再利用可能です。
ロジックはCrossPage
クラスに抽象化できると考えます。
CrossPage クラスを設計する
以下のパラメータを受け取ります
`data` - 行数据
`key` - 行数据唯一值
`max` - 最大选中行数
`toggleRowSelection` - 切换行数据选中/取消选中的方法
次のメソッドを提供します
`onRowSelectChange` - 外部点行数据点击的时候调用此方法
`onDataChange` - 外部数据变化的时候调用此方法
`clear` - 清空所有选中行
コンストラクターの一般的なコードは次のとおりです
constructor (options={
}) {
this.crossPageMap = new Map()
this.key = options.key || 'id'
this.data = options.data || []
this.max = options.max || Number.MAX_SAFE_INTEGER
this.toggleRowSelection = options.toggleRowSelection
if(typeof this.toggleRowSelection !== 'function') throw new Error('toggleRowSelection is not function')
}
プライベートのcrossPageMapを設定する
crossPageMap
ヤンの祖先よ、問題はこれからです。インスタンスにマウントすると、外部からこの変数に直接アクセスして変更できるようになります。
これにより、内部データ ロジックが混乱する可能性があるため、外部アクセスを禁止する必要があります。
#
修飾子を使用してプライベート プロパティを実装できます。詳細については、「 」を参照してください。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields
完全なコード
- クロスページ.js
/**
* @description 跨页选择
* @param {Object} options
* @param {String} options.key 行数据唯一标识
* @param {Array} options.data 行数据
* @param {Number} options.max 最大勾选行数
* @param {Function} options.toggleRowSelection 设置行数据选中/取消选中的方法,必传
*/
export const CrossPage = class {
#crossPageMap = new Map();
constructor (options={
}) {
this.key = options.key || 'id'
this.data = options.data || []
this.max = options.max || Number.MAX_SAFE_INTEGER
this.toggleRowSelection = options.toggleRowSelection
if(typeof this.toggleRowSelection !== 'function') throw new Error('toggleRowSelection is not function')
}
get keys(){
return Array.from(this.#crossPageMap.keys())
}
get values(){
return Array.from(this.#crossPageMap.values())
}
get size(){
return this.#crossPageMap.size
}
clear(){
this.#crossPageMap.clear()
this.updateViews()
}
onRowSelectChange (row) {
if(typeof row !== 'object') return console.error('row is not object')
const {
key,toggleRowSelection} = this
const checked = this.#crossPageMap.has(row[key])
if(checked) this.#crossPageMap.delete(row[key])
else {
this.#crossPageMap.set(row[key],row)
if(this.size>this.max){
this.#crossPageMap.delete(row[key])
toggleRowSelection(row,false)
}
}
}
onDataChange(list){
this.data = list
this.updateViews()
}
updateViews(){
const {
data,toggleRowSelection,key} = this
data.forEach(row=>{
toggleRowSelection(row,this.#crossPageMap.has(row[key]))
})
}
}
- クロスページ.vue
<template>
<div>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@select="handleSelectChange"
@select-all="handleSelectAllChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
label="日期"
width="120"
prop="date"
/>
<el-table-column
prop="name"
label="姓名"
width="120"
/>
</el-table>
<el-button @click="clear">
清空
</el-button>
<el-button @click="keys">
获取 keys
</el-button>
<el-button @click="values">
获取 values
</el-button>
<el-pagination
background
:current-page.sync="currentPage"
layout="prev, pager, next"
:total="1000"
@current-change="currentChange"
/>
</div>
</template>
<script>
import {
CrossPage } from './CrossPage'
export default {
data () {
return {
currentPage: 1,
pageSize: 10,
totalData: Array.from({
length: 1000 }, (_, index) => {
return {
date: '2016-05-03',
id: index,
name: '王小虎' + index
}
}),
multipleSelection: []
}
},
computed: {
tableData () {
const {
currentPage, totalData, pageSize } = this
return totalData.slice((currentPage - 1) * pageSize, currentPage * pageSize)
}
},
mounted () {
this.crossPageIns = new CrossPage({
key: 'id',
max: 2,
data: this.tableData,
toggleRowSelection: this.$refs.multipleTable.toggleRowSelection
})
},
methods: {
clear () {
this.crossPageIns.clear()
},
keys () {
console.log('keys:', this.crossPageIns.keys)
},
values () {
console.log('values:', this.crossPageIns.values)
},
currentChange (page) {
this.currentPage = page
// 调用实例 onDataChange 方法
this.crossPageIns.onDataChange(this.tableData)
},
handleSelectChange (val, row) {
// 调用实例 onRowSelectChange 方法
this.crossPageIns.onRowSelectChange(row)
},
handleSelectAllChange (val) {
this.tableData.forEach(row => {
this.crossPageIns.onRowSelectChange(row)
})
}
}
}
</script>
最後に書きます
今後もやりたいことがまだまだたくさんあります
requestIdleCallback
単一ページ上の大量のデータのtoggleRowSelection
レンダリング効率を向上させるために使用します。- デフォルトで選択された項目の構成を提供します
- …