← 返回首页
Vue3基础教程(二十)
发表时间:2021-08-10 01:25:29
Vue3响应原理

Vue3的响应式原理的核心是: - 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等等。 - 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作。

相关的参考文档:

Proxy参考文档

Reflect参考文档

1.什么是Proxy MDN 上的描述是:Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的。

下面我们看个例子,就一目了然了。

let obj = {
    a : 1
}
let proxyObj = new Proxy(obj,{
    get : function (target,prop) {
        return prop in target ? target[prop] : undefined
    },
    set : function (target,prop,value) {
        target[prop] = 888;
    }
})

console.log(proxyObj.a);        // 1
console.log(proxyObj.b);        // undefined

proxyObj.a = 100;
console.log(proxyObj.a)         // 888

上述例子中,我们事先定义了一个对象 obj , 通过 Proxy 构造器生成了一个 proxyObj 对象,并对其的 set(写入) 和 get (读取) 行为重新做了修改。

当我们访问对象内原本存在的属性时,会返回原有属性内对应的值,如果试图访问一个不存在的属性时,会返回0 ,即我们访问 proxyObj.a 时,原本对象中有 a 属性,因此会返回 1 ,当我们试图访问对象中不存在的 b 属性时,就返回 undefined,当我们试图去设置新的属性值的时候,总是会返回 888 ,因此,即便我们对 proxyObj.a 赋值为 100 ,但是并不会生效,依旧会返回 888!

2.Vue3的响应式原理分析

Vue3的响应式通过Proxy+Reflect实现。 实例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Proxy 与 Reflect</title>
</head>
<body>
<script>
    const user = {
        name: "John",
        age: 12
    };

    /*
    proxyUser是代理对象, user是被代理对象
    后面所有的操作都是通过代理对象来操作被代理对象内部属性
    */
    const proxyUser = new Proxy(user, {

        get(target, prop) {
            console.log('劫持get()', prop)
            return Reflect.get(target, prop)
        },

        set(target, prop, val) {
            console.log('劫持set()', prop, val)
            return Reflect.set(target, prop, val); // (2)
        },

        deleteProperty(target, prop) {
            console.log('劫持delete属性', prop)
            return Reflect.deleteProperty(target, prop)
        }
    });
    // 读取属性值
    console.log(proxyUser === user)
    console.log(proxyUser.name, proxyUser.age)
    // 设置属性值
    proxyUser.name = 'bob'
    proxyUser.age = 13
    console.log(user)
    // 添加属性
    proxyUser.sex = '男'
    console.log(user)
    // 删除属性
    delete proxyUser.age
    console.log(user)
</script>
</body>
</html>

运行结果:

false
劫持get() name
劫持get() age
John 12
劫持set() name bob
劫持set() age 13
{name: "bob", age: 13}
劫持set() sex 男
{name: "bob", age: 13, sex: "男"}
劫持delete属性 age
{name: "bob", sex: "男"}

把上小节案例改写如下:

通过点击按钮实现更新学生的课程列表。

<template>
    <!--与vue2模板的区别:不需要定义一个根标签-->
    <h1>第一个Vue3案例</h1>
    <hr>
    <table class="mytab">
        <tr>
            <th>学号</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>课程</th>
        </tr>
        <tr>
            <td>{{proxyStu.sid}}</td>
            <td>{{proxyStu.sname}}</td>
            <td>{{proxyStu.age}}</td>
            <td>
                <li v-for="(c,index) in proxyStu.courses" :key="index">{{c}}</li>
            </td>
        </tr>
        <tr>
            <td colspan="4">
                <button @click="changeStudentInfo">更改学生属性</button>
            </td>
        </tr>
    </table>
</template>

<script lang="ts">
    import {defineComponent, ref, reactive} from 'vue';

    export default defineComponent({
        name: 'App',
        setup() {
            let stu: any = {
                sid: 'S001',
                sname: 'zhangsan',
                age: 20,
                courses:['英语', '物理', '化学']
            }
            //返回代理对象
            const proxyStu = reactive(stu);
            function changeStudentInfo() {
                proxyStu.courses[1] = '语文'
                proxyStu.courses.push('历史');
                console.log(stu);
                console.log(proxyStu);
            }
            return {
                stu,
                proxyStu,
                changeStudentInfo
            }
        }
    });
</script>

运行效果: