Vue3响应式原理

  1. 什么是响应式

响应式是一种可以使我们声明式地处理变化的编程范式。举个栗子,在excel表格中,A3中的值是由A1和A2中的值相加而来,如果改变A1和A2中的值,A3则会自动计算。在js中怎么实现呢?我们需要定义一个函数update(),其中会返回A1和A2的和。这个update函数就被称为副作用effect,而其中用到的A1和A2则被视为这个作用的依赖。然后我们要在依赖变化时,调用这个副作用函数。如何知道依赖变化了呢?JavaScript 中有两种劫持 property 访问的方式:getter / settersProxies以下我们来介绍Proxy这种方式,也就是Vue3中reactive响应式。

  1. 数据劫持(proxy代理)

Vue3中,reactive响应式数据,是由Proxy实现的。以下是实现的简易版reactive。

reactive.ts

import { track, trigger } from "./effect.js";
const isObject = (target) => target !== null && typeof target === 'object';
export const reactive = (target) => {
    return new Proxy(target, {
        get(target, key, receiver) {
            let res = Reflect.get(target, key, receiver);
            track(target, key);
            // 如果 target 的属性 还是一个对象,那么将该属性也变成响应式
            if (isObject(res)) {
                return reactive(res);
            }
            return Reflect.get(target, key, receiver);
        },
        set(target, key, newValue, receiver) {
            let res = Reflect.set(target, key, newValue, receiver);
            // 要在修改属性值之后 进行 依赖处理
            trigger(target, key);
            return res;
        }
    });
};

上述代码中,使用Proxy进行数据劫持。如果访问target中的属性时,会触发getter函数。getter函数中,会调用track()函数进行依赖收集,然后判断该属性的属性值是否为一个对象,如果不是,则返回该属性值(Reflect.get()),如果是,则继续调用reactive函数,实现深层次的响应。

  1. 依赖收集tracker和副作用触发trigger

在依赖变化时,我们需要知道哪几个依赖变化了,并将他们收集起来。然后触发副作用函数。

track(target, key)中,我们通过WeakMap 这种数据结构来收集副作用函数,WeakMap的键对应的是传入的源对象target,而值是一个Map对象;

Map对象的键对应的是传入的target的属性名key,而值是一个Set;

Set中用于存储副作用函数。

WeakMap、Map、Set是ES6新增的数据结构,不了解的可查看阮一峰的ES6入门文档。

数据格式如下:

trigger(target, key)中,我们根据传入的target和key,通过WeakMap,找到对应的副作用函数Effect,然后执行。

以下是完整示例effect.ts

let activeEffect: Function
export const effect = (fn: Function) => {
    // 这里使用闭包,将副作用函数传递出去
    const _effect = function() {
        activeEffect = _effect
        fn()
    }
    // 开始时,先触发一次,渲染页面
    _effect()
}

// 定义一个WeakMap
const targetMap = new WeakMap()
// 依赖收集
export const track = (target: object, key: string | symbol) => {
    let depsMap = targetMap.get(target)
    if(!depsMap){
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }
    let deps = depsMap.get(key)
    if(!deps) {
        deps = new Set()
        depsMap.set(key, deps)
    }
    deps.add(activeEffect)
}
// 依赖处理
export const trigger = (target: object, key: string | symbol) => {
    const depsMap = targetMap.get(target)
    const deps = depsMap.get(key)
    deps.forEach((effect: () => any) => {
        effect()
    })
}
  1. 测试一下

现在我们来测试一下,新建一个html,并将reactive.ts和effect.ts引入:

index.html

<div id="app"></div>
<script type="module">
    import {reactive} from './reactive.js'
    import {effect} from './effect.js'
    const man = reactive({
        name: 'zhangsan',
        age: 19,
        job: {
            job1: {
                salary: '20k'
            }
        }
    })
    effect(()=>{
        console.log('数据改变了');
        console.log(man.name, man.age,man.job.job1.salary);
        document.querySelector('#app').innerText = `${man.name} - ${man.age} - ${man.job.job1.salary}`
    })
    setTimeout(() => {
        man.name = '张三'
        setTimeout(()=>{
            man.age = 23
            setTimeout(()=>{
                man.job.job1.salary = '21K'
            }, 1000)
        },1000)
        console.log(man);
    },1000)
</script>

这里我们只是模拟一下响应式,简单的操作一下Dom,没有实现Vue中虚拟Dom和模板解析的部分。上面代码首先通过reactive函数生成一个响应式对象。我们在effect中传入一个副作用函数,函数中,访问了name、age、salary,此时,Proxy代理对象就会通过getter函数进行依赖收集。在定时器中,属性值发生变化,Proxy代理对象就会调用setter函数,触发收集到的副作用函数。

我们可以使用live-server启动试验一下:

可以发现,一秒后,name发生变化,两秒后age也跟着改变,三秒后salary也变化了。至此我们的reactive响应式就算是成功了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值