上面刚刚讲完 Vue 监听对象的改变,接下来本应该说说数组的监听过程,但是在这里需要插播一节 Vue.set() ,这是因为 Vue.set() 与对象的连接较为紧密,所以串联在一起方便理解。

1、首先,定义一批数据用来渲染到页面上展示

<body>
  <div id="root">
    <h2>姓名:{{student.name}}</h2>
    <h2>年龄:真实{{student.age.realAge}} 虚拟{{student.age.virtualAge}}</h2>
    <h2>性别:{{student.sex}}</h2>
  </div>

  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#root',
      data() {
        return {
          student: {
            name: 'al',
            age: {
              realAge: 27,
              virtualAge: 25
            }
          }
        }
      },
    })
  </script>
</body>

 2、页面展示完成之后,我发现页面上的性别属性没有展示出来,看了代码之后才知道是性别属性 sex 在初始化的时候没有定义。那我们想让 sex 属性展示出来应该怎么办呢?

        如果是在初始化的过程中我们直接将 sex 属性添加到 data 中,那么在组件编译之后,sex 属性时可以直接展示在页面上的,但是如果现在不能在 data 中直接添加这个属性,那要怎么办呢?

        首先想到的是,在控制台中去添加这个属性,因为我们已经使用 vm 接收到了这个 Vue 实例,所以可以直接对 该实例做操作。例如:

 我们通过 vm实例._data( vm._data在初始化过程中,通过Object.defineProperty()) 将原本的 data 中定义的属性全部收集加工成为了响应式的属性)中的 student 对象中添加了一个 sex 属性,添加完了之后发现页面上并没有渲染性别字段,查看 vm._data 之后发现,虽然将 sex 属性添加到了 student 之中,但是这个属性很明显是没有被加工成响应式的,因为没有get 和 set 函数。因为 Vue 的数据代理模式,我们也可以直接在 vm 上访问到这个属性

 但是也可以发现,在vm实例上的 sex 属性,和vm._data 中的是一样的,都不是响应式的。

如果我们不在 vm._data 上添加 sex属性,而是直接在 vm.student 上添加发现还是不会生效

 而且实际上这个操作( 直接向 vm 上添加属性 )是错误的,虽然页面是不会报错的,但是我们需要知道的是,vm上的 student 实际上就是由于 Vue的数据代理,从 _data 上拿过来的。所以通过 vm._data 来改还说的过去,但是直接在vm上改就离谱了。

那么我就是要添加这个sex属性,还能在页面上展示,且我改变了这个属性的值之后,页面同样会重新渲染,有什么办法呢?

Vue.set():添加响应式属性 

我们通过在 vm._data 上添加属性或直接在vm上添加属性发现都是不好使的,那这个时候 Vue 就很贴心的帮我们提供了一个方法,使用 Vue.set() 添加的属性,同样会被 Vue 加工成响应式。

 

 我们在控制台上直接输入这个方法,然后会发现这个方法提示了我们需要三个参数

        target:需要向哪个目标对象添加

        key:需要添加的属性名

        val:需要添加的属性的值

在我向 vm._data.student 中添加了 值为 男 的 sex 属性之后,我们发现页面上展示了设置的值,查看 vm._data 发现,此时的 sex 已经成为了响应式数据,拥有了 get 和 set 方法

 当我们改变 sex 的值时,页面也会同步更改

 我们在使用 set 方法时 target 传递的是  vm._data.student ,但是实际上可以使用 vm.student,这是因为 Vue 的数据代理之后  vm._data.student === vm.student ,我们只是想取到 student 这个对象,是可以直接从 vm 上拿到的,只是不要像上面说那样,直接往 vm 上添加属性。

 vm.$set()

 vm.$set() 这个方法 和 Vue.set()  方法的作用完全一样,只不过一个是挂载在 Vue上的全局api,一个是挂载在 vm实例上

 局限性

 但是,这两个方法都是存在局限性的。

 官网上是这么解释的,那这到底是个什么意思呢,我们来验证一下,把上面那个例子简化一下

<body>
  <div id="root">
    <h2>姓名:{{user.name}}</h2>
    <h2>地址:{{address}}</h2>
  </div>

  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#root',
      data() {
        return {
          user: {
            name: 'al',
          }
        }
      },
    })
  </script>
</body>

此时,页面上有一个地址展示,但是在 data 内部,我并没有定义一个 address 属性,所以页面无法展示具体地址 

且页面报错了 

 

这个错误的大致意思就是,address 属性没有在 data 内被定义,然后就在实例上使用了,这个错误肯定是正常的,因为 访问一个对象内部不存在的属性,默认值为undefined,Vue也不会展示该属性值,但是如果这个属性直接定义在 data 最外层的,类似这个 address ,那肯定是会报错的,因为这相当于访问了一个 undefined.address 。

那这个错误是正常的,我们就先不管他了,接下来我们要像上面一样,添加一个 address 属性,但是这时候要注意了,我们的 address 添加的位置,不是想 sex 一样 添加在深层对象内部的,而是直接添加在 data 最外层的,这样才不会造成读取不到数据,所以我们需要添加到 vm._data 中

但是,添加之后却出了问题,说的是:避免在运行时向 Vue 实例或其根 $data 添加响应式属性 - 在 data 选项中预先声明它。

那我不像 vm._data 中添加,我直接像 vm 上添加是个啥情况

 

还是同样的问题,那这个方法不是没用了么,那还咋添加数据啊,难道非要在初始化的时候在 data 内定义这个属性么?那这也太不方便了。 这个时候我们来仔细看一下这个错误说的是个啥。

简而言之就是不要向 vm实例对象中添加属性 或 直接向 vm._data 中添加属性。

回想一下上面的 sex 案例,我们是在 vm._data.student 这个对象中添加了 sex 属性,而不是直接在 vm._data 中添加的,那我们按照这个思路来处理一下,把 address 这个属性不要直接放在 _data 的最外层,我们把它包起来

<body>
  <div id="root">
    <h2>姓名:{{user.name}}</h2>
    <h2>地址:{{info.address}}</h2>
  </div>

  <script>
    const vm = new Vue({
      el: '#root',
      data() {
        return {
          info: {

          },
          user: {
            name: 'al',
          }
        }
      },
    })
  </script>
</body>

       

 包起来之后,我们再尝试向 vm.info 中添加 address属性 ,发现此时是成功的 ,且 address 也是响应式的属性。

总结

1、如果我们在初始化之后想要添加一个响应式数据,可以使用 set 方法,改方法传递三个参数,分别是:targe( 需要向哪个目标对象中添加 ),key( 添加的属性 ),val( 属性的值 )

2、Vue.set() 和 vm.$set() 这两个方法的作用一样,传参也一样,不一样的是一个是挂载在Vue上的全局api,一个是挂载到 Vue实例对象上的方法

3、使用 set() 方法添加属性时,不能直接添加到 vm实例对象上,也不能直接添加到根数据data中,需要添加到 data 中的某一个对象上才能生效

4、因为Vue的数据代理,所以我们在使用 set() 方法时,获取目标对象时,可以使用 vm.xxx,也可以使用 vm._data.xxx

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