vue3生命周期原理(简易)

551 阅读3分钟

前言

  • 将调用的生命周期函数, 用户传入的函数
  • push到组件实例上的对应生命周期属性中
  • 在特定的时期 去执行对应生命周期
  • 如下面的示例
  • 执行setup让组件实例收集用户传入的函数
  • 要考虑组件实例this变化(父setup->子setup), 采用闭包的形式 将组件实例在调用时存储起来
  • 在组件第一次渲染时执行onBeforeMount, onMounted
  • 在组件更新比对时执行onBeforeUpdate, onUpdated

示例

<script src="../node_modules/@vue/runtime-dom/dist/runtime-dom.global.js"></script>
<div id="app"></div>
<script>
  const {
    createApp,
    h,
    reactive,
    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    getCurrentInstance
  } = VueRuntimeDOM

  let App = {
    setup(props, context) {
      let state = reactive({ flag: true })
      let instance = getCurrentInstance()
      console.log('获取当前实例', instance)

      setTimeout(() => { state.flag = false }, 2000)

      onBeforeMount(() => { console.log('onBeforeMount1') })
      onBeforeMount(() => { console.log('onBeforeMount2') })
      onMounted(() => { console.log('onMounted') })
      onBeforeUpdate(() => { console.log('onBeforeUpdate') })
      onUpdated(() => { console.log('onUpdated') })

      return () => { return h('div', state.flag) }
    }
  }

  let app = createApp(App, { name: 'Tom', age: 25 })
  app.mount('#app')

</script>
图片替换文本

定义生命周期类型

component.ts

import { isFunction, isObject, ShapeFlags } from "@vue/shared/src"
import { PublicInstanceProxyHandlers } from "./componentPublicInstance"

/***************** 辅助模块 ********************/
// 看这里
export enum LifeCycleHooks {
  BEFORE_MOUNT = 'bm',
  MOUNTED = 'm',
  BEFORE_UPDATE = 'bu',
  UPDATED = 'u'
}

/**
 * @description 执行setup时 获取当前的实例
 * @description 让在生命周期里 获取实例
 * @param currentInstance 当前实例
 */
export let currentInstance = null

export let setCurrentInstance = (instance) =>{
  currentInstance = instance
}

export let getCurrentInstance = () =>{
  return currentInstance
}

/***************** 核心模块 ********************/
let uid = 0
/**
 * @description 创建组件实例
 * @param vnode 组件的vnode
 */
export function createComponentInstance(vnode) {
  const instance = {
    uid: uid++,
    vnode,
    type: vnode.type,   // 组件的options
    props: {},
    attrs: {},
    slots: {},
    ctx: {},
    data: {},
    setupState: {},     // setup返回的对象
    proxy: null,        // render函数的参数代理
    render: null,       // render函数
    subTree: null,      // render函数 返回的结果就是subTree(h)
    isMounted: false,   // 组件是否挂载
    update: null,       // 组件的更新函数 就是创建effect函数
    
    bm: null,           // onBeforeMount  数组
    m: null,            // onMounted      数组
    bu: null,           // onBeforeUpdate 数组
    u: null,            // onUpdated      数组
  }
  // 用于render函数的代理
  instance.ctx = { _: instance }

  return instance
}


/**
 * @description 调用当前实例的setup方法 用setup的返回值填充setupState 和 对应的render方法
 * @param instance 组件实例
 */
function setupStatefulComponent(instance) {
  // 1.代理 传递给render函数的参数
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)

  // 2.获取组件的类型 拿到组件的setup方法
  let Component = instance.type
  let { setup } = Component

  if (setup) {
    // 看这里 执行setup时 将当前示例
    currentInstance = instance
    let setupContext = createSetupContext(instance)
    const setupResult = setup(instance.props, setupContext)
    currentInstance = null
    
    handleSetupResult(instance, setupResult)
  } else {

    finishComponentSetup(instance)
  }
}

/**
 * @description setup返回的结果处理 可能是对象(setupState) 或者是render函数
 * @param instance     组件实例
 * @param setupResult  setup返回的结果
 */
