节点的渲染

2023/7/14 vue

# 渲染器设计

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function createRenderer(options) {
    const { createElement, insert, setElementText } = options;

    function mountElement(vnode, container) {
      // 调用 createElement 函数创建元素
      const el = createElement(vnode.type);
      if (typeof vnode.children === "string") {
        // 调用 setElementText 设置元素的文本节点
        setElementText(el, vnode.children);
      }
      // 调用 insert 函数将元素插入到容器内
      insert(el, container);
    }

    // 给节点打补丁
    function patch(n1, n2, container) {
      if (!n1) {
        mountElement(n2, container);
      }
    }

    // 渲染节点
    function render(vnode, container) {
      if (vnode) {
        patch(container._vnode, vnode, container);
      } else {
        if (container._vnode) {
          container.innerHTML = "";
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const vnode = {
    type: "h1",
    children: "hello",
  };

  const renderer2 = createRenderer({
    // 用于创建元素
    createElement(tag) {
      console.log(`创建元素 ${tag}`);
      return { tag };
    },
    // 用于设置元素的文本节点
    setElementText(el, text) {
      console.log(`设置 ${JSON.stringify(el)} 的文本内容:${text}`);
      el.text = text;
    },
    // 用于在给定的 parent 下添加指定元素
    insert(el, parent, anchor = null) {
      console.log(
        `${JSON.stringify(el)} 添加到 ${JSON.stringify(parent)}`
      );
      parent.children = el;
    },
  });

  const container = { type: "root" };
  renderer2.render(vnode, container);
</script>

# 挂载子节点和元素属性

子节点:children 数组

元素属性:id

const vnode = {
  type: "div",
  props: {
    id: "foo",
  },
  children: [
    {
      type: "p",
      children: "hello",
    },
  ],
};

挂载节点

function mountElement(vnode, container) {
  const el = createElement(vnode.type);
  if (typeof vnode.children === "string") {
    setElementText(el, vnode.children);
  } else if (Array.isArray(vnode.children)) {
    // 如果 children 是数组,则遍历每一个子节点,并调用 patch 函数挂载它
    vnode.children.forEach((child) => {
      patch(null, child, el);
    });
  }

  // 如果 vnode.props 存在才处理它
  if (vnode.props) {
    // 遍历 vnode.props
    for (const key in vnode.props) {
      // 调用 setAttribute 将属性设置到元素上
      el.setAttribute(key, vnode.props[key]);
    }
  }
  insert(el, container);
}

function patch(n1, n2, container) {
  // 旧的节点没有
  if (!n1) {
    // 挂载节点
    mountElement(n2, container);
  }
}

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function createRenderer(options) {
    const { createElement, insert, setElementText } = options;

    function mountElement(vnode, container) {
      const el = createElement(vnode.type);
      if (typeof vnode.children === "string") {
        setElementText(el, vnode.children);
      } else if (Array.isArray(vnode.children)) {
        // 如果 children 是数组,则遍历每一个子节点,并调用 patch 函数挂载它
        vnode.children.forEach((child) => {
          patch(null, child, el);
        });
      }

      // 如果 vnode.props 存在才处理它
      if (vnode.props) {
        // 遍历 vnode.props
        for (const key in vnode.props) {
          // 调用 setAttribute 将属性设置到元素上
          el.setAttribute(key, vnode.props[key]);
        }
      }
      insert(el, container);
    }

    function patch(n1, n2, container) {
      // 旧的节点没有
      if (!n1) {
        // 挂载节点
        mountElement(n2, container);
      }
    }

    function render(vnode, container) {
      if (vnode) {
        patch(container._vnode, vnode, container);
      } else {
        if (container._vnode) {
          container.innerHTML = "";
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      // 把el插入到parent里
      // anchor 插入的位置
      parent.insertBefore(el, anchor);
    },
  });

  const vnode = {
    type: "div",
    props: {
      id: "foo",
    },
    children: [
      {
        type: "p",
        children: "hello",
      },
    ],
  };
  renderer.render(vnode, document.querySelector("#app"));
</script>

# 正确地设置元素属性

el.disabled = true 设置 DOM Properties

const vnode = {
  type: "button",
  props: {
    disabled: "",
  },
  children: "Button",
};

设置属性

// 判断是否作为属性设置
function shouldSetAsProps(el, key, value) {
  if (key === "form" && el.tagName === "input") {
    return false;
  }

  return key in el;
}

for (const key in vnode.props) {
  // 判断是否是form或input
  const value = vnode.props[key];
  if (shouldSetAsProps(el, key, value)) {
    console.log(el, key);
    const type = typeof el[key];
    if (type === "boolean" && value === "") {
      el[key] = true;
    } else {
      el[key] = value;
    }
  } else {
    el.setAttribute(key, vnode.props[key]);
  }
}

简化: 提取 patchProps

if(vnode.props){
    for (const key in vnode.props){
        patchProps(el,key,null,vnode.props[key])
    }
}


const renderer = createRenderer({
    ...,
    patchProps(el,key,preValue,nextValue){
        if(shouldSetAsProps(el,key,nextValue)){
            console.log(el,key)
            const type = typeof el[key];
            if(type === 'boolean' && nextValue === ''){
                el[key] = true;
            }else {
                el[key] = nextValue;
            }
        }else {
            el.setAttribute(key,vnode.props[key])
        }
    }
})

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const { createElement, insert, setElementText, patchProps } = options;

    function mountElement(vnode, container) {
      const 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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, container);
    }

    function patch(n1, n2, container) {
      if (!n1) {
        mountElement(n2, container);
      }
    }

    function render(vnode, container) {
      if (vnode) {
        patch(container._vnode, vnode, container);
      } else {
        if (container._vnode) {
          container.innerHTML = "";
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    patchProps(el, key, preValue, nextValue) {
      if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });

  const vnode = {
    type: "button",
    props: {
      disabled: "",
    },
    children: "Button",
  };
  renderer.render(vnode, document.querySelector("#app"));
</script>

# class 的处理

const vnode = {
  type: "p",
  props: {
    class: "foo bar baz",
  },
  children: "text",
};

设置 class

const renderer = createRenderer({
  // 省略
  patchProps(el, key, preValue, nextValue) {
    if (key === "class") {
      el.className = nextValue || "";
    } else if (shouldSetAsProps(el, key, nextValue)) {
      // 省略
    } else {
      // 省略
    }
  },
});

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const { createElement, insert, setElementText, patchProps } = options;

    function mountElement(vnode, container) {
      const 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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, container);
    }

    function patch(n1, n2, container) {
      if (!n1) {
        mountElement(n2, container);
      }
    }

    function render(vnode, container) {
      if (vnode) {
        patch(container._vnode, vnode, container);
      } else {
        if (container._vnode) {
          container.innerHTML = "";
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    patchProps(el, key, preValue, nextValue) {
      if (key === "class") {
        el.className = nextValue || "";
      } else if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });

  const vnode = {
    type: "p",
    props: {
      class: "foo bar baz",
    },
    children: "text",
  };
  renderer.render(vnode, document.querySelector("#app"));
</script>

# 卸载操作

// 初次挂载
renderer.render(vnode, document.querySelector("#app"));
// 新 vnode 为 null,意味着卸载之前渲染的内容
renderer.render(null, document.querySelector("#app"));

卸载节点

// 给元素修改和添加属性
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);
    }
  }
}

// 打补丁
function patch(n1, n2, container) {
  // n1 之前的节点 n2新节点
  // 如果之前没有挂载节点则挂载
  if (!n1) {
    mountElement(n2, container);
  } else {
    // 否则更新属性
    patchElement(n1, n2);
  }
}

// 卸载节点
function unmount(vnode) {
  const parent = vnode.el.parentNode;
  if (parent) {
    parent.removeChild(vnode.el);
  }
}

function render(vnode, container) {
  if (vnode) {
    // 新节点有,则打补丁
    patch(container._vnode, vnode, container);
  } else {
    // vnode=null 新节点每有,之前挂载过节点则卸载节点
    if (container._vnode) {
      unmount(container._vnode);
    }
  }
  container._vnode = vnode;
}

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const { createElement, insert, setElementText, patchProps } = options;

    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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, 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);
        }
      }
    }

    // 打补丁
    function patch(n1, n2, container) {
      // n1 之前的节点 n2新节点
      // 如果之前没有挂载节点则挂载
      if (!n1) {
        mountElement(n2, container);
      } else {
        // 否则更新属性
        patchElement(n1, n2);
      }
    }

    // 卸载节点
    function unmount(vnode) {
      const parent = vnode.el.parentNode;
      if (parent) {
        parent.removeChild(vnode.el);
      }
    }

    function render(vnode, container) {
      if (vnode) {
        // 新节点有,则打补丁
        patch(container._vnode, vnode, container);
      } else {
        // vnode=null 新节点每有,之前挂载过节点则卸载节点
        if (container._vnode) {
          unmount(container._vnode);
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    patchProps(el, key, preValue, nextValue) {
      if (key === "class") {
        el.className = nextValue || "";
      } else if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });

  const vnode = {
    type: "p",
    props: {
      class: "foo bar baz",
    },
    children: "text",
  };
  renderer.render(vnode, document.querySelector("#app"));
  renderer.render(null, document.querySelector("#app"));
</script>

# 区分 vnode 的类型

节点的类型判断

const oldVNode = {
  type: "p",
  children: "text",
};

const newVNode = {
  type: "div",
  children: "hello",
};

节点区分

function patch(n1, n2, container) {
  // 如果 n1 存在,则对比 n1 和 n2 的类型
  if (n1 && n1.type !== n2.type) {
    // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
    // 卸载旧的node
    unmount(n1);
    n1 = null;
  }
  // 代码运行到这里,证明 n1 和 n2 所描述的内容相同
  const { type } = n2;
  // 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
  if (typeof type === "string") {
    if (!n1) {
      // n1不存在,及旧的节点不存在,则直接挂载新的
      mountElement(n2, container);
    } else {
      // 对比n1,n2打上补丁
      patchElement(n1, n2);
    }
  } else if (typeof type === "object") {
    // 如果 n2.type 的值的类型是对象,则它描述的是组件
  }
}

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const { createElement, insert, setElementText, patchProps } = options;

    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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, 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);
        }
      }
    }

    function patch(n1, n2, container) {
      // 如果 n1 存在,则对比 n1 和 n2 的类型
      if (n1 && n1.type !== n2.type) {
        // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
        // 卸载旧的node
        unmount(n1);
        n1 = null;
      }
      // 代码运行到这里,证明 n1 和 n2 所描述的内容相同
      const { type } = n2;
      // 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
      if (typeof type === "string") {
        if (!n1) {
          // n1不存在,及旧的节点不存在,则直接挂载新的
          mountElement(n2, container);
        } else {
          // 对比n1,n2打上补丁
          patchElement(n1, n2);
        }
      } else if (typeof type === "object") {
        // 如果 n2.type 的值的类型是对象,则它描述的是组件
      }
    }

    function unmount(vnode) {
      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) {
          // container.innerHTML = "";
          unmount(container._vnode);
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    patchProps(el, key, preValue, nextValue) {
      if (key === "class") {
        el.className = nextValue || "";
      } else if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });

  const vnode = {
    type: "p",
    props: {
      class: "foo bar baz",
    },
    children: "text",
  };
  renderer.render(vnode, document.querySelector("#app"));

  const newVnode = {
    type: "div",
    props: {
      id: "foo",
    },
    children: "hello",
  };
  setTimeout(() => {
    renderer.render(newVnode, document.querySelector("#app"));
  }, 1000);
