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寻找该对象--> 修改该对象要方便的多。 特别是在数据层级比较深的时候。通过索引来查找可能会出现

    changeItemValue(itemAIndex, itemBIndex, itemCindex, itemDIndex, value) {
      this.list[itemAIndex].childList[itemBIndex].childList[itemCindex].childList[itemDIndex].title = value
    }
复制代码

这酸爽~

我们举一个引用的小场景

页面的列表中, 每一个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,以后会修复的话那,那毫无疑问我要加班了。。

第二次写在最后

我用的这种方法, 我觉得不踏实,我写出来和大家一起讨论,抛砖引玉, 希望有大佬能指点或者分享经验就是我的目的, 评论一上来就开骂是咋回事。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值