第十二章 组件的实现原理

62 阅读2分钟

组件的实现原理

从用户的层面看来,一个组件就是一个选项对象或者是一个函数, 在patch的时候,

function patch(n1, n2, container, anchor) {
    if (typeof type === "string") {
    //...
  } else if (type === Text) {
    //...
  } else if (type === Fragment) {
    //... 
  } else if (typeof type === "object" || typeof type === "function") {
    // 渲染组件的过程 
    if (!n1) {
      mountComponent(n2, container, anchor);
    } else {
      patchComponent(n1, n2, anchor);
    }
  }
}

mountComponent逐行分析

  // 当前活跃组件的指针, 执行挂载的组件 
  let currentInstance = null; 
  // 挂载组件的方法
  function mountComponent(vnode, container, anchor) {
    // 是否是函数组件 
    const isFunctional = typeof vnode.type === 'function'
    let componentOptions = vnode.type
    // 函数组件的处理过程
    if (isFunctional) {
      componentOptions = {
        render: vnode.type,  // 直接将函数 赋值给render函数
        props: vnode.type.props 
      }
    }

    // 从组件选项中 解构出用户提供的内容
    let { render, data, setup, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption } = componentOptions
    // 调用beforeCreate 钩子函数
    beforeCreate && beforeCreate()
    // 将传入的data响应化  
    const state = data ? reactive(data()) : null
    // 将 vnode中的 props 和 组件上选项props进行整合 
    const [props, attrs] = resolveProps(propsOption, vnode.props)
    // 获取solt 
    const slots = vnode.children || {}

    // 组件实例本质上就是一个状态集合(或一个对象), 它维护着组件运行过程中的所有信息、如注册到组件的生命周期函数、组件渲染的子树(su ubTree)、组件是否已经被挂载、组件自身的状态(data)等
    const instance = {
      state,   
      props: shallowReactive(props),
      isMounted: false,  //是否被挂载
      subTree: null,
      slots,
      mounted: []
      // ... 
    }

    // 传递给stepup()函数的emit,就是它 , emit('change', '001');
    function emit(event, ...payload) {
      // 先把change 转换成 onChange  
      const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
      // 调用 当前组件上 注入的 props.onChange 事件, 由子组件出发 
      const handler = instance.props[eventName]
      if (handler) {
        handler(...payload)
      } else {
        console.error('事件不存在')
      }
    }

    // setup函数 
    let setupState = null
    if (setup) {
      // setup函数的入参
      const setupContext = { attrs, emit, slots }
      // 设置当前组件
      const prevInstance = setCurrentInstance(instance)
      // 执行setup函数, 并收集结果, setup函数中的 props是只读的
      const setupResult = setup(shallowReadonly(instance.props), setupContext)
      setCurrentInstance(prevInstance)
      // setup函数的两种情况: 1-可以返回一个render函数, 2-可以返回一个对象
      if (typeof setupResult === 'function') {
        if (render) console.error('setup 函数返回渲染函数,render 选项将被忽略')
        render = setupResult
      } else {
        setupState = setupContext
      }
    }
    // vnode 也要和当前组件实例管理 
    vnode.component = instance
    // 渲染函数是如何获取响应是数据的?
    // 获取数据的优先级:data >  props  > setup 
    const renderContext = new Proxy(instance, {
      get(t, k, r) {
        const { state, props, slots } = t
        // 获取slot
        if (k === '$slots') return slots
        // 优先从state中取数据
        if (state && k in state) {
          return state[k]
        // 再从props中去数据
        } else if (k in props) {
          return props[k]
        // 最后从setupState中去数据
        } 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 && created.call(renderContext)


    // 挂载和patch中,所有响应式的数据 都会被收集为依赖, 当前数据变化时, 下面代码会被再次执行 
    effect(() => {
      const subTree = render.call(renderContext, renderContext)
      if (!instance.isMounted) {
        // 挂载之前的钩子函数
        beforeMount && beforeMount.call(renderContext)
        // 挂载,oldvnode为null
        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
    })
  }