Vue.js 3 源码分析

374 阅读3分钟

Vue.js 3 源码分析

Vue.js 3 是流行的前端框架,它通过数据驱动的方式来组织代码,并使用模板语法表示视图和组件之间的关系。在本文中,我们将深入探讨 Vue.js 3 的源码,包括核心代码、平台相关代码等部分。

目录结构

Vue.js 3 源代码的目录结构如下:

- packages # 包含了独立发布的包,例如 compiler-core 和 reactivity
- dist # 构建后生成的文件,可以直接使用或者作为发布
  - vue.global.js # 完整版,包含所有的组件和 API
  - vue.global.prod.js # 压缩版
  - vue.runtime.global.js # 仅包含运行时的代码和模板编译器
  - vue.runtime.global.prod.js # 压缩版
- src # 掌握 Vue.js 核心代码的关键,包含了该框架的所有源代码
  - compiler-core # 编译相关的核心代码,包括模板解析、代码生成等
  - reactivity # 响应式系统的核心代码
  - runtime-core # 运行时的核心代码,包括虚拟 DOM、组件实例等
  - server-renderer # 服务端渲染相关的代码
  - shared # 共享代码,包括常量、工具函数等
- test # 包含了所有的测试用例

核心代码

响应式系统

Vue.js 3 的响应式系统与 Vue.js 2 不同,采用了基于 Proxy 的实现方式,而不是 Object.defineProperty。Proxy 不仅能监听对象的读写操作,还能监听属性的删除、枚举等操作。

// reactivity/src/reactive.ts

