针对对象的特定方法

<template>
  <div>
    <div>
      <span>用户名: {{ userInfo.name }}</span>
      <span>用户性别: {{ userInfo.sex }}</span>
      <span v-if="userInfo.officialAccount">
        公众号: {{ userInfo.officialAccount }}
      </span>
    </div>
    <button @click="handleAddOfficialAccount">添加公众号</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {
        name: '子君',
        sex: '男'
      }
    }
  },
  methods: {
    // 在这里添加用户的公众号
    handleAddOfficialAccount() {
      this.userInfo.officialAccount = '前端有的玩'
    }
  }
}
</script>

在上边的代码中,我们希望点击添加公众号按钮,添加公众号属性,但是,点击按钮之后没有并没有生效。
原因:在vue内部,数据响应是通过Object.definePrototype监听对象每一个键的getter、setter方法来实现的,但是通过这种方法只能监听到已有的属性,新增的属性是无法监测到的,下边小编提供四种方式:

方式一:将本来要新增的属性在data中提前定义好

userInfo: {
  name: '子君',
  sex: '男',
  officialAccount: ''
}

// 在这里添加用户的公众号
handleAddOfficialAccount() {
  console.log('点击了')
  this.userInfo.officialAccount = '我来了'
}

方法二:直接替换掉userInfo

// 在这里添加用户的公众号
handleAddOfficialAccount() {
  this.userInfo = {
    ...this.userInfo,
    officialAccount: '我来了'
  }
}

使用 Vue.set
其实上边两种方法都有点取巧的嫌疑,其实对于新增属性,vue官方专门提供了一个新的方法Vue.set用来解决新增属性无法触发数据响应。
Vue.set方法定义:

/**
* target 要修改的对象
* prpertyName 要添加的属性名称
* value 要添加的属性值
*/
Vue.set( target, propertyName, value )
import Vue from 'vue'

// 在这里添加用户的公众号
handleAddOfficialAccount() {
  Vue.set(this.userInfo,'officialAccount', '前端有的玩')
}

但是每次用到set方法,还要把vue引入进来好麻烦,所以为了简便起见,将vue的set方法挂在到了Vue的原型链上了,即Vue.prototype.$set = Vue.set,所以在vue内部就直接可以用this.$set代替vue.set

// 在这里添加用户的公众号
handleAddOfficialAccount() {
  this.$set(this.userInfo, 'officialAccount', '我来了')
}

当赋值的属性还没有被定义的时候用vue.set,其他一般时候不会使用

方式四:使用$forceUpdate
$forceUpdate的存在,让许多前端开发者不会再去注意 数据双向绑定的原理,无论什么时候,反正我修改了data之后,调用一下$forceUpdate就会让vue组件重新去渲染,bug是不会存在的。但是实际上这个方法并不建议使用,因为它会引起许多不必要的性能消耗。

针对数组的特定方法
其实不仅仅是对象,数组也存在数据修改之后不响应的情况

<template>
<div>
  <ul>
    <li v-for="item in list" :key="item">
      {{ item }}
    </li>
  </ul>
  <button @click="handleChangeName">修改名称</button>
</div>
</template>
<script>
export default {
  data() {
    return {
      list: ['张三', '李四']
    }
  },
  methods: {
    // 修改用户名称
    handleChangeName() {
      this.list[0] = '王五'
    }
  }
}
</script>

上边代码希望将张三的名字换成王五,实际上这个修改并不能生效,这是因为vue不能检测到以下变动的数组:

  1. 当你利用索引直接设置一个项时,例如this.list[index] = newVal
  2. 修改数组的length属性,例如:this.list.length = 0

向上边提到的Vue.set和$forceUpdate()都可以解决这个问题

// 修改用户名称
handleChangeName() {
  this.list[0] = '王五'
  this.$forceUpdate()
}

// 修改用户名称
handleChangeName() {
  this.$set(this.list, 0, '王二')
}

在操作数组的时候,我们一般会用到数据提供的很多方法,比如push、pop、splice等等,在vue中调用数组上面提供的这些方法修改数组的值是可以触发数据响应的,比如上面的代码可以改为以下的代码即可触发数据响应

// 修改用户名称
handleChangeName() {
  this.list.splice(0, 1, '王石')
}

实际上,如果vue仅仅依赖getter和setter,是无法做到在数组调用push、pop等方法来触发数据响应的,因此vue实际上是通过劫持这些方法,对这些方法进行包装变异来实现的
vue对数组的以下方法进行的包装变异:

  1. push
  2. pop
  3. shift
  4. unshift
  5. splice
  6. sort
  7. reverse

所以在操作数组的时候,调用上面的这些方法是可以保证数据可以正常响应,下面是vue源码中包装数组方法的代码:

var original = arrayProto[method];
  def(arrayMethods, method, function mutator () {
    // 将 arguments 转换为数组
    var args = [], len = arguments.length;
    while ( len-- ) args[ len ] = arguments[ len ];
    var result = original.apply(this, args);
    // 这儿的用法同dependArray(value),就是为了取得dep
    var ob = this.__ob__;
    var inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break
      case 'splice':
        inserted = args.slice(2);
        break
    }
    // 如果有新的数据插入,则插入的数据也要进行一个响应式
    if (inserted) { ob.observeArray(inserted); }
   // 通知依赖进行更新
    ob.dep.notify();
    return result
  });
Logo

前往低代码交流专区

更多推荐