Vue3 渲染器的核心功能:挂载与更新(very important!)

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8" />

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

    <title>Vue3渲染器的核心功能:挂载与更新</title>

    <style>

      .bgColor {

        background-color: deeppink;

      }

      .fontColor {

        color: #fff;

        font-size: 24px;

      }

    </style>

  </head>

  <body>

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

    <script>

      /**

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

       */

      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" },

            ],

          },

        ],

      };

      /**

       * 自定义渲染器

       */

      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) {

          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);

            } 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);

            }

          }

        }

        /**

         * 更新子节点

         */

        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);

        }

        /**

         * 更新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算法

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

              n2.children.forEach((c) => patch(null, c, 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) {

          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);

        }

        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 vnode = {

        type: "div",

        props: {

          id: "foo",

        },

        children: [

          {

            type: "div",

            children: [

              {

                type: "p",

                children: "千山鸟飞绝,万径人踪灭。",

              },

              {

                type: "p",

                children: "孤舟蓑笠翁,独钓寒江雪。",

              },

              newVnode1,

              newVnode1,

              newVnode1,

            ],

            props: {

              class: "bgColor fontColor",

              onClick: [

                () => {

                  alert("clicked 1");

                },

                () => {

                  alert("clicked 2");

                },

              ],

              onContextmenu: () => {

                alert("contextmenu");

              },

            },

          },

          {

            type: "button",

            props: {

              disabled: "", // 这是一个特殊属性案例处理

            },

            children: "这是一个按钮",

          },

          {

            type: "input",

            props: {

              form: "form1",

            },

          },

          fragmentNode,

        ],

      };

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

    </script>

  </body>

</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值