</script>

# 事件处理

// 情况1
const vnode = {
  type: "p",
  props: {
    // 使用 onXxx 描述事件
    onClick: () => {
      alert("clicked");
    },
  },
  children: "text",
};
// 情况2
const vnode = {
  type: "p",
  props: {
    onClick: () => {
      alert("clicked");
    },
    onContextmenu: () => {
      alert("contextmenu");
    },
  },
  children: "text",
};
// 情况3
const vnode = {
  type: "p",
  props: {
    onClick: [
      () => {
        alert("clicked 1");
      },
      () => {
        alert("clicked 2");
      },
    ],
  },
  children: "text",
};

事件处理

const renderer = createRenderer({
  // 省略...
  patchProps(el, key, preValue, nextValue) {
    // 匹配以 on 开头的属性,视其为事件
    if (/^on/.test(key)) {
      // 获取为该元素伪造的事件处理函数 invoker
      let invokers = el._vei || (el._vei = {});
      let invoker = invokers[key];
      // 根据属性名称得到对应的事件名称,例如 onClick ---> click
      const name = key.slice(2).toLowerCase();
      if (nextValue) {
        if (!invoker) {
          // 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
          // vei 是 vue event invoker 的首字母缩写
          invoker = el._vei[key] = (e) => {
            // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
            if (Array.isArray(invoker.value)) {
              invoker.value.forEach((fn) => fn(e));
            } else {
              invoker.value(e);
            }
          };
          // 将真正的事件处理函数赋值给 invoker.value
          invoker.value = nextValue;
          // 绑定 invoker 作为事件处理函数
          el.addEventListener(name, invoker);
        } else {
          // 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
          invoker.value = nextValue;
        }
      } else if (invoker) {
        // 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
        el.removeEventListener(name, invoker);
      }
    } else if (key === "class") {
      // 省略...
    } else if (shouldSetAsProps(el, key, nextValue)) {
      // 省略...
    } else {
      // 省略...
    }
  },
});

