数据响应式的中心思想

Vue.js 一个核心思想是数据驱动,视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。

我对 Vue 数据响应式的理解 - 图1
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

检测变化的注意事项

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

对于对象

由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

也就是说,必须有一个’n’,才能监听并代理obj.n(mydata)

  1. Object.defineProperties(obj,'n',{...})

但是如果没有给n怎么办呢?

示例一

Vue会给出一个警告

  1. new Vue({
  2. data: {},
  3. template: `
  4. <div>{{n}}</div>
  5. `
  6. }).$mount("#app");

2148_0.png

但是有办法绕过这个警告

示例二

data是一个对象,对象里面有个a,但是在页面中显示的是对象的b,因为Vue只检查第一层,所以不会报错

  1. new Vue({
  2. data: {
  3. obj: {
  4. a: 0 // obj.a 会被 Vue 监听 & 代理
  5. }
  6. },
  7. template: `
  8. <div>
  9. {{obj.b}}
  10. <button @click="setB">set b</button>
  11. </div>
  12. `,
  13. methods: {
  14. setB() {
  15. this.obj.b = 1; //页面中不会显示 1
  16. }
  17. }
  18. }).$mount("#app");

Vue只监听obj.a,如果a产生变化能反映在视图里,但出现在页面里的是b,如果改变b不会反应在视图里,因为Vue没法监听一开始不存在的obj.b。

解决方法

方法一:开始声明好key,后面再加属性

  1. data: {
  2. obj: {
  3. a: 0 // obj.a 会被 Vue 监听 & 代理
  4. + b:undefind
  5. }
  6. },

方法二: 使用Vue.set或者this.$set

  1. methods: {
  2. setB() {
  3. Vue.set(this.obj, "b", 1)
  4. //或
  5. this.$set(this.obj, "b", 1)
  6. }
  7. }
  • 作用:
    • 增加key
    • 自动创建代理和监听(如果没有创建过)
    • 触发UI更新(不会立刻更新)

对于数组

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

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

举个例子:

  1. var vm = new Vue({
  2. data: {
  3. items: ['a', 'b', 'c']
  4. }
  5. })
  6. vm.items[1] = 'x' // 不是响应性的
  7. vm.items.length = 2 // 不是响应性的

下面我们来看一个实际示例:

  1. new Vue({
  2. data: {
  3. array: ["a", "b", "c"]
  4. },
  5. template: `
  6. <div>
  7. {{array}}
  8. <button @click="setD">set d</button>
  9. </div>
  10. `,
  11. methods: {
  12. setD() {
  13. this.array[3] = "d";
  14. }
  15. }
  16. }).$mount("#app");
  • 如果我们直接添加d是做不到的,因为这个数组实际上是array:{0:’a’,1:’b’,2:’c’},添加一个d相当于添加一个下标。
  • 虽然使用 vm.$set 实例方法可以解决这个问题,但这样我们的所有数组都要用set,没有办法提前声明。
  • 而且因为无法提前预测数组的长度,也不能直接在data里提前声明。

解决方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新,见文档变异方法章节。
方法包括

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

总结

对象中新增的key

  • Vue没有办法事先监听和代理
  • 要使用set来新增key,创建监听和代理,更新UI
  • 最好提前把属性都写出来,不要新增key
  • 但数组做不到不新增key

数组中新增的key

  • 也可以用set来新增key,更新UI(但不能创建监听和代理)
  • 不过Vue篡改了7个API方便你对数组进行增删
  • 这七个API会自动处理监听和代理,并更新UI
  • 结论:数组新增key最好通过7个API