Vue-next源码新鲜出炉一

vue3 作为目前最火的技术之一,除了学会使用以外,肯定是想在深入到源码里面,了解其实现原理,取其精华,提高自己的核心竞争力,无奈代码量太大,不知从何处下手,推荐开课吧花果山崔老师的mini-vue,虽然源码有些改动,但解读思路是一样的。

准备工作

1、TypeScript学习,Vue 3采用TS构建,学会TS很有必要
2、ES6+相关知识,如 ProxyReflect 、Symbol、泛型等。

├── packages
│   ├── compiler-core // 编译器核心
│   ├── compiler-dom // dom解析&编译
│   ├── compiler-sfc // 文件编译系统│   ├── compiler-ssr // 服务端渲染
│   ├── reactivity // 数据响应
│   ├── runtime-core // 虚拟DOM渲染-核心
│   ├── runtime-dom // dom即时编译
│   ├── runtime-test // 测试runtime
│   ├── server-renderer // ssr
│   ├── shared // 帮助
│   ├── size-check // runtime包size检测
│   ├── template-explorer
│   └── vue // 构建vue

这个流程将从用户使用的 api createApp 来作为入口点,分析vue 的内部执行流程

1、createApp

作用流程
进行初始化,基于 rootComponent 生成 vnode, 进行 render
image.png
使用

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 注册路由
setupRouter(app)

router.isReady().then(() => {
  app.mount('#app')
})

createAppAPI源码部分

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    const context = createAppContext()
    //  当前 vue 的实例,全局唯一
    const app: App = (context.app = {
      // 属性
    	_uid: uid++,
      _component: rootComponent as ConcreteComponent, // 存放整个组件原始配置树
      _props: rootProps,
      _container: null,  // 根容器 , 虚拟dom 入口节点
      _context: context, //  上下文对象
      _instance: null,
      version,
      get config() {
        return context.config  // 全局配置
      },
      // 方法
      use(plugin: Plugin, ...options: any[]) {},
      mixin(mixin: ComponentOptions) {},
      component(name: string, component?: Component): any {},
      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
           const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
        },
       unmount() {}
      provide(key, value) {}
    })
  	return app
  }
}
export function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {}
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap()
  }
}

抽象模拟

import { render } from "./renderer";
import { createVNode } from "./vnode";

// createApp
// 在 vue3 里面 createApp 是属于 renderer 对象的
// 而 renderer 对象需要创建
// 这里我们暂时不实现

export const createApp = (rootComponent) => {
  const app = {
    _component: rootComponent,
    mount(rootContainer) {
      console.log("基于根组件创建 vnode");
      const vnode = createVNode(rootComponent);
      console.log("调用 render,基于 vnode 进行开箱");
      render(vnode, rootContainer);
    },
  };

  return app;
};

2、虚拟节点(VNode)

  1. VNode 表示虚拟节点 Virtual DOM,不是真的 DOM 节点,是对象用于描述节点的信息
  2. 他只是用 javascript 对象来描述真实 DOM,把DOM标签,属性,内容都变成对象的属性
  3. 过程就是,把template模板描述成 VNode,然后一系列操作之后通过 VNode 形成真实DOM进行挂载
  4. 虚拟 DOM:对由 Vue 组件树建立起来的整个 VNode 树的称呼,由 VNode 组成的

什么作用:
1、兼容性强,不受执行环境的影响。VNode 因为是 JS 对象,不管 Node 还是浏览器,都可以执行, 从而获得了服务端渲染、原生渲染、手写渲染函数等能力
2、减少操作 DOM。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新DOM,不需要频繁操作DOM,从而提高页面性能
过程:
模板 → 渲染函数 → 虚拟DOM树 → 真实DOM
a675965c467fe86b1ea1ff344e2bd869.png

源码部分

位置 runtime-core/src/vnode.ts 文件

export interface VNode<
  HostNode = RendererNode,
  HostElement = RendererElement,
  ExtraProps = { [key: string]: any }
> {
  ...
	// 内部属性
}

VNode本质是个对象,属性按照作用分为5类:
image.png

1、内部属性

__v_isVNode: true // 标识是否为VNode
[ReactiveFlags.SKIP]: true // 标识VNode不是observable
type: VNodeTypes // VNode 类型
props: (VNodeProps & ExtraProps) | null // 属性信息
key: string | number | null // 特殊 attribute 主要用在 Vue 的虚拟 DOM 算法
ref: VNodeNormalizedRef | null // 被用来给元素或子组件注册引用信息。
scopeId: string | null // SFC only
children: VNodeNormalizedChildren // 保存子节点
component: ComponentInternalInstance | null // 指向VNode对应的组件实例
dirs: DirectiveBinding[] | null // 保存应用在VNode的指令信息
transition: TransitionHooks<HostElement> | null // 存储过渡效果信息