# 事件冒泡与更新时机问题

const { effect, ref } = VueReactivity;
const bol = ref(false);

effect(() => {
  // 创建 vnode
  const vnode = {
    type: "div",
    props: bol.value
      ? {
          onClick: () => {
            alert("父元素 clicked");
          },
        }
      : {},
    children: [
      {
        type: "p",
        props: {
          onClick: () => {
            bol.value = true;
          },
        },
        children: "text",
      },
    ],
  };
  // 渲染 vnode
  renderer.render(vnode, document.querySelector("#app"));
});

更新时机

















 
 
 









 




















const renderer = createRenderer({
  // 省略...
  patchProps(el, key, preValue, nextValue) {
    // 匹配以 on 开头的属性,视其为事件
    if (/^on/.test(key)) {
      // 获取为该元素伪造的事件处理函数 invoker
      let invokers = el._vei || (el._vei = {});
      let invoker = invokers[key];
      // 根据属性名称得到对应的事件名称,例如 onClick ---> click
      const name = key.slice(2).toLowerCase();
      if (nextValue) {
        if (!invoker) {
          // 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
          // vei 是 vue event invoker 的首字母缩写
          invoker = el._vei[key] = (e) => {
            // 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
            if (e.timeStamp < invoker.attached) {
              return;
            }
            // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
            if (Array.isArray(invoker.value)) {
              invoker.value.forEach((fn) => fn(e));
            } else {
              invoker.value(e);
            }
          };
          // 将真正的事件处理函数赋值给 invoker.value
          invoker.value = nextValue;
          invoker.attached = performance.now();
          // 绑定 invoker 作为事件处理函数
          el.addEventListener(name, invoker);
        } else {
          // 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
          invoker.value = nextValue;
        }
      } else if (invoker) {
        // 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
        el.removeEventListener(name, invoker);
      }
    } else if (key === "class") {
      // 省略...
    } else if (shouldSetAsProps(el, key, nextValue)) {
      // 省略...
    } else {
      // 省略...
    }
  },
});

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const { createElement, insert, setElementText, patchProps } = options;

    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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, 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);
        }
      }
    }

    function patch(n1, n2, container) {
      // 如果 n1 存在,则对比 n1 和 n2 的类型
      if (n1 && n1.type !== n2.type) {
        // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
        // 卸载旧的node
        unmount(n1);
        n1 = null;
      }
      // 代码运行到这里,证明 n1 和 n2 所描述的内容相同
      const { type } = n2;
      // 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
      if (typeof type === "string") {
        if (!n1) {
          // n1不存在,及旧的节点不存在,则直接挂载新的
          mountElement(n2, container);
        } else {
          // 对比n1,n2打上补丁
          patchElement(n1, n2);
        }
      } else if (typeof type === "object") {
        // 如果 n2.type 的值的类型是对象,则它描述的是组件
      }
    }

    function unmount(vnode) {
      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) {
          // container.innerHTML = "";
          unmount(container._vnode);
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    patchProps(el, key, preValue, nextValue) {
      // 匹配以 on 开头的属性,视其为事件
      if (/^on/.test(key)) {
        // 获取为该元素伪造的事件处理函数 invoker
        let invokers = el._vei || (el._vei = {});
        let invoker = invokers[key];
        // 根据属性名称得到对应的事件名称,例如 onClick ---> click
        const name = key.slice(2).toLowerCase();
        if (nextValue) {
          if (!invoker) {
            // 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
            // vei 是 vue event invoker 的首字母缩写
            invoker = el._vei[key] = (e) => {
              // 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
              if (e.timeStamp < invoker.attached) {
                return;
              }
              // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
              if (Array.isArray(invoker.value)) {
                invoker.value.forEach((fn) => fn(e));
              } else {
                invoker.value(e);
              }
            };
            // 将真正的事件处理函数赋值给 invoker.value
            invoker.value = nextValue;
            invoker.attached = performance.now();
            // 绑定 invoker 作为事件处理函数
            el.addEventListener(name, invoker);
          } else {
            // 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
            invoker.value = nextValue;
          }
        } else if (invoker) {
          // 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
          el.removeEventListener(name, invoker);
        }
      } else if (key === "class") {
        el.className = nextValue || "";
      } else if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });

  const { effect, ref } = VueReactivity;
  const bol = ref(false);

  effect(() => {
    // 创建 vnode
    const vnode = {
      type: "div",
      props: bol.value
        ? {
            onClick: () => {
              alert("父元素 clicked");
            },
          }
        : {},
      children: [
        {
          type: "p",
          props: {
            onClick: () => {
              bol.value = true;
            },
          },
          children: "text",
        },
      ],
    };
    // 渲染 vnode
    renderer.render(vnode, document.querySelector("#app"));
  });
