[Vue3]简易版Vue

简易版Vue

实现ref功能

ref功能主要是收集依赖和触发依赖的过程。

javascript 复制代码
export class Dep {   // 创建一个类,使用时使用new Dep
    constructor(value) { // 初始化可传入一个值
        this._val = value;
        this.effects = new Set(); //收集依赖的容器,使用set数据结构
    }

    get value() { //对value值进行拦截,获取时收集依赖到effects中
        this.depend();
        return this._val;
    }

    set value(val) { // 设置的同时触发所有收集到的依赖
        this._val = val;
        this.notice()
    }

    depend() {
        if (currentEffect) {
            this.effects.add(currentEffect); //将依赖放入effects中
        }
    }

    notice() {
        this.effects.forEach( fn => {  //触发所有收集的依赖
            fn();
        })
    }
}

let currentEffect = null;

export function effectWatch(fn) { //收集依赖的函数,所有函数必须用这个函数包裹
    currentEffect = fn;
    fn()
    currentEffect = null;
}
javascript 复制代码
// index.js
const a = new Dep(10);
let b = 0;
effectWatch( () => {
    b = a.value + 10;
    console.log(b)
})
a.value = 20;

实现reactive功能

reactive主要是让对象也可以进行依赖的收集,这就需要为对象的每一个key创建对应的Dep。

javascript 复制代码
const targetsMap = new Map(); // 用map数据结构来存储,因为它的key可以是对象

export function reactive(raw) { //传入的raw是一个对象
    return new Proxy(raw, { //拦截raw上所有的get和set
        get(target, key) {
            let depMap = targetsMap.get(raw); // 为每一个raw创建对应的map
            if (!depMap) {
                depMap = new Map();
                targetsMap.set(raw, depMap);
            }
            let dep = depMap.get(key); // 给raw上的每一个值创建Dep
            if (!dep) {
                dep = new Dep();
                depMap.set(key, dep);
            }
            dep.depend();
            return Reflect.get(target, key)
        },
        set(target, key, value) {
            let depMap = targetsMap.get(raw);
            if (!depMap) {
                depMap = new Map();
                targetsMap.set(raw, depMap);
            }
            let dep = depMap.get(key);
            if (!dep) {
                dep = new Dep();
                depMap.set(key, dep);
            }
            const result = Reflect.set(target, key, value)
            dep.notice();
            return result;
        }
    })
}
javascript 复制代码
//test
const user = reactive({
    age: 10
})
let nextAge = 0;
effectWatch( () => {
    nextAge = user.age + 1;
    console.log(nextAge);
})
user.age++;

简易版Vue雏形

使用上面的reactive和effectWatch功能可以实现miniVue的雏形

javascript 复制代码
import { effectWatch, reactive } from './core/index.js';

const App = {
    render(context) {
        effectWatch(() => {
            document.querySelector('#app').textContent = '';
            const element = document.createElement('div');
            const text = document.createTextNode('nihao');
            const text1 = document.createTextNode(context.obj.count);
            element.append(text);
            element.append(text1);
            document.querySelector('#app').append(element)
        })
    },

    setup() {
        const obj = reactive({
            count: 1
        })
        window.obj = obj
        return{
            obj
        }
    }
}

App.render(App.setup())
//通过在console中输入obj.count的值修改视图

优化

将代码抽离,effectWatch在框架中调用,视图的清空和append也在框架中调用

javascript 复制代码
export function createApp(rootComponent) {
    return {
        mount(rootContainer) {
            const setupResult = rootComponent.setup();
            effectWatch(() => {
                rootContainer.textContent = '';
                const element = rootComponent.render(setupResult);
                rootContainer.append(element);
            })
        }
    }
}

export const App = {
    render(context) {
        const element = document.createElement('div');
        const text = document.createTextNode('nihao');
        const text1 = document.createTextNode(context.obj.count);
        element.append(text);
        element.append(text1);
        return element;
    },

    setup() {
        const obj = reactive({
            count: 1
        })
        window.obj = obj
        return {
            obj
        }
    }
}

优化并使用虚拟Dom

在上面的代码中,每次都会更新所有节点,需要进行优化,只更新变化的节点

将节点关键信息转化成一个对象。props是一个对象,代表节点上的attrs,children是一个数组,可以有多个

javascript 复制代码
export function h(tag, props, children) {
    return {
        tag,
        props,
        children
    }
}

在App中

javascript 复制代码
import { reactive, h } from './core/index.js';

export const App = {
    render(context) {
        return h('div', {}, [h('p', {}, 'nihao'), h('p', {}, context.obj.count)])
    },

    setup() {
        const obj = reactive({
            count: 1
        })
        window.obj = obj
        return {
            obj
        }
    }
}

此时获取的element只是一个对象,需要将其映射成真实的Dom

映射真实Dom

依次处理tag props 和children,把他们变成真实的节点

javascript 复制代码
function createElement(tag) {
    return document.createElement(tag);
}
function patchProps(el, key, prevValue, nextValue) {
    el.setAttribute(key, nextValue);
}
export function mountElement(element, root) {

    const { tag, props, children } = element;
    const el = createElement(tag);

    for (const key in props) {
        const val = props[key];
        patchProps(el, key, null, val);
    }

    if (typeof children === 'string') {
        const textNode = document.createTextNode(children);
        el.append(textNode)
    } else if (Array.isArray(children)) {
        children.forEach((v) => {
            mountElement(v, el)
        })
    }

    root.append(el)

}
相关推荐
江城开朗的豌豆2 小时前
退出登录后头像还在?这个缓存问题坑过多少前端!
前端·javascript·vue.js
江城开朗的豌豆2 小时前
Vue的'读心术':它怎么知道数据偷偷变了?
前端·javascript·vue.js
江城开朗的豌豆3 小时前
手把手教你造一个自己的v-model:原来双向绑定这么简单!
前端·javascript·vue.js
我在北京coding3 小时前
el-tree 懒加载 loadNode
前端·vue.js·elementui
江城开朗的豌豆3 小时前
v-for中key值的作用:为什么我总被要求加这个'没用的'属性?
前端·javascript·vue.js
markyankee1017 小时前
Vue 组件系统深度解析
vue.js
bitbitDown7 小时前
从一个Bug到Vue核心原理:聊聊模板作用域的那些事
前端·javascript·vue.js
_advance7 小时前
Vue2 Mixin 深入分析
前端·vue.js
萌新小码农‍7 小时前
Vue集成MarkDown
前端·javascript·vue.js
TE-茶叶蛋19 小时前
Flutter、Vue 3 和 React 在 UI 布局比较
vue.js·flutter·react.js