[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)

}
相关推荐
仰望.9 小时前
vue 表格组件 vxe-table 树表格懒加载支持拖拽排序的详细用法
前端·javascript·vue.js·vxe-table
康小旺13 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
vue.js·typescript
徐小夕15 小时前
JitKnow AI知识库 4.7 发布|多文档推理+拖拽 Agent + 技能库,打造更懂业务的 AI 知识中台
前端·vue.js·github
u01110267518 小时前
Canvas 导出 PNG 时 quality 参数为什么不起作用
前端·javascript·vue.js
计算机编程-吉哥20 小时前
计算机毕业设计 基于 SpringBoot + Vue 的剧本杀系统 Java毕业设计【任务书、开题报告、中期报告、论文、源码】
java·vue.js·spring boot·毕业设计·课程设计·计算机毕业设计选题
这里是杨杨吖20 小时前
SpringCloud+Vue民宿房屋租赁系统 附带详细运行指导视频
vue.js·spring boot·spring cloud·房屋·民宿租赁
小大宇1 天前
Vue知识点6-computed计算属性
前端·javascript·vue.js
程序员海军1 天前
尤雨溪 : Vue 4 已经在规划中了!
前端·javascript·vue.js
徐小夕2 天前
doc2docx方案,开源!
前端·vue.js·github
徐小夕2 天前
JitOFD:一款面向信创与档案场景的 Web 端 OFD 编辑器
前端·vue.js·github