创建一个简单的迷你Vue3-2

经过前面两章,我们已经建立了模板渲染系统和响应式数据系统。

那么接下来我们就要将他们组合在一起,以及加上一些生命周期的处理等等。来合成一个真正的mini-vue3.

首先是要将之前两块内容整合到一起:

<html>
    <head>
        <title>Mini-Vue3</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            // compile & render system
            
            // 渲染函数,将函数描述的模版转换为对象表述的json对象
            function h(tag, props, children) {
                return {
                tag,
                props,
                children,
                };
            }

            // 将指定的vnode装在到container上
            function mount(vnode, container) {
                const el = document.createElement(vnode.tag);
                // 保存vnode的dom引用
                vnode.el = el;

                // props
                if (vnode.props) {
                for (const key in vnode.props) {
                    const value = vnode.props[key];
                    el.setAttribute(key, value);
                }
                }

                // children
                if (vnode.children) {
                // 区分字符串还是array,简单处理
                if (typeof vnode.children === 'string') {
                    el.textContent = vnode.children;
                } else {
                    vnode.children.forEach(child => {
                    mount(child, el);
                    });
                }
                }

                container.appendChild(el);
            }

            // 比对已有vnode和新node,进行更新
            function patch(oldNode, newNode) {
                if (oldNode.tag === newNode.tag) {
                const el = oldNode.el;
          newNode.el = oldNode.el;
// 如果tag相同,则需要进行下一步的各种判断,props的判断,更新,children的判断,更新
                // 首先处理props
                const oldProps = oldNode.props || {};
                const newProps = newNode.props || {};

                // 首先判断新props,如果旧的有,则更新,无,则添加
                for (const key in newProps) {
                    const oldValue = oldProps[key];
                    const newValue = newProps[key];

                    if (newValue !== oldValue) {
                    // 如果新的与旧的不同,则更新
                    el.setAttribute(key, newValue);
                    }
                }
                // 然后判断旧props里面,如果新的没有,则删除
                for (const key in oldProps) {
                    if (!(key in newProps)) {
                    el.removeAttribute(key);
                    }
                }

                // 然后将props设置为新
                oldNode.props = newProps;

                // 接着处理children, 这里我们简单处理,认为children要么是string,要么是array
                const oldChildren = oldNode.children;
                const newChildren = newNode.children;

                if (typeof oldChildren === 'string') {
                    // 对于旧子节点为string的情况
                    if (typeof newChildren === 'string') {
                    // 如果新子节点也是string,则直接替换即可
                    el.textContent = newChildren;
                    oldNode.children = newChildren;
                    } else {
                    // 新节点是array
                    // 清空原有子节点内容
                    el.innerHTML = '';
                    newChildren.forEach(child => {
                        mount(child, el);
                    });
                    oldNode.children = newChildren;
                    }
                } else {
                    // 旧节点是array
                    if (typeof newChildren === 'string') {
                    el.innerHTML = newChildren;
                    oldNode.children = newChildren;
                    } else {
                    // 两个都是array,就简单处理,只是比对同顺序

                    // 先取两个数组同样长度对比,直接patch
                    const sameLength = Math.min(oldChildren.length, newChildren.length);
                    for (let i = 0; i < sameLength; i++) {
                        // 替换新元素
                        patch(oldChildren[i], newChildren[i]);
                    }
                    // 然后如果旧数组还有,则移除多出部分
                    if (oldChildren.length > sameLength) {
                        oldChildren.slice(sameLength).forEach(child => {
                        el.removeChild(child.el);
                        });
                    }

                    // 如果新数组还有,则添加多出部分
                    if (newChildren.length > sameLength) {
                        newChildren.slice(sameLength).forEach(child => {
                        mount(child, el);
                        });
                    }
                    }
                }
                
                } else {
                // 如果tag都不同,则直接替换即可
                const parent = oldNode.el.parentNode;
                mount(newNode, parent);
                parent.removeChild(oldNode.el);
                oldNode.el = newNode.el;
                }
            }


            // reactive system
            let activeEffect = null;

            class Dep {
                subscribers = new Set();

                // 添加依赖
                depend() {
                    if (activeEffect) {
                        this.subscribers.add(activeEffect);
                    }
                }

                // 依赖更新,通知回调
                notify() {
                    this.subscribers.forEach(effect => {
                        effect();
                    });
                }
            }

            /**
             * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行
             */
            function watchEffect(effect) {
                activeEffect = effect;
                effect();
                activeEffect = null;
            }

            const targetMap = new WeakMap();

            function getDep(target, key) {
                // 添加整个对象的依赖
                let currMap = targetMap.get(target);

                if (!currMap) {
                    currMap = new Map();
                    targetMap.set(target, currMap);
                }

                // 获取具体key的依赖
                let dep = currMap.get(key);
                // 如果没有添加过则添加依赖
                if (!dep) {
                    dep = new Dep();
                    currMap.set(key, dep);
                }

                return dep;
            }

            const reactiveHandlers = {
                get(target, key, receiver) {
                    const dep = getDep(target, key);

                    // 为什么每次读取都要添加依赖,因为有可能会增加新的依赖
                    dep.depend();
                    return Reflect.get(target, key, receiver);
                },
                set(target, key, value, receiver) {                    
                    const dep = getDep(target, key);

                    // 类似于之前的Object.set
                    const ret = Reflect.set(target, key, value, receiver);

                    // 通知执行依赖
                    dep.notify();

                    // 因为proxy的set必须要返回一个boolean的值告诉设置成功与否,因此这里返回系统api的结果即可
                    return ret;
                }
            };

            /**
             * @description 给对象封装并返回响应式代理
             */
            function reactive(oldObj) {
                return new Proxy(oldObj, reactiveHandlers);
            }

        </script>
    </body>
