1、vue 架构变化
compile-dom ---> compile-core
reactivity
runtime-dom ---> runtime-core
2、关于打包
1、 vue 是用 rollup 打包
2、执行 用 execa 库 执行 rollup 命令
3、打包格式:
global ---> umd ---> iife 自调函数
cjs ---> nodejs 环境
es ----> es6 环境
3、源码初始化过程
0、 使用(关于为什么做成这样、 和vue2 不同)
1、避免实例污染
2、摇树优化 (以前很多方法挂载实例上)
3、语义上更好理解
createApp({
data() {
return {
counter: 1
}
}
})
.use(store)
.use(router)
.mount('#app')
1、createApp 方法 在 runtime-dom 模块
export const createApp = ((...args) => {
// 首先获取渲染器
// 实际上createApp是由渲染器调用
const app = ensureRenderer().createApp(...args)
const { mount } = app
app.mount = (containerOrSelector: Element | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
//.... 编译模板
const proxy = mount(container)
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}
return app
}) as CreateAppFunction<Element>
2、渲染器
const app = ensureRenderer().createApp(...args)
// 1、该方法返回渲染器
ensureRenderer()
// ensureRenderer() => return renderer || createRenderer() // 单例模式
// renderer (渲染器)作为全局变量 创建过就会有值
// 2、createRenderer({patchProp,...nodeOps}) 创建渲染器(属性比较和替换, dom操作的方法)
createRenderer()
// return baseCreateRenderer ()
// 3、
baseCreateRenderer({patchProp, ...nodeOps})
// 目录位置:packages\runtime-core\src\renderer.ts
// baseCreateRenderer return 结果
// return ==> {
// render, // 渲染方法 render(vnode,container) 转换操作
// hydrate, // 注水 用于服务端渲染
// createApp: createAppAPI(render, hydrate) 返回一个方法
//}
// 4、createAppAPI(render, hydrate) return app 实例
createAppAPI(render, hydrate)
// 实例包含 {use、component、mixins、directive、mount、unmount}
// packages\runtime-core\src\apiCreateApp.ts
3、关于 mount
目录位置:vue-next\packages\runtime-core\src\apiCreateApp.ts (227行)
主要做了以下几件事
// 初始化虚拟dom树
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
) // 得到vnode
vnode.appContext = context
// HMR root reload 热更新
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer)
}
}
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any) // 服务端渲染
} else {
// 浏览器端渲染 ------------------------------
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer // 根节点保存
4、 render 方法
根据有没有dom 来走不同流程(container._vnode)是否有vnode
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 看这里
// 参数1 存在走更新
// 参数1 不存在走挂载
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode // 挂到dom上
}
5、patch 比较虚拟dom 更新, 也包含初次渲染
目录位置:packages\runtime-core\src\renderer.ts
const patch: PatchFn = (
n1, // 老虚拟节点
n2, // 新虚拟节点
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
// patching & not same type, unmount old tree 不是同一个根节点类型 卸载n1
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
// 获取新节点类型
const { type, ref, shapeFlag } = n2
switch (type) {
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: // 非单根节点
processFragment(...)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(...)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 初始化走这里
processComponent(...)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(...) // 处理传送门
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(...)
}
}
// set ref 设置 ef
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
}
}
初始化渲染步骤:
初始化走 component (processComponent)流程 (不是fragment)
processComponent => mountComponent 初次走挂载组件
patch 组件的过程
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
// keepalive 激活
(parentComponent!.ctx as KeepAliveContext).activate()
} else {
// 初始化
mountComponent()
}
} else {
// 更新
updateComponent(n1, n2, optimized)
}
}
6、mountComponent 简化流程
目录:packages\runtime-core\src\renderer.ts
// 1.创建实例
const instance = createComponentInstance(...) // 创建组件实例
// ....
// 2组件安装 类似于 vue2_init 合并merge options,// 属性初始化 // 数据响应式 // 插槽处理
setupComponent(instance)
// ....
// 3、 安装渲染函数的副作用
setupRenderEffect()
7、setupComponent(instance)
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false // 是否是ssr渲染
) {
isInSSRComponentSetup = isSSR
const { props, children, shapeFlag } = instance.vnode
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR) // 这里是关键
: undefined
isInSSRComponentSetup = false
return setupResult
}
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 0. create render proxy property access cache
instance.accessCache = Object.create(null)
// 1. create public instance / render proxy
// also mark it raw so it's never observed
// 获取上下文 这里就是响应式了
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) // 数据响应式
// 2. call setup() 处理setup函数
const { setup } = Component // 处理setup
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
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) {
// return the promise so server-renderer can wait on it
return setupResult.then((resolvedResult: unknown) => {
handleSetupResult(instance, resolvedResult, isSSR)
})
} else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise.
// bail here and wait for re-entry.
instance.asyncDep = setupResult
} else if (__DEV__) {
warn(
`setup() returned a Promise, but the version of Vue you are using ` +
`does not support it yet.`
)
}
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
// 没有设置setup 走这里
finishComponentSetup(instance, isSSR)
}
}
setup 和 data 同时存在, setup 的值 优先级更高
8、finishComponentSetup(instance, isSSR) 这个还待研究
目录: packages\runtime-core\src\component.ts
9、setupRenderEffect 安装副作用
会走分初次渲染和更新组件
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// create reactive effect for rendering effect函数注册
instance.update = effect(function componentEffect() {
if (!instance.isMounted) { // 未挂载的情况
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
// beforeMount hook 生命周期
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
// 1、首先获取当前组件的vnode
const subTree = (instance.subTree = renderComponentRoot(instance))
if (el && hydrateNode) {
// ssr服务端渲染
// vnode has adopted host node - perform hydration instead of mount.
hydrateNode(
initialVNode.el as Node,
subTree,
instance,
parentSuspense
)
} else {
// 走patch 如果是多根节点走fragment
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
}
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, initialVNode)
}, parentSuspense)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
const { a } = instance
if (
a &&
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) {
queuePostRenderEffect(a, parentSuspense)
}
instance.isMounted = true
} else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree
instance.subTree = nextTree
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
// .....
}