自己创建一个mini-vue

简介: mini-vue本章在之前的章节的基础中实现了一个简单的vue框架,其中响应式的函数有略微变化不过大致原理相同。致谢Vue Mastery非常好的课程,可以转载,但请声明源链接:文章源链接justin3go.com(有些latex公式某些平台不能渲染可查看这个网站)
published: true
date: 2022-2-3
tags: '前端框架 Vue'


mini-vue

本章在之前的章节的基础中实现了一个简单的vue框架,其中响应式的函数有略微变化不过大致原理相同。

致谢Vue Mastery非常好的课程,可以转载,但请声明源链接:文章源链接justin3go.com(有些latex公式某些平台不能渲染可查看这个网站)

<div id="app"></div>
<script>
//vdom部分
function h(tag, props, children) {
  return {
    tag,
    props,
    children,
  };
}
function mount(vnode, container) {
  const { tag, props, children } = vnode;
  const el = (vnode.el = document.createElement(tag));
  if (props) {
    for (let key in props) {
      const value = props[key];
      if (key.startsWith("on")) {
        el.addEventListener(key.slice(2).toLowerCase(), value);
      } else {
        el.setAttribute(key, value);
      }
    }
  }
  if (children) {
    if (Array.isArray(children)) {
      children.forEach((child) => {
        if (typeof child === "string") {
          el.append(child);
        } else if (typeof child === "object") {
          mount(child, el);
        }
      });
    } else {
      el.append(children);
    }
  }
  container.append(el);
}
function patch(n1, n2) {
  if (n1.tag === n2.tag) {
    const el = (n2.el = n1.el);
    //diff props
    const oldProps = n1.props || {};
    const newProps = n2.props || {};
    //添加新的属性或更改原来已有但变化了的属性
    for (let key in newProps) {
      const oldValue = oldProps[key];
      const newValue = newProps[key];
      if (newValue !== oldValue) {
        el.setAttribute(key, newValue);
      }
    }
    //移除新属性中没有的属性
    for (let key in oldProps) {
      if (!(key in newProps)) {
        el.removeAttribute(key);
      }
    }
    //diff children
    const oldChildren = n1.children;
    const newChildren = n2.children;
    if (typeof newChildren === "string") {
      if (typeof oldChildren === "string") {
        if (oldChildren !== newChildren) {
          el.innerHTML = newChildren;
        }
      }
    } else if (typeof oldChildren === "string" && Array.isArray(newChildren)) {
      el.innerHTML = "";
      newChildren.forEach((child) => mount(child, el));
    } else if (Array.isArray(oldChildren) && Array.isArray(newChildren)) {
      const minLength = Math.min(oldChildren.length, newChildren.length);
      for (let i = 0; i < minLength; i++) {
        patch(oldChildren[i], newChildren[i]);
      }
      if (oldChildren.length === minLength) {
        for (let i = minLength; i < newChildren.length; i++) {
          mount(newChildren[i], el);
        }
      } else {
        for (let i = minLength; i < oldChildren.length; i++) {
          el.removeChild(oldChildren[i].el);
        }
      }
    }
  } else {
    //replace
  }
}
//reactivity部分
let activeEffect = null;
class Dep {
  subs = new Set();
  depend() {
    if (activeEffect) {
      this.subs.add(activeEffect);
    }
  }
  notify() {
    this.subs.forEach((sub) => sub());
  }
}
function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}
const targetMap = new WeakMap();
function getDep(target, key) {
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map());
  }
  const depMap = targetMap.get(target);
  if (!depMap.has(key)) {
    depMap.set(key, new Dep());
  }
  return depMap.get(key);
}
const reactiveHandlers = {
  get(target, key, receiver) {
    // dep
    const dep = getDep(target, key);
    dep.depend();
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    const dep = getDep(target, key);
    const ret = Reflect.set(target, key, value, receiver);
    dep.notify();
    return ret;
  },
};
function reactive(raw) {
  return new Proxy(raw, reactiveHandlers);
}
//component组件实例
//container要挂载的dom元素
function mountApp(component, container) {
  let isMounted = false;
  let oldVdom;
  watchEffect(() => {
    if (!isMounted) {
      //第一次挂载
      oldVdom = component.render();
      mount(oldVdom, container);
      isMounted = true;
    } else {
      //数据变化,要进行更新
      const newVdom = component.render();
      patch(oldVdom, newVdom);
      oldVdom = newVdom;
    }
  });
}
const App = {
  data: reactive({
    count: 0,
  }),
  render() {
    return h("div", null, [
      h(
        "div",
        {
          onClick: () => App.data.count++,
        },
        String(this.data.count)
      ),
    ]);
  },
};
//一个点击自增的计数器
mountApp(App, document.getElementById("app"));
</script>



目录
相关文章
|
JavaScript 前端开发
Vue系列教程(11)- 组件详解(Vue.component、props)
Vue系列教程(11)- 组件详解(Vue.component、props)
70 0
|
2月前
|
JavaScript
Vue2.0、Vue3.0分别使用v-model封装组件[Vue必会]
本文介绍了在Vue 2和Vue 3中如何使用`v-model`来实现组件间的双向数据绑定,包括在Vue 2中使用`value`和`input`事件,以及在Vue 3中使用`modelValue`和`update:modelValue`事件的方法。
263 22
|
3月前
|
JavaScript 前端开发 API
一个非常 nb 的 Vue 组件 (含Vue3版本)
一个非常 nb 的 Vue 组件 (含Vue3版本)
|
JavaScript
vue非单文件组件的使用
vue非单文件组件的使用
|
JavaScript
VUE学习笔记--组件
VUE学习笔记--组件
|
6月前
|
JavaScript 前端开发 索引
vue 实现任务步骤条
vue 实现任务步骤条
239 0
|
缓存 JavaScript 开发者
vue非单文件组件
vue非单文件组件
114 0
|
JavaScript 前端开发
Vue--组件
Vue--组件
|
JavaScript
Vue(Vue2+Vue3)——79.分析vue3.0初始化工程结构
Vue(Vue2+Vue3)——79.分析vue3.0初始化工程结构
|
JavaScript 前端开发 API
vue3 源码学习,实现一个 mini-vue(五):watch 侦听器
vue3 源码学习,实现一个 mini-vue(五):watch 侦听器
vue3 源码学习,实现一个 mini-vue(五):watch 侦听器