示例效果
场景
后端返回一个列表数据,用户将对这个列表进行编辑,最终将编辑好的列表传给后端
对于用户而言,做的事情就是 删除操作,那在删除的时候可能不小心删错了,又想恢复到原来的数据,如果只是简单的 push ,那么数据的顺序在视图上会显得有些凌乱
原始数据
const dataSource = [
{ id: 124, value: '数据-1' },
{ id: 456, value: '数据-2' },
{ id: 472, value: '数据-3' },
{ id: 789, value: '数据-4' },
{ id: 479, value: '数据-5' },
{ id: 189, value: '数据-6' },
{ id: 156, value: '数据-7' }
]
在删除之后的还原操作 如何保证相对顺序? 应该插入到哪个位置呢,依据的规则是什么?
问题解决
构造辅助的数据结构
目的是 根据某条数据找到插入的位置,因此将此条数据和位置进行关联
对于示例数据而言,构造的新数据是这样
const indexMap = {
"124": 0,
"456": 1,
"472": 2,
"789": 3,
"479": 4,
"189": 5,
"156": 6
}
键 是数据的唯一标识,值 是数据的索引
根据数据id找到插入位置
比较要插入的数据的索引 (在 indexMap 中的值) 和 目标列表索引数组(在 indexMap 中的值),之后得出要插入的位置
function getInsertIdx(originIdx, idxArray) {
let ans = 0
for (let i = 0; i < idxArray.length; i++) {
if (originIdx > idxArray[i]) ans = i + 1
}
return ans
}
最后调用splice方法即可
如果数据有子级呢
显然这种计算方式是没问题的
示例全部代码(不带子级的)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数组的相对位置不变</title>
<style>
summary {
font-weight: bold;
}
li {
margin: 5px 0;
}
</style>
</head>
<body>
<script src="https://unpkg.com/vue@3"></script>
<div id="app" style="display: grid; grid-template-columns: repeat(2, 1fr)">
<details open>
<summary>原始数据</summary>
<ul>
<li v-for="(item, idx) in list" :key="item.id">
{{ item.value }}
<button @click="deleteItem(item.id, idx)">删除</button>
</li>
</ul>
</details>
<details open>
<summary>已删除的</summary>
<ul>
<li v-for="(item, idx) in deletedList" :key="item.id">
{{ item.value }}
<button @click="restore(item.id, idx)">还原</button>
</li>
</ul>
</details>
</div>
<script>
function getInsertIdx(originIdx, idxArray) {
let ans = 0
for (let i = 0; i < idxArray.length; i++) {
if (originIdx > idxArray[i]) ans = i + 1
}
return ans
}
const dataSource = [
{ id: 124, value: '数据-1' },
{ id: 456, value: '数据-2' },
{ id: 472, value: '数据-3' },
{ id: 789, value: '数据-4' },
{ id: 479, value: '数据-5' },
{ id: 189, value: '数据-6' },
{ id: 156, value: '数据-7' }
]
const app = Vue.createApp({
setup() {
const state = Vue.reactive({
list: [],
deletedList: [],
indexMap: {}
})
Vue.onMounted(() => {
setTimeout(() => {
state.list = dataSource
state.indexMap = dataSource.reduce((acc, item, index) => Object.assign(acc, { [item.id]: index }), {})
console.log(state)
}, 100)
})
const deleteItem = (id, index) => {
const originIndex = state.indexMap[id]
const originIndexArray = state.deletedList.map(item => state.indexMap[item.id])
const insertIdx = getInsertIdx(originIndex, originIndexArray)
const [deletedItem] = state.list.splice(index, 1)
state.deletedList.splice(insertIdx, 0, deletedItem)
}
const restore = (id, index) => {
const originIndex = state.indexMap[id]
const originIndexArray = state.list.map(item => state.indexMap[item.id])
const insertIdx = getInsertIdx(originIndex, originIndexArray)
const [restoreItem] = state.deletedList.splice(index, 1)
state.list.splice(insertIdx, 0, restoreItem)
}
return { ...Vue.toRefs(state), deleteItem, restore }
}
})
app.mount('#app')
</script>
</body>
</html>