第十二章-组件的实现原理

70 阅读6分钟

一、渲染组件

组件的vnode如下

const MyComponent = {
  name: "MyComponent",
  data() {
    return {foo: 1}
  },
  render() {
    return {
      type: "div",
      children: this.foo,
    }
  },
  created() {},
  beforeCreate() {},
  mounted() {},
  beforeMount() {},
  updated() {},
  beforeUpdate() {}
}
const vnode = {
  type: MyComponent
}
renderer.render(vnode, document.getElementById("app"))

在patch添加组件类型的判断

function patch(n1, n2, container, anchor) {
    if(n1 && n1.type !== n2.type) {
      unmount(n1);
      n1 = null;
    }
    let { type } = n2;
    if(typeof type === "string") { 
       // 普通标签元素
    } else if(typeof  type === "object"){  
       if(!n1) {
         mountComponent()
       } else {
         patchComponent()
       }
    } else if(type === Text){
       // 文本节点
    } else if(type === Fragment) {
       // 片段节点
    } else {
      // 省略了其他类型的vnode
    }
  }

mountComponent的实现

 function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render } = componentOptions;
    let subTree = render();
    patch(null, subTree, container, anchor);
  }

二、组件状态和自更新

1、组件自身状态的初始化

 function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions;
    let state = reactive(data())
    let subTree = render.call(state, state);  // render函数内部可以通过this访问组件自身状态数据
    patch(null, subTree, container, anchor);
  }

2、自更新

当组件自身状态发生变化时, 需要有能力触发组件更新

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions;
    let state = reactive(data())
    effect(() => {
      let subTree = render.call(state, state);  // render函数内部可以通过this访问组件自身状态数据
      patch(null, subTree, container, anchor);
    })
  }

优化: 无论对响应式数据进行多少次修改, 副作用函数只会重新执行一次, 需要对effec函数添加调度器

let isFlushing = false;
  let queue = new Set();
  let p = Promise.resolve();
  function queueJob(job) {
    queue.add(job)
    if(!isFlushing) {
      isFlushing = true;
      p.then(() => {
        try {
          queue.forEach(job => job())
        } finally {
          isFlushing = false;
          queue.clear() // 任务执行完成后需要清除
        }
      })
    }
  }
  function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions;
    let state = reactive(data())
    effect(() => {
      let subTree = render.call(state, state);  // render函数内部可以通过this访问组件自身状态数据
      patch(null, subTree, container, anchor);
    }, {
      scheduler: queueJob,
    })
  }

三、组件实例与组件的生命周期

3.1组件示例instance

数据更新的时, 需要新的subTree和上一次组件所渲染的subTree进打补丁

 function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions
    let state = reactive(data())
    const instance = {
      state,
      isMounted: false,
      subTree: null,
    }
    vnode.component = instance;
    effect(() => {
      let subTree = render.call(state, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
      } else {
        patch(instance.subTree, subTree, container, anchor)
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })
  }
  • state: 组件自身的状态数据, 即data
  • isMounted: 组件是否被挂载
  • subTree: 组件的渲染函数返回的虚拟DOM, 即组件的subTree

3.2、组件生命周期

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated } = componentOptions
    beforeCreate && beforeCreate();
    let state = reactive(data())
    const instance = {
      state,
      isMounted: false,
      subTree: null,
    }
    vnode.instance = instance;
    created && created();
    effect(() => {
      let subTree = render.call(state, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
        beforeMount && beforeMount()
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        mounted && mounted();
      } else {
        beforeUpdate && beforeUpdate()
        patch(instance.subTree, subTree, container, anchor);
        updated && updated();
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })
  }

实际上,由于可能存在多个同样的组件生命周期钩子, 比如mixins的钩子, 通常需要将组件生命周期的钩子序列化为一个数组

四、props与组件的被动更新

4.1、props

