我的源码学习之路(16)--- vue3(4)

94 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情

前言

我觉得 V3的源码比V2费劲些,上一篇是写了一个demo,顺着运行的过程找源代码,发现并不是特别的流畅。匆匆结束了上一篇文章,下来参考了一些大佬们的文章。有点眉目了。我觉得自己又可以了。

GO ON!

GO ON

上一次提到createapp,V3中运行代码时候已经要使用新的方式了,如下

image.png

而源码中如何从入口文件找到createApp这个方法的呢?

路径:

  1. export * from '@vue/runtime-dom'(93行)[packages/vue/src/index.ts]

2.[packages/runtime-dom/src/index.ts]

image.png

这个方法主要是对mount进行了封装,挂载了mount函数,做了三件事

  • 基于creatApp的参数创建虚拟节点
  • 基于虚拟节点和容器元素进行渲染
  • 最后返回虚拟节点component属性的代理对象

犹记得V2中也是new App().$mount,封装了一层又一层的mount

image.png

render函数

这个是V3中的重点函数,是通过ensureRenderer().render(...args),由此可以看到里面调用的patch函数,重中之重

  const render: RootRenderFunction = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) { // 没有传入新的虚拟节点,当存在旧虚拟节点,则卸载旧虚拟节点
        unmount(container._vnode, null, null, true)
      }
    } else { // 存在新虚拟节点,则执行patch,比较新旧虚拟节点
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPreFlushCbs()
    flushPostFlushCbs()
    container._vnode = vnode
  }

patch[packages/runtime-core/src/renderer.ts]

主要完成了:

  • 创建需要新增的节点
  • 移除已经废弃的节点
  • 移动或修改需要更新的节点
const patch: PatchFn = (
  n1, // 旧节点
  n2, // 新节点
  container, // 容器
  anchor = null, // 锚点,算法过程得参照节点
  parentComponent = null,
  parentSuspense = null,
  isSVG = false,
  slotScopeIds = null,
  optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren //优化模式标识
) => {
  if (n1 === n2) { // 新旧节点相同,直接返回
    return
  }

  // patching & not same type, unmount old tree
  if (n1 && !isSameVNodeType(n1, n2)) { // 不是相同类型的节点,直接卸载旧节点 (V3新的特性)
    anchor = getNextHostNode(n1)
    unmount(n1, parentComponent, parentSuspense, true)
    n1 = null
  }

  if (n2.patchFlag === PatchFlags.BAIL) { //PatchFlag 是 BAIL 类型,则跳出优化模式
    optimized = false
    n2.dynamicChildren = null
  }

  const { type, ref, shapeFlag } = n2
  switch (type) { //根据vNode类型,执行不同的算法
    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:// 这是V3新特性 ,可以多个根节点
      processFragment(
        n1,
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
      break
    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
        processElement(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        processComponent(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else if (shapeFlag & ShapeFlags.TELEPORT) {
        ;(type as typeof TeleportImpl).process(
          n1 as TeleportVNode,
          n2 as TeleportVNode,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized,
          internals
        )
      } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
        ;(type as typeof SuspenseImpl).process(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized,
          internals
        )
      } else if (__DEV__) {
        warn('Invalid VNode type:', type, `(${typeof type})`)
      }
  }

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

在这个重要的方法中,有几点是V3做了优化的(对比V2而已)

patchFlag

-  根据节点的特点打上标记**patchFlag**,再次比较的时候就只关闭没有打标记的,不用像以前一样全量比较,即使在多层的嵌套下依然有效
-  `可以减少遍历次数,从而实现性能提升`
<div id="bar" :class="foo">Hello World</div> // 这个节点id 和文本都会打上标记,只关注:class就可以了

新增的补丁标记:

    TEXT = 1 // 动态文本节点
    CLASS=1<<1,1 // 2 // 动态 class
    STYLE=1<<2, // 4 // 动态 style
    PROPS=1<<3, // 8 // 动态属性,但不包含类名和样式
    FULLPR0PS=1<<4, // 16 // 具有动态key属性,当key改变时,需要进行完整的 diff 比较
    HYDRATE_ EVENTS = 1 << 5, // 32 // 带有监听事件的节点
    STABLE FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的fragment
    KEYED_ FRAGMENT = 1 << 7, // 128 // 带有key属性的 fragment 或部分子字节有 key
    UNKEYED FRAGMENT = 1<< 8, // 256 // 子节点没有key 的fragment
    NEED PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
    DYNAMIC_SLOTS = 1 << 10 // 1024 // 动态slot
    HOISTED = -1 // 静态节点
    BALL = -2

Fragment

  • V3中可以支持多个根节点(V2只能有一个根节点)
<template>
    <div>1</div>
    <div>2</div>
    <div>3</div>
</template>
  • 支持了render jsx写法(就像react是的,难怪说V3越来越靠近react)
render(){
    return (
        <div>1213123</div>
    )
}

setup[packages/runtime-core/src/component.ts]

    setup() // setup.length === 0

    setup(props) // setup.length === 1

    setup(props, { emit, attrs }) // setup.length === 2
function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
// 保存着组件的选项
  const Component = instance.type as ComponentOptions 
...
  // 0. 创建一个渲染代理的属性的访问缓存,可以减少读取次数
  instance.accessCache = Object.create(null)
  // 1. 创建一个公共的示例或渲染器代理
  // 它将被标记为 raw,所以它不会被追踪
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))

  // 2. 调用 setup()
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null) 
// setup.length > 1表示的是 `setup(props){}` props 是必传的
/*
createSetupContext(instance)  这个函数会返回下面这个对象,
return {
  get attrs() {
    return attrs || (attrs = createAttrsProxy(instance))
  },
  slots: instance.slots,
  emit: instance.emit,
  expose
}

*/
    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) {
        // 返回一个 promise,因此服务端渲染可以等待它执行。
        return setupResult
          .then((resolvedResult: unknown) => {
            handleSetupResult(instance, resolvedResult, isSSR)
          })
          .catch(e => {
            handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
          })
      }
    } else {
      // 捕获 Setup 执行结果
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)// 完成组件初始化
  }
}

后记

本文仅作为自己一个阅读记录,具体还是要看大佬们的文章

下一篇:还没想好要写啥,我最近要准备面试题了 想看看整理下面试的内容。