function handleSetupResult(instance, setupResult) {
  if (isFunction(setupResult)) {
    instance.render = setupResult
  } else if (isObject(setupResult)) {
    instance.setupState = setupResult
  }

  finishComponentSetup(instance)
}

/**
 * @description 完成组件的render函数
 * @param instance 组件实例
 */
function finishComponentSetup(instance) {
  let Component = instance.type

  if (!instance.render) {
    // 对template模板进行编译 产生render函数
    // 需要将生成render函数放在实例上
    if(!Component.render && Component.template) {
      // complier...
    }

    instance.render = Component.render
  }

  // 对vue2.0API做了兼容处理 就是循环合并
  // 源码applyOptions
}

/**
 * @description 创建setup参数 上下文
 */
function createSetupContext(instance) {
  return {
    attrs: instance.attrs,
    slots: instance.slots,
    emit: () => {},
    expose: () => {}
  }
}

将生命周期存储到实例上(重点)

apiLifecycle.ts

import { currentInstance, LifeCycleHooks, setCurrentInstance } from "./component"

/**
 * @description 执行生命周期函数 依次执行
 */
 export const invokeArrayFns  = (fns) =>{
  for(let i = 0; i < fns.length; i++) {
    fns[i]()
  }
}

/**
 * @description   注入对应的生命周期 并将函数push到对应的 组件实例上的生命周期类型数组
 * @param type    类型
 * @param hook    用户传入的函数
 * @param target  对应的组件实例
 */
const injectHook = (type, hook, target) => {
  if (!target) {
    return console.warn('injection APIs can only be used during execution of setup(), 只能使用在setup中')
  } else {
    // 如果没有 就赋值 如instance.bm = []
    const hooks = target[type] || (target[type] = [])

    // 采用闭包 保留实例
    const wrappedHook = () => {
      setCurrentInstance(target)
      hook.call(target)
      setCurrentInstance(null)
    }

    hooks.push(wrappedHook)
  }
}

/**
 * @description         创建对应的生命周期类型
 * @param lifecycle     类型
 * @returns {Function}  对应的生命周期函数
 */
const createHook = (lifecycle) => (hook, target = currentInstance) => {
  injectHook(lifecycle, hook, target)
}

export const onBeforeMount = createHook(LifeCycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifeCycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifeCycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifeCycleHooks.UPDATED)

渲染和更新时

render.ts

/**
 * @description 创建一个渲染器
 * @param rendererOptions 针对浏览器的一些元素 文本的增删改查
 */
export function createRenderer(rendererOptions) {
  /**
   * @desc 浏览器的操作方法
   */
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    nextSibling: hostNextSibling,
  } = rendererOptions

  /**
   * @desc 创建组件的effect函数 执行render函数
   */
  const setupRenderEffect = (instance, container) => {
    instance.update = effect(function componentEffect() {
      // 生命周期
      const { bm, m, bu, u } = instance

      if (!instance.isMounted) {
        // onBeforeMount
        if (bm) { invokeArrayFns(bm) }

        let proxyToUse = instance.proxy
        let subTree = instance.subTree = instance.render.call(proxyToUse, proxyToUse)

        // 用render函数的返回值 继续渲染
        patch(null, subTree, container)
        instance.isMounted = true

        // mounted 必须在我们子组件完成后 才会调用自己
        // 这里不考虑 所以直接写了(异步队列)
        // onMounted
        if (m) { invokeArrayFns(m) }

      } else {
        // onBeforeUpdate
        if (bu) { invokeArrayFns(bu) }

        // 更新 diff vnode
        const prevTree = instance.subTree
        let proxyToUse = instance.proxy
        const nextTree = instance.render.call(proxyToUse, proxyToUse)
        // instance.subTree = nextTree

        patch(prevTree, nextTree, container)

        // 不考虑子组件
        // onUpdated
        if (u) { invokeArrayFns(u) }
      }
    }, {
      scheduler: queueJob
    })

  }

// ...
}