前言

前段时间读了几遍Vue3响应式核心的源码 对vue3响应式核心 明白了依赖收集和响应式原理 今天自己动手简单的实现一下 加深一下理解

响应式核心

const targetMap = new WeakMap()
let activeEffect

// track ==> 依赖追踪收集核心
function track(target, type, key) {
    console.log('track')
    let depsMap = targetMap.get(target);
    // 检查是否被追踪
    if (!depsMap) {
        // 没有被追踪 新建一个并添加进去
        targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key);
    // 检查当前依赖列表是否包含当前key
    if (!dep) {
        // 没有就代表没被追踪 新建一个
        depsMap.set(key, (dep = new Set()))
    }

    // 检查依赖中是否包含activeEffect
    // activeEffect的赋值在 effect 中  而 effect的调用在mount的时候
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        // activeEffect.deps.push(dep)
    }
}

function trigger(target, type, key) {
    console.log('trigger')
    const depsMap = targetMap.get(target)
    if (!depsMap) {
        // never been tracked 没有被追踪 直接溜溜球
        return;
    }

    // let deps = []
    // if(key !== undefined){
    //     deps.push(depsMap.get(key))
    // }
    //
    let dep = depsMap.get(key)
    // 如果有依赖 那就执行 dep在track中设置为一个Set类型 里面保存着effect
    if (dep) {
        dep.forEach(effect => {
            effect.run()
        })
    }
}

function effect(fn, options) {
    const _effect = new ReactiveEffect(fn)
    _effect.run()
    const runner = _effect.run.bind(_effect);
    runner.effect = _effect;
    return runner;
}

class ReactiveEffect {
    constructor(fn, scheduler = null) {
        this.fn = fn
        this.scheduler = scheduler
    }

    run() {
        activeEffect = this
        return this.fn()
    }

}


// 所有经过reactive代理的集合
const reactiveMap = new WeakMap()
const isObject = (val) => val !== null && typeof val === 'object';

export function reactive(target) {

    const proxy = new Proxy(target, {
        // target:目标对象 key:被获取的键 receiver: Proxy或者继承Proxy的对象
        get(target, key, receiver) {
            // get获取值的时候顺手进行依赖收集
            const res = Reflect.get(target, key, receiver)
            track(target, "get", key);
            // 如果值是个对象 递归
            if (isObject(res)) {
                reactive(res)
            }
            return res
        },
        set(target, key, value, receiver) {

            const result = Reflect.set(target, key, value, receiver);
            trigger(target, "set", key, value);
            //
            return result
        }
    })
    reactiveMap.set(target, proxy)
    return proxy
}


export function mount(instance, el) {
    // 先清空容器内容
    el.innerHTML = ''
    effect(function () {
        instance.$data && update(el, instance);
    })
    instance.$data = instance.setup();
    // 执行一次render
    update(el, instance);
}

function update(container, instance) {
    const vnode = instance.render()

    patch(container._vnode || null, vnode, container)
    container._vnode = vnode
}

function patch(vnode, newVnode, container) {
    // 如果两次一样 直接返回
    if (vnode === newVnode) {
        return;
    }
    // 首次挂载
    if (vnode == null) {
        let el = newVnode.el = document.createElement(newVnode.tagName)
        console.log(newVnode)
        // 如果子节点是数组 递归挂载
        if (Array.isArray(newVnode.children)) {
            newVnode.children.forEach(c => {
                patch(null, c, el)
            })
        } else {
            el.textContent = newVnode.children
        }

        if (newVnode.props && newVnode.props.onClick) {
            el.addEventListener('click', newVnode.props.onClick)
        }

        container.appendChild(el)
    } else {
        //不是首次挂载 对比更新
        const el = (newVnode.el = vnode.el)
        if (Array.isArray(newVnode.children)) {
            newVnode.children.forEach((c, idx) => {
                patch(vnode.children[idx], c, el)
            })
        } else {
        if (vnode.children == newVnode.children) {
            return
         }
            el.textContent = newVnode.children
        }
    }
}

function h(tagName, props, children) {
    const vnode = {
        tagName,
        props,
        children,
        el: null
    }

    return vnode
}
import { reactive, h } from './index.js'
const App = {
    $data: null,
    setup() {
        let count = reactive({num: 0})

        const handleClick = () => {
            count.num++
        }

        return {
            count,
            handleClick
        };
    },
    render() {

        // `
        //     <div>
        //         <p>{{count.num}}</p>
        //         <button @click="handleClick"> ADD </button>
        //     </div>
        // `

        // 
        const _ctx = this.$data
        const _cache = []

        return h('div', null, [
            h('p', null, _ctx.count.num),
            h('button', {
                onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
            }, 'ADD')
        ])
    }
}

mount(App, document.body)

浏览器上跑一下看效果

2021-11-09 20.22.38.gif

总结

vue3的设计真的是让人眼前一亮 捋顺这些东西也是让人头秃 QAQ 后期有空在把详细的解释注上

目录