vue3 初始化过程

194 阅读5分钟

1、vue 架构变化

compile-dom ---> compile-core

reactivity

runtime-dom ---> runtime-core

2、关于打包

1、 vue 是用 rollup 打包

2、执行 用 execa 库 执行 rollup 命令

3、打包格式:

global ---> umd ---> iife 自调函数

cjs ---> nodejs 环境

es ----> es6 环境

3、源码初始化过程

0、 使用(关于为什么做成这样、 和vue2 不同)

1、避免实例污染

2、摇树优化 (以前很多方法挂载实例上)

3、语义上更好理解


    createApp({
      data() {
        return {
          counter: 1
        }
      }
    })
    .use(store)
    .use(router)
    .mount('#app')

1、createApp 方法 在 runtime-dom 模块

export const createApp = ((...args) => {
  // 首先获取渲染器
  // 实际上createApp是由渲染器调用
  const app = ensureRenderer().createApp(...args)

  const { mount } = app
  
  app.mount = (containerOrSelector: Element | string): any => {
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    //.... 编译模板
    const proxy = mount(container)
    container.removeAttribute('v-cloak')
    container.setAttribute('data-v-app', '')
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

2、渲染器

const app = ensureRenderer().createApp(...args)

// 1、该方法返回渲染器
ensureRenderer() 
// ensureRenderer()  =>  return renderer || createRenderer()  // 单例模式
// renderer (渲染器)作为全局变量 创建过就会有值

// 2、createRenderer({patchProp,...nodeOps}) 创建渲染器(属性比较和替换, dom操作的方法)
createRenderer() 
// return baseCreateRenderer ()

// 3、
baseCreateRenderer({patchProp, ...nodeOps})
// 目录位置:packages\runtime-core\src\renderer.ts

  // baseCreateRenderer return 结果
  // return ==> {
  //  render, // 渲染方法 render(vnode,container) 转换操作
  //  hydrate, // 注水 用于服务端渲染
  //  createApp: createAppAPI(render, hydrate) 返回一个方法
  //}


// 4、createAppAPI(render, hydrate) return app 实例
createAppAPI(render, hydrate)
// 实例包含 {use、component、mixins、directive、mount、unmount}
// packages\runtime-core\src\apiCreateApp.ts

3、关于 mount

目录位置:vue-next\packages\runtime-core\src\apiCreateApp.ts (227行)

主要做了以下几件事


          // 初始化虚拟dom树
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          ) // 得到vnode

          vnode.appContext = context

          // HMR root reload 热更新
          if (__DEV__) {
            context.reload = () => {
              render(cloneVNode(vnode), rootContainer)
            }
          }

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any) // 服务端渲染
          } else {
            // 浏览器端渲染 ------------------------------
            render(vnode, rootContainer)
          }
          isMounted = true
          app._container = rootContainer // 根节点保存

4、 render 方法

根据有没有dom 来走不同流程(container._vnode)是否有vnode

 const render: RootRenderFunction = (vnode, container) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      // 看这里
      // 参数1 存在走更新
      // 参数1 不存在走挂载
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode // 挂到dom上
  }

5、patch 比较虚拟dom 更新, 也包含初次渲染

目录位置:packages\runtime-core\src\renderer.ts

const patch: PatchFn = (
    n1, // 老虚拟节点
    n2, // 新虚拟节点
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    optimized = false
  ) => {
    // patching & not same type, unmount old tree 不是同一个根节点类型 卸载n1
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    // 获取新节点类型
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text: // 文本
        processText(n1, n2, container, anchor)
        break
      case Comment: // 注释
        processCommentNode(n1, n2, container, anchor)
        break
      case Static: // 静态
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      case Fragment: // 非单根节点
        processFragment(...)
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(...)
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // 初始化走这里
          processComponent(...)
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(...) // 处理传送门
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(...)
        }
    }

    // set ref           设置 ef 
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
    }
  }

初始化渲染步骤:

初始化走 component (processComponent)流程 (不是fragment)

processComponent => mountComponent 初次走挂载组件

patch 组件的过程

  const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
    if (n1 == null) {
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        // keepalive 激活
        (parentComponent!.ctx as KeepAliveContext).activate()
      } else {
        // 初始化
        mountComponent()
      }
    } else {
      // 更新
      updateComponent(n1, n2, optimized)
    }
  }

6、mountComponent 简化流程

目录:packages\runtime-core\src\renderer.ts

