【Vue】数据代理

Vue2 的响应式

实现原理:

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

data 属性中,只有属性描述符 configurable 为 true 的属性才可以被 VueJS 进行依赖收集。VueJS 会在 data 属性中的每个属性上调用 Object.defineProperty() 方法,为它们添加 getter 和 setter。这样,当你访问或修改 data 属性中的属性时,VueJS 就可以知道它们被访问或修改了,从而触发相应的更新。


存在问题:

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



代理数组数据

Vue 会加工、代理 data_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>
const vm = new Vue({
    el: '#app',
    data: {
        arr: ['s', 'p', 'm'],
    },
});

方法 1:使用变更方法

可以使用能改变原数组的方法:1. push()、 2. pop()、 3. unshift()、 4. shift()、 5. splice()、 6. sort()、 7. reverse();这 7 个方法都被 Vue 重写过,使用它们更新数据能触发页面的重新渲染。

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

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


方法 2:替换数组

可以通过 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>
const vm = new Vue({
    el: '#app',
    data: {
        obj: { name: 'superman', age: 22 },
    },
});

方法:替换对象

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

<button @click="change">修改整个对象</button>
<span v-for="item in obj"> {{item}} </span>
const 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 & $data,在这之前,Vue 会先对 data 内的数据进行加工,使其成为响应性数据。
如果我们直接在浏览器控制台给 Vue 实例添加属性,那这个后期添加上来的属性,因为没有被 Vue 加工,所以不是响应性数据。

vm.obj.gender = 'male'; // 无效写法
// 等效于 vm.$data.obj.gender = 'male'

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

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

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

<p v-for="(item, name, index) of obj">{{index}} - {{name}} - {{item}}</p>
<button @click="change">调用 set 修改对象数据</button>
const 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 方法
  • 删除 [元素]:过滤 [数组],然后覆盖原数据 / 使用 [变更方法] / 使用 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-删除失败
    },
});

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值