开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情
前言
我觉得 V3的源码比V2费劲些,上一篇是写了一个demo,顺着运行的过程找源代码,发现并不是特别的流畅。匆匆结束了上一篇文章,下来参考了一些大佬们的文章。有点眉目了。我觉得自己又可以了。
GO ON!
GO ON
上一次提到createapp,V3中运行代码时候已经要使用新的方式了,如下
而源码中如何从入口文件找到createApp这个方法的呢?
路径:
export * from '@vue/runtime-dom'(93行)[packages/vue/src/index.ts]
2.[packages/runtime-dom/src/index.ts]
这个方法主要是对mount进行了封装,挂载了mount函数,做了三件事
- 基于creatApp的参数创建虚拟节点
- 基于虚拟节点和容器元素进行渲染
- 最后返回虚拟节点component属性的代理对象
犹记得V2中也是new App().$mount,封装了一层又一层的mount
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)// 完成组件初始化
}
}
后记
本文仅作为自己一个阅读记录,具体还是要看大佬们的文章
下一篇:还没想好要写啥,我最近要准备面试题了 想看看整理下面试的内容。