入口使用:
因此先从createApp入手
createApp
是runtime-dom
模块导出的,主要做了以下事情:
- 调用
runtime-core
模块的createRenderer
方法,生成渲染器并调用其createApp
方法,生成APP实例。 - 取出原
mount
方法,然后重写APP实例上面的mount
方法,该方法任务是:找到实际dom container,并清空container里面内容,最后调用原mount
方法,完成 DOM 的挂载 - 返回APP实例
createApp 代码实现:
/**
* createApp 函数
*/
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
// 开发环境 校验 组件的 name 是不是内置的标签同名
if (__DEV__) {
injectNativeTagCheck(app)
}
const { mount } = app
/**
* 重写了 mount 函数
* @param containerOrSelector
*/
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// container 是真实的 DOM 元素
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component // 组件的options
// 默认 组件的 template 是 挂载元素的内容
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// 清空 容器中的内容
container.innerHTML = ''
const proxy = mount(container)
if (container instanceof Element) {
// 删除元素上的 v-cloak 指令
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
}) as CreateAppFunction<Element>
追踪到了createRenderer
上面讲到调用了runtime-core
模块的createRenderer
方法,生成渲染器并调用其createApp
方法
那么我们然后继续追踪可以看到createRenderer
返回了baseCreateRenderer
, 这个才是重点代码! 让我们看看baseCreateRenderer
方法干了啥~~
这里是dome-core非常重要的部分,baseCreateRenderer
都有啥:
- 接受
options
参数,options
可以自定义增删改查的API,runtime-dom
调用的时候传入的就是dom的增删改查api。 - 有个
patch
方法,是个总调度员,用以判断类型,并调用不同的增删改查的api, - 定义了各种类型的元素操作的方法,基本是对传入的options进行一层包装。
- 定义
render
方法,该方法调用patch
方法,进行挂载元素操作 - 返回一个对象,包含
createApp
方法。
源码:
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// WEB 平台获取到的是操作 DOM 的方法
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
const patch = () => {...}
const processText = () => {...}
const processCommentNode = () => {...}
const mountStaticNode = () => {...}
const patchStaticNode = () => {...}
const moveStaticNode = () => {...}
const removeStaticNode = () => {...}
const processElement = () => {...}
const mountElement = () => {...}
const setScopeId = () => {...}
const mountChildren = () => {...}
const patchElement = () => {...}
const patchBlockChildren = () => {...}
const patchProps = () => {...}
const processFragment = () => {...}
const processComponent = () => {...}
const mountComponent = () => {...}
const updateComponent = () => {...}
const setupRenderEffect = () => {...}
const updateComponentPreRender = () => {...}
const patchChildren = () => {...}
const patchUnkeyedChildren = () => {...}
const patchKeyedChildren = () => {...}
const move = () => {...}
const unmount = () => {...}
const remove = () => {...}
const removeFragment = () => {...}
const unmountComponent = () => {...}
const unmountChildren = () => {...}
const getNextHostNode = () => {...}
const render = () => {...}
return {
render,
hydrate,
// createApp 入口
createApp: createAppAPI(render, hydrate)
}
}
在 createApp
中调用了 ensureRenderer().createApp(...args)
方法获取 app
的实例,就是 baseCreateRenderer 返回的对象中的 createApp
函数,通过 createAppAPI 函数生成的一个函数。
继续追踪createAppAPI
createAppAPI是由runtime-core
模块导出,让我们去看看,createAppAPI做了什么
-
首先,createAppAPI返回一个函数。
-
接收一个
baseCreateRenderer
里面定义的render
方法。 -
函数定义APP,以及挂一些方法,比如:
mount
,unmount
,mixin
,component
等,并返回app。
源码:/**
-
返回 app 实例
-
@param render
-
@param hydrate / export function createAppAPI( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction { /*
- 接收两个参数
- rootComponent 根组件
- rootProps 传递给根组件的 props */ return function createApp(rootComponent, rootProps = null) { const context = createAppContext() // 返回一个对象 // 安装的插件 const installedPlugins = new Set() // 是否挂载 let isMounted = false
const app = (context.app = { _uid: uid++, // 唯一id _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, version, // vue 版本 get config() { // config 是一个只读对象,设置 config 在开发环境会报警告 return context.config }, use() {...}, mixin() {...}, component() {...}, directive() {...}, mount() {...}, unmount() {...}, provide() {...} })
return app } }
-
createAppAPI生成的app对象的mount方法!!!
作用生成vnode,然后调用render,render方法是在调用 createAppAPI 方法 的时候,传入的参数。也就是 baseCreateRenderer 方法中定义的 render 方法
mount
源码:
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
return vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},
render方法通过patch将vnode转化成真实dom
patch 过程中,会根据 vnode 的 type 不同,调用不同的处理节点的方法,这里主要看处理 component 的方法 processComponent,因为这里会执行 setup 和收集依赖。
processComponent
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
作用:调用mountComponent或者updateComponent
mountComponent
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// createComponentInstance 会对 instance 做处理, ctx 的不一致,就是在这个方法处理的
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// ...
// 这里调用 setup 方法,setup 返回的值,会保存在 instance.setupState 中
setupComponent(instance)
// ...
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
// 处理 setup 是 promise 的情况,在 promise 的状态 resolve 之后,才执行 setupRenderEffect 函数
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
// ...
return
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
作用:通过createComponentInstance
生成instance
,函数最后执行了 setupRenderEffect
方法,在这个方法的执行过程中,会收集 vnode
中使用到的依赖
createComponentInstance
runtime-core
模块
定义组件实例各种属性并返回
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
// ...
const instance: ComponentInternalInstance = {
// 这里定义了各种属性,比如:effects, data, props等
}
// 开发环境对 ctx 做的特殊处理
// 项目开发中不能使用这个 ctx,生产环境不支持
if (__DEV__) {
instance.ctx = createRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
}
setupRenderEffect
create reactive effect for rendering
setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
instance.update = effect(function componentEffect() {
// 新建组件
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
// 对子节点处理 这里会执行 组件的 render 函数
// render 函数对 ref / reactive 的值的获取,都会把当前函数作为依赖变更需要触发的函数收集
const subTree = (instance.subTree = renderComponentRoot(instance))
patch( // 这个patch执行会完成DOM的挂载
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
instance.isMounted = true
initialVNode = container = anchor = null as any
} else {
// 更新组件
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
const nextTree = renderComponentRoot(instance)
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
)
next.el = nextTree.el
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
setupRenderEffect
调用了effect
,只有effect中执行的函数,才会做依赖收集
renderComponentRoot
方法创建组件的子节点,这个方法执行了子组件的render方法,render方法中收集reactivity数据,并将本effect
函数作为变更的依赖收集
在执行 effect 的时候,传递了第二个参数 prodEffectOptions,这个参数中,有一个 scheduler 方法,这个是依赖更新之后会调用的调度器,这个调度器决定什么时候执行 DOM 更新,而不是每次依赖变化都对 DOM 做修改。
初始mount组件:
1: 生成实例:在mountComponent
时候,先生成component instance
,
2: 低啊用setup
: 调用setupComponent
, 将setup
返回的值,会保存在 instance.setupState
中。
3: 最后调用setupRenderEffect
收集依赖。