</html>

接下来则是要像真正在Vue里面写组件那样来实现这个mini-vue3:

因为现在我们只是实现了基础的API,但是我们实际使用Vue都是通过组件的形式来组织代码的,并且通过Vue的参数来将组件传入并挂<html>

    <head>
        <title>Mini-Vue3</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            // compile & render system
            
            // 渲染函数,将函数描述的模版转换为对象表述的json对象
            function h(tag, props, children) {
                return {
                tag,
                props,
                children,
                };
            }

            // 将指定的vnode装在到container上
            function mount(vnode, container) {
                const el = document.createElement(vnode.tag);
                // 保存vnode的dom引用
                vnode.el = el;

                // props
                if (vnode.props) {
                for (const key in vnode.props) {
                    const value = vnode.props[key];
                    el.setAttribute(key, value);
                }
                }

                // children
                if (vnode.children) {
                // 区分字符串还是array,简单处理
                if (typeof vnode.children === 'string') {
                    el.textContent = vnode.children;
                } else {
                    vnode.children.forEach(child => {
                    mount(child, el);
                    });
                }
                }

                container.appendChild(el);
            }

            // 比对已有vnode和新node,进行更新
            function patch(oldNode, newNode) {
                if (oldNode.tag === newNode.tag) {
                const el = oldNode.el;
          newNode.el = oldNode.el;
// 如果tag相同,则需要进行下一步的各种判断,props的判断,更新,children的判断,更新 // 首先处理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判断新props,如果旧的有,则更新,无,则添加 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的与旧的不同,则更新 el.setAttribute(key, newValue); } } // 然后判断旧props里面,如果新的没有,则删除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然后将props设置为新 oldNode.props = newProps; // 接着处理children, 这里我们简单处理,认为children要么是string,要么是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 对于旧子节点为string的情况 if (typeof newChildren === 'string') { // 如果新子节点也是string,则直接替换即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新节点是array // 清空原有子节点内容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 旧节点是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 两个都是array,就简单处理,只是比对同顺序 // 先取两个数组同样长度对比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替换新元素 patch(oldChildren[i], newChildren[i]); } // 然后如果旧数组还有,则移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新数组还有,则添加多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,则直接替换即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 添加依赖 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依赖更新,通知回调 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 添加整个对象的依赖 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 获取具体key的依赖 let dep = currMap.get(key); // 如果没有添加过则添加依赖 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 为什么每次读取都要添加依赖,因为有可能会增加新的依赖 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 类似于之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知执行依赖 dep.notify(); // 因为proxy的set必须要返回一个boolean的值告诉设置成功与否,因此这里返回系统api的结果即可 return ret; } }; /** * @description 给对象封装并返回响应式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } // 我们现在来按照Vue组件的格式定义一个简单的自定义组件 const App = { data: reactive({ count: 0, }), render() { return h('div', null, App.data.count); }, }; // 接着我们需要通过一个安装的方法来将这个组件挂载并且需要 // 在依赖的数据发生变化后重新渲染组件 </script> </body> </html>

因此这里我们需要建立一个安装方法来挂载这个组件

<html>
    <head>
        <title>Mini-Vue3</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            // compile & render system
            
            // 渲染函数,将函数描述的模版转换为对象表述的json对象
            function h(tag, props, children) {
                return {
                tag,
                props,
                children,
                };
            }

            // 将指定的vnode装在到container上
            function mount(vnode, container) {
                const el = document.createElement(vnode.tag);
                // 保存vnode的dom引用
                vnode.el = el;

                // props
                if (vnode.props) {
                for (const key in vnode.props) {
                    const value = vnode.props[key];
                    el.setAttribute(key, value);
                }
                }

                // children
                if (vnode.children) {
                // 区分字符串还是array,简单处理
                if (typeof vnode.children === 'string') {
                    el.textContent = vnode.children;
                } else {
                    vnode.children.forEach(child => {
                    mount(child, el);
                    });
                }
                }

                container.appendChild(el);
            }

            // 比对已有vnode和新node,进行更新
            function patch(oldNode, newNode) {
                if (oldNode.tag === newNode.tag) {
                const el = oldNode.el;
          newNode.el = oldNode.el;
// 如果tag相同,则需要进行下一步的各种判断,props的判断,更新,children的判断,更新 // 首先处理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判断新props,如果旧的有,则更新,无,则添加 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的与旧的不同,则更新 el.setAttribute(key, newValue); } } // 然后判断旧props里面,如果新的没有,则删除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然后将props设置为新 oldNode.props = newProps; // 接着处理children, 这里我们简单处理,认为children要么是string,要么是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 对于旧子节点为string的情况 if (typeof newChildren === 'string') { // 如果新子节点也是string,则直接替换即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新节点是array // 清空原有子节点内容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 旧节点是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 两个都是array,就简单处理,只是比对同顺序 // 先取两个数组同样长度对比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替换新元素 patch(oldChildren[i], newChildren[i]); } // 然后如果旧数组还有,则移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新数组还有,则添加多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,则直接替换即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 添加依赖 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依赖更新,通知回调 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 添加整个对象的依赖 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 获取具体key的依赖 let dep = currMap.get(key); // 如果没有添加过则添加依赖 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 为什么每次读取都要添加依赖,因为有可能会增加新的依赖 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 类似于之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知执行依赖 dep.notify(); // 因为proxy的set必须要返回一个boolean的值告诉设置成功与否,因此这里返回系统api的结果即可 return ret; } }; /** * @description 给对象封装并返回响应式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } /** * @description 将组件挂载到dom上 */ function mountApp(component, container) { // 先拿到vdom const vdom = component.render(); // 挂载 mount(vdom, container); } // 我们现在来按照Vue组件的格式定义一个简单的自定义组件 const App = { data: reactive({ count: 1, }), render() { return h('div', null, String(App.data.count)); // 因为前面vdom限定了string }, }; // 接着我们需要通过一个安装的方法来将这个组件挂载并且需要 // 在依赖的数据发生变化后重新渲染组件 mountApp(App, document.getElementById('app')) </script> </body> </html>

此时可以看下目前的效果:

 

 

 

已经渲染成功了,但是我们还少了一些东西,那就是我们应当要触发我们依赖的对象的数据变更,并且我们的Dom要能重新渲染,因此我们应当在watchEffect方法中去更新我们的dom,

新的代码如下:

<html>
    <head>
        <title>Mini-Vue3</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            // compile & render system
            
            // 渲染函数,将函数描述的模版转换为对象表述的json对象
            function h(tag, props, children) {
                return {
                tag,
                props,
                children,
                };
            }

            // 将指定的vnode装在到container上
            function mount(vnode, container) {
                const el = document.createElement(vnode.tag);
                // 保存vnode的dom引用
                vnode.el = el;

                // props
                if (vnode.props) {
                for (const key in vnode.props) {
                    const value = vnode.props[key];
                    el.setAttribute(key, value);
                }
                }

                // children
                if (vnode.children) {
                // 区分字符串还是array,简单处理
                if (typeof vnode.children === 'string') {
                    el.textContent = vnode.children;
                } else {
                    vnode.children.forEach(child => {
                    mount(child, el);
                    });
                }
                }

                container.appendChild(el);
            }

            // 比对已有vnode和新node,进行更新
            function patch(oldNode, newNode) {
                if (oldNode.tag === newNode.tag) {
                const el = oldNode.el;
          newNode.el = oldNode.el;
// 如果tag相同,则需要进行下一步的各种判断,props的判断,更新,children的判断,更新 // 首先处理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判断新props,如果旧的有,则更新,无,则添加 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的与旧的不同,则更新 el.setAttribute(key, newValue); } } // 然后判断旧props里面,如果新的没有,则删除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然后将props设置为新 oldNode.props = newProps; // 接着处理children, 这里我们简单处理,认为children要么是string,要么是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 对于旧子节点为string的情况 if (typeof newChildren === 'string') { // 如果新子节点也是string,则直接替换即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新节点是array // 清空原有子节点内容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 旧节点是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 两个都是array,就简单处理,只是比对同顺序 // 先取两个数组同样长度对比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替换新元素 patch(oldChildren[i], newChildren[i]); } // 然后如果旧数组还有,则移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新数组还有,则添加多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,则直接替换即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 添加依赖 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依赖更新,通知回调 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 添加整个对象的依赖 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 获取具体key的依赖 let dep = currMap.get(key); // 如果没有添加过则添加依赖 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 为什么每次读取都要添加依赖,因为有可能会增加新的依赖 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 类似于之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知执行依赖 dep.notify(); // 因为proxy的set必须要返回一个boolean的值告诉设置成功与否,因此这里返回系统api的结果即可 return ret; } }; /** * @description 给对象封装并返回响应式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } /** * @description 将组件挂载到dom上 */ function mountApp(component, container) { // 是否已经挂载过 let isMounted = false; let vdom; // 注册watchEffect的回调 // 如果没有挂载过,则挂载,否则patch watchEffect(() => { if (!isMounted) { vdom = component.render(); mount(vdom, container); } else { const vom2 = component.render(); patch(vdom, vdom2); vdom = vdom2; } }); } // 我们现在来按照Vue组件的格式定义一个简单的自定义组件 const App = { data: reactive({ count: 1, }), render() { return h('div', null, String(App.data.count)); // 因为前面vdom限定了string }, }; // 接着我们需要通过一个安装的方法来将这个组件挂载并且需要 // 在依赖的数据发生变化后重新渲染组件 mountApp(App, document.getElementById('app')) </script> </body> </html>

但是因为我们还没有更新数据触发effect的机制,因此我们修改下mount方法,引入事件绑定:

 

<html>
    <head>
        <title>Mini-Vue3</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            // compile & render system
            
            // 渲染函数,将函数描述的模版转换为对象表述的json对象
            function h(tag, props, children) {
                return {
                tag,
                props,
                children,
                };
            }

            // 将指定的vnode装在到container上
            function mount(vnode, container) {
                const el = document.createElement(vnode.tag);
                // 保存vnode的dom引用
                vnode.el = el;

                // props
                if (vnode.props) {
                    for (const key in vnode.props) {
                        const value = vnode.props[key];
                        if (key.indexOf('on') === 0) {
                            // 如果属性以on开头则说明是方法
                            const funName = key.substring(2).toLowerCase();
                            el.addEventListener(funName, value);
                        } else {
                            el.setAttribute(key, value);
                        }

                    }
                }

                // children
                if (vnode.children) {
                    // 区分字符串还是array,简单处理
                    if (typeof vnode.children === 'string') {
                        el.textContent = vnode.children;
                    } else {
                        vnode.children.forEach(child => {
                            mount(child, el);
                        });
                    }
                }

                container.appendChild(el);
            }

            // 比对已有vnode和新node,进行更新
            function patch(oldNode, newNode) {
                if (oldNode.tag === newNode.tag) {
                const el = oldNode.el;
                newNode.el = oldNode.el;
                // 如果tag相同,则需要进行下一步的各种判断,props的判断,更新,children的判断,更新
                // 首先处理props
                const oldProps = oldNode.props || {};
                const newProps = newNode.props || {};

                // 首先判断新props,如果旧的有,则更新,无,则添加
                for (const key in newProps) {
                    const oldValue = oldProps[key];
                    const newValue = newProps[key];

                    if (newValue !== oldValue) {
                    // 如果新的与旧的不同,则更新
                    el.setAttribute(key, newValue);
                    }
                }
                // 然后判断旧props里面,如果新的没有,则删除
                for (const key in oldProps) {
                    if (!(key in newProps)) {
                    el.removeAttribute(key);
                    }
                }

                // 然后将props设置为新
                oldNode.props = newProps;

                // 接着处理children, 这里我们简单处理,认为children要么是string,要么是array
                const oldChildren = oldNode.children;
                const newChildren = newNode.children;

                if (typeof oldChildren === 'string') {
                    // 对于旧子节点为string的情况
                    if (typeof newChildren === 'string') {
                    // 如果新子节点也是string,则直接替换即可
                    el.textContent = newChildren;
                    oldNode.children = newChildren;
                    } else {
                    // 新节点是array
                    // 清空原有子节点内容
                    el.innerHTML = '';
                    newChildren.forEach(child => {
                        mount(child, el);
                    });
                    oldNode.children = newChildren;
                    }
                } else {
                    // 旧节点是array
                    if (typeof newChildren === 'string') {
                    el.innerHTML = newChildren;
                    oldNode.children = newChildren;
                    } else {
                    // 两个都是array,就简单处理,只是比对同顺序

                    // 先取两个数组同样长度对比,直接patch
                    const sameLength = Math.min(oldChildren.length, newChildren.length);
                    for (let i = 0; i < sameLength; i++) {
                        // 替换新元素
                        patch(oldChildren[i], newChildren[i]);
                    }
                    // 然后如果旧数组还有,则移除多出部分
                    if (oldChildren.length > sameLength) {
                        oldChildren.slice(sameLength).forEach(child => {
                        el.removeChild(child.el);
                        });
                    }

                    // 如果新数组还有,则添加多出部分
                    if (newChildren.length > sameLength) {
                        newChildren.slice(sameLength).forEach(child => {
                        mount(child, el);
                        });
                    }
                    }
                }
                
                } else {
                // 如果tag都不同,则直接替换即可
                const parent = oldNode.el.parentNode;
                mount(newNode, parent);
                parent.removeChild(oldNode.el);
                oldNode.el = newNode.el;
                }
            }


            // reactive system
            let activeEffect = null;

            class Dep {
                subscribers = new Set();

                // 添加依赖
                depend() {
                    if (activeEffect) {
                        this.subscribers.add(activeEffect);
                    }
                }

                // 依赖更新,通知回调
                notify() {
                    this.subscribers.forEach(effect => {
                        effect();
                    });
                }
            }

            /**
             * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行
             */
            function watchEffect(effect) {
                activeEffect = effect;
                effect();
                activeEffect = null;
            }

            const targetMap = new WeakMap();

            function getDep(target, key) {
                // 添加整个对象的依赖
                let currMap = targetMap.get(target);

                if (!currMap) {
                    currMap = new Map();
                    targetMap.set(target, currMap);
                }

                // 获取具体key的依赖
                let dep = currMap.get(key);
                // 如果没有添加过则添加依赖
                if (!dep) {
                    dep = new Dep();
                    currMap.set(key, dep);
                }

                return dep;
            }

            const reactiveHandlers = {
                get(target, key, receiver) {
                    const dep = getDep(target, key);

                    // 为什么每次读取都要添加依赖,因为有可能会增加新的依赖
                    dep.depend();
                    return Reflect.get(target, key, receiver);
                },
                set(target, key, value, receiver) {                    
                    const dep = getDep(target, key);

                    // 类似于之前的Object.set
                    const ret = Reflect.set(target, key, value, receiver);

                    // 通知执行依赖
                    dep.notify();

                    // 因为proxy的set必须要返回一个boolean的值告诉设置成功与否,因此这里返回系统api的结果即可
                    return ret;
                }
            };

            /**
             * @description 给对象封装并返回响应式代理
             */
            function reactive(oldObj) {
                return new Proxy(oldObj, reactiveHandlers);
            }

            /**
             * @description 将组件挂载到dom上
             */
            function mountApp(component, container) {
                // 是否已经挂载过
                let isMounted = false;   
                let vdom;

                // 注册watchEffect的回调
                // 如果没有挂载过,则挂载,否则patch
                watchEffect(() => {
                    if (!isMounted) {
                        vdom = component.render();
                        mount(vdom, container);
                        isMounted = true;
                    } else {
                        const vdom2 = component.render();
                        patch(vdom, vdom2);
                        vdom = vdom2;
                    }
                });
            }

            // 我们现在来按照Vue组件的格式定义一个简单的自定义组件
            const App = {
                data: reactive({
                    count: 1,
                }),
                render() {
                    return h('div', {
                        onClick: () => {
                            // 添加点击事件方法
                            this.data.count += 1;
                        }
                    }, String(this.data.count)); // 因为前面vdom限定了string
                },
            };

            // 接着我们需要通过一个安装的方法来将这个组件挂载并且需要
            // 在依赖的数据发生变化后重新渲染组件
            mountApp(App, document.getElementById('app'))
        </script>
    </body>
</html>

然后效果ok,没有问题:

 

至此我们的mini-vue3已经跑起来了,只不过我们模板是需要我们自己去写render function,如果把编译系统也加入,使得我们直接通过模板写vue组件,那就几乎是一个完整功能的vue了。

后续或许要来实现他,我们的这一个小系列已经完成了,接下来会基于这个mini-vue3来写Vue3中的一些新特性的实现。

 

posted on 2020-07-19 11:29  笨鸟哥  阅读(173)  评论(0)    收藏  举报

导航