Vue 的数据响应式

2021-01-03  本文已影响0人  我是Msorry

深入响应式原理文档

从下面这个小例子带你走进Vue数据响应式

const myData = {
  n: 0
}
 console.log(myData) 

new Vue({
  data: myData,
  template: `
    <div>{{n}}</div>
  `
}).$mount("#app");

setTimeout(()=>{
  myData.n += 10
  console.log(myData) 
},3000)

Object {n: 0}
Object {n: (...)}

一个对象作为数据传递给Vue后,变得不一样了,究竟发生了什么?

const vm = new Vue({data:myData})做了什么?

1.vm成为myData的代理(proxy),如果没有vmthis会代指这个对象
2.对myData的所有属性进行监听,一旦myData的属性发生变化,vm立即知道
3.属性变化后,调用render(data),UI = render(data)

const data = {a:1,b:2}

function proxy({data}) {
  const obj = {}
  for (let key in data) {
    //监听data
    let value = data[key]
    Object.defineProperty(data, key, {
        get() {
            return value
          },
          set(v) {
            value = v
          }
      })
      // obj 就是代理
    Object.defineProperty(obj, key, {
      get() {
          return data[key]
        },
        set(value) {
          data.n = value
        }
    })

  }
  return obj 
}
console.log(data)
let vm = proxy({data:data})
console.log(data)
//在外部修改数据,看vm是否也跟着发生变化
data.a = 2
console.log(vm.a)

Object {a: 1,b: 2}
Object {a: (...),b: (...)}
2

同理,Vue对methods和computed也有处理

数据响应式

当数据发生改变后,Vue会通知到使用该数据的代码,自动视图更新

Vue只监听第一层属性的变化,不监听嵌套属性,否则效率低

因为obj.b没有在data中没有,所以不会被监听,undefined状态的数据Vue不会渲染到页面

new Vue({
  data: {
    obj: {
      a: 0 // obj.a 会被 Vue 监听 & 代理
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      this.obj.b = 1; //请问,页面中会显示 1 吗?
    }
  }
}).$mount("#app");

解决办法

1.用.set方法

Vue.set(this.obj,"b",1)
this.$set(this.obj,"b",1)
console.log(Vue.set===this.$set)//true,这两种方式一模一样

Vue.set做了两件事:新增属性,并自动地对这个属性创建代理和监听
Vue发现这个属性从undefined的状态变成1,触发render,更新页面

2.提前在data中声明好,初始值设为undefined


但是,当为数组时,无法提前声明,每次用Vue.set很麻烦

new Vue({
  data: {
    array: ["a", "b", "c"]
  },
  template: `
    <div>
      {{array}}
      <button @click="setD">set d</button>
    </div>
  `,
  methods: {
    setD() {
      this.array[3] = "d"; //请问,页面中会显示 'd' 吗?
      // 等下,你为什么不用 this.array.push('d')
    }
  }
}).$mount("#app");

解决方法

this.array.push(value)
console.log(this.array)

通过控制台可以看出,push方法变了,Vue篡改了数组的API
变异方法文档

Vue的变异方法实现思路(并不是源码)

Vue会给这个数组加一层原型,对于数组的变化,自动Vue.set
篡改push方法,对于Array的变化通过Vue.set通知给Vue

class VueArray extends Array{
  push(...args){
      const oldLength = this.length //this 当前数组
      super.push(...args)//调用父类的push
      for(let i = oldLength;i < this.length;i++){
        Vue.set(this,i,this[i])
      }
  }
}

const vueArrayPrototype = { 
  push: function(){ 
        return Array.prototype.push.apply(this, arguments) 
  } 
} 
vueArrayPrototype.__proto__ = Array.prototype //不是标准属性

const array = Object.create(vueArrayPrototype) array.push(1)

总结

  1. const vm = new Vue({data:myData}) 这个语句中,this就是vmvmmyData的代理,所以this.n能够读取
  2. Vue没有办法监听和代理新的Key
  3. 要使用set来新增Key,创建代理和监听,更新UI
  4. 最好提前把属性都写出来,不要新增Key
  5. 但是数组做不到「不新增Key」
  6. 数组也可以用set来新增Key,创建代理和监听,更新UI
  7. Vue为解决这个困扰,篡改7个数组API
  8. 这7个API会自动处理监听和代理,并更新UI

https://www.jianshu.com/p/4dff7c2cdaaa

上一篇下一篇

猜你喜欢

热点阅读