【Vue】数据代理

Vue2 的响应式

实现原理:

  • 数组:通过重写 [更新方法] 来实现拦截
  • 对象:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)

存在问题:

  • 数组:通过下标 [添加]、[删除]、[修改] 元素,页面不会更新
  • 对象:[添加]、[删除] 属性,页面不会更新



代理数组数据

Vue 会加工、代理 data_data,通过浏览器控制台可以观察到,vm._data 的属性都是响应式数据(有对应的 getter、setter)
但是,如果我们后期自己给 Vue 实例添加属性,该属性没有经过 Vue 加工,所以不是响应式数据 (没有 getter、setter)

如果我们在 data 中放置一个数组数据,该数组会被代理但数组内的元素不会被代理
就是说,只有修改数组本身,页面才会响应;而修改数组元素的话,页面不会响应

所以,通过 this.arr[index] [删除]、[添加]、[修改] 数组的元素,数据会被修改,但页面不会重新渲染

<button @click="arr.length = arr.length - 1"> 删除数组元素 button>
<button @click="arr[arr.length] = 'z'"> 添加数组元素 button>
<button @click="arr[0] = '0'"> 修改数组元素 button>
<span v-for="item in arr"> {{item}} span>
let vm = new Vue({
    el: "#app",
    data: {
        arr: ["s", "p", "m"]
    }
});

  1. 变更方法

可以使用能改变原数组的方法:
push()、②pop()、③unshift()、④shift()、⑤splice()、⑥sort()、⑦reverse()
这 7 个方法都被 Vue 重写过,修改完数据后,能即时显示回页面

<button @click="arr.shift()"> 删除数组元素 button>
<button @click="arr.push('z')"> 添加数组元素 button>
<button @click="arr.splice(0, 1, '0')"> 修改数组元素 button>

可以发现,现在不只是数据发生了变化,页面也会重新渲染了


  1. 替换数组

可以通过 filtermap 等函数,对原数组变量进行重新赋值

<button @click="arr = arr.filter(ele => ele == 's')"> 过滤数组元素 button>
<span v-for="item in arr"> {{item}} span>

你可能认为这将重新渲染整个列表。事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用 实现了一些智能的启发式方法,所以用一个含有大量相同元素的数组去替换原来的数组是非常高效的操作



代理对象数据

如果我在 data 里面放置了一个对象数据,该对象会被代理对象里面的属性也会被代理
就是说,不论我们修改对象本身,还是 [修改] 对象的属性,数据都是响应性的,会即时显示回页面

但是,如果我们为对象 [添加]、[删除] 属性,数据会被改变,但页面不会重新渲染

<button @click="obj.sex = 'male'"> 添加 sex 属性 button>
<button @click="delete obj.name"> 删除 name 属性 button>
<button @click="obj.age++"> 修改 age 属性值 button>
<span v-for="item in obj"> {{item}} span>
let vm = new Vue({
    el: "#app",
    data: {
        obj: { name: "superman", age: 22 }
    }
});

  1. 替换对象

我们可以把整个对象数据替换掉,来显示更新后的对象数据

<button @click="change"> 修改整个对象 button>
<span v-for="item in obj"> {{item}} span>
let vm = new Vue({
    el: "#app",
    data: {
        obj: { name: "superman", age: 22 }
    },
    methods: {
        change() {
            let newObj = {}; // 创建新对象
            for (let [key, value] of Object.entries(this.obj)) { // 使用解构赋值
                newObj[key] = value; // 复制原对象的属性
            };
            newObj.sex = "male"; // 添加属性 / 修改属性值
            this.obj = newObj; // 对原对象重新赋值
        }
    }
});



set 方法

我们知道 data 会被 Vue 代理为 _data,在这之前,Vue 会先对 data 内的数据进行加工,使其成为响应性数据
如果我们直接在浏览器控制台给 Vue 实例添加属性,那这个后期添加上来的属性,因为没有被 Vue 加工,所以不是响应性数据

vm[._data].obj.gender = 'male'; // 无效写法

Vue.set(对象, "属性名", 属性值) / vm.$set(对象, "属性名", 属性值) - 用于设置 [属性] / [元素](响应性数据)

Vue.set(vm[._data].obj, 'gender', 'male'); // 牛逼写法
vm.$set(vm[._data].obj, 'gender', 'male'); // 也牛逼写法

这是 Vue 提供给我们的 API,使我们后期添加的数据,也经过 Vue 处理,成为响应性数据

<p v-for="(item, name, index) of obj"> {{index}} - {{name}} - {{item}} p>
<button @click="change"> 调用 set 修改对象数据 button>
let vm = new Vue({
    el: "#app",
    data: {
        obj: { name: "superman", age: 21, }
    },
    methods: {
        change() {
            Vue.set(this.obj, "age", "22"); // 修改属性值
            this.$set(this.obj, "sex", "male"); // 添加属性
        }
    }
});
  • 这个方法也可以应用到数组身上,给数组 [添加元素] / [修改元素值]:
    vm.$set(数组, 下标, 元素值) / Vue.set(数组, 下标, 元素值)
Vue.set(vm[._data].arr, 0, 'new value');
vm.$set(vm[._data].arr, 0, 'new value');

注意:添加的对象不能是 [Vue 实例] (vm) / [Vue 实例的根数据对象] (vm._data)
就是说,不能通过这个方法给 Vue 添加 [响应式数据]



delete 方法

  • 删除 [属性] - 过滤 [对象],然后覆盖原数据
  • 删除 [元素] - 过滤 [数组],然后覆盖原数据 / 使用 [变更方法]
  • 或者,调用 delete 方法

Vue.delete(对象, "属性名") / vm.$delete(对象, "属性名") - 用于删除 [属性] / [元素]

<template>
    <div>
        <p v-show="person.name">{{ person.name }}p>
        <p>{{ person.age }}p>
        <button @click="change_person">点击修改button>
    div>
template>

<script>
import Vue from "vue"
export default {
    name: "App",
    data() {
        return {
            person: { name: "superman", age: 21 }
        }
    },
    methods: {
        change_person() {
            Vue.delete(this.person, "name")
            // this.$delete(this.person, "name")
        }
    },
};
script>



Vue3 的响应式

  • 通过 [Proxy 代理] 拦截对象中属性的变化,包括 [获取]、[添加]、[删除]、[修改] 属性
  • 使用 [Reflect 反射] 操作 (获取、添加、删除、修改) 被代理对象的属性
new Proxy(data, {
    // 拦截 [获取] 属性
    get(target, prop) {
        return Reflect.get(target, prop) // 返回属性值
    },
    // 拦截 [添加]、[修改] 属性
    set(target, prop, value) {
        return Reflect.set(target, prop, value) // true-执行成功、false-执行失败
    },
    // 拦截 [删除] 属性
    deleteProperty(target, prop) {
        return Reflect.deleteProperty(target, prop) // true-删除成功、false-删除失败
    }
})

你可能感兴趣的:(Vue,vue.js,vue)