</script>

# 更新子节点

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, "");
    }
    // 如果也没有旧子节点,那么什么都不需要做
  }
}

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const { createElement, insert, setElementText, patchProps } = options;

    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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, container);
    }

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

    function patch(n1, n2, container) {
      // 如果 n1 存在,则对比 n1 和 n2 的类型
      if (n1 && n1.type !== n2.type) {
        // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
        // 卸载旧的node
        unmount(n1);
        n1 = null;
      }
      // 代码运行到这里,证明 n1 和 n2 所描述的内容相同
      const { type } = n2;
      // 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
      if (typeof type === "string") {
        if (!n1) {
          // n1不存在,及旧的节点不存在,则直接挂载新的
          mountElement(n2, container);
        } else {
          // 对比n1,n2打上补丁
          patchElement(n1, n2);
        }
      } else if (typeof type === "object") {
        // 如果 n2.type 的值的类型是对象,则它描述的是组件
      }
    }

    function unmount(vnode) {
      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) {
          // container.innerHTML = "";
          unmount(container._vnode);
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    patchProps(el, key, preValue, nextValue) {
      // 匹配以 on 开头的属性,视其为事件
      if (/^on/.test(key)) {
        // 获取为该元素伪造的事件处理函数 invoker
        let invokers = el._vei || (el._vei = {});
        let invoker = invokers[key];
        // 根据属性名称得到对应的事件名称,例如 onClick ---> click
        const name = key.slice(2).toLowerCase();
        if (nextValue) {
          if (!invoker) {
            // 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
            // vei 是 vue event invoker 的首字母缩写
            invoker = el._vei[key] = (e) => {
              // 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
              if (e.timeStamp < invoker.attached) {
                return;
              }
              // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
              if (Array.isArray(invoker.value)) {
                invoker.value.forEach((fn) => fn(e));
              } else {
                invoker.value(e);
              }
            };
            // 将真正的事件处理函数赋值给 invoker.value
            invoker.value = nextValue;
            invoker.attached = performance.now();
            // 绑定 invoker 作为事件处理函数
            el.addEventListener(name, invoker);
          } else {
            // 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
            invoker.value = nextValue;
          }
        } else if (invoker) {
          // 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
          el.removeEventListener(name, invoker);
        }
      } else if (key === "class") {
        el.className = nextValue || "";
      } else if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });

  const oldVnode = {
    type: "div",
    children: "test",
  };
  renderer.render(oldVnode, document.querySelector("#app"));

  const newVnode = {
    type: "div",
    children: null,
  };

  renderer.render(newVnode, document.querySelector("#app"));
