实现vue3源码-reactive及effect

231 阅读3分钟

vue3使用这么久,也做了项目,但仍然对一些深层不太了解,遂找到几个狐朋狗友一起撸源码

本章作为vue3源码的大门,我和vue3源码也就此展开一段历程,望掘宝们也能工作顺利

vue3响应式核心

reactive

proxy

reactive实现

effect

track<依赖收集>

trigger<依赖触发>

代码块

结语

reactive

proxy

proxy可以简单理解为一个拦截器,可以对外界操作进行拦截层的过滤/改写等操作

ES6提供Proxy构造函数,即用于生成Proxy实例进行后续使用

const proxy = new Proxy(target, handler)

其中target指的是需要拦截的目标对象,handler定义拦截行为,无操作等同于直接操作原对象

reactive实现

先看下代码基本结构,响应式最重要的就是依赖收集和依赖触发,还需和effect搭配使用

export function reactive(raw: any) {
    return new Proxy(raw, {
        get(target, key) {
            const res = Reflect.get(target, key)

            // 依赖收集
            track(target, key)
            return res
        },
        set(target, key, value) {
            const res = Reflect.set(target, key, value)

            // 触发依赖
            trigger(target, key)
            return res
        }
    })
}

这里为什么不用 key[value] 呢,在代理对象中使用 Reflect 才能得到一直符合期望的值<展开太多,可自行百度>

effect

class ReactiveEffect {
    private _fn: any
    constructor(fn) {
        this._fn = fn
    }
    run() {
        this._fn()
    }
}

export function effect(fn) {
    // fn

    const _effect = new ReactiveEffect(fn)
    _effect.run()
}

基本的副作用函数也实现了,下面可以实现track及trigger

track

class ReactiveEffect {
    private _fn: any
    constructor(fn) {
        this._fn = fn
    }
    run() {
        // 全局变量收集effect fn
        activeEffect = this
        this._fn()
    }
}

const targetMap = new WeakMap()
export function track(target, key) {
    const depMap = targetMap.get(target)
    if (!depMap) {
        const depMap = new Map()
        targetMap.set(target, depMap)
    }
    const dep = depMap.get(key)
    if (!dep) {
        const dep = new Set()
        depMap.set(key, dep)
    }
}

// 全局变量收集effect fn
let activeEffect
export function effect(fn) {
    // fn

    const _effect = new ReactiveEffect(fn)
    _effect.run()
}

targetMap数据格式

1690210243467.jpg

关系一层层建立起来的,dep存放起来,使用取出即可

trigger

依赖已经收集起来,只需要用trigger执行即可

export function trigger(target, key) {
    let depsMap = targetMap.get(target)
    let dep = depsMap.get(key)

    for (const effect of dep) {
        effect.run()
    }
}

基本的依赖收集,依赖触发就完成了,即实现了vue3的响应式的核心源码

代码块

effect.ts

class ReactiveEffect {
    private _fn: any
    constructor(fn) {
        this._fn = fn
    }
    run() {
        // 全局变量收集effect fn
        activeEffect = this
        this._fn()
    }
}


const targetMap = new WeakMap()
export function track(target, key) {
    // target -> key -> dep
    let depsMap = targetMap.get(target)
    // 初始化判断
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }

    let dep = depsMap.get(key)
    if (!dep) {
        dep = new Set()
        depsMap.set(key, dep)
    }
    dep.add(activeEffect)

}

export function trigger(target, key) {
    let depsMap = targetMap.get(target)
    let dep = depsMap.get(key)

    for (const effect of dep) {
        effect.run()
    }
}

// 全局变量收集effect fn
let activeEffect
export function effect(fn) {
    // fn

    const _effect = new ReactiveEffect(fn)
    _effect.run()
}

reactive.ts

import { track, trigger } from './effect'
export function reactive(raw: any) {
    return new Proxy(raw, {
        get(target, key) {
            const res = Reflect.get(target, key)

            // 依赖收集
            track(target, key)
            return res
        },
        set(target, key, value) {
            const res = Reflect.set(target, key, value)

            // 触发依赖
            trigger(target, key)
            return res
        }
    })
}

补充<优化effect>

我们在调用effect时,返回一个runner函数,它可以拿到我们传给effect的fn,然后调用fn获取它的返回值

image.png

class ReactiveEffect {
    private _fn: any
    constructor(fn) {
        this._fn = fn
    }
    run() {
        // 全局变量收集effect fn
        activeEffect = this
        return this._fn()
    }
}
// 全局变量收集effect fn
let activeEffect
export function effect(fn) {
    // fn

    const _effect = new ReactiveEffect(fn)
    _effect.run()
    return _effect.run.bind(_effect)
}

使用bind是因为this问题

结语

源码不止这些,这里只是做了一个极简的实现,包括TS类型,细节处理,测试环节,代码规范等,这些都需要后期处理,如果有哪些不对的还希望各位能够指正,也算是对我的洗礼了

后续也会有更多vue3源码及其他技术的文章