vue开发黑科技--利用引用类型的值处理复杂数据的编辑
現在前端框架和之前的前端開發方式有一個重要的區別————基于數據驅動。我們不需要再去關注dom本身,而是將主要精力放在如何操作數據上面。實際開發中,可以抽象成
既然全部在完數據, 數據類型、算法就跑不掉了。
本片介紹一個基于引用類型的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中的數據傳遞過程中, 是不涉及深拷貝的。 是通過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尋找該對象--> 修改該對象要方便的多。 特別是在數據層級比較深的時候。通過索引來查找可能會出現
這酸爽~
我們舉一個引用的小場景
頁面的列表中, 每一個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}} 復制代碼我們通過這種方式改變值之后,
在組建視圖, 我們能看到組建內的isSelect值已經更新了, 父組件的計算屬性中 第一個對象的值也更新了 但是在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上的值不能這么操作, 組建傳入的值也是相當于深拷貝的, 不能這么玩了
如上圖, 計算屬性的值正常更新了, 通過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層。關鍵在于每一個數據節點都是有對應的增刪改的編輯需求, 并且不能分布提交。 那么這個頁面的編輯。怎么處理?
- 方案一: 完全不組件化, 一把撈
- 方案二: 按照數據節點拆分成子組件, 通過props傳遞數據,每一個修改動作都一路emit到根組件進行。
乍一看起來完全OK, 但是位于第六層的數據要向上傳遞多少次呢。 對應的索引有多少? 根組件每一個層級的數據都要寫一個增刪改方法, 根組件又亂掉了 - 方案三: 將全部數據維護到vuex處理, 所有修改數據的方法使用commit在vuex中修改
這種方法,實際上是吧方案二的根組件數據處理搬到的vuex中, 并且省下了emit和props, 綜合起來算是比較好的方案了。 很遺憾的是, 同事不接受。。 - 方案四: 就是上文提到的方法。
關于維護: 在vue的谷歌開發插件中是可以完整的看到數據的流向、改變的 每個一個組件維護了什么,修改了什么是和數據結構完全對應,并在當前清晰可見,維護起來起來應該還ok?
不過如評論所說, 如果vue認為修改props內部數據是缺陷或者是BUG,以后會修復的話那,那毫無疑問我要加班了。。
第二次寫在最后
我用的這種方法, 我覺得不踏實,我寫出來和大家一起討論,拋磚引玉, 希望有大佬能指點或者分享經驗就是我的目的, 評論一上來就開罵是咋回事。
總結
以上是生活随笔為你收集整理的vue开发黑科技--利用引用类型的值处理复杂数据的编辑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MVC设计模式深入理解
- 下一篇: LeetCode 编程 二