</script>

# 文本节点

const Text = Symbol();
const vnode = {
  type: Text,
  children: "Some Text",
};

文本节点处理

function patch(n1, n2, container) {
  // 省略...
  if (typeof type === "string") {
    // 省略...
  } else if (type === Text) {
    if (!n1) {
      // n1不存在,及旧的节点不存在,则直接挂载新的
      const el = (n2.el = createText(n2.children));
      // 将文本节点插入到容器中
      insert(el, container);
    } else {
      // 如果旧 vnode 存在,只需要使用新文本节点的文本内容更新旧文本节点即可
      const e1 = (n2.el = n1.el);
      if (n2.children !== n1.children) {
        e1.nodeValue = n2.children;
      }
    }
  }
}

完整代码

See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
  function shouldSetAsProps(el, key, value) {
    if (key === "form" && el.tagName === "input") {
      return false;
    }

    return key in el;
  }
  function createRenderer(options) {
    const {
      createElement,
      insert,
      setElementText,
      patchProps,
      createText,
      setText,
    } = options;

    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) {
          patchProps(el, key, null, vnode.props[key]);
        }
      }
      insert(el, container);
    }

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

    function patch(n1, n2, container) {
      // 如果 n1 存在,则对比 n1 和 n2 的类型
      if (n1 && n1.type !== n2.type) {
        // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
        // 卸载旧的node
        unmount(n1);
        n1 = null;
      }
      // 代码运行到这里,证明 n1 和 n2 所描述的内容相同
      const { type } = n2;
      // 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
      if (typeof type === "string") {
        if (!n1) {
          // n1不存在,及旧的节点不存在,则直接挂载新的
          mountElement(n2, container);
        } else {
          // 对比n1,n2打上补丁
          patchElement(n1, n2);
        }
      } else if (type === Text) {
        if (!n1) {
          // n1不存在,及旧的节点不存在,则直接挂载新的
          const el = (n2.el = createText(n2.children));
          // 将文本节点插入到容器中
          insert(el, container);
        } else {
          // 如果旧 vnode 存在,只需要使用新文本节点的文本内容更新旧文本节点即可
          const e1 = (n2.el = n1.el);
          if (n2.children !== n1.children) {
            e1.nodeValue = n2.children;
          }
        }
      }
    }

    function unmount(vnode) {
      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) {
          // container.innerHTML = "";
          unmount(container._vnode);
        }
      }
      container._vnode = vnode;
    }

    return {
      render,
    };
  }

  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag);
    },
    setElementText(el, text) {
      el.textContent = text;
    },
    insert(el, parent, anchor = null) {
      console.log(el, parent, anchor);
      parent.insertBefore(el, anchor);
    },
    createText(text) {
      return document.createTextNode(text);
    },
    setText(el, text) {
      el.nodeValue = text;
    },
    patchProps(el, key, preValue, nextValue) {
      // 匹配以 on 开头的属性,视其为事件
      if (/^on/.test(key)) {
        // 获取为该元素伪造的事件处理函数 invoker
        let invokers = el._vei || (el._vei = {});
        let invoker = invokers[key];
        // 根据属性名称得到对应的事件名称,例如 onClick ---> click
        const name = key.slice(2).toLowerCase();
        if (nextValue) {
          if (!invoker) {
            // 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
            // vei 是 vue event invoker 的首字母缩写
            invoker = el._vei[key] = (e) => {
              // 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
              if (e.timeStamp < invoker.attached) {
                return;
              }
              // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
              if (Array.isArray(invoker.value)) {
                invoker.value.forEach((fn) => fn(e));
              } else {
                invoker.value(e);
              }
            };
            // 将真正的事件处理函数赋值给 invoker.value
            invoker.value = nextValue;
            invoker.attached = performance.now();
            // 绑定 invoker 作为事件处理函数
            el.addEventListener(name, invoker);
          } else {
            // 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
            invoker.value = nextValue;
          }
        } else if (invoker) {
          // 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
          el.removeEventListener(name, invoker);
        }
      } else if (key === "class") {
        el.className = nextValue || "";
      } else if (shouldSetAsProps(el, key, nextValue)) {
        console.log(el, key);
        const type = typeof el[key];
        if (type === "boolean" && nextValue === "") {
          el[key] = true;
        } else {
          el[key] = nextValue;
        }
      } else {
        el.setAttribute(key, vnode.props[key]);
      }
    },
  });
  const Text = Symbol();
  const newVnode = {
    type: Text,
    children: "Some Text",
  };
  renderer.render(newVnode, document.querySelector("#app"));

  const oldVnode = {
    type: Text,
    children: "Some Text2",
  };
  renderer.render(oldVnode, document.querySelector("#app"));
