vue源码解读

356 阅读6分钟

 入口使用:

因此先从createApp入手

createAppruntime-dom模块导出的,主要做了以下事情:

  • 调用runtime-core模块的createRenderer方法,生成渲染器并调用其createApp方法,生成APP实例。
  • 取出原mount方法,然后重写APP实例上面的mount方法,该方法任务是:找到实际dom container,并清空container里面内容,最后调用原mount方法,完成 DOM 的挂载
  • 返回APP实例
createApp 代码实现:
/**
 * createApp 函数
 */
export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)
​
  // 开发环境 校验 组件的 name 是不是内置的标签同名
  if (__DEV__) {
    injectNativeTagCheck(app)
  }
​
  const { mount } = app
  /**
   * 重写了 mount 函数
   * @param containerOrSelector 
   */
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // container 是真实的 DOM 元素
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    const component = app._component // 组件的options
    // 默认 组件的 template 是 挂载元素的内容
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
    }
    // 清空 容器中的内容
    container.innerHTML = ''
    const proxy = mount(container)
    if (container instanceof Element) {
      // 删除元素上的 v-cloak 指令
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }
​
  return app
}) as CreateAppFunction<Element>

追踪到了createRenderer

上面讲到调用了runtime-core模块的createRenderer方法,生成渲染器并调用其createApp方法
那么我们然后继续追踪可以看到createRenderer 返回了baseCreateRenderer, 这个才是重点代码! 让我们看看baseCreateRenderer方法干了啥~~

这里是dome-core非常重要的部分,baseCreateRenderer 都有啥:

  1. 接受options参数,options可以自定义增删改查的API, runtime-dom调用的时候传入的就是dom的增删改查api。
  2. 有个patch方法,是个总调度员,用以判断类型,并调用不同的增删改查的api,
  3. 定义了各种类型的元素操作的方法,基本是对传入的options进行一层包装。
  4. 定义render方法,该方法调用patch方法,进行挂载元素操作
  5. 返回一个对象,包含createApp方法。

源码:

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  // WEB 平台获取到的是操作 DOM 的方法
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options
  const patch = () => {...}
  const processText = () => {...}
  const processCommentNode = () => {...}
  const mountStaticNode = () => {...}
  const patchStaticNode = () => {...}
  const moveStaticNode = () => {...}
  const removeStaticNode = () => {...}
  const processElement = () => {...}
  const mountElement = () => {...}
  const setScopeId = () => {...}
  const mountChildren = () => {...}
  const patchElement = () => {...}
  const patchBlockChildren = () => {...}
  const patchProps = () => {...}
  const processFragment = () => {...}
  const processComponent = () => {...}
  const mountComponent = () => {...}
  const updateComponent = () => {...}
  const setupRenderEffect = () => {...}
  const updateComponentPreRender = () => {...}
  const patchChildren = () => {...}
  const patchUnkeyedChildren = () => {...}
  const patchKeyedChildren = () => {...}
  const move = () => {...}
  const unmount = () => {...}
  const remove = () => {...}
  const removeFragment = () => {...}
  const unmountComponent = () => {...}
  const unmountChildren = () => {...}
  const getNextHostNode = () => {...}
  const render = () => {...}
  return {
    render,
    hydrate,
    // createApp 入口
    createApp: createAppAPI(render, hydrate)
  }
}

createApp 中调用了 ensureRenderer().createApp(...args) 方法获取 app 的实例,就是 baseCreateRenderer 返回的对象中的 createApp 函数,通过 createAppAPI 函数生成的一个函数。

继续追踪createAppAPI

