Vue3.0+TS

Vue3+TS Day19 - Vue源码理论、编译器、渲染器、

2021-12-14  本文已影响0人  望穿秋水小作坊

一、Vue源码理论基础

1、DOM是什么的简称?DOM和js之间的关系?

2、我们传统的前端开发中,编写的原生HTML,最终被渲染到浏览器上,它是什么样的过程呢?

image.png

3、三大前端流行框架Vue、Angular、React都采用虚拟DOM的技术,说说虚拟DOM的优势?(两方面吧)

image.png

4、从Vue的template到最终展示在浏览器页面的过程是什么?

image.png

5、Vue有三大核心系统,分别是什么?对应什么工作?

image.png image.png

二、自己实现一个Mini-Vue

1、为什么在Vue2里面,如果给响应式对象新增属性时,需要调用 Vue.$set() 方法呢?

2、为什么Vue3选择Proxy呢?

image.png

3、Proxy的缺点,导致什么情况下要选择Vue2?

-【Proxy】不支持 【> IE9.0 系列】,所以如果要支持 【> IE9.0 系统】,还是要选择Vue2

4、实现Mini-Vue

image.png
// index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app"></div>

  <script src="./renderer.js"></script>
  <script src="./reactive.js"></script>
  <script src="./index.js"></script>
  <script>
    // 1.创建跟组件
    const App = {
      data: reactive({
        counter: 101
      }),
      render() {
        return h("div", { class: "my-div" }, [
          h("h2", { class: "title" }, `当前计数:${this.data.counter}`),
          h("button", {
            onClick: () => {
              this.data.counter++;
              console.log(this.data.counter);
            }
          }, "+1")
        ])
      }
    }
    // 2.挂载跟组件
    const app = createApp(App);
    app.mount("#app");
  </script>
</body>
</html>
// index.js

function createApp(rootComponent) {
  return {
    mount(selector) {
      const container =  document.querySelector(selector);
      let isMounted = false;
      let oldVNode = null;

      watchEffect(() => {
        if (!isMounted) {
          oldVNode = rootComponent.render();
          mount(oldVNode, container);
          isMounted = true;
        } else {
          let newVNode = rootComponent.render();
          patch(oldVNode, newVNode);
          oldVNode = newVNode;
          console.log("watchEffect监听到VNode改变,需要patch");
        }
      })
    }
  }
}
// reactive.js

class Dep {
  constructor() {
    this.subscribers = new Set();
  }

  addEffect(effect) {
    this.subscribers.add(effect);
  }

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


let currentEffect = null;
function watchEffect(effect) {
  currentEffect = effect;
  effect();
  currentEffect = null;
}

const targetMap = new WeakMap();
function getDep(raw, key) {
  let rawMap = targetMap.get(raw);
  if (!rawMap) {
    rawMap = new Map();
    targetMap.set(raw, rawMap);
  }

  let keyDep = rawMap.get(key);
  if (!keyDep) {
    keyDep = new Dep();
    rawMap.set(key, keyDep);
  }
  return keyDep;
}

// vue3对raw进行数据Proxy
function reactive(raw) {
  return new Proxy(raw, {
    get: function (obj, key) {
      if (typeof currentEffect === 'function') {
        let dep = getDep(raw, key);
        dep.addEffect(currentEffect);
      }
      return obj[key];
    },
    set: function (obj, key, newValue) {
      if (obj[key] !== newValue) {
        obj[key] = newValue;
        let dep = getDep(raw, key);
        dep.notify();
      }
    }
  });
}

const info = reactive({ counter: 100, name: "why" });
const foo = reactive({ height: 1.8 });

watchEffect(function () {
  console.log("effect1:", info.counter * 2, info.name);
});

watchEffect(function () {
  console.log("effect2:", info.counter * info.counter);
});

watchEffect(function () {
  console.log("effect3:", info.counter + 10, info.name);
});

watchEffect(function () {
  console.log("effect4:", foo.height);
});

console.log("==========以下是响应式触发========1=");
// info.counter++;
// info.name = "kobe";

foo.height = 2;


// 如果我们修改info.counter,应该只触发1、2、3
// 如果我们修改info.name,应该只触发1、3
// 如果我们修改foo.height,应该只触发4
// renderer.js

function h(tag, props, children) {
  return {
    tag,
    props,
    children
  }
}

function mount(vnode, container) {
  // 创建元素
  const el = document.createElement(vnode.tag);
  vnode.el = el;

  // 添加属性
  if (vnode.props) {
    for (const key in vnode.props) {
      const value = vnode.props[key];

      if (key.startsWith("on")) { //对事件监听的判断
        el.addEventListener(key.slice(2).toLowerCase(), value);
      } else {
        el.setAttribute(key, value);
      }
    }
  }

  //处理child
  if (vnode.children) {
    if (typeof vnode.children === "string") {
      el.textContent = vnode.children;
    } else {
      vnode.children.forEach(item => {
        mount(item, el);
      });
    }
  }

  // 4.将el挂载到container上
  container.appendChild(el);

}

const patch = (n1, n2) => {
  if (n1.tag !== n2.tag) {
    const n1ElParent = n1.el.parentElement;
    n1ElParent.removeChild(n1.el);
    mount(n2, n1ElParent);
  } else {
    // 1.取出element对象, 并且在n2中进行保存
    const el = n2.el = n1.el;

    // 2.处理props
    const oldProps = n1.props || {};
    const newProps = n2.props || {};
    // 2.1.获取所有的newProps添加到el
    for (const key in newProps) {
      const oldValue = oldProps[key];
      const newValue = newProps[key];
      if (newValue !== oldValue) {
        if (key.startsWith("on")) { // 对事件监听的判断
          el.addEventListener(key.slice(2).toLowerCase(), newValue)
        } else {
          el.setAttribute(key, newValue);
        }
      }
    }

    // 2.2.删除旧的props
    for (const key in oldProps) {
      if (key.startsWith("on")) { // 对事件监听的判断
        const value = oldProps[key];
        el.removeEventListener(key.slice(2).toLowerCase(), value)
      } 
      if (!(key in newProps)) {
        el.removeAttribute(key);
      }
    }

    // 3.处理children
    const oldChildren = n1.children || [];
    const newChidlren = n2.children || [];

    if (typeof newChidlren === "string") { // 情况一: newChildren本身是一个string
      // 边界情况 (edge case)
      if (typeof oldChildren === "string") {
        if (newChidlren !== oldChildren) {
          el.textContent = newChidlren
        }
      } else {
        el.innerHTML = newChidlren;
      }
    } else { // 情况二: newChildren本身是一个数组
      if (typeof oldChildren === "string") {
        el.innerHTML = "";
        newChidlren.forEach(item => {
          mount(item, el);
        })
      } else {
        // oldChildren: [v1, v2, v3, v8, v9]
        // newChildren: [v1, v5, v6]
        // 1.前面有相同节点的原生进行patch操作
        const commonLength = Math.min(oldChildren.length, newChidlren.length);
        for (let i = 0; i < commonLength; i++) {
          patch(oldChildren[i], newChidlren[i]);
        }

        // 2.newChildren.length > oldChildren.length
        if (newChidlren.length > oldChildren.length) {
          newChidlren.slice(oldChildren.length).forEach(item => {
            mount(item, el);
          })
        }

        // 3.newChildren.length < oldChildren.length
        if (newChidlren.length < oldChildren.length) {
          oldChildren.slice(newChidlren.length).forEach(item => {
            el.removeChild(item.el);
          })
        }
      }
    }
  }
}

上一篇 下一篇

猜你喜欢

热点阅读