Vue 组件的实现原理

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>组件的实现原理</title>

    <style>

      .bgColor {

        background-color: deeppink;

      }

      .fontColor {

        color: #fff;

        font-size: 24px;

      }

      p {

        border: 1px dashed #ccc;

        padding: 6px;

      }

    </style>

    <!-- <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script> -->

    <script src="../lib/reactivity.global.js"></script>

  </head>

  <body>

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

    <script>

      // 引入响应式方法

      const { reactive, ref, effect, shallowReactive, shallowReadonly } =

        VueReactivity;

      /**

       * 定义文本节点和注释节点

       */

      const Text = Symbol();

      const Comment = Symbol();

      const Fragment = Symbol();

      const newVnode1 = {

        type: Text,

        children: "我是文本内容 ",

      };

      const newVnode2 = {

        type: Comment,

        children: "我是注释内容",

      };

      const fragmentNode = {

        type: "ul",

        children: [

          {

            type: Fragment,

            children: [

              { type: "li", children: "item 1" },

              { type: "li", children: "item 2" },

              { type: "li", children: "item 3" },

            ],

          },

        ],

      };

      // 任务缓存队列

      const queue = new Set();

      let isFlushing = false;

      const p = Promise.resolve();

      function queueJob(job) {

        queue.add(job);

        if (!isFlushing) {

          isFlushing = true;

          p.then(() => {

            try {

              queue.forEach((job) => job());

            } finally {

              isFlushing = false;

              queue.length = 0;

            }

          });

        }

      }

      // 全局变量

      let currentInstance = null;

      function setCurrentInstance(instance) {

        currentInstance = instance;

      }

      function onMounted(fn) {

        if (currentInstance) {

          currentInstance.mounted.push(fn);

        } else {

          console.error("onMounted 函数只能在 setup 中调用");

        }

      }

      /**

       * 自定义渲染器

       */

      function createRenderer(options) {

        const {

          createElement,

          insert,

          setElementText,

          patchProps,

          createText,

          setText,

        } = options;

        /**

         * 卸载

         */

        function unmount(vnode) {

          if (vnode.type === Fragment) {

            vnode.children.forEach((c) => unmount(c));

            return;

          }

          const parent = vnode.el.parentNode;

          if (parent) {

            parent.removeChild(vnode.el);

          }

        }

        function render(vnode, container) {

          if (vnode) {

            patch(container._vnode, vnode, container);

          } else {

            // 删除旧节点

            if (container._vnode) {

              unmount(container._vnode);

            }

          }

          container._vnode = vnode;

        }

        /**

         * 打补丁

         */

        function patch(n1, n2, container, anchor) {

          if (n1 && n1.type !== n2.type) {

            unmount(n1);

            n1 = null;

          }

          const { type } = n2;

          //   console.log(type);

          if (typeof type === "string") {

            if (!n1) {

              mountElement(n2, container, anchor);

            } else {

              patchElement(n1, n2);

            }

          } else if (type === Text) {

            // console.log("文本节点");

            // 文本节点

            if (!n1) {

              const el = (n2.el = createText(n2.children));

              insert(el, container);

            } else {

              const el = (n2.el = n1.el);

              if (n2.children !== n1.children) {

                setText(el, n2.children);

              }

            }

          } else if (type === Fragment) {

            // 片断

            if (!n1) {

              // 将Fragment的 children逐个挂载

              n2.children.forEach((c) => patch(null, c, container));

            } else {

              patchChildren(n1, n2, container);

            }

          } else if (typeof type === "object") {

            if (!n1) {

              // 挂载组件

              mountComponent(n2, container, anchor);

            } else {

              // 更新组件

              patchComponent(n1, n2, anchor);

            }

          }

        }

        /**

         * 更新组件

         */

        function patchComponent(n1, n2, anchor) {

          const instance = (n2.component = n1.component);

          const { props } = instance;

          if (hasPropsChanged(n1.props, n2.props)) {

            const [nextProps] = resolveProps(n2.type.props, n2.props);

            for (const k in nextProps) {

              props[k] = nextProps[k];

            }

            for (const k in props) {

              if (!(k in nextProps)) {

                delete props[k];

              }

            }

          }

        }

        function hasPropsChanged(prevProps, nextProps) {

          const nextKeys = Object.keys(nextProps);

          if (nextKeys.length !== Object.keys(prevProps).length) {

            return true;

          }

          for (let i = 0; i < nextKeys.length; i++) {

            const key = nextKeys[i];

            if (nextProps[key] !== prevProps[key]) {

              return true;

            }

          }

          return false;

        }

        /**

         * 挂载组件

         */

        function mountComponent(vnode, container, anchor) {

          const componentOptions = vnode.type;

          const {

            render: renderOld,

            data,

            props: propsOption,

            beforeCreate,

            created,

            beforeMount,

            mounted,

            beforeUpdate,

            updated,

            setup,

          } = componentOptions;

          let render = renderOld;

          beforeCreate && beforeCreate();

          // 构造响应式数据

          const state = reactive(data());

          // 解析出 props 数据与 attrs 数据

          const [props, attrs] = resolveProps(propsOption, vnode.props);

          console.log("props", props);

          console.log("attrs", attrs);

          const slots = vnode.children || {};

          // 定义组件实例

          const instance = {

            state,

            isMounted: false,

            subTree: null,

            props: shallowReactive(props),

            slots,

            mounted: [],

          };

          function emit(event, ...payload) {

            const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;

            const handler = instance.props[eventName];

            if (handler) {

              handler(...payload);

            } else {

              console.error("事件不存在");

            }

          }

          // 新增处理 setup 的逻辑

          const setupContext = { attrs, emit, slots };

          setCurrentInstance(instance);

          const setupResult = setup(

            shallowReadonly(instance.props),

            setupContext

          );

          setCurrentInstance(null);

          let setupState = null;

          if (typeof setupResult === "function") {

            if (render) {

              console.error("setup 函数返回渲染函数,render 选项将被忽略");

            }

            render = setupResult;

          } else {

            setupState = setupContext;

          }

          vnode.component = instance;

          // 创建渲染上下文对象,本质是组件实例的代理

          const renderContext = new Proxy(instance, {

            get(t, k, r) {

              const { state, props, slots } = t;

              if (k === "$slots") {

                return slots;

              }

              if (state && k in state) {

                return state[k];

              } else if (k in props) {

                return props[k];

              } else if (setupState && k in setupState) {

                return setupState[k];

              } else {

                console.error("不存在");

              }

            },

            set(t, k, v, r) {

              const { state, props } = t;

              if (state && k in state) {

                state[k] = v;

              } else if (k in props) {

                props[k] = v;

              } else if (setupState && k in setupState) {

                setupState[k] = v;

              } else {

                console.error("不存在");

              }

            },

          });

          created && created.call(renderContext);

          // 将组件的 render 函数调用包装到 effect 内

          effect(

            () => {

              const subTree = render.call(renderContext, renderContext);

              if (!instance.isMounted) {

                beforeMount && beforeMount.call(renderContext);

                patch(null, subTree, container, anchor);

                instance.isMounted = true;

                // mounted && mounted.call(renderContext);

                instance.mounted &&

                  instance.mounted.forEach((hook) => hook.call(renderContext));

              } else {

                beforeUpdate && beforeUpdate.call(renderContext);

                patch(instance.subTree, subTree, container, anchor);

                updated && updated.call(renderContext);

              }

              instance.subTree = subTree;

            },

            {

              scheduler: queueJob,

            }

          );

        }

        function resolveProps(options, propsData) {

          const props = {};

          const attrs = {};

          for (const key in propsData) {

            if (key in options || key.startsWith("on")) {

              props[key] = propsData[key];

            } else {

              attrs[key] = propsData[key];

            }

          }

          return [props, attrs];

        }

        /**

         * 更新子节点

         */

        function patchElement(n1, n2) {

          const el = (n2.el = n1.el);

          const oldProps = n1.props;

          const newProps = n2.props;

          for (const key in newProps) {

            if (newProps[key] !== oldProps[key]) {

              patchProps(el, key, oldProps[key], newProps[key]);

            }

          }

          for (const key in oldProps) {

            if (!(key in newProps)) {

              patchProps(el, key, oldProps[key], null);

            }

          }

          patchChildren(n1, n2, el);

        }

        /**

         * 抽离封装核心 Diff算法

         */

        function diff(n1, n2, container) {

          console.log("此处是核心的 Diff算法");

          console.log(n1);

          console.log(n2);

          const newChildren = n2.children;

          const oldChildren = n1.children;

          let j = 0;

          let oldVNode = oldChildren[j];

          let newVNode = newChildren[j];

          // 1.更新相同的前置节点

          while (oldVNode.key === newVNode.key) {

            patch(oldVNode, newVNode, container);

            j++;

            oldVNode = oldChildren[j];

            newVNode = newChildren[j];

          }

          let oldEnd = oldChildren.length - 1;

          let newEnd = newChildren.length - 1;

          oldVNode = oldChildren[oldEnd];

          newVNode = newChildren[newEnd];

          // 2.更新相同的后置节点

          while (oldVNode.key === newVNode.key) {

            patch(oldVNode, newVNode, container);

            oldEnd--;

            newEnd--;

            oldVNode = oldChildren[oldEnd];

            newVNode = newChildren[newEnd];

          }

          // 3.处理新增节点

          if (j > oldEnd && j <= newEnd) {

            const anchorIndex = newEnd + 1;

            const anchor =

              anchorIndex < newChildren.length

                ? newChildren[anchorIndex].el

                : null;

            while (j <= newEnd) {

              patch(null, newChildren[j++], anchor);

            }

          } else if (j > newEnd && j <= oldEnd) {

            // 4.处理移除节点

            while (j <= oldEnd) {

              unmount(oldChildren[j++]);

            }

          } else {

            // 5.判断是否需要进行DOM移动

            const count = newEnd - j + 1;

            const source = new Array(count).fill(-1);

            const oldStart = j;

            const newStart = j;

            // 如何判断节点是否需要移动

            let moved = false;

            let pos = 0;

            // 【血战到底!】

            // 构建索引表

            const keyIndex = {};

            for (let i = newStart; i <= newEnd; i++) {

              keyIndex[newChildren[i].key] = i;

            }

            // 新增 patched 变量,代表更新过的节点数量

            let patched = 0;

            for (let i = oldStart; i <= oldEnd; i++) {

              const oldVNode = oldChildren[i];

              // for(let k = newStart; k <= newEnd; k++) {

              //   const newVNode = newChildren[k]

              //   if (oldVNode.key === newVNode.key) {

              //     patch(oldVNode, newVNode, container)

              //     source[k - newStart] = i

              //   }

              // }

              if (patched < count) {

                const k = keyIndex[oldVNode.key];

                if (typeof k !== "undefined") {

                  newVNode = newChildren[k];

                  patch(oldVNode, newVNode, container);

                  patched++;

                  // 此处是关键!构造出来这么个玩意到底有什么用呢?

                  source[k - newStart] = i;

                  // 判断节点是否需要移动

                  if (k < pos) {

                    moved = true;

                  } else {

                    pos = k;

                  }

                } else {

                  unmount(oldVNode);

                }

              } else {

                unmount(oldVNode);

              }

            }

            if (moved) {

              // 如果 moved 为真,则需要进行 DOM 移动操作

              const seq = getSequence(source);

              console.log("seq", seq);

              let s = seq.length - 1;

              let i = count - 1;

              for (i; i >= 0; i--) {

                if (source[i] === -1) {

                  // 新增节点

                  const pos = i + newStart;

                  const newVNode = newChildren[pos];

                  const nextPos = pos + 1;

                  // 锚点

                  const anchor =

                    nextPos < newChildren.length

                      ? newChildren[nextPos].el

                      : null;

                  // 挂载

                  patch(null, newVNode, container, anchor);

                } else if (i !== seq[s]) {

                  // 该节点需要移动

                  const pos = i + newStart;

                  const newVNode = newChildren[pos];

                  const nextPos = pos + 1;

                  // 锚点

                  const anchor =

                    nextPos < newChildren.length

                      ? newChildren[nextPos].el

                      : null;

                  console.log("newVNode.el", newVNode);

                  // 移动

                  insert(newVNode.el, container, anchor);

                } else {

                  // 该节点不需要移动

                  s--;

                }

              }

            }

          }

        }

        /**

         * 经典算法:最长递增子序列

         */

        function getSequence(arr) {

          const p = arr.slice();

          const result = [0];

          let i, j, u, v, c;

          const len = arr.length;

          for (i = 0; i < len; i++) {

            const arrI = arr[i];

            if (arrI !== 0) {

              j = result[result.length - 1];

              if (arr[j] < arrI) {

                p[i] = j;

                result.push(i);

                continue;

              }

              u = 0;

              v = result.length - 1;

              while (u < v) {

                c = ((u + v) / 2) | 0;

                if (arr[result[c]] < arrI) {

                  u = c + 1;

                } else {

                  v = c;

                }

              }

              if (arrI < arr[result[u]]) {

                if (u > 0) {

                  p[i] = result[u - 1];

                }

                result[u] = i;

              }

            }

          }

          u = result.length;

          v = result[u - 1];

          while (u-- > 0) {

            result[u] = v;

            v = p[v];

          }

          return result;

        }

        /**

         * 更新children

         */

        function patchChildren(n1, n2, container) {

          if (typeof n2.children === "string") {

            if (Array.isArray(n1.children)) {

              n1.children.forEach((c) => unmount(c));

            }

            setElementText(container, n2.children);

          } else if (Array.isArray(n2.children)) {

            if (Array.isArray(n1.children)) {

              // 此处是核心的 Diff算法

              diff(n1, n2, container);

            } else {

              setElementText(container, "");

              // 挂载新节点

              n2.children.forEach((c) => patch(null, c, container));

            }

          } else {

            // 新子节点不存在,则卸载旧节点

            if (Array.isArray(n1.children)) {

              n1.children.forEach((c) => unmount(c));

            } else if (typeof n1.children === "string") {

              setElementText(container, "");

            }

          }

        }

        // 主要改动这里

        function mountElement(vnode, container, anchor) {

          const el = (vnode.el = createElement(vnode.type));

          if (typeof vnode.children === "string") {

            setElementText(el, vnode.children);

          } else if (Array.isArray(vnode.children)) {

            vnode.children.forEach((child) => {

              patch(null, child, el);

            });

          }

          if (vnode.props) {

            for (const key in vnode.props) {

              const value = vnode.props[key];

              patchProps(el, key, null, value);

            }

          }

          insert(el, container, anchor);

        }

        function shouldSetAsProps(el, key, value) {

          if (key === "form" && el.tagName === "INPUT") {

            return false;

          }

          return key in el;

        }

        return { render };

      }

      /**

       * 公共函数

       */

      function shouldSetAsProps(el, key, value) {

        if (key === "form" && el.tagName === "INPUT") {

          console.log("el", el);

          return false;

        }

        return key in el;

      }

      // 测试代码

      const renderer = createRenderer({

        createElement(tag) {

          return document.createElement(tag);

        },

        setElementText(el, text) {

          el.textContent = text;

        },

        insert(el, parent, anchor = null) {

          parent.insertBefore(el, anchor);

        },

        createText(text) {

          return document.createTextNode(text);

        },

        setText(el, text) {

          el.nodeValue = text;

        },

        patchProps(el, key, prevValue, nextValue) {

          if (/^on/.test(key)) {

            // const name = key.slice(2).toLowerCase();

            // prevValue && el.removeEventListener(name, prevValue)

            // el.addEventListener(name, nextValue);

            const invokers = el._vei || (el._vei = {});

            let invoker = invokers[key];

            const name = key.slice(2).toLowerCase();

            if (nextValue) {

              if (!invoker) {

                // 绑定一个伪造的事件处理函数invoker

                invoker = el._vei[key] = (e) => {

                  // e.timeStamp 触发事件的时间

                  if (e.timeStamp < invoker.attached) {

                    return;

                  }

                  if (Array.isArray(invoker.value)) {

                    invoker.value.forEach((fn) => fn(e));

                  } else {

                    invoker.value(e);

                  }

                };

                invoker.value = nextValue;

                // 绑定事件的时间

                invoker.attached = performance.now();

                el.addEventListener(name, invoker);

              } else {

                invoker.value = nextValue;

              }

            } else {

              el.removeEventListener(name, invoker);

            }

          } else if (key === "class") {

            el.className = nextValue || "";

          } else if (shouldSetAsProps(el, key, nextValue)) {

            const type = typeof el[key];

            if (type === "boolean" && nextValue === "") {

              el[key] = true;

            } else {

              el[key] = nextValue;

            }

          } else {

            el.setAttribute(key, nextValue);

          }

        },

      });

      // 组件

      const MyComponent = {

        name: "MyComponent",

        props: {

          title: String,

        },

        data() {

          return {

            foo: "hello world",

          };

        },

        setup(props, { emit }) {

          onMounted(() => {

            console.log("mounted 1");

          });

          onMounted(() => {

            console.log("mounted 2");

          });

          // emit('change', 1,2)

          //   return () => {

          //     return {

          //       type: "h1",

          //       children: "Hello setup",

          //       props: {

          //         onclick: () => {

          //           emit("change", 1, 2);

          //         },

          //       },

          //     };

          //   };

          const count = ref(10);

          return {

            count,

          };

        },

        render() {

          // 返回虚拟 DOM

          //   return {

          //     type: "div",

          //     children: `foo 的值是:${this.foo}, title is: ${this.title}, count is: ${this.count}`,

          //   };

          return {

            type: "div",

            children: [

              {

                type: "header",

                children: [this.$slots.header()],

              },

              {

                type: "div",

                children: [this.$slots.body()],

              },

              {

                type: "footer",

                children: [this.$slots.footer()],

              },

            ],

          };

        },

        beforeCreate() {

          console.log("beforeCreate");

        },

        created() {

          console.log("created");

        },

        beforeMount() {

          console.log("beforeMount");

        },

        mounted() {

          console.log("mounted");

        },

        beforeUpdate() {

          console.log("beforeUpdate");

        },

        updated() {

          console.log("updated");

        },

      };

      const vnode1 = {

        type: MyComponent,

        props: {

          title: "A big Title",

          onChange: (...payload) => {

            console.log("payload", payload);

            alert("handle emit");

          },

        },

        children: {

          header() {

            return { type: "h1", children: "我是标题" };

          },

          body() {

            return { type: "section", children: "我是内容" };

          },

          footer() {

            return { type: "p", children: "我是注脚" };

          },

        },

      };

      //   const vnode2 = {

      //     type: MyComponent,

      //     props: {

      //       title: "A small Title",

      //     },

      //   };

      renderer.render(vnode1, document.querySelector("#app"));

      //   setTimeout(() => {

      //     renderer.render(vnode2, document.querySelector("#app"));

      //   }, 2000);

    </script>

  </body>

</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值