对数组某个元素的删除与还原如何保证原有相对顺序不变

337 阅读1分钟

示例效果

动画.gif

场景

后端返回一个列表数据,用户将对这个列表进行编辑,最终将编辑好的列表传给后端

对于用户而言,做的事情就是 删除操作,那在删除的时候可能不小心删错了,又想恢复到原来的数据,如果只是简单的 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方法即可

如果数据有子级呢

显然这种计算方式是没问题的

动画2.gif

示例全部代码(不带子级的)

<!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>