<my-component title="A big title" :other="val" />
// 渲染到页面上
<div>`count is A big title</div>

对应的vnode

const MyComponent = {
  render() {
    return {
      type: "div",
      children: `count is${this.title}`
    }
  },
  props: {
     title: "String"
  }
}
const vnode = {
  type: MyComponent,
  props: {
    title: "A big Title",
    other: this.value
  }
}

props有两部分构成

  • 为组件传递的props数据, 即vnode.props
  • 组件对象中定义的props选项, 即MyComponent.props

重点: 将vnode.props中, 属MyComponent.props和不属于MyComponent.props区分出来

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
    beforeCreate && beforeCreate();
    let state = reactive(data())
    let [ props, attrs ] = resolveProps(propsOption, vnode.props);
    const instance = {
      state,
      props: shallowReactive(props),
      isMounted: false,
      subTree: null,
    }
    vnode.component = instance;
    created && created();
    effect(() => {
      let subTree = render.call(state, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
        beforeMount && beforeMount()
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        mounted && mounted();
      } else {
        beforeUpdate && beforeUpdate()
        patch(instance.subTree, subTree, container, anchor);
        updated && updated();
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })
  }
// 用于解析组件props和attrs数据
  function resolveProps(option, propsData) {
     let props = {};
     let attrs = {};
     for(let key in option) {
        if(key in propsData) {
          props[key] = propsData;
        } else {
          attrs[key] = option[key];
        }
     }
     return [props, attrs]
  }

注意:还有默认值、类型校验的处理

4.2、被动更新

参数更新 -> 触发effect函数 -> 已经mounted过 -> 进行patch -> patchComponent

 function hasPropsChanged(prevProps, nextProps) {
    const prevKey = Object.keys(prevProps).length;
    const nextKey = Object.keys(nextProps).length;
    if(prevKey !== nextKey) return true;
    for(let key in prevKey) {
       if(nextKey[key] !== prevKey[key]) return true;
    }
    return false;
  }
  
 function patchComponent(n1, n2, container) {
    const instance = n2.component = n1.component; // 获取组件实例, 并且给新的vnode绑定实例
    const {props} = instance;  // 获取当前的props;
    if(hasPropsChanged(n1.props, n2.props)) {  // 检测props是否发生变化, 如果没有变化则不需要更新
       let [nextProps] = resolveProps(n2.type.props, n2.props);
       // 更新props
       for(let key in nextProps) {
         props[key] = nextProps[key];
       }
       // 删除不要的props
       for(let key in props) {
         if(!(key in nextProps)) delete props[key];
       }
    }
  }

其中: props数据与组件自身的状态数据都需要暴露到渲染函数中, 并使得渲染函数能够通过this访问他们

解决方法: 封装一个渲染上下文, 并且进行代理

   const renderContext = new Proxy(instance, {
      get(target, p, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          return state[p]
        } else if(p in props) {
          return props[p]
        } else {
          console.log("不存在")
        }
      },
      set(target, p, value, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          state[p] = value;
        } else if(p in props) {
          console.log("props is readonly")
        } else {
          console.log("不存在");
        }
      }
    });
 function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
    beforeCreate && beforeCreate();
    let state = reactive(data())
    let [ props, attrs ] = resolveProps(propsOption, vnode.props);
    const instance = {
      state,
      props: shallowReactive(props),
      isMounted: false,
      subTree: null,
    }
    vnode.component = instance;
    const renderContext = new Proxy(instance, {
      get(target, p, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          return state[p]
        } else if(p in props) {
          return props[p]
        } else {
          console.log("不存在")
        }
      },
      set(target, p, value, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          state[p] = value;
        } else if(p in props) {
          console.log("props is readonly")
        } else {
          console.log("不存在");
        }
      }
    });
    created && created();
    effect(() => {
      let subTree = render.call(renderContext, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
        beforeMount && beforeMount()
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        mounted && mounted();
      } else {
        beforeUpdate && beforeUpdate()
        patch(instance.subTree, subTree, container, anchor);
        updated && updated();
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })
  }

注意: 除了组件自身数据、props数据外, 完整的组件还包含methods, computed等选项中定义的数据和方法,这些都需要在渲染上下文对象中处理

五、setup函数的作用与实现

 function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { setup, render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
    beforeCreate && beforeCreate();
    let state = data ? reactive(data()) : null;
    let [ props, attrs ] = resolveProps(propsOption, vnode.props);
    const instance = {
      state,
      props: shallowReactive(props),
      isMounted: false,
      subTree: null,
    }
    const setupContext = {attrs};
    const setupResult = setup(shallowReadonly(vnode.props), setupContext); // 参数1: 只读的props,防止被更改, 参数2: 上下文,包括 slot, emit , attrs, expose
    let setupState = null
    if(typeof  setupResult === "function") {
      // 如果setup返回的值是函数,则如果存在render则报告冲突,将该函数作为render函数使用
      if(render) console.error("setup 函数返回渲染函数, render函数将被忽略");
    } else {
      // 如果setup返回的值不是函数,则作为数据状态赋值给setupState
      setupState = setupResult;
    }
    vnode.component = instance;
    const renderContext = new Proxy(instance, {
      get(target, p, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          return state[p]
        } else if(p in props) {
          return props[p]
        } else if(setupState && p in setup) {
          return setupState[p] // 渲染上下文增加对setupState的支持
        } else  {
          console.log("不存在")
        }
      },
      set(target, p, value, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          state[p] = value;
        } else if(p in props) {
          console.log("props is readonly")
        } else if (setupState && p in setupState){
          setupState[p] = value; // 渲染上下文增加对setupState的支持
        } else {
          console.log("不存在");
        }
      }
    });
    created && created();
    effect(() => {
      let subTree = render.call(renderContext, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
        beforeMount && beforeMount()
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        mounted && mounted();
      } else {
        beforeUpdate && beforeUpdate()
        patch(instance.subTree, subTree, container, anchor);
        updated && updated();
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })
  }

setup的返回结果

  • 函数,函数将作为组件的render使用

    const Comp = {
      setup() {
        return () => {
          return {type: "div", children: "text1"}
        }
      },
      render() {
        return  {type: "div", children: "text2"} // 这个会被覆盖
      }
    }
    
  • 返回一个对象, 该对象中包含的数据将暴露给模板使用

    const Comp = {
      setup() {
        let count = ref(0)
        return  {count}
      },
      render() {
        return  {type: "div", children: `${this.count}`}  
      }
    }
    

setup中接受的参数

const Comp = {
  setup(props, setupContext) {
     let value = props.data;
     let {attrs, slot, emit, expose} = setupContext;
  },
}
  • props对象,
  • setupContext
    • slots 组件接收到的插槽
    • emit 用于发射自定义事件
    • attrs 没有显式声明为props的属性
    • expose 用来显式地对外暴露组件数据

六、组件事件与emit的实现

  function emit(event, ...payload) {
      let eventName = `on${event[0].toUpperCase()}${event.slice(0)}`;  // change => onChange
       const handler = props[eventName];
       if(handler) {
         handler(...payload);
       } else {
         console.log("事件不存在")
       }
    }
    const setupContext = {attrs, emit};

上面有个问题, 拿的是props中值, 但是之前的逻辑, 如果没有type.props中定义, 那么会被划分到attrs, 所以拆解props的代码需要修改

function resolveProps(option, propsData) {
    let props = {};
    let attrs = {};
    for(let key in option) {
      if(key in propsData  || key.startsWith("on") { // 以字符串on开头的props, 不管是否显式声明, 都将其添加到props数据中, 而非attrs
        props[key] = propsData;
      } else {
        attrs[key] = option[key];
      }
    }
    return [props, attrs]
  }

七、插槽的工作原理与实现

// MyComponent.vue
<template>
   <header><slot name= "header"/></header>
   <div>
      <slot name="body"></slot>
   </div>
   <footer><slot name="footer"></footer>
</template>
// index.vue
<MyComponent>
    <template #header>
      <h1>我是标题</h1>
    </template>
    <template #body>
      <section>我是内容</section>
    </template>
    <template #footer>
     <p>我是注脚</p>
    </template>
  </MyComponent>

编译后

// myComponent的渲染函数
 function render() {
   return [
     {
       type: 'header',
       children: [this.$slots.header()]
     },
     {
       type: 'body',
       children: [this.$slots.body()]
     },
     {
       type: 'footer',
       children: [this.$slots.footer()]
     }
   ]
 }
// index.vue的渲染函数
  function render() {
    return {
      type: MyComponent,
      // 组件的 children 会被编译成一个对象
      children: {
        header() {
          return { type: 'h1', children: '我是标题' }
        },
        body() {
           return { type: 'section', children: '我是内容' }
         },
        footer() {
          return { type: 'p', children: '我是注脚' }
        }
      }
   }
 }

所以slot的获取可以通过下面方式获取

const slots = vnode.children || [];  // 父节点的中关于子节点的children才是子节点的slot, 
// vnode.children  节点插槽
// vnode.type.chidlren  节点内部实现, 包括插槽的定义

八、注册生命周期

  • 一个组件可以注册多个onMounted
  • 每个组件都可以注册属于自己的onMounted;

像前面的activedEffect一样, 组件也可以设置一个currentInstance

 let currentInstance = null;
  function setCurrentInstance(instance) {
    currentInstance = instance;
  }
function mountComponent(vnode, container, anchor) {
    const instance = {
      state,
      props: shallowReactive(props),
      isMounted: false,
      subTree: null,
      mounted: [],//在组件实例中添加mounted数组, 用来储存通过onMounted函数注册的生命周期钩子函数
    }
    setCurrentInstance(instance);  // setup之前
    const setupResult = setup(shallowReadonly(vnode.props), setupContext); 
    setCurrentInstance(null);
  }

注意点: setCurrentInstance在是setup之前调用, setup之后重置的, 为什么是setup,因为onMounted函数只能在setup中调用

function onMounted(fn) {
    if(currentInstance) {
      currentInstance.mounted.push(fn);
    } else {
      console.error("onMounted 函数只能在setup中调用");
    }
  }
   effect(() => {
      let subTree = render.call(renderContext, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
         // 省略代码
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
      } else {
         // 省略代码
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })

不完全的代码如下

const { effect, ref, reactive, shallowReactive} =  VueReactivity;

const Text = Symbol();
const Comment = Symbol();
const Fragment = Symbol();

function createRenderer(options) {
  const{ createElement, setElementText, insert, patchProps, unmount, setText, createText } = options;
  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, anchor)
      })
    }
    if(vnode.props) {
      for(let 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 = n1.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 patchChildren(n1, n2, container) {
    if(typeof n2.children === "string"){    // 1、新节点是文本节点, 1.1、没有节点 直接设置 1.2、文本节点 直接设置  1.3、数组节点 卸载新增
      if(Array.isArray(n1.children)){
        n1.children.forEach(child => unmount(child))
      }
      setElementText(container, n2.children);
    } else if(Array.isArray(n2.children)) {   // 2、新节点是数组节点
      if(Array.isArray(n1.children)) {       // 2.1、节点是数组节点   // 这里是diff算法, 暂时全部卸载后再更新
        n1.children.forEach(c => unmount(c));
        n2.children.forEach(c => patch(null, c, container))
      } else  {                              // 2.2、节点为空 不处理后挂载,2.3、文本节点 设置为空后挂载
        setElementText(container, "");
        n2.children.forEach(c => patch(null, c, container))
      }
    } else {  // 3、新节点为空, 3.1、没有节点 不用处理 3.2、文本节点 设置为空, 3.3、数组节点 直接卸载
      if(Array.isArray(n1.children)) {
        n1.children.forEach(child => unmount(child))
      } else if(typeof  n1.children === "string") {
        setElementText(container, "");
      }
    }
  }
  let isFlushing = false;
  let queue = new Set();
  let p = Promise.resolve();
  function queueJob(job) {
    queue.add(job)
    if(!isFlushing) {
      isFlushing = true;
      p.then(() => {
        try {
          queue.forEach(job => job())
        } finally {
          isFlushing = false;
          queue.clear() // 任务执行完成后需要清除
        }
      })
    }
  }
  function resolveProps(option, propsData) {
    let props = {};
    let attrs = {};
    for(let key in option) {
      if(key in propsData  || key.startsWith("on") {
        props[key] = propsData;
      } else {
        attrs[key] = option[key];
      }
    }
    return [props, attrs]
  }
  let currentInstance = null;
  function setCurrentInstance(instance) {
    currentInstance = instance;
  }
  function onMounted(fn) {
    if(currentInstance) {
      currentInstance.mounted.push(fn);
    } else {
      console.error("onMounted 函数只能在setup中调用");
    }
  }
  function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { setup, render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
    beforeCreate && beforeCreate();
    let state = data ? reactive(data()) : null;
    let [ props, attrs ] = resolveProps(propsOption, vnode.props);
    const instance = {
      state,
      props: shallowReactive(props),
      isMounted: false,
      subTree: null,
      mounted: [],
    }
    function emit(event, ...payload) {
      let eventName = `on${event[0].toUpperCase()}${event.slice(0)}`;  // change => onChange
      const handler = props[eventName];
      if(handler) {
        handler(...payload);
      } else {
        console.log("事件不存在")
      }
    }
    const setupContext = {attrs, emit};
    setCurrentInstance(instance);
    const setupResult = setup(shallowReadonly(vnode.props), setupContext); // 参数1: 只读的props,防止被更改, 参数2: 上下文,包括 slot, emit , attrs, expose
    setCurrentInstance(null);
    let setupState = null
    if(typeof  setupResult === "function") {
      // 如果setup返回的值是函数,则如果存在render则报告冲突,将该函数作为render函数使用
      if(render) console.error("setup 函数返回渲染函数, render函数将被忽略");
    } else {
      // 如果setup返回的值不是函数,则作为数据状态赋值给setupState
      setupState = setupResult;
    }
    vnode.component = instance;
    const renderContext = new Proxy(instance, {
      get(target, p, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          return state[p]
        } else if(p in props) {
          return props[p]
        } else if(setupState && p in setup) {
          return setupState[p] // 渲染上下文增加对setupState的支持
        } else  {
          console.log("不存在")
        }
      },
      set(target, p, value, receiver) {
        const {state, props} = target;
        if(state && p in state) {
          state[p] = value;
        } else if(p in props) {
          console.log("props is readonly")
        } else if (setupState && p in setupState){
          setupState[p] = value; // 渲染上下文增加对setupState的支持
        } else {
          console.log("不存在");
        }
      }
    });
    created && created();
    effect(() => {
      let subTree = render.call(renderContext, state);  // render函数内部可以通过this访问组件自身状态数据
      if(!instance.isMounted) {
        beforeMount && beforeMount()
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
      } else {
        beforeUpdate && beforeUpdate()
        patch(instance.subTree, subTree, container, anchor);
        updated && updated();
      }
      instance.subTree = subTree
    }, {
      scheduler: queueJob,
    })
  }
  function hasPropsChanged(prevProps, nextProps) {
    const prevKey = Object.keys(prevProps).length;
    const nextKey = Object.keys(nextProps).length;
    if(prevKey !== nextKey) return true;
    for(let key in prevKey) {
      if(nextKey[key] !== prevKey[key]) return true;
    }
    return false;
  }
  function patchComponent(n1, n2, container) {
    const instance = n2.component = n1.component; // 获取组件实例, 并且给新的vnode绑定实例
    const {props} = instance;  // 获取当前的props;
    if(hasPropsChanged(n1.props, n2.props)) {  // 检测props是否发生变化, 如果没有变化则不需要更新
      let [nextProps] = resolveProps(n2.type.props, n2.props);
      // 更新props
      for(let key in nextProps) {
        props[key] = nextProps[key];
      }
      // 删除不要的props
      for(let key in props) {
        if(!(key in nextProps)) delete props[key];
      }
    }
  }
// n1旧vnode n2新vnode, container容器
  function patch(n1, n2, container, anchor) {
    if(n1 && n1.type !== n2.type) {
      unmount(n1);
      n1 = null;
    }
    let { type } = n2;
    if(typeof type === "string") {  // 节点是普通标签元素
      if(!n1) {
        mountElement(n2, container, anchor);  //挂载节点
      } else {
        patchElement(n1, n2); // 更新节点
      }
    } else if(typeof  type === "object"){  // 节点是组件
      if(!n1) {
        mountComponent(n2, container, anchor)
      } else {
        patchComponent()
      }
    } else if(type === Text){
      // 前面有判断,能进入到这个判断分支的, 要不节点为空, 要不同为Text节点
      if(!n1) {
        const el = n2.el = createText(n2.children);
        insert(el, container, null);
      } else {
        const el = n2.el = n1.el;
        if(n1.children !== n2.children) {
          setText(el, n2.children)
        }
      }
    } else if(type === Fragment) {
      if(!n1) {
        n2.children.forEach(c => patch(null, c, container))
      } else {
        patchChildren(n1, n2, container)
      }
    } else {
      // 省略了其他类型的vnode
    }
  }
  function render(vnode, container) {
    if(vnode){
      // 打补丁(挂载也是一种特殊的打补丁)
      patch(container._vnode, vnode, container)
    } else {
      if(container._vnode) {
        unmount(container._vnode);
      }
    }
    container._vnode = vnode;
  }
  return {
    render,
  }
}

function createElement(tag){
  return document.createElement(tag);
}
function setElementText(el, text){
  el.textContent = text;
}
function insert(el, parent, anchor = null) {
  parent.insertBefore(el, anchor)  // parent父节点 el需要插入的节点  anchor插入时需要插入在这个节点前面
}
function patchProps(el,key, oldValue, newValue) {
  // 暂时放在这里
  function  shouldSetAsProps(el, key, value) {
    if(el.tagName === "INPUT" && key === "form") return false;
    return key in el;
  }
  if(/^on/.test(key)) {
    const name = key.slice(2).toLowerCase();
    let invokers = el._vei || (el._vei = {});
    let invoker = invokers[name];
    if(newValue) {
      if(!invoker) {
        invoker = el._vei = function(e) {
          if(e.timeStamp < invoker.attached) return;
          if(Array.isArray(invoker.value)) {
            invoker.value.forEach(fn => fn(e))
          } else {
            invoker.value(e);
          }
        }
        invoker.value = newValue;
        invoker.attached = performance.now();
        el.addEventListener(name, invoker) ; // 在之前没有绑定过数据的情况下进行数据的监听
      } else {
        invoker.value  = newValue;
      }
      // 为什么不在这里添加addEventListener, 因为每监听一次, 就多一次事件触发
    } else if(invoker)  {
      el.removeEventListener(name, invoker)
    }
  } else if(key === "class") {
    el.className = newValue || ""
  } else if(shouldSetAsProps(el, key, newValue)){
    let type = typeof el[key];
    if(type === "boolean" && newValue === "") {
      el[key] = true;
    } else {
      el[key] = newValue;
    }
  } else {
    el.setAttribute(key, newValue)
  }
}
function unmount(vnode) {
  if(vnode.type === Fragment) {
    vnode.children.forEach(c => unmount(c))
  }
  const parent = vnode.el.parent;
  if(parent) parent.removeChild(vnode.el);
}
function createText(text) {
  return document.createTextNode(text)
}
function setText(el, text) {
  el.nodeValue = text;
}

const renderer = createRenderer({
  createElement,
  setElementText,
  insert,
  patchProps,
  createText,
  setText
});

const Comp = {
  setup(props, setupContext) {
    let value = props.data;
    let {attrs, slot, emit, expose} = setupContext;
  },
}