vue開發黑科技--利用引用型別的值處理複雜資料的編輯

xanggang發表於2018-10-15

現在前端框架和之前的前端開發方式有一個重要的區別————基於資料驅動。我們不需要再去關注dom本身,而是將主要精力放在如何運算元據上面。實際開發中,可以抽象成

vue開發黑科技--利用引用型別的值處理複雜資料的編輯

既然全部在完資料, 資料型別、演算法就跑不掉了。

本片介紹一個基於引用型別的vue黑科技, 在使用vue開發的時候可以更加方便。

引用型別

首先, 抄一段別人的部落格

www.cnblogs.com/leiting/p/8…

1.JavaScript中的變數型別有哪些?

(1)值型別:字串(string)、數值(number)、布林值(boolean)、none、undefined

(2)引用型別:物件(Object)、陣列(Array)、函式(Function)

2.值型別和引用型別的區別
    (1)值型別:1、佔用空間固定,儲存在棧中(當一個方法執行時,每個方法都會建立自己的記憶體棧,在這個方法內定義的變數將會逐個放入這塊棧記憶體裡,隨著方法的執行結束,這個方法的記憶體棧也將自然銷燬了。因此,所有在方法中定義的變數都是放在棧記憶體中的;棧中儲存的是基礎變數以及一些物件的引用變數,基礎變數的值是儲存在棧中,而引用變數儲存在棧中的是指向堆中的陣列或者物件的地址,這就是為何修改引用型別總會影響到其他指向這個地址的引用變數。)
    2、儲存與複製的是值本身
    3、使用typeof檢測資料的型別
    4、基本型別資料是值型別

(2)引用型別:
    1、佔用空間不固定,儲存在堆中(當我們在程式中建立一個物件時,這個物件將被儲存到執行時資料區中,以便反覆利用(因為物件的建立成本通常較大),這個執行時資料區就是堆記憶體。堆記憶體中的物件不會隨方法的結束而銷燬,即使方法結束後,這個物件還可能被另一個引用變數所引用(方法的引數傳遞時很常見),則這個物件依然不會被銷燬,只有當一個物件沒有任何引用變數引用它時,系統的垃圾回收機制才會在核實的時候回收它。)
    2、儲存與複製的是指向物件的一個指標
    3、使用instanceof檢測資料型別
    4、使用new()方法構造出的物件是引用型
複製程式碼

基礎知識就是這些了。 舉個小栗子。

vue開發黑科技--利用引用型別的值處理複雜資料的編輯
總之記住一句話, 值型別傳值, 引用型別傳址

vue中的資料傳遞

在vue中的資料傳遞過程中, 是不涉及深拷貝的。 是通過props、vuex、v-bind等方法傳遞的引用型別都是傳遞的記憶體指標 在來個小栗子

<template>
  <div class="content">
    <ul>
      <li v-for="(item, index) in list" :key="index" >
        <span @click="changeItemValue(item)">{{item.title}}</span>
        <span @click="deleteItem(index, list)">刪除</span>
      </li>
      <li @click="addItem(list)">新增item</li>
    </ul>
  </div>
</template>

<script>

export default {
  name: 'index',
  data () {
    return {
      list: [
        {
          title: 'index0',
        },
        {
          title: 'index1'
        },
        {
          title: 'index2'
        },
        {
          title: 'index3'
        },
        {
          title: 'index4'
        }
      ]
    }
  },
  methods: {
    changeItemValue(item) {
      item.title = item.title += '--|'
    },
    deleteItem(index, list) {
      list.splice(index, 1)
    },
    addItem(list) {
      list.push({
        title: 'index' + list.length
      })
    }
  }
}
</script>

<style lang="scss" scoped>
  ul {
    width: 500px;
    margin: 200px auto;
    li {
      width: 200px;
      height: 50px;
      line-height: 50px;
      border: 1px solid pink;
      span:nth-child(2){
        margin-left: 10px;
        color:red;
      }
    }
  }
</style>

複製程式碼

