vue|vue怎么触发视图更新

576 阅读2分钟

vue文档中的响应式原理一节,说明了什么情况下视图不会更新,但是详细说明为什么。本文旨在记录下自己学习的疑惑

思考这几个问题

  1. //list会不会更新?
    <template>
      <div id="app">
        <div v-for="(item, index) in list" :key="index">{{item}}</div>
      </div>
    </template>
    <script>
    export default {
      name: 'App',
      data(){
        return {
          list: [1, 2, 3, 4]
        }
      },
      mounted(){
        this.list[4] = 5
      }
    }
    </script>
    
  2. //list会不会更新?
    <template>
      <div id="app">
        <div v-for="(item, index) in list" :key="index">{{item}}</div>
      </div>
    </template>
    <script>
    export default {
      name: 'App',
      data(){
        return {
          list: [1, 2, 3, 4]
        }
      },
      mounted(){
        this.list.push(5)
      }
    }
    </script>
    

根据文档 1不会。 2 会

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

这是为什么?

因为数组的更新没触发拦截器,我们知道vue的响应式原理是

使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty

怎样会触发Object.defineProperty更新

首先拦截器只会对每个对象的每个属性监听,所以只能根据对象的每个属性拦截

  • 对于数组,数组内部更改拦截不到,只有对list整体更改才能否拦截到

  • 对于对象也一样
  • 这是因为 比如 obj.list.a = 1对于obj.listrhs也就是get操作,没有对obj.listset操作,拦截器是对属性做set操作,所以拦截不到

vue内部遍历了data所有属性给这些属性加了拦截器,递归遍历

vue数组方法

为什么开头的例子2中,对数组push了能被检测到更新呢? push也是修改list的属性不是修改data,所以首先不会被拦截器拦截到

为什么push能被监听到,是因为vue重写了数组方法,用这些方法能触发视图更新

数组中的对象

//数组中的对象会添加拦截器
<template>
  <div id="app">
    <div v-for="(item, index) in list" :key="index">{{item}}</div>
  </div>
</template>
<script>
export default {
  name: 'App',
  data(){
    return {
      list: [{a: 1}]
    }
  },
  mounted(){
    this.list.a = 2
  }
}
</script>

Proxy

proxy是数组代理, 对obj的任何属性修改都会进入set方法,最大的特点就是对任何属性做监听,因为他不是对属性的拦截器。是对对象本身的代理

不光能对存在的属性代理。不存在的也能监听

proxy比较灵活。但是为什么vue不用这个呢?因为vue2出来的时候没有proxy,在vue3会换成proxy

练习

  1. <template>
      <div id="app">
      </div>
    </template>
    <script>
    export default {
      name: 'App',
      data(){
        return {
          a: 1,
          b: 2,
          c: 2
        }
      },
      computed: {
        value(){
          return this.a + this.b
        }
      }, 
      mounted(){
        this.a = 2
      },
      updated(){
        console.log(111)
      }
    }
    ​
    </script>
    

    不会console111因为没有在template里注册,因为规则是对template依赖收集,用到了才更新

  2. <template>
      <div id="app">
        {{a}}
      </div>
    </template>
    <script>
    export default {
      name: 'App',
      data(){
        return {
          a: 1,
          b: 2,
          c: 2
        }
      },
      computed: {
        value(){
          return this.a + this.b
        }
      }, 
      mounted(){
        this.a = 1
      },
      updated(){
        console.log(111)
      }
    }
    ​
    </script>
    

    不会console111,因为虽然a触发set了但是值没变化,这种情况不会触发视图更新和update钩子

  3. <template>
      <div id="app">
        {{a}}
      </div>
    </template>
    <script>
    export default {
      name: 'App',
      data(){
        return {
          a: [],
          b: 2,
          c: 2
        }
      },
      computed: {
        value(){
          return this.a + this.b
        }
      }, 
      mounted(){
        this.a = []
      },
      updated(){
        console.log(111)
      }
    }
    ​
    </script>
    

    会更新,因为数组是引用数据类型,两个不是同一个变量

    总结:怎么触发视图更新

    1. 被拦截器拦截到set

    2. 被模板依赖收集到(有render不用)

    3. set的value有更改