Vue 数据响应式的理解

151 阅读3分钟

1、什么是响应式

响应式,是实现数据驱动视图的第一步,监听数据变化,使得用户在设置数据时,可以通知vue内部进行视图更新。

主要原理在文档的 深入响应式原理章节中可以找到。

用代码来实现:

<template>
    <div>
        <div> {{ name }} </div>
        <button @click="changeName">改名字</button>
    </div>
</template>
<script>
export default {
    data () {
        return {
            name: 'A'
        }
    },
    methods: {
        changeName () {
            this.name = 'B'
        }
    }
}
</script>

2、实现响应式

在深入学习数据响应式原理之前

我们需要学习一下

getters 与 setters

defineProperty()

需求一:姓名不要括号得到值:

let obj2 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    return this.姓 + this.名;
  },
  age: 18
};

console.log("需求二:" + obj2.姓名);

总结:get用于获取一个值(在函数前面加get,不加括号的函数)

需求二:姓名可以被写

let obj3 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    return this.姓 + this.名;
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  },
  age: 18
};
//在定义完一个对象之后,添加新的get和set,只能通过Object.defineProperty()
var _xxx=0      //声明全局变量
Object.defineProperty(obj3,'xxx',{
    get(){
        return _xxx
    },
    set(value){
    _xxx=value
    }
})

obj3.姓名 = '高媛媛'

console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)

总结:set接受一个新的值。用xxx 触发set函数

在定义完一个对象之后,添加新的getset,只能通过Object.defineProperty()

通过上面的代码,已经知道getter/setter用于对属性的读写进行监控。 Object.defineProperty可以给对象添加属性value,可以给对象添加getter/serter

使用代理

需求三:需求:我们需要存储一个n的值,有一个条件就是n不能小于0。 请问我们怎样保证,不管用户怎么修改n的值,都可以满足我们的要求。

let myData = {n:0}
let data = proxy({ data:myData }) // 括号里是匿名对象,无法访问

function proxy({data}/* 解构赋值,*/){
  let value = data.n    // 声明一个新的value来获取data.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(value){
      if(value<0)return//这句话多余了
      data.n = value
    }
  })
  
  return obj // obj 就是代理
}


  • 添加代理data成为myData的代理对象,对这个对象进行操作

  • myData所有属性进行监控

  • 为什么要监控?,为了防止myData的属性变了,data不知道

  • vm知道了又如何? 知道属性变了就可以调用render(data)

  • 声明一个新的value来获取data.n,这样就可以监听data.n的变化

  • 接着定义一个新的(虚拟)n来覆盖原来的data.n,如果你要读取n的值,就调用get函数;如果你要设置新的值,就把新的值给value

其他扩展:

Vue.set 和 this.$set

  • 作用一: 新增key
  • 如果没有创建过,自动创建代理和监听
  • 触发UI更新(但不会立刻更新)

举个例子:

//引用完整版 Vue
import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    obj: {
      a: 0 // obj.a 会被 Vue 监听 & 代理
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      Vue.set(this.obj,'b',1)
      // this.$set(this.obj,'b',1)  //这句和上面等同
    }
  }
}).$mount("#app");

数组的解决方案

Q: 刚才说了如果data中新增了key有两种方法可以解决,但有没有可能没有办法提前声明好,只能使用set呢?比如说data里有数组怎么办?

A:好问题,继续看。

方案一:可以用Vue.set或this.$set(不推荐)

方案二:尤雨溪为我们提供了一种数组的变异方法

//引用完整版 Vue
import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    array: ["a", "b", "c"]
  },
  template: `
    <div>
      {{array}}
      <button @click="setD">set d</button>
    </div>
  `,
  methods: {
    setD() {
      // this.$set(this.array,3,'d')  //可以实现,不推荐
      this.array.push('d')  //可以实现,推荐
    }
  }
}).$mount("#app");

尤雨溪的做法: 篡改数组的API,见文档中变更方法,这 7 个API都会被Vue篡改,调用后会更新UI.