// 1.创建实例
const instance = createComponentInstance(...) // 创建组件实例
// ....
// 2组件安装 类似于 vue2_init  合并merge options,// 属性初始化 // 数据响应式 // 插槽处理
setupComponent(instance)
// ....
// 3、 安装渲染函数的副作用
setupRenderEffect()

7、setupComponent(instance)

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false // 是否是ssr渲染
) {
  isInSSRComponentSetup = isSSR

  const { props, children, shapeFlag } = instance.vnode
  const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR) // 这里是关键
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}
function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  // 0. create render proxy property access cache
  instance.accessCache = Object.create(null)
  // 1. create public instance / render proxy
  // also mark it raw so it's never observed
  // 获取上下文  这里就是响应式了
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) // 数据响应式

  // 2. call setup() 处理setup函数
  const { setup } = Component // 处理setup
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    currentInstance = instance
    pauseTracking()
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    currentInstance = null

    if (isPromise(setupResult)) {
      if (isSSR) {
        // return the promise so server-renderer can wait on it
        return setupResult.then((resolvedResult: unknown) => {
          handleSetupResult(instance, resolvedResult, isSSR)
        })
      } else if (__FEATURE_SUSPENSE__) {
        // async setup returned Promise.
        // bail here and wait for re-entry.
        instance.asyncDep = setupResult
      } else if (__DEV__) {
        warn(
          `setup() returned a Promise, but the version of Vue you are using ` +
            `does not support it yet.`
        )
      }
    } else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    // 没有设置setup 走这里
    finishComponentSetup(instance, isSSR)
  }
}

setup 和 data 同时存在, setup 的值 优先级更高

8、finishComponentSetup(instance, isSSR) 这个还待研究

目录: packages\runtime-core\src\component.ts

9、setupRenderEffect 安装副作用

会走分初次渲染和更新组件

 const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // create reactive effect for rendering  effect函数注册 
    instance.update = effect(function componentEffect() {
      if (!instance.isMounted) {   // 未挂载的情况
        let vnodeHook: VNodeHook | null | undefined
        const { el, props } = initialVNode
        const { bm, m, parent } = instance

        // beforeMount hook 生命周期
        if (bm) {
          invokeArrayFns(bm)
        }
        // onVnodeBeforeMount
        if ((vnodeHook = props && props.onVnodeBeforeMount)) {
          invokeVNodeHook(vnodeHook, parent, initialVNode)
        }

        // 1、首先获取当前组件的vnode
        const subTree = (instance.subTree = renderComponentRoot(instance))

        if (el && hydrateNode) {
           // ssr服务端渲染
          // vnode has adopted host node - perform hydration instead of mount.
          hydrateNode(
            initialVNode.el as Node,
            subTree,
            instance,
            parentSuspense
          )

        } else {
          // 走patch 如果是多根节点走fragment
          patch(
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )

          initialVNode.el = subTree.el
        }
        // mounted hook
        if (m) {
          queuePostRenderEffect(m, parentSuspense)
        }
        // onVnodeMounted
        if ((vnodeHook = props && props.onVnodeMounted)) {
          queuePostRenderEffect(() => {
            invokeVNodeHook(vnodeHook!, parent, initialVNode)
          }, parentSuspense)
        }
        // activated hook for keep-alive roots.
        // #1742 activated hook must be accessed after first render
        // since the hook may be injected by a child keep-alive
        const { a } = instance
        if (
          a &&
          initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
        ) {
          queuePostRenderEffect(a, parentSuspense)
        }
        instance.isMounted = true
      } else {
        // updateComponent
        // This is triggered by mutation of component's own state (next: null)
        // OR parent calling processComponent (next: VNode)
        let { next, bu, u, parent, vnode } = instance
        let originNext = next
        let vnodeHook: VNodeHook | null | undefined

        if (next) {
          next.el = vnode.el
          updateComponentPreRender(instance, next, optimized)
        } else {
          next = vnode
        }

        // beforeUpdate hook
        if (bu) {
          invokeArrayFns(bu)
        }
        // onVnodeBeforeUpdate
        if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
          invokeVNodeHook(vnodeHook, parent, next, vnode)
        }


        const nextTree = renderComponentRoot(instance)
        if (__DEV__) {
          endMeasure(instance, `render`)
        }
        const prevTree = instance.subTree
        instance.subTree = nextTree

        patch(
          prevTree,
          nextTree,
          // parent may have changed if it's in a teleport
          hostParentNode(prevTree.el!)!,
          // anchor may have changed if it's in a fragment
          getNextHostNode(prevTree),
          instance,
          parentSuspense,
          isSVG
        )
        // .....
  }