Vue 数据响应式的理解

369 阅读1分钟

数据响应式

什么是响应式?

  • 若一个物体能对外界的刺激做出反应,那它就是响应式
  • 例:我打你,你会喊疼,这就是响应式

Vue的data 是响应式

  • const vm = new Vue({data:{n:0})
  • 如果我修改了 vm.n,那么 UI中的 n就会响应我

例子

const myData = {
  n: 0
}
console.log(myData)
const vm = new Vue({
  data: myData,
  template: `
    <div>{{n}}<button @click = 'add'>+10</button</div>
  `,
  methods: {
    add() {
      myData.n += 10
    }
  }
}).$mount('#app')

setTimeout(() => {
  myData.n += 10
  console.log(myData)
}, 3000)
/*
第一次打印
{n: 0} 

第二次个打印及内部
{__ob__: we} 
n: (...)    
__ob__: we {value: {…}, dep: ce, vmCount: 1}
get n: ƒ ()
set n: ƒ (t)
__proto__: Object
*/

解析

  • data的值n发生了变化,这就是响应式

  • 第二次内部的{n:(...)}说明 n 并不是真实的存在,而是有一个get n 和 set n,他们来模拟对n 的读写操作

  • 而且Vue让vm 成为 myData的代理,会对myData的属性进行监控,为了防止myData的属性变了,但vm却不知道

  • 为什么要让vm 知道?知道属性变了才可以调用render(data)

Object.defineProperty和data的联系

  • 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter
  • getter/setter 用于对属性的读写进行监控

例子:需求n不能小于0

let myData = { n: 0 }
let data5 = proxy({ data: myData })
function proxy({ data }) {
  //首先拿到这个值
  let value = data.n
  //然后这里在从新设置n,原来的data.n 就被替代了
  Object.defineProperty(data, 'n', {
    get() {
      return value
    },
    set(newValue) {
      if (newValue < 0) return
      value = newValue
    }
  })
  //上面代码话会监听 data
  const obj = {}
  Object.defineProperty(obj, 'n', {
    get() {
      return data.n
    },
    set(val) {
      if (val < 0) return
      data.n = val

    }
  })
  return obj//代理
}
console.log(`需求五 :${data5.n}`)//0
myData.n = -1
console.log(`需求五 :${data5.n}`)//0
myData.n = 1
console.log(`需求五 :${data5.n}`)//1
data5.n = 2
console.log(`需求五 :${data5.n}`)//2

上面代码中可以看出:

  • 当myData.n 发生变化时,会被监听
  • 当data5.n 发生变化时也会被监听
  • 这样就可以防止在两个地方都写成小于0了

!!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
    }
  }
}).$mount('#app')

存在问题:当我点击set b视图不会显示1,Vue没法监听不存在的obj.b

解决办法:使用Vue.set 或者 this.$set

new Vue({
  data: {
    obj: {
      a: 0,//obj.a 会被 Vue监听 和 代理
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click='setB'>set b</button>
      <button @click ='add'>+1</button>
    </div>
  `,
  methods: {
    setB() {
      Vue.set(this.obj,'b',1)//它会传给Object.defineProperty(obj,'b',{b:1})
      
      //this.$set(this.obj,'b',1)第二种写法,和前面一个写法是相等的,没有区别
    },
    add() {
      this.obj.b += 1//因为前面已经设置了,所以这里的 obj.b 是已经存在了
    }
  }
}).$mount('#app')

对于数组

new Vue({
  data: {
    array: ['a', 'b', 'c']
    //数组你可以理解为 array:{0:'a',1:'b',2:'c'}
  },
  template: `
    <div>
      {{array}}
      <button @click='setD'>set d</button>
    </div>
  `,
  methods: {
    setD() {
      //this.array[3] = 'd'//这样根本不会设置,它里面没有 array[3],它观察不到
       Vue.set(this.array, 3, 'd')
    }
  }
}).$mount('#app')

存在问题this.array[3] = 'd' 这样不会显示,Vue.set(this.array, 3, 'd') 这样可以,但是,这可能就是用户的数据,根本不知道有多少

解决办法:Vue中有7个API都被篡改了,pop、push、reverse、shift、sort、splice、unshift,调用后会更新UI,其中 push方法就可以使用,它可以直接添加到上面

new Vue({
  data: {
    array: ['a', 'b', 'c']
    //数组你可以理解为 array:{0:'a',1:'b',2:'c'}
  },
  template: `
    <div>
      {{array}}
      <button @click='setD'>set d</button>
    </div>
  `,
  methods: {
    setD() {
      this.array.push('d')
    }
  }
}).$mount('#app'