在上面的例子中, 我們都沒有使用this.list[index]的方式來獲取需要修改的物件, 實際上, 方法裡面傳入的item就是this.list裡對應的item的引用
這樣書寫會比通過傳入索引--> 通過索引在list尋找該物件--> 修改該物件要方便的多。 特別是在資料層級比較深的時候。通過索引來查詢可能會出現

    changeItemValue(itemAIndex, itemBIndex, itemCindex, itemDIndex, value) {
      this.list[itemAIndex].childList[itemBIndex].childList[itemCindex].childList[itemDIndex].title = value
    }
複製程式碼

這酸爽~

我們舉一個引用的小場景

vue開發黑科技--利用引用型別的值處理複雜資料的編輯
頁面的列表中, 每一個item有一個開關, 需要在儲存的時候取出全部全部選中的

<template>
  <div class="content">
    <ul>
      <li v-for="(item, index) in list" :key="index" >
        <span>{{item.title}}</span>
        <span @click="changeItemValue(item)">{{item.isSelect ? '選中' : '未選中'}}</span>
      </li>
      <li @click="save">儲存</li>
    </ul>
  </div>
</template>

<script>

export default {
  name: 'index',
  data () {
    return {
      list: [
        {
          title: 'index0',
          isSelect: false
        },
        {
          title: 'index1',
          isSelect: false
        },
        {
          title: 'index2',
          isSelect: false
        },
        {
          title: 'index3',
          isSelect: false
        },
        {
          title: 'index4',
          isSelect: false
        }
      ]
    }
  },
  methods: {
    changeItemValue(item) {
      item.isSelect = !item.isSelect
    },
    save() {
      const data = this.list.filter(_ => _.isSelect)
      console.log(data)
    }
  }
}
</script>

複製程式碼

這得益於vue的訪問劫持方法, 在修改物件的時候, 可以直接觸發物件的觀察者, 觸發資料的更新和各種watch、computed、UI。 而vue的陣列型別則是由vue特殊處理過的,才能實現對push、splice等方法的更新,這部分可以翻翻vue原始碼。

接下來我們講一講通過props的方式向子元件傳遞的情況, 眾所周知,vue是不允許在元件內修改通過props傳入的值的。實際中呢:

如果傳入的資料是值型別的, 那麼不允許修改這個值 例如 this.string = ''
如果傳入的資料是引用型別, 那麼不允許修改這個資料的記憶體地址,反之呢,我們可以修改這個資料中的子資料
複製程式碼

感覺上這種操作是違反vue的單向資料流思想的, 但是實在是在開發中太好用了, 所以我只能說這是一種黑科技 來個例子, 我們修改一下上面的程式碼, 將li作為一個元件來管理一個物件

<template>
  <div class="content">
    <ul>
      <Item v-for="(item, index) in list" :key="index" :item="item" />
      <li @click="save">儲存</li>
    </ul>
  </div>
</template>

<script>

import Item from './Item'

export default {
  name: 'index',
  components: { Item },
  data () {
    return {
      list: [
        {
          title: 'index0',
          isSelect: false
        },
        {
          title: 'index1',
          isSelect: false
        },
        {
          title: 'index2',
          isSelect: false
        },
        {
          title: 'index3',
          isSelect: false
        },
        {
          title: 'index4',
          isSelect: false
        }
      ]
    }
  },
  methods: {
    save() {
      const data = this.list.filter(_ => _.isSelect)
      console.log(data)
    }
  }
}
</script>
複製程式碼
<template>
  <li>
    <span>{{item.title}}</span>
    <span @click="changeItemValue">{{item.isSelect ? '選中' : '未選中'}}</span>
  </li>
</template>
<script>
export default {
  name: 'Item',
  props: ['item'],
  methods: {
    changeItemValue() {
      this.item.isSelect = !this.item.isSelect
      // 注意 如上面所說 在這裡直接修改item就會報錯, 反之 只修改item下面的值並不會
    }
  }
}
</script>
複製程式碼

