v-for 數組資料順序變動和畫面渲染問題
Tags: webdev, vue
Published: 2022-08-19 10:07:00

## 問題 Q1

實作拖拉的元件，當拉(如下圖)名為name的theader物件，拉到number欄位


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9wp0c4irm6t8nftdirf3.png)

欄位名稱後面的數字代表該欄位在資料陣列的index

然後放開，照理說name欄位和number欄位應該要互換，但是...


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqs9urev5q5nduq09kbl.png)

可以看到theader欄位沒有發生變動，但tbody所應對的theader有正確渲染，然後欄位名稱後的數字也有正確變化，代表資料陣列是有正確改動的，但是畫面沒有正確渲染。

```html
<table class="table table-bordered">
  <thead>
    <drag-wrap :list="headers" tag="tr" @watchData="watchData">
      <drag-item v-for="(header, index) in headers" :key="index" tag="th">
        {{ header }} , {{ index }}
      </drag-item>
    </drag-wrap>
  </thead>
  <tbody>
    <tr v-for="(item, index) in items" :key="item.number">
      <td v-for="(header, sindex) in headers" :key="sindex">
        {{ item[header] }}
      </td>
    </tr>
  </tbody>
</table>
```

```jsx
data() {
    return {
      headers: ['number', 'name', 'sex', 'age', 'position'],
      items: [
        {
          number: 35,
          name: 'Durant',
          sex: 'male',
          age: 34,
          position: 'SF' 
        },
        {
          number: 23,
          name: 'James',
          sex: 'male',
          age: 37,
          position: 'SF' 
        },
        {
          number: 30,
          name: 'Curry',
          sex: 'male',
          age: 34,
          position: 'PG' 
        },
      ]
    }
  },
```

## 解決 for Q1

```html
<table class="table table-bordered">
  <thead>
    <drag-wrap :list="headers" tag="tr" @watchData="watchData">
      <drag-item v-for="(header, index) in headers" :key="header" tag="th">
        {{ header }} , {{ index }}
      </drag-item>
    </drag-wrap>
  </thead>
  <tbody>
    <tr v-for="(item, index) in items" :key="item.number">
      <td v-for="(header, sindex) in headers" :key="sindex">
        {{ item[header] }}
      </td>
    </tr>
  </tbody>
</table>
```

第四行v-for的key屬性由index改成header(陣列元素本身 string)，詳細原因的原理還尚未弄明白，需要更知道key在v-for所代表的意義

[https://cn.vuejs.org/v2/guide/list.html#维护状态](https://cn.vuejs.org/v2/guide/list.html#%E7%BB%B4%E6%8A%A4%E7%8A%B6%E6%80%81)

## 問題 Q2

跟Q1 類似，一樣是表格變動不如預期

## 解決 for Q2

先解釋程式flow : 

在元件內部有用props接收外部data的資料(headers)，每次移動完成後，會觸發ondragend事件，

該事件執行的function會先拿到打亂資料的index，再去跟props的原始header陣列度對比，拿到拖拉完成後的新headers陣列。

但這樣就會發生問題..

因為我發現每次拖拉完成後，都會重新render內部元件(drag-wrap元件)，而重新render後，從父元件拿到的props資料header就不會是最原始的順序，而是每次拖拉前的header順序

所以..

在內部元件drag-wrap 的created階段先接收最原始的header，保存下來後，每次拖拉結束後，與打亂資料的index比對的headers就會是最原始的順序。

以下code:

created階段origin_list變數會接收props list(外部傳入參數是headers)

```jsx
// drag-wrap.js 
import {h, defineComponent} from 'vue'

const drapWrapComponent = defineComponent({
    name: 'dragWrap',
    props: {
        list: {
            type: Array
        },
        tag: {
            type: String,
            default: 'div'
        }
    },
    emits: ['watchData'],
    data() {
        return {
            fromDom: null,
            toDom: null,
            children: [],
            origin_list: [],
        }
    },
    render() {
        try {
            // ES6 解構賦值
            const { $slots, tag } = this;
            return h(
                tag, 
                {
                    ref: 'wrap',
                    ondragenter: event => {
                        event.preventDefault()
                    },
                    ondragover: event => {
                        event.preventDefault()
                    }
                }, 
                $slots.default())
        } catch (err) {
            return h('pre', {style: {color: 'red'}}, err.stack)
        }
    },
    created() {
        this.emitter.on('dragstart', this.onDragstart)
        this.emitter.on('dragenter', this.onDragenter)
        this.emitter.on('dragend', this.onDragend)
        this.emitter.on('putChild', child => {
            this.children.push(child)
            console.log(child)
        })
        this.origin_list = this.list
        console.log(this.origin_list);
    },
    methods: {
        onDragstart(el) {
            this.fromDom = el
            console.log('拿起' + this.fromDom.innerHTML)
        },
        onDragenter(el) {
            this.toDom = el
            if(this.toDom === this.fromDom) return
            console.log('進入到' + this.toDom.innerHTML)
            // 判斷進入node是否在起始node前面
            if(this.isPrevNode(this.fromDom, this.toDom)) {
                // 把參數一放在參數二前面
                console.log('交換');
                this.$refs.wrap.insertBefore(this.fromDom, this.toDom)
            } else {
                console.log('交換');
                this.$refs.wrap.insertBefore(this.fromDom, this.toDom.nextSibling)
            }
        },
        onDragend() {
            // this.$emit('watchData', this.list)
            console.log('執行onDragend')
            if(!this.list.length) return
            // this.$el.children是偽陣列
            // 獲取drag-item的Dom
            console.log(this.$el.children);
            const realDomOrder = [...this.$el.children].filter(child => 
                child.classList.contains('__drag_item')
            )
            this.getDataOrder(realDomOrder, this.children)
        },
        // to是否在from的前面
        isPrevNode(from, to) {
            while(from.previousSibling != null) {
                if(from.previousSibling === to) {
                    return true
                }
                from = from.previousSibling
            }
        },
        getDataOrder(realList, originList) {
            // 拿到打亂item的index
            console.log(realList);
            console.log(originList);
            const order = realList.map(realItem => {
                return originList.findIndex(dragItem => realItem === dragItem)
            })
            console.log(order);
            const newData = []
            order.forEach((item, i) => {
                newData[i] = this.origin_list[item]
            })
            console.log(newData);
            this.$emit('watchData', newData)
        }
    },

})

export default drapWrapComponent
```