</script>

# Fragment

const Fragment = Symbol();
const vnode = {
  type: "div",
  children: [
    {
      type: Fragment,
      children: [
        { type: "p", children: "text 1" },
        { type: "p", children: "text 2" },
        { type: "p", children: "text 3" },
      ],
    },
  ],
};

Fragment 处理

function patch(n1, n2, container) {
  // 省略...
  if (typeof type === "string") {
    // 省略...
  } else if (type === Text) {
    // 省略...
  } else if (type === Fragment) {
    if (!n1) {
      // 如果旧 vnode 不存在,则只需要将 Fragment 的 children 逐个挂载即可
      n2.children.forEach((c) => patch(null, c, container));
    } else {
      // 如果旧 vnode 存在,则只需要更新 Fragment 的 children 即可
      patchChildren(n1, n2, container);
    }
  }
}
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

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

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

function shouldSetAsProps(el,key,value){
    if(key === 'form' && el.tagName === 'input'){
        return false;
    }

    return key in el;
}
function createRenderer(options){
    const {
        createElement,
        insert,
        setElementText,
        patchProps,
        createText,
        setText
    } = options;



    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){
                patchProps(el,key,null,vnode.props[key])
            }
        }
        insert(el,container)
    }

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

    function patch(n1,n2,container){
        // 如果 n1 存在,则对比 n1 和 n2 的类型
        if(n1 && n1.type !== n2.type){
            // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
            // 卸载旧的node
            unmount(n1)
            n1 = null;
        }
        // 代码运行到这里,证明 n1 和 n2 所描述的内容相同
        const {type} = n2;
        // 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
        if(typeof type === 'string'){
            if(!n1){
                // n1不存在,及旧的节点不存在,则直接挂载新的
                mountElement(n2,container)
            }else {
                // 对比n1,n2打上补丁
                patchElement(n1, n2)
            }
        } else if (type === Text){
            if(!n1){
                // n1不存在,及旧的节点不存在,则直接挂载新的
                const el = n2.el = createText(n2.children);
                // 将文本节点插入到容器中
                insert(el,container)
            }else {
                // 如果旧 vnode 存在,只需要使用新文本节点的文本内容更新旧文本节点即可
                const e1 = n2.el = n1.el;
                if(n2.children!== n1.children){
                    e1.nodeValue = n2.children
                }
            }
        }else if(type === Fragment){
            if(!n1){
                // 如果旧 vnode 不存在,则只需要将 Fragment 的 children 逐个挂载即可
                n2.children.forEach(c => patch(null,c,container))
            }else {
                // 如果旧 vnode 存在,则只需要更新 Fragment 的 children 即可
                patchChildren(n1,n2,container)
            }
        }
    }

    function unmount(vnode){
        // 在卸载时,如果卸载的 vnode 类型为 Fragment,则需要卸载其 children
        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){
                // container.innerHTML = "";
                unmount(container._vnode)
            }
        }
        container._vnode = vnode;
    }

    return {
        render
    }
}