執行起來, 和之前並沒有差異, 實際上props傳進去的也是這個物件的引用, 修改的時候父元件的值也被同步修改了。這樣我們可以在子元件裡面修改對應的值, 而不需要$emit到父元件去修改。在處理複雜資料的時候, 可以減少很多負擔 基於這種模式, 我們在處理一個複雜資料的編輯的時候, 就可以將每一塊相對獨立的子資料分別用元件去維護。 而且子元件的資料相對對立, 層級淺的時候, 我們還可以方便的使用computed計算屬性來實現一些資料的校驗,UI的處理。

在使用vuex的時候

在使用vudex做狀態管理的時候, 情況和pros差不多。

如果繫結的的資料是值型別的, 那麼不允許修改這個值 例如 this.string = ''
如果繫結的的資料是引用型別, 那麼不允許修改這個資料的記憶體地址,反之呢,我們可以修改這個資料中的子資料
複製程式碼

但是有一點, 在使用vue-devtools工具的中會有點差異,簡單來說通過這種方式修改了state中的值,在vue-devtools工具的vuex部分是不會更新的, 但是實際上資料是已經改變了。。 依舊是先前的那個例子, 我們將資料來源從data改為vuex

  computed: {
    list() {
      return this.$store.state.list
    }
  }
複製程式碼

我們通過這種方式改變值之後,

vue開發黑科技--利用引用型別的值處理複雜資料的編輯
在組建檢視, 我們能看到組建內的isSelect值已經更新了,

vue開發黑科技--利用引用型別的值處理複雜資料的編輯
父元件的計算屬性中 第一個物件的值也更新了

vue開發黑科技--利用引用型別的值處理複雜資料的編輯
但是在vuex檢視中 這個值沒有被更新, 列印出來的值也是更新了的。。 如果有強迫症的話, 可以手動更新一下

  mutations: {
    changeList(state, list) {
      state.list = list
    }
  },
  
   this.$store.commit('changeList', this.$store.state.list)
複製程式碼

應用

基於這種方法, 我們在處理複雜資料的時候, 可以將相對獨立的資料塊分割出來用一個單獨的vue元件來維護和修改。最後的修改結果都可以在原有的資料樹中體現,在提交的時候對這個跟資料進行處理就好。而不用每一次修改都emit到父元件中處理。

注意事項

  • 基於這種值引用的形式, 在子元件修改相應值的時候, 初始值其實已經被汙染了, 所以有需要的話要做資料的深拷貝
  • 在處理陣列的時候, vue底層對響應的陣列操作都有特殊處理過, 所以只要不直接修改陣列的引用地址, 都可以觸發資料的更新, 但是不能使用tihs.list = this.list.map(cb)類似的方法, 因為他們都會返回一個新的陣列
  • 計算屬性和vuex的getter返回的值不能這樣處理, 準確的說, 這兩個值本身就不能修改, 但是通過計算屬性返回vuex的值例外
  • 在直接修改物件子值的時候, watch會有異常, 無法正確的獲得oldVal的值。
  • 在react中也可以類似的實現, 但是react不是基於資料訪問劫持的, 所以修改之後還要手動state一次, 微信小程式同理
  • 在微信小程式中, 寫在模板中的函式是不能傳參的,通過data寫在dom上的值不能這麼操作, 組建傳入的值也是相當於深拷貝的, 不能這麼玩了

vue開發黑科技--利用引用型別的值處理複雜資料的編輯

如上圖, 計算屬性的值正常更新了, 通過deep watch的值, 兩個都是新的值, 無法取得oldVal, 而不用deep的時候, 這個watch根本不會觸發。

寫在最後

通過這種方式, 在處理比較複雜的資料的時候有奇效, 但是隱隱約約還是有些怪異,表面穩如老狗,實際慌得不行。 也請大佬解惑

  • 這樣處理是不是違反了單向資料流的思想
  • 會不會有其他的未知的隱患

10/16 第一次更新

大家都在說複雜的引用型別難以維護的情況,我不得不吐槽一下了。。 我們經常拿到的資料是這樣的