export function reactive(target: object) {
  if (target && target.__v_isReadonly) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

export function createReactiveObject(
  target: any,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 省略其他代码 ...

  const observed = new Proxy(target, handlers)

  return observed
}

其中,mutableHandlers 和 mutableCollectionHandlers 分别为对象和数组的 Proxy Handler。

运行时

Vue.js 3 的运行时部分主要由 runtime-core 和 renderer-core 两部分组成。runtime-core 包括虚拟 DOM、组件实例等功能,renderer-core 则包括渲染器相关代码(如 createApp 函数)。

在组件实例的创建过程中,需要先通过 createComponentInstance 函数创建一个组件实例,并将组件的配置对象与虚拟 DOM 节点进行关联。随后,在 setup 函数中执行组件的初始化操作,包括 props 和 data 的初始化、watcher 的创建、事件的注册等。

// runtime-core/src/component.ts

export function createComponentInstance(
  vnode: VNode | null,
  parent: ComponentInternalInstance | null
): ComponentInternalInstance {
  const instance: ComponentInternalInstance = {
    // 省略其他属性 ...
    setupState: {},
    proxy: null,
    subTree: null,
    suspense: null,
    isMounted: false,
    isUnmounted: false,
    [ReactiveFlags.SKIP]: true,
  }

  // 省略其他代码 ...

  // setup component instance
  setupComponent(instance)

  return instance
}

function setupComponent(instance: ComponentInternalInstance) {
  // props and attrs (attribute bindings)
  const propsOptions = instance.type.props
  resolveProps(instance, propsOptions)

  // 省略其他初始化代码 ...

  // setup() is a user-land function, the user may be using `this`
  // inside it, which can be captured by vm/instance proxies, which
  // also exposes the `isVue` flag, so we need to reset it
  resetTracking()
  const { setup } = Component.prototype
  if (setup) {
    const setupContext = createSetupContext(instance)
    const setupResult = callWithErrorHandling(setup, instance, [props, setupContext])
    if (isObject(setupResult)) {
      instance.setupState = reactive(setupResult)
    } else if (setupResult !== undefined) {
      console.warn(`setup() should return an object. Received: ${setupResult}`)
    }
  }
  // 省略其他代码 ...
}

编译器

Vue.js 3 的编译器部分包括 compiler-core 和 compiler-dom 两个主要部分。其中,compiler-core 主要负责模板解析和 AST 转换等功能,compiler-dom 则负责将 AST 转为渲染函数。

在编译器中,通过 parse 函数将模板转为 AST,并通过 transform 函数进行 AST 转换。随后,再通过 codegen 函数将 AST 转化为渲染函数的字符串表示形式。

// compiler-core/src/parse.ts

export function parse(
  template: string,
  options: ParserOptions = {},
): RootNode {
  const ast = baseParse(template, {
    ...options,
    // 省略其他配置 ...
  })

  // 省略其他代码 ...

  return ast
}

function baseParse(
  content: string,
  options: ParserOptions,
) {
  // 省略其他代码 ...

  // 解析
  while (index < length) {
    parseHtmlExpression()
  }

  // 省略其他代码 ...

  return createRoot(
    children,
    0 /* loc */,
    source,
  )
}
// compiler-core/src/transform.ts

export function transform(ast: RootNode, options: TransformOptions) {
  const context = createTransformContext(ast, options)
  traverseNode(ast, context)
}

function traverseNode(node: TemplateChildNode, context: TransformContext) {
  switch (node.type) {
    case NodeTypes.ELEMENT:
      // 省略其他代码 ...

      for (let i = 0; i < props.length; i++) {
        if (props[i].type === NodeTypes.DIRECTIVE) {
          // DirectiveNode 处理
          transformDirective(prop, node, context)
        } else {
          // AttributeNode 处理
          // 省略其他代码 ...
        }
      }

      // 省略其他代码 ...

      break

    case NodeTypes.TEXT:
      // 省略其他代码 ...
  }
}
// compiler-core/src/codegen.ts

export function generate(
  ast: RootNode | TemplateChildNode,
  options: CodegenOptions = {},
): CodegenResult {
  const context = createCodegenContext(ast, options)
  const code = ast ? genNode(ast, context) : ''

  return {
    code,
    map: context.map ? context.map.toJSON() : undefined,
  }
}

function genNode(node: TemplateChildNode, context: CodegenContext) {
  // 省略其他代码 ...

  switch (node.type) {
    case NodeTypes.ELEMENT:
      // 省略其他代码 ...

      let codegenNode: string
      if (node.tagType === ElementTypes.ELEMENT) {
        // transform 后的处理
        codegenNode = genElement(node, context)
      } else {
        // 省略其他代码 ...
      }

      // 省略其他代码 ...

      return codegenNode

    case NodeTypes.TEXT:
      // 省略其他代码 ...
  }
}

平台相关代码

Vue.js 3 的平台相关代码包括了不同平台(如 Web 和 Weex)的特定代码。对于 Web 平台,主要包括了与浏览器 DOM 相关的平台支持。

其中,runtime-dom 包含了与 DOM 操作相关的平台支持代码,例如创建节点、更新节点等操作。对于组件的渲染,可以通过 createRenderer 函数创建一个渲染器,并通过 render 函数将组件渲染到指定的容器中。

// runtime-dom/src/index.ts

export * from './renderer'

// 省略其他代码 ...
// runtime-dom/src/renderer.ts

export function createRenderer(options?: RendererOptions<Node, Element>) {
  return baseCreateRenderer<Node, Element>(nodeOps, patchProp, hydrate, options)
}

type RendererOptions<Node, Element> = {
  // 省略其他配置 ...

  patchProp?: PatchFn<Node, Element>
}

function baseCreateRenderer<Node extends INode, Element extends IElement>(
  nodeOps: NodeOps<Node, Element>,
  patchProp: PatchFn<Node, Element>,
  hydrate?: HydrationRenderer<Node, Element>,
  options?: RendererOptions<Node, Element>,
): Renderer<Node, Element> {
  // 省略其他代码 ...

  const { patch, forcePatchProp } = createPatchFunction<Node, Element>(backend)

  return {
    createApp: createAppAPI<Node, Element>(patch, hydrate),
    createVNode: createVNodeWithArgsTransform(
      (n, children) => {
        if (n !== StaticMarkup) {
          if (isObject(n)) {
            normalizeVNode(n)
          }
          if (children) {
            normalizeChildren(n, children)
          }
        }
        return n as VNode<Node, Element>
      },
    ),
    render: (vnode, container) => {
      if (vnode == null) {
        if (container._vnode) {
          unmount(container._vnode, null, null, true)
        }
      } else {
        patch(
          container instanceof SVGElement ? (container as SVGSVGElement).ownerSVGElement! : container,
          vnode,
          container._vnode,
          null,
          null,
          null,
          null,
          true,
          false,
        )
      }
      resetActiveInstance()
      // 省略其他代码 ...
    },

    // 省略其他 API ...
  }
}

总结

Vue.js 3 源码分析主要介绍了响应式系统、运行时、编译器和平台相关代码等部分。通过深入探讨这些核心功能的实现原理,可以更好地理解 Vue.js 框架的设计思路,对于自己开发类似的前端框架也有所帮助。