2、DOM 属性

el: HostNode | null // 真实DOM 节点
anchor: HostNode | null // fragment anchor程序锚点
target: HostElement | null // teleport target
targetAnchor: HostNode | null // teleport target anchor
staticCount: number // number of elements contained in a static vnode

3、suspense 属性

suspense: SuspenseBoundary | null
ssContent: VNode | null
ssFallback: VNode | null

4、 optimization 属性(优化)

shapeFlag: number
patchFlag: number
dynamicProps: string[] | null
dynamicChildren: VNode[] | null

5 、应用上下文属性

appContext: AppContext | null
...

创建 VNode

Vue-Next提供了h函数,实际执行的就是createVNode(),由于频繁使用封装了一层


// packages/runtime-core/src/h.ts
// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  const l = arguments.length
  if (l === 2) {
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      // single vnode without props
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren])
      }
      // props without children
      return createVNode(type, propsOrChildren)
    } else {
      // omit props
      return createVNode(type, null, propsOrChildren)
    }
  } else {
    if (l > 3) {
      children = Array.prototype.slice.call(arguments, 2)
    } else if (l === 3 && isVNode(children)) {
      children = [children]
    }
    return createVNode(type, propsOrChildren, children)
  }
}

h函数主要逻辑就是根据参数个数和参数类型,执行相应处理操作,调用 createVNode 函数来创建 VNode 对象
开发实例

app.component('componentHello', {
  template: "<div>Hello World!<div>"
})

编译一下

import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, "Hello World!"))
}

编译结果来看,生成一个render函数,执行createElementBlock()函数,是什么东东,一探究竟
还是看源码吧

export let currentBlock: VNode[]
// packages/runtime-core/src/vnode.ts
/**
 * @private
 */
export function createElementBlock(
  type: string | typeof Fragment,
  props?: Record<string, any> | null,
  children?: any,
  patchFlag?: number,
  dynamicProps?: string[],
  shapeFlag?: number
) {
  return setupBlock(
    createBaseVNode(
      type,
      props,
      children,
      patchFlag,
      dynamicProps,
      shapeFlag,
      true /* isBlock */
    )
  )
}

function setupBlock(vnode: VNode) {
  // save current block children on the block vnode
  vnode.dynamicChildren =
    isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
  // close block
  closeBlock()
  // a block is always going to be patched, so track it as a child of its
  // parent block
  if (isBlockTreeEnabled > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  return vnode
}


function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  const vnode = {
    __v_isVNode: true,
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  } as VNode
  
...
  return vnode
}

最后看到,还是生成 vnode放到 currentBlock中,实际作用还是创建是VNode。
现在我们知道了createVNode的作用,接下来具体看看代码实现

createVNode代码解读

位置:runtime-core/src/vnode.ts
代码量:89行+83行,分为两部分createVNode和 createBaseVNode(也叫createElementVNode)

代码部分

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  
  /* 注意 type 有可能是 string 也有可能是对象
  * 如果是对象的话,那么就是用户设置的 options
  * type 为 string 的时候
  * createVNode("div")
  * type 为组件对象的时候
  */ createVNode(App)
  ...
  
  
   return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

createBaseVNode() // 上面已经出现过

函数可以接收 6 个参数

参数type

export type VNodeTypes =
  | string 
  | VNode
  | Component
  | typeof Text
  | typeof Static
  | typeof Comment
  | typeof Fragment
  | typeof TeleportImpl
  | typeof SuspenseImpl

// packages/runtime-core/src/vnode.ts
export const Text = Symbol(__DEV__ ? 'Text' : undefined)
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
export const Static = Symbol(__DEV__ ? 'Static' : undefined)
 
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
  __isFragment: true
  new (): {
    $props: VNodeProps
  }
}

image.png
那么定义那么多的类型有什么意义呢?这是因为进行渲染render在 patch 阶段,基于 vnode 的类型进行不同类型的组件处理

参数 props (Data & VNodeProps)

// TypeScript 内置的工具类型 Record
export type Data = Record<string, unknown>
  
// 含有 key 和 ref 属性之外,其他的属性主要是定义了与生命周期有关的钩子
export type VNodeProps = {
  key?: string | number | symbol
  ref?: VNodeRef

  // vnode hooks
  onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[]
  onVnodeMounted?: VNodeMountHook | VNodeMountHook[]
  onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[]
  onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[]
  onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[]
  onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[]
}

