自己创建一个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)
79 0
|
8月前
|
JavaScript
vue组件中data为什么必须是一个函数
Vue.js 组件的 data 选项必须是一个函数,这是为了确保每个组件实例都有自己的独立数据副本,从而避免数据污染和确保组件的复用性和可预测性
|
2月前
|
JavaScript 前端开发 API
Vue 3 中 v-model 与 Vue 2 中 v-model 的区别是什么?
总的来说,Vue 3 中的 `v-model` 在灵活性、与组合式 API 的结合、对自定义组件的支持等方面都有了明显的提升和改进,使其更适应现代前端开发的需求和趋势。但需要注意的是,在迁移过程中可能需要对一些代码进行调整和适配。
118 60
|
3月前
|
JavaScript
vue 组件中的 data 为什么是一个函数 ?
【10月更文挑战第8天】 在 Vue 组件中,`data` 被定义为一个函数而非普通对象,以确保每个组件实例拥有独立的数据空间,避免数据混乱。这种方式还支持数据的响应式更新、组件的继承与扩展,并有助于避免潜在问题,提升应用的可靠性和性能。
32 2
|
4月前
|
API
|
6月前
|
JavaScript 编译器
|
7月前
|
JavaScript
vue组件中data为什么必须是一个函数?
vue组件中data为什么必须是一个函数?
50 1
|
JavaScript
53Vue - 组件的使用(data 必须是函数)
53Vue - 组件的使用(data 必须是函数)
36 0
|
8月前
vue2 系列:自定义 v-model
vue2 系列:自定义 v-model
157 0
|
JavaScript
vue 组件中 data 为什么必须是函数
vue 组件中 data 为什么必须是函数
91 0

相关实验场景

更多