Vue为什么需要$set?

232 阅读1分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

先看下面场景

我们写一个todolist:

  1. list中有姓名、性别、年龄;
  2. 声明数据,数据中没有年龄;
  3. 做一个表单:选中一个姓名,然后给这个人添加加年龄;

效果图

22f1bcb3d15eef024e408d4a4bc61884.png

代码结构

<template>
    <div>
      <div class="list">
        <div v-for="(item, index) of list" :key="index">
          姓名:{{item.name}},性别:{{item.sex}},年龄:{{item.age}}
        </div>
      </div>
      <div class="operation">
        <div>
          姓名:
          <select v-model="name">
            <option v-for="(item, index) of list" :key="index" :value="item.name">
              {{item.name}}
            </option>
          </select>
        </div>
        <div>
          年龄:<input v-model="age" type="text" />
        </div>
        <div>
          <button @click="add">添加</button>
        </div>
      </div>
    </div>
</template>

<script>
export default {
  name: 'App',
  data: () => {
    return {
      name: '',
      age: '',
      list: [
        {name: '胡歌', sex: '男'},
        {name: '刘亦菲', sex: '女'},
      ]
    }
  },
  methods: {
    add() {
      console.log(this.list)
      for (let item of this.list) {
        if (item.name === this.name) {
          item.age = this.age;
          return;
        }
      }
      console.log('未查询到')
    }
  }
}
</script>

遇到的问题

当选中‘刘亦菲’后,点击‘添加’按钮,页面没有反应???于是我们打印一下list:

a5d984df1f8cdf8defa3e39bd4633790.png

  1. age数据已经添加进去了;
  2. list没有‘age’属性的set、get;

原因分析

vue在data中的数据进行劫持,是在创建阶段。之后通过item.age = this.age再添加数据时。不再添加劫持也不会触发组件刷新。

解决问题

这种场景我们可以使用$set来解决;

item.age = this.age;

替换成:

this.$set(item, 'age', this.age);

你会发现问题解决了。

$set源码解析

function set (target, key, val) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    target.splice(key, 1, val);
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val
  }
  var ob = (target).__ob__;
  if (!ob) {
    target[key] = val;
    return val
  }
  defineReactive$$1(ob.value, key, val);
  ob.dep.notify();
  return val
}

上面代码做过一些简化。重点的部分是:

  1. (key in target && !(key in Object.prototype))已经存在key,更新。不存在则添加key。
  2. defineReactive$$1(ob.value, key, val)添加劫持。
  3. ob.dep.notify()通知组件更新。

Vue3.0

Vue3.0后就不需要$set了,因为Proxy是对整个对象进行劫持(仍需递归)。所有不需要考虑某个key。