vue核心面试题:描述组件渲染和更新过程
一、概念:渲染组件时,会通过 Vue.extend 方法构建子组件的构造函数,初始化组件的时候会进行实例化。终手动调用 $mount() 进行组件挂载渲染。更新组件时会进行 patchVnode 流程.核心就是diff算法。二、组件渲染流程一般组件渲染时,我们会这样写h => h(App),app最后是一个对象,会通过一个createElement方法把这个app对象渲染成一个虚拟节点,然后调
一、概念:
渲染组件时,会通过 Vue.extend 方法构建子组件的构造函数,初始化组件的时候会进行实例化。终手动调用 $mount() 进行组件挂载渲染。更新组件时会进行 patchVnode 流程.核心就是diff算法。
二、组件渲染流程
一般组件渲染时,我们会这样写h => h(App),app最后是一个对象,会通过一个createElement方法把这个app对象渲染成一个虚拟节点,然后调用update更新成一个真实节点。
组件在渲染的时候会执行lifecycle文件中的mountComponent方法,在这个方法中会调用vm._render()和vm._update()。这是就会通过createElement方法来渲染一个组件。
createElement是怎么渲染出一个虚拟节点的?将对象传进去之后,如果是一个string类型,就会认为是一个普通的dom。如果传的是一个对象,会认为是一个组件了,就会调用createComponent方法创建组件,在创建组件的时候会调用一个Vue.extend方法(extend是传入一个对象,可以给你这个对象创建出一个构造函数,这个构造函数就是当时app的构造函数),创建完成之后会给组件加上一些Hooks(钩子函数),是组件的一些内部钩子函数:init,prepatch,insert,destroy。当我们组件初始化的时候会调用init钩子,当我们当时节点被插入的时候会调用insert钩子,当删除的时候会调destroy钩子,当进行比对的时候会调用prepatch钩子。最后返回组件vnode。
当我们调用update方法时,会根据这个虚拟节点进行渲染。我们在创建元素的时候,这个元素是一个组件类型,它就会去调用组件的init方法,内部就会new.vnode.componentOptions.Ctor(options),new我们刚才创建的构造函数,只要一new就会重新去执行vue的原有的渲染过程,会给当前的组件加上一个watcher去渲染,这样的话就会把这个组件渲染出来,最终插入到页面上。new这样一个实例之后会手动的调用$mount方法。
三、源码
1.createElement方法 (src/core/vdom/create-element.js)
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// 判断tag的类型,如果是string就创建普通dom
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
// 如果不是string就会调用createComponent创建组件
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
2.创建vnode(src/core/vdom/ceate-component.js)
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
3.创建组件createComponent(src/core/vdom/ceate-component.js)
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
// 调用当前的_base,_base就是Vue
const baseCtor = context.$options._base
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor) // 调用Vue的extend方法,通过传入的对象构造出一个构造函数来
}
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
resolveConstructorOptions(Ctor)
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
const listeners = data.on
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
installComponentHooks(data) // 给组件挂载内部钩子函数
const name = Ctor.options.name || tag
// 创建虚拟节点
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
4.installComponentHooks (src/core/vdom/ceate-component.js)
// 给当前的对象增加一个hook
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
// componentVNodeHooks中包含四个方法:init,prepatch,insert,destroy
const componentVNodeHooks = {
// 初始化的时候调用init,组件初始化的核心,里面做了两件事,第一就是创建一个child
// 第二就是调用$mount方法
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 通过调用createComponentInstanceForVnode创建一个child,通过vnode创建组件的实例
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// 并且调用$mount
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
queueActivatedComponent(componentInstance)
} else {
activateChildComponent(componentInstance, true /* direct */)
}
}
},
destroy (vnode: MountedComponentVNode) {
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy()
} else {
deactivateChildComponent(componentInstance, true /* direct */)
}
}
}
}
5.createComponentInstanceForVnode
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
return new vnode.componentOptions.Ctor(options)
// new我们刚写的构造函数,当new的时候就会走组件的初始化
}
更多推荐
所有评论(0)