主要逻辑

// packages/runtime-core/src/vnode.ts
function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: ${type}.`)
    }
    type = Comment
  }

  // 处理VNode类型,比如处理动态组件的场景:<component :is="vnode"/>
  if (isVNode(type)) {
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    return cloned
  }

 // 类组件规范化处理
  if (isClassComponent(type)) {
    type = type.__vccOpts
  }

  // 2.x 异步/功能组件兼容
  if (__COMPAT__) {
    type = convertLegacyComponent(type, currentRenderingInstance)
  }

  // 类和样式规范化处理
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    props = guardReactiveProps(props)!
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }

   // 把vnode的类型信息转换为二进制位图
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0

  if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
    type = toRaw(type)
    warn(
      `Vue received a Component which was made a reactive object. This can ` +
        `lead to unnecessary performance overhead, and should be avoided by ` +
        `marking the component with \`markRaw\` or using \`shallowRef\` ` +
        `instead of \`ref\`.`,
      `\nComponent that was made reactive: `,
      type
    )
  }

  // 创建VNode对象
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

1、createBaseVNode() 主要只作用是生成规范的VNode 对象,
2、这里就要提到normalizeChildren函数,是一个递归函数,根据VNode,传入的children,修正VNode的type值和children的VNode

export function normalizeChildren(vnode: VNode, children: unknown) {
  let type = 0
  const { shapeFlag } = vnode
  if (children == null) {
    children = null
  } else if (isArray(children)) {
    type = ShapeFlags.ARRAY_CHILDREN
  } else if (typeof children === 'object') {
    if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.TELEPORT)) {
      // Normalize slot to plain children for plain element and Teleport
      const slot = (children as any).default
      if (slot) {
        // _c marker is added by withCtx() indicating this is a compiled slot
        slot._c && (slot._d = false)
        normalizeChildren(vnode, slot())
        slot._c && (slot._d = true)
      }
      return
    } else {
      type = ShapeFlags.SLOTS_CHILDREN
      const slotFlag = (children as RawSlots)._
      if (!slotFlag && !(InternalObjectKey in children!)) {
        // if slots are not normalized, attach context instance
        // (compiled / normalized slots already have context)
        ;(children as RawSlots)._ctx = currentRenderingInstance
      } else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
        // a child component receives forwarded slots from the parent.
        // its slot type is determined by its parent's slot type.
        if (
          (currentRenderingInstance.slots as RawSlots)._ === SlotFlags.STABLE
        ) {
          ;(children as RawSlots)._ = SlotFlags.STABLE
        } else {
          ;(children as RawSlots)._ = SlotFlags.DYNAMIC
          vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
        }
      }
    }
  } else if (isFunction(children)) {
    children = { default: children, _ctx: currentRenderingInstance }
    type = ShapeFlags.SLOTS_CHILDREN
  } else {
    children = String(children)
    // force teleport children to array so it can be moved around
    if (shapeFlag & ShapeFlags.TELEPORT) {
      type = ShapeFlags.ARRAY_CHILDREN
      children = [createTextVNode(children as string)]
    } else {
      type = ShapeFlags.TEXT_CHILDREN
    }
  }
  vnode.children = children as VNodeNormalizedChildren
  vnode.shapeFlag |= type
}

抽象模拟

export const createVNode = function (
  type: any,
  props?: any = {},
  children?: string | Array<any>
) {
  // 注意 type 有可能是 string 也有可能是对象
  // 如果是对象的话,那么就是用户设置的 options
  // type 为 string 的时候
  // createVNode("div")
  // type 为组件对象的时候
  // createVNode(App)
  const vnode = {
    el: null,
    component: null,
    key: props.key || null,
    type,
    props,
    children,
    shapeFlag: getShapeFlag(type),
  };

  // 基于 children 再次设置 shapeFlag
  if (Array.isArray(children)) {
    vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
  } else if (typeof children === "string") {
    vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
  }

  normalizeChildren(vnode, children);

  return vnode;
};

export function normalizeChildren(vnode, children) {
  if (typeof children === "object") {
    // 暂时主要是为了标识出 slots_children 这个类型来
    // 暂时我们只有 element 类型和 component 类型的组件
    // 所以我们这里除了 element ,那么只要是 component 的话,那么children 肯定就是 slots 了
    if (vnode.shapeFlag & ShapeFlags.ELEMENT) {
      // 如果是 element 类型的话,那么 children 肯定不是 slots
    } else {
      // 这里就必然是 component 了,
      vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN;
    }
  }
}
posted @ 2021-09-06 17:27  有什么奇怪  阅读(311)  评论(0编辑  收藏  举报