const renderer = createRenderer({
    createElement(tag){
        return document.createElement(tag)
    },
    setElementText(el,text){
        el.textContent = text;
    },
    insert(el,parent,anchor = null){
        console.log(el,parent,anchor)
        parent.insertBefore(el,anchor);
    },
    createText(text){
      return document.createTextNode(text);
    },
    setText(el,text){
      el.nodeValue = text;
    },
    patchProps(el,key,preValue,nextValue){
        // 匹配以 on 开头的属性,视其为事件
        if(/^on/.test(key)){
            // 获取为该元素伪造的事件处理函数 invoker
            let invokers = el._vei || (el._vei = {});
            let invoker = invokers[key];
            // 根据属性名称得到对应的事件名称,例如 onClick ---> click
            const name = key.slice(2).toLowerCase();
            if(nextValue){
                if(!invoker){
                    // 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
                    // vei 是 vue event invoker 的首字母缩写
                    invoker = el._vei[key] = (e)=>{
                        // 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
                        if (e.timeStamp < invoker.attached) {
                            return
                        }
                        // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
                        if(Array.isArray(invoker.value)){
                           invoker.value.forEach(fn=>fn(e))
                        }else {
                            invoker.value(e)
                        }
                    }
                    // 将真正的事件处理函数赋值给 invoker.value
                    invoker.value = nextValue;
                    invoker.attached = performance.now()
                    // 绑定 invoker 作为事件处理函数
                    el.addEventListener(name,invoker)
                }else {
                    // 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
                    invoker.value = nextValue;
                }
            }else if(invoker){
                // 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
                el.removeEventListener(name,invoker);
            }
        }else if(key === 'class'){
            el.className = nextValue || '';
        }else if(shouldSetAsProps(el,key,nextValue)){
            console.log(el,key)
            const type = typeof el[key];
            if(type === 'boolean' && nextValue === ''){
                el[key] = true;
            }else {
                el[key] = nextValue;
            }
        }else {
            el.setAttribute(key,vnode.props[key])
        }
    }
})
const Fragment = Symbol()
const vnode = {
    type: 'div',
    children: [
        {
            type: Fragment,
            children: [
                { type: 'p', children: 'text 1' },
                { type: 'p', children: 'text 2' },
                { type: 'p', children: 'text 3' }
            ]
        }
    ]
}
renderer.render(vnode, document.querySelector('#app'))
</script>