createAppAPI是由runtime-core模块导出,让我们去看看,createAppAPI做了什么

  • 首先,createAppAPI返回一个函数。

  • 接收一个baseCreateRenderer里面定义的render方法。

  • 函数定义APP,以及挂一些方法,比如:mount,unmount,mixin,component等,并返回app。
    源码:

    /**

    • 返回 app 实例

    • @param render

    • @param hydrate / export function createAppAPI( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction { /*

      • 接收两个参数
      • rootComponent 根组件
      • rootProps 传递给根组件的 props */ return function createApp(rootComponent, rootProps = null) { const context = createAppContext() // 返回一个对象 // 安装的插件 const installedPlugins = new Set() // 是否挂载 let isMounted = false

      const app = (context.app = { _uid: uid++, // 唯一id _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, ​ version, // vue 版本 ​ get config() { // config 是一个只读对象,设置 config 在开发环境会报警告 return context.config }, ​ use() {...}, mixin() {...}, component() {...}, directive() {...}, mount() {...}, unmount() {...}, provide() {...} })

      return app } }

createAppAPI生成的app对象的mount方法!!!

作用生成vnode,然后调用render,render方法是在调用 createAppAPI 方法 的时候,传入的参数。也就是 baseCreateRenderer 方法中定义的 render 方法
mount源码:

 mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        if (!isMounted) {
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          // store app context on the root VNode.
          // this will be set on the root instance on initial mount.
          vnode.appContext = context
          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            render(vnode, rootContainer, isSVG)
          }
          isMounted = true
          app._container = rootContainer

          return vnode.component!.proxy
        } else if (__DEV__) {
          warn(
            `App has already been mounted.\n` +
              `If you want to remount the same app, move your app creation logic ` +
              `into a factory function and create fresh app instances for each ` +
              `mount - e.g. \`const createMyApp = () => createApp(App)\``
          )
        }
      },

render方法通过patch将vnode转化成真实dom

patch 过程中,会根据 vnode 的 type 不同,调用不同的处理节点的方法,这里主要看处理 component 的方法 processComponent,因为这里会执行 setup 和收集依赖。

processComponent

 const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
    if (n1 == null) {
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      updateComponent(n1, n2, optimized)
    }
  }

作用:调用mountComponent或者updateComponent

mountComponent

const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // createComponentInstance 会对 instance 做处理, ctx 的不一致,就是在这个方法处理的
    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ))
    // ...
    // 这里调用 setup 方法,setup 返回的值,会保存在 instance.setupState 中
    setupComponent(instance)
    // ...
    if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
      // 处理 setup 是 promise 的情况,在 promise 的状态 resolve 之后,才执行 setupRenderEffect 函数
      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
      // ...
      return
    }
​
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
  }

作用:通过createComponentInstance生成instance,函数最后执行了 setupRenderEffect 方法,在这个方法的执行过程中,会收集 vnode 中使用到的依赖

createComponentInstance

runtime-core模块
定义组件实例各种属性并返回

export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  // ...
  const instance: ComponentInternalInstance = {
    // 这里定义了各种属性,比如:effects, data, props等
  }
  // 开发环境对 ctx 做的特殊处理
  // 项目开发中不能使用这个 ctx,生产环境不支持
  if (__DEV__) {
    instance.ctx = createRenderContext(instance)
  } else {
    instance.ctx = { _: instance }
  }
  instance.root = parent ? parent.root : instance
  instance.emit = emit.bind(null, instance)
​
  return instance
}

setupRenderEffect

create reactive effect for rendering

setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    instance.update = effect(function componentEffect() {
      // 新建组件
      if (!instance.isMounted) {
        let vnodeHook: VNodeHook | null | undefined
        const { el, props } = initialVNode
        // 对子节点处理  这里会执行 组件的 render 函数
        // render 函数对 ref / reactive 的值的获取,都会把当前函数作为依赖变更需要触发的函数收集
        const subTree = (instance.subTree = renderComponentRoot(instance))
        patch( // 这个patch执行会完成DOM的挂载
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          initialVNode.el = subTree.el
        instance.isMounted = true
        initialVNode = container = anchor = null as any
      } else {
        // 更新组件
        let { next, bu, u, parent, vnode } = instance
        let originNext = next
        let vnodeHook: VNodeHook | null | undefined
        const nextTree = renderComponentRoot(instance)
        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
        )
        next.el = nextTree.el
      }
    }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)

setupRenderEffect调用了effect,只有effect中执行的函数,才会做依赖收集
renderComponentRoot方法创建组件的子节点,这个方法执行了子组件的render方法,render方法中收集reactivity数据,并将本effect函数作为变更的依赖收集
在执行 effect 的时候,传递了第二个参数 prodEffectOptions,这个参数中,有一个 scheduler 方法,这个是依赖更新之后会调用的调度器,这个调度器决定什么时候执行 DOM 更新,而不是每次依赖变化都对 DOM 做修改。

初始mount组件:
1: 生成实例:在mountComponent时候,先生成component instance,
2: 低啊用setup: 调用setupComponent, 将setup 返回的值,会保存在 instance.setupState 中。
3: 最后调用setupRenderEffect收集依赖。