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 框架的设计思路,对于自己开发类似的前端框架也有所帮助。