[
  {
    "id": 592,
    "catalogueCode": "catalogueCode",
    "catalogueRule": "catalogueRule",
    "catalogueName": "catalogueName",
    "days": "3",
    "expectedDate": null,
    "groups": [
      {
        "groupName": "groupName",
        "subsets": [
          {
            "items": [
              {
                "catalogueRule": "catalogueRule",
                "ruleId": 1,
                "ruleName": "ruleName",
                "catalogueCode": "catalogueCode",
                "ruleScore": 2
              },
              {
                "catalogueRule": "catalogueRule",
                "ruleId": 77,
                "ruleName": "ruleName",
                "catalogueCode": "catalogueCode",
                "ruleScore": 2
              }
            ]
          }
        ]
      }
    ],
    "goals": [
      {
        "goalId": 642,
        "catalogueName": "catalogueName",
        "catalogueRule": "catalogueRule",
        "catalogueCode": "catalogueCode",
        "remark": null,
        "resultId": 592,
        "sortNum": null,
        "measures": [
          {
            "measureId": 2541,
            "catalogueCode": "catalogueCode",
            "catalogueRule": "catalogueRule",
            "catalogueName": "catalogueName",
            "customizeId": null,
            "sort": 0,
            "checked": true,
            "shortActivityMap": {
              "key1": [
                {
                  "catalogueName": "catalogueName",
                  "catalogueRule": "catalogueRule",
                  "catalogueCode": "catalogueCode",
                  "resultId": 2541,
                  "longActivityList": [],
                  "specialSecondType": "3",
                  "frequencyName": "bid",
                  "executionTime": "08:00,16:00",
                  "shortActivityId": 82
                }
              ],
              "key2": [
                {
                  "catalogueName": "catalogueName",
                  "catalogueRule": "catalogueRule",
                  "catalogueCode": "catalogueCode",
                  "resultId": 2541,
                  "longActivityList": [],
                  "specialSecondType": "3",
                  "frequencyName": "bid",
                  "executionTime": "08:00,16:00",
                  "shortActivityId": 82
                }
              ]
            }
          }
        ],
        "appraisals": [
          {
            "appraisalId": 2048,
            "catalogueName": "catalogueName",
            "catalogueRule": "catalogueRule",
            "catalogueCode": "catalogueCode",
            "remark": null,
            "resultId": null,
            "targetCode": "targetCode",
            "sortNum": null
          }
        ]
      }
    ],
    "totalScore": 4
  },
]
複製程式碼

這個層級相對還是比較少的, 最深資料接近6層。關鍵在於每一個資料節點都是有對應的增刪改的編輯需求, 並且不能分佈提交。 那麼這個頁面的編輯。怎麼處理?

  • 方案一: 完全不元件化, 一把撈

vue開發黑科技--利用引用型別的值處理複雜資料的編輯
這個元件裡面維護的狀態, 已經不忍直視了。。程式碼就更不用說, 幾千行一把撈

  • 方案二: 按照資料節點拆分成子元件, 通過props傳遞資料,每一個修改動作都一路emit到根元件進行。
    乍一看起來完全OK, 但是位於第六層的資料要向上傳遞多少次呢。 對應的索引有多少? 根元件每一個層級的資料都要寫一個增刪改方法, 根元件又亂掉了
  • 方案三: 將全部資料維護到vuex處理, 所有修改資料的方法使用commit在vuex中修改
    這種方法,實際上是吧方案二的根元件資料處理搬到的vuex中, 並且省下了emit和props, 綜合起來算是比較好的方案了。 很遺憾的是, 同事不接受。。
  • 方案四: 就是上文提到的方法。
    關於維護: 在vue的谷歌開發外掛中是可以完整的看到資料的流向、改變的
    vue開發黑科技--利用引用型別的值處理複雜資料的編輯
    每個一個元件維護了什麼,修改了什麼是和資料結構完全對應,並在當前清晰可見,維護起來起來應該還ok?

不過如評論所說, 如果vue認為修改props內部資料是缺陷或者是BUG,以後會修復的話那,那毫無疑問我要加班了。。

第二次寫在最後

我用的這種方法, 我覺得不踏實,我寫出來和大家一起討論,拋磚引玉, 希望有大佬能指點或者分享經驗就是我的目的, 評論一上來就開罵是咋回事。

相關文章