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

}
相关推荐
不想吃饭e1 天前
在uniapp/vue项目中全局挂载component
前端·vue.js·uni-app
知识分享小能手1 天前
React学习教程,从入门到精通,React AJAX 语法知识点与案例详解(18)
前端·javascript·vue.js·学习·react.js·ajax·vue3
朗迹 - 张伟1 天前
Gin-Vue-Admin学习笔记
vue.js·学习·gin
古夕1 天前
前端文件下载的三种方式:a标签、Blob、ArrayBuffer
前端·javascript·vue.js
武昌库里写JAVA1 天前
Java设计模式中的几种常用设计模式
vue.js·spring boot·sql·layui·课程设计
wow_DG1 天前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(九):Vue2 性能优化
javascript·vue.js·性能优化
一 乐1 天前
美食分享|基于Springboot和vue的地方美食分享网站系统设计与实现(源码+数据库+文档)
java·vue.js·spring boot·论文·毕设·美食·地方美食分享网站系统
我是日安1 天前
从零到一打造 Vue3 响应式系统 Day 4 - 核心概念:收集依赖、触发更新
前端·vue.js
wow_DG1 天前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(八):Vuex 内部机制
前端·javascript·vue.js