vue3 作为目前最火的技术之一,除了学会使用以外,肯定是想在深入到源码里面,了解其实现原理,取其精华,提高自己的核心竞争力,无奈代码量太大,不知从何处下手,推荐开课吧花果山崔老师的mini-vue,虽然源码有些改动,但解读思路是一样的。
准备工作
1、TypeScript学习,Vue 3采用TS构建,学会TS很有必要
2、ES6+相关知识,如 Proxy 、 Reflect 、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
使用
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)
- VNode 表示虚拟节点 Virtual DOM,不是真的 DOM 节点,是对象用于描述节点的信息
- 他只是用 javascript 对象来描述真实 DOM,把DOM标签,属性,内容都变成对象的属性
- 过程就是,把template模板描述成 VNode,然后一系列操作之后通过 VNode 形成真实DOM进行挂载
- 虚拟 DOM:对由 Vue 组件树建立起来的整个 VNode 树的称呼,由 VNode 组成的
什么作用:
1、兼容性强,不受执行环境的影响。VNode 因为是 JS 对象,不管 Node 还是浏览器,都可以执行, 从而获得了服务端渲染、原生渲染、手写渲染函数等能力
2、减少操作 DOM。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新DOM,不需要频繁操作DOM,从而提高页面性能
过程:
模板 → 渲染函数 → 虚拟DOM树 → 真实DOM
源码部分
位置 runtime-core/src/vnode.ts 文件
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
ExtraProps = { [key: string]: any }
> {
...
// 内部属性
}
VNode本质是个对象,属性按照作用分为5类:
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
}
}
那么定义那么多的类型有什么意义呢?这是因为进行渲染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\`.`,
`
Component 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;
}
}
}