vuejs设计与实现 4-6 响应式

### Vue

1. 响应式系统的作用与实现

2. 非原始值的响应式方案

3. 原始值的响应式方案

1. 响应式系统的作用与实现

  1. 响应式数据与副作用函数
  • 副作用函数
  • “副作用函数”通常指的是除了返回值之外,还会对函数外部的状态产生影响的函数。如,一个函数修改了全局变量、修改了传入的引用类型参数(而不是返回新的对象或数组)、进行了输入输出操作(如写入文件、发送网络请求等),这些都被认为是产生了副作用。
  • 产生副作用可能会使代码的可预测性降低,增加调试和理解代码的难度。
function effect() {
    document.body.innerText = 'hello';
}
// 当effect函数执行时,它会设置body的文本内容,但除了effect函数之外的其他函数都可以读取或设置body的文本内容,即effect函数的执行会直接或间接影响其他函数的执行,这时就说effect函数产生了副作用。副作用很容易产生,如一个函数修改了全局变量;
  • 响应式数据
const obj = {a: '11'}
function effect(){
    document.body.innerText = obj.a;
}
// 定义一个obj对象,副作用函数会读取obj.a属性值,当obj.a变化时,希望副作用函数会重新执行,如果能实现这个目标,那么obj即是响应式数据

// 1. 当副作用函数effect执行时,会触发字段obj.text的读取操作;
// 2. 当修改obj.text的值时,会触发字段obj.text的设置操作;

// 考虑拦截一个对象的读取和设置操作
// 1. 创建一个用于存储副作用函数的桶bucket,Set类型;
// 2. 定义原始数据data,obj是原始数据的代理对象;分别设置get和set拦截函数,用于拦截读取和设置操作;
// 3. 当读取属性时将副作用函数effect添加到桶里,即bucket.add(effect),然后返回属性值;当设置属性值时先更新原始数据,再将副作用函数从桶里取出并重新执行,即可实现响应式数据

// 响应式数据的基本实现
// 存储副作用函数的桶
const bucket = new Set();
// 原始数据
const data = { text: "hello world"};
// 对原始数据的代理
const obj = new Proxy(data, {
    // 拦截读取操作
    get(target, key) {
        // 将副作用函数effect添加到存储副作用函数的桶中(这里的副作用函数名称暂时被定死了,后期可以通过设置一个全局变量来存储被注册的副作用函数)
        bucket.add(effect)
        // 返回属性值
        return target[key];
    },
    // 拦截设置操作
    set(target, key, newVal){
        // 设置属性值
        target[key] = newVal;
        // 把副作用函数从桶里取出并执行
        bucket.forEach(fn => fn())
        // 返回true代表设置操作成功
        return true;
    }
})

  1. 响应式系统
  • 一个响应式系统的工作流程:1. 当读取操作发生时,将副作用函数收集到桶中;2. 当设置操作发生时,从桶中取出副作用函数并执行;
// 用一个全局变了存储被注册的副作用函数
let activeEffect;
// effect函数用于注册副作用函数
function effect(fn){
    // 当调用effect注册副作用函数时,将副作用函数fn赋值给activeEffect
    activeEffect = fn;
    // 执行副作用函数
    fn();
}
effect(
    // 使用一个匿名的副作用函数
    () => {
        document.body.innerText = obj.text;
    }
)

const obj = new Proxy(data, {
    get(target, key) {
        // 将activeEffect中存储的副作用函数收集到桶中
        if (activeEffect){
            bucket.add(activeEffect)
        }
        return target[key]
    },
    set(tarhet, key, newVal) {
        target[key] = newVal;
        bucket.forEach(fn => fn())
        return true;
    }
})

// 没有在副作用函数与被操作的目标字段之间建立明确的联系
const obj = new Proxy(data, {
    // 拦截读取操作
    get(target, key) {
        // 将副作用函数activeEffect添加到存储副作用函数的桶中
        track(target, key);
        return target[key];
    },
    set(target, key, newVal) {
        target[key] = newVal;
        // 把副作用函数从桶里取出并执行
        trigger(target, key);
    }
})
// 在get拦截函数内调用track  函数追踪变化
function track(target, key){
    if(!activeEffect) return;
    let depsMap = bucket.get(target);
    if (!depsMap){
        bucket.set(target, (depsMap=new Map()))
    }
    let deps = depsMap.get(key);
    if(!deps){
        depsMap.set(key, (deps=new Set()))
    }
    depsMap.add(activeEffect)
}

// 在set拦截函数内调用trigger 副作用函数触发变化
function trigger(target, key) {
    const depsMap = bucket.get(target);
    if(!depsMap) return;
    const effects = depsMap.get(key);
    effects && effects.forEach(fn => fn())
}
  • track函数:用来追踪和收集依赖的;
  • trigger函数:用来触发副作用函数重新执行;
  1. computed
  • 本质上是一个懒执行的副作用函数,通过lazy选项使得副作用函数可以懒执行;被标记为懒执行的副作用函数可以通过手动方式让其执行;利用这个特定,设计了计算属性,当读取计算属性的值时,只需要手动执行副作用函数即可;当计算属性依赖的响应式数据发生变化时,会通过scheduler将dirty标记设置为true,代表脏,这样,下次读取计算属性的值时,会重新计算真正的值。
  1. watch
  • 本质就是观测一个响应式数据,当数据发生变化时通知并执行相应的回调函数;利用了effect及options.scheduler选项

2. 非原始值的响应式方案

  • Vue.js 3的响应式数据是基于Proxy实现的,Proxy可以为其他对象创建一个代理对象;所谓代理,指的是对一个对象基本语义的代理。允许拦截并重新定义对一个对象的基本操作。在实现代理的过程中,遇到了访问器属性的this指向问题,需要使用Reflect.*方法并指定正确的reveiver来解决
  • JavaScript中有两种对象,常规对象、异质对象;
  • 深响应、浅响应,深只读、浅只读;这里的深浅指的是对象的层级,浅响应或浅只读代表仅代理对象的第一层属性,即只有对象的第一层属性值是响应的或只读的;深响应或深只读则恰恰相反,为了实现深响应或深只读,需要在返回属性值之前,对值做一层包装,将其包装为响应式或只读式数据后再返回。

3. 原始值的响应式方案

  • ref本质上是一个包裹对象,因为JavaScript的Proxy无法提供对原始值的代理,所以需要使用一层对象作为包裹,间接实现原始值的响应式方案。由于包裹对象本质上与普通对象没有区别,因此为了区分ref与普通响应式对象,还为包裹对象定义了一个值为true的属性,即__v_isRef,用它来作为ref的标识。
  • ref除了能够用于原始值的响应式方案外,还能用来解决响应式丢失的问题,为了解决该问题,实现了toRef及toRefs,本质上是对响应式数据做了一层包装或者叫访问代理。
  • 自动脱ref的能力,自动对暴露到模板中的响应式数据进行脱ref处理,这样,用户在模板中使用响应式数据时,就无须关系一个值是不是ref了。






参考&感谢各路大神

1. vue.js设计与实现-霍春阳

posted @ 2024-07-24 21:21  安静的嘶吼  阅读(6)  评论(0编辑  收藏  举报