Vue源码学习

Posted 忘忘碎斌bin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue源码学习相关的知识,希望对你有一定的参考价值。

实现一个 Mini Vue

渲染系统

上一篇:渲染系统:h函数、mount函数

patch函数

function patch(oldVNode, newVNode) {
  if (oldVNode.tag !== newVNode.tag) {
    const el = oldVNode.el.parentElement;
    el.removeChild(oldVNode.el);
    mount(newVNode, el)
  } else {
    const el = newVNode.el = oldVNode.el;

    // 处理props
    const oldProps = oldVNode.props || {};
    const newProps = newVNode.props || {};
    for (const key in newProps) {
      const oldValue = oldProps[key];
      const newValue = newProps[key];
      if (newValue !== oldValue) {
        if (key.startsWith("on")) {
          el.addEventListener(key.split("on")[1].toLowerCase(), newValue)
        } else {
          el.setAttribute(key, newValue);
        }
      }
    }

    for (const key in oldProps) {
      const oldValue = oldProps[key];
        if (key.startsWith("on")) {
          el.removeEventListener(key.split("on")[1].toLowerCase(), oldValue)
        } else {
          el.removeAttribute(key)
        }
    }

    // 处理children

    const oldChildren = oldVNode.children || [];
    const newChildren = newVNode.children || [];
    if (typeof newChildren === "string") {
      if (typeof oldChildren === "string") {
        if (newChildren !== oldChildren) {
          el.textContent = newChildren;
        }
      } else {
        el.innerhtml = newChildren;
      }
    } else {
      // 新的VNode是一个数组
      if (typeof oldChildren === "string") {
        el.innerHTML = "";
        newChildren.forEach(element => {
          mount(element, el)
        });
      } else {
        const oldChildrenLength = oldChildren.length;
        const newChildrenLength = newChildren.length;
        const minLength = Math.min(oldChildrenLength, newChildrenLength);

        for (let i = 0; i < minLength; i++) {
          patch(oldChildren[i], newChildren[i])
        }
        if (oldChildrenLength > newChildrenLength) {
          oldChildren.slice(minLength).forEach(node => {
            el.removeChild(node.el)
          })
        }
        if (newChildrenLength > oldChildrenLength) {
          newChildren.slice(minLength).forEach(node => {
            mount(node, el)
          })
        }
      }
    }
  }
}

测试:

响应式系统

vue2的实现方式

//  依赖函数的保存
class Dep {
  constructor() {
    this.subscribers = []
  }
  depend() {
    if (currentEffect) {
      this.subscribers.push(currentEffect)
    }
  }

  notify() {
    this.subscribers.forEach(effect => {
      effect()
    })
  }
}


let target = new WeakMap();
function getDep(raw, key) {
  let deepMap = target.get(raw);
  if (!deepMap) {
    deepMap = new Map();
    target.set(raw, deepMap)
  }
  let dep = deepMap.get(key);
  if (!dep) {
    dep = new Dep();
    deepMap.set(key, dep)
  }
  return dep
}

// 通过watchEffect函数来收集依赖
let currentEffect = null;
function watchEffect(effect) {
  currentEffect = effect;
  effect();
  currentEffect = null;
}

// 数据结构来保存数据在那些函数内有依赖
// vue内使用了  WeakMap & Map
function reactive(raw) {
  Object.keys(raw).forEach(key => {
    const dep = getDep(raw, key);
    let value = raw[key]
    Object.defineProperty(raw, key, {
      get() {
        dep.depend();
        return value
      },
      set(newValue) {
        value = newValue;
        dep.notify()
      }
    })
  })
  return raw
}


// 测试
const info = reactive({ name: "coderbin", age: 21 })

watchEffect(function () {
  console.log("effect1:" + info.name, info.age);
})
watchEffect(function () {
  console.log("effect2:", info.age);
})

info.name = "aaa";

结果:

vue3实现响应式的方式 Proxy

Proxy的优势

  • Object.defineProperty劫持对象熟悉时,新增属性,需要再次调用Object.defineProperty才能对其实现劫持,而proxy是对整个对象进行劫持
  • Object.defineProperty需改原本对象,触发拦截。而proxy是修改proxy实例时触发拦截
  • proxy支持 in delete 操作符的捕获器
    缺点:兼容性问题。

实现:只需修改reactive函数

// vue3对raw进行数据劫持
function reactive(raw) {
	let dep = null;
	return new Proxy(raw, {
		get(target, key) {
			dep = getDep(target, key);
			dep.depend();
			return target[key]
		},
		set(target, key, newValue) {
			target[key] = newValue
			dep = getDep(target, key);
			dep.notify()
		}
	})
}

入口模块(挂载函数)

参照Vue3 : createApp( App ).mount( “#app” );

function createApp(App) {
  return {
    mount(selectors) {
      const app = document.querySelector(selectors);
      let oldVnode = null;
      let newVnode = null;
      let isMounted = false;
      watchEffect(function() {
        if (!isMounted) {
          oldVnode = App.render();
          mount(oldVnode, app);
          isMounted = true;
        } else {
          newVnode = App.render();
          patch(oldVnode, newVnode);
          oldVnode = newVnode;
        }
      });
    },
  };
}

测试

<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="app"></div>
    <!-- <script src=" 上方文件代码引入 ..." > </script> -->
    <script>
      const App = {
        data: reactive({
          count: 0,
        }),
        render: function () {
          return h("div", null, [
            h("h2", null, `当前计数:${this.data.count}`),
            h(
              "button",
              {
                onclick: () => {
                  this.data.count++;
                },
              },
              "+1"
            ),
          ]);
        },
      };
      createApp(App).mount("#app");
    </script>
  </body>
</html>

以上是关于Vue源码学习的主要内容,如果未能解决你的问题,请参考以下文章

译Vue源码学习:Vue对象构造函数

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段(vue主模板)

初步了解VUE源码

VSCode自定义代码片段11——vue路由的配置

VSCode自定义代码片段11——vue路由的配置