问题:内置组件为什么不需要引入?
答:内置组件默认是全局引入的。
/**
* Teleport 组件定义
*/
const Teleport = {
__isTeleport: true,
// 组件创建和更新
process(nl, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals) {
if (n1 == null) {
// 创建逻辑
} else {
// 更新逻辑
}
},
// 组件删除
remove(vnode, { r: remove, o: { remove: hostRemove } }) {
// 删除逻辑
},
move: moveTeleport,
hydrate: hydrateTeleport
}
/**
* 组件创建、更新
*/
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
// 如果存在新旧节点,且新旧节点类型不同,则销毁旧节点
if (n1 && !isSameVNodeType(nl, n2)) { }
// 不同节点采用对应的处理方式
const { type, shapeFlag } = n2
switch (type) {
// 处理文本节点
case Text:
break
// 处理注释节点
case Comment:
break
// 处理静态节点
case Static:
break
// 处理 Fragment 元素
case Fragment:
break
default:
// 处理普通 DOM 元素
if (shapeFlag & 1/* ELEMENT */) { }
// 处理组件
else if (shapeFlag & 6/* COMPONENT*/) { }
// 处理 TELEPORT
else if (shapeFlag & 64/* TELEPORT */) {
type.process(nl, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
// 处理 SUSPENSE
else if (shapeFlag & 128/* SUSPENSE */) { }
}
}
/**
* Teleport 组件创建、更新
*/
function process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals) {
const { mc: mountChildren, pc: patchChildren, pbc: patchBlockChildren, o: { insert, querySelector, createText, createComment } } = internals
const disabled = isTeleportDisabled(n2.props)
const { shapeFlag, children } = n2
// 创建 Teleport
if (n1 == null) {
// 1、向主视图里插入节点(在非生产环境下插入注释节点,在生产环境下插入空白文本节点)
const placeholder = (n2.el = (process.env.NODE_ENV !== 'production') ? createComment('teleport start') : createText(''))
const mainAnchor = (n2.anchor = (process.env.NODE_ENV !== 'production') ? createComment('teleport end') : createText(''))
insert(placeholder, container, anchor)
insert(mainAnchor, container, anchor)
// 2、获取目标移动的 DOM 节点
const target = (n2.target = resolveTarget(n2.props, querySelector))
const targetAnchor = (n2.targetAnchor = createText(''))
// 存在则添加目标节点
if (target) {
insert(targetAnchor, target)
}
// 不存在则报警告
else if ((process.env.NODE_ENV !== 'production')) {
warn(/* ... */)
}
// 3、往目标节点插入 Teleport 组件的子节点
const mount = (container, anchor) => {
// 子节点为数组,遍历挂载子节点
if (shapeFlag & 16/* ARRAY_CHILDREN */) {
mountChildren(children, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
}
}
// disabled 情况就在原先的位置挂载
if (disabled) {
mount(container, mainAnchor)
}
// 挂载到 target 的位置
else if (target) {
mount(target, targetAnchor)
}
}
// 更新 Teleport
else {
// 1、更新子节点
n2.el = nl.el
const mainAnchor = (n2.anchor = nl.anchor)
const target = (n2.target = n1.target)
const targetAnchor = (n2.targetAnchor = nl.targetAnchor)
const wasDisabled = isTeleportDisabled(n1.props) // 之前是不是 disabled 状态
const currentContainer = wasDisabled ? container : target
const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
// 优化更新
if (n2.dynamicChildren) {
patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, currentContainer, parentComponent, parentSuspense, isSVG)
if (n2.shapeFlag & 16/* ARRAY_CHILDREN */) {
const oldChildren = n1.children
const children = n2.children
for (let i = 0; i < children.length; i++) {
if (!children[i].el) {
children[i].el = oldChildren[i].el
}
}
}
}
// 组件全量比对
else if (!optimized) {
patchChildren(n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, isSVG)
}
// 2、处理 disabled 属性以及 to 属性的变化情况
// 新节点不显示,旧节点显示(把子节点移动回主容器)
if (disabled) {
if (!wasDisabled) {
moveTeleport(n2, container, mainAnchor, internals, 1/* TOGGLE */)
}
}
// 新节点展示
else {
// 目标元素改变
if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
const nextTarget = (n2.target = resolveTarget(n2.props, querySelector))
// 存在新的目标节点,则移动到新的目标元素
if (nextTarget) {
moveTeleport(n2, nextTarget, null, internals, 0/* TARGET_CHANGE */)
}
// 不存在新的目标节点,且在非生产环境下则报警告
else if ((process.env.NODE_ENV !== 'production')) {
warn(/* ... */)
}
}
// 新节点展示,旧节点不展示(把子节点移动到目标元素位置)
else if (wasDisabled) {
moveTeleport(n2, target, targetAnchor, internals, 1/* ROGGLE */)
}
}
}
}
/**
* Teleport 组件移除
*/
function remove(vnode, { r: remove, o: { remove: hostRemove } }) {
const { shapeFlag, children, anchor } = vnode
// 移除主视图渲染的锚点(Teleport start 的注释节点)
hostRemove(anchor)
// 如果子节点为数组,则遍历 Teleport 节点进行移除
if (shapeFlag & 16/* ARRAY_CHILDREN */) {
for (let i = 0; i < children.length; i++) {
remove(children[i])
}
}
}
实际是一个抽象节点,渲染的是它的第一个子节点。
/**
* KeepAlive 组件定义
*/
const KeepAliveImpl = {
name: `KeepAlive`,
__isKeepAlive: true,
inheritRef: true,
// 用户自定义的配置
props: {
include: [String, RegExp, Array], // 包含
exclude: [String, RegExp, Array], // 不包含
max: [String, Number], // 最大缓存个数(默认不限制个数)
},
setup(props, { slots }) {
const cache = new Map()
const keys = new Set()
let current = null
const instance = getCurrentlnstance()
const parentSuspense = instance.suspense
const sharedContext = instance.ctx
const { renderer: { p: patch, m: move, um: _unmount, o: { createElement } } } = sharedContext
const storageContainer = createElement('div')
// 执行 deactivate 钩子函数
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
const instance = vnode.component
// 将该节点添加到 DOM 中
move(vnode, container, anchor, 0/* ENTER */, parentSuspense)
patch(instance.vnode, vnode, container, anchor, instance, parentSuspense, isSVG, optimized)
queuePostRenderEffect(() => {
instance.isDeactivated = false
if (instance.a) {
invokeArrayFns(instance.a)
}
// 执行组件的 onBeforeMount 钩子函数
const vnodeHook = vnode.props && vnode.props.onVnodeMounted
if (vnodeHook) {
invokeVNodeHook(vnodeHook, instance.parent, vnode)
}
}, parentSuspense)
}
// 执行 deactivate 钩子函数
sharedContext.deactivate = (vnode) => {
constinstance = vnode.component
// 从 DOM 中移除该节点
move(vnode, storageContainer, null, 1/* LEAVE */, parentSuspense)
queuePostRenderEffect(() => {
if (instance.da) {
invokeArrayFns(instance.da)
}
// 执行组件的 onBeforeUnmount 钩子函数
const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted
if (vnodeHook) {
invokeVNodeHook(vnodeHook, instance.parent, vnode)
}
instance.isDeactivated = true
}, parentSuspense)
}
function unmount(vnode) {
resetShapeFlag(vnode)
_unmount(vnode, instance, parentSuspense)
}
// 删除缓存(LRU 思想)
function pruneCache(filter) {
cache.forEach((vnode, key) => {
const name = getName(vnode.type)
if (name && (!filter || !filter(name))) {
pruneCacheEntry(key)
}
})
}
function pruneCacheEntry(key) {
const cached = cacheget(key)
if (!current || cached.type !== current.type) {
unmount(cached)
} else if (current) {
resetShapeFlag(current)
}
cache.delete(key)
keys.delete(key)
}
// 监听用户提供的配置的变化
watch(() => [props.include, props.exclude], ([include, exclude]) => {
include && pruneCache(name => matches(include, name))
exclude && !pruneCache(name => matches(exclude, name))
})
let pendingCacheKey = null
const cacheSubtree = () => {
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, instance.subTree)
}
}
// 在执行 onBeforeMount、onBeforeUpdate 钩子函数时执行 cacheSubtree 来缓存组件
onBeforeMount(cacheSubtree)
onBeforeUpdate(cacheSubtree)
// 在组件卸载时遍历缓存的组件进行卸载
onBeforeUnmount(() => {
cache.forEach(cached => {
const { subTree, suspense } = instance
if (cached.type === subTree.type) {
resetShapeFlag(subTree)
const da = subTree.component.da
da && queuePostRenderEffect(da, suspense)
return
}
unmount(cached)
})
})
// 返回组件渲染函数
return () => {
pendingCacheKey = null
if (!slots.default) return null
// 1、组件渲染
// 获取子节点(包裹的组件)
const children = slots.default()
let vnode = children[0]
// 因为 只能有一个子组件,因此如果子组件的数量大于 1,则报警告
if (children.length > 1) {
if ((process.env.NODE_ENV !== 'production')) {
warn(/* ... */)
}
current = null
return children
} else if (!isVNode(vnode) || !(vnode.shapeFlag & 4/* STATEFUL_COMPONENT */)) {
current = null
return vnode
}
const comp = vnode.type
const name = getName(comp)
const { include, exclude, max } = props
if ((include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name))) {
return (current = vnode)
}
// 2、缓存 vnode
const key = vnode.key == null ? comp : vnode.key
const cachedVNode = cache.get(key) // 缓存的 vnode
if (vnode.el) {
vnode = cloneVNode(vnode)
}
pendingCacheKey = key
// 存在缓存的 vnode
if (cachedVNode) {
vnode.el = cachedVNode.el // vnode 对应的 DOM
vnode.component = cachedVNode.component // vnode 对应的组件实例
// 更新 vnode 的 shapeFlag,避免 vnode 节点作为新节点被挂载
vnode.shapeFlag |= 512/* COMPONENT_KEPT_ALIVE */
// 让这个 key 始终新鲜
keys.delete(key)
keys.add(key)
}
// 不存在缓存的 vnode
else {
keys.add(key)
// 删除最久不用的 key,符合 LRU 思想
if (max && keys.size > parselnt(max, 10)) {
pruneCacheEntry(keys.values().next().value)
}
}
// 避免 vnode 被卸载
vnode.shapeFlag |= 256/* COMPONENT_SHOULD_KEEP_ALIVE */
current = vnode
return vnode
}
}
}
/**
* 缓存子树
*/
const cacheSubtree = () => {
// pendingCacheKey 是在 的 render 函数中才会被赋值,所以 在首次进入 onBeforeMount 钩子函数时不会缓存
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, instance.subTree)
}
}
问题:
组件是如何注册
activated
、deactivated
钩子函数?答:
activated
钩子函数在组件beforeMount
钩子函数时执行;deactivated
钩子函数在组件beforeUnmount
钩子函数时执行。
/**
* KeepAlive 组件的创建
*/
const processComponent = (nl, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
if (n1 == null) {
// 处理 KeepAlive 组件
if (n2.shapeFlag & 512/* COMPONENT_KEPT ALIVE */) {
parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized)
}
// 挂载组件
else {
mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
}
}
// 更新组件
else { }
}
/**
* KeepAlive 缓存组件的创建
* 实现: 包裹的子组件在其渲染后、下一次更新前会被缓存,缓存后的子组件在下一次渲染的时候直接从缓存中拿到子树 vnode 以及其对应的 DOM 元素直接渲染
*/
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
const instance = vnode.component
move(vnode, container, anchor, 0/* ENTER */, parentSuspense)
patch(instance.vnode, vnode, container, anchor, instance, parentSuspense, isSVG, optimized)
queuePostRenderEffect(() => {
instance.isDeactivated = false
if (instance.a) {
invokeArrayFns(instance.a)
}
const vnodeHook = vnode.props && vnode.props.onVnodeMounted
if (vnodeHook) {
invokeVNodeHook(vnodeHook, instance.parent, vnode)
}
}, parentSuspense)
}
/**
* KeepAlive 组件的卸载
*/
const unmount = (vnode, parentComponent, parentSuspense, doRemove = false) => {
const { shapeFlag } = vnode
if (shapeFlag & 256/* COMPONENT_SHOULD_KEEP ALIVE */) {
parentComponent.ctx.deactivate(vnode)
return
}
// 卸载组件
}
/**
* KeepAlive 在卸载时执行 onBeforeUnmount 钩子函数
*/
onBeforeUnmount(() => {
cache.forEach(cached => {
const [subTree, suspense] = instance
if (cached.type === subTree.type) {
resetShapeFlag(subTree)
const da = subTree.component.da
da && queuePostRenderEffect(da, suspense)
return
}
unmount(cached)
})
})
渲染的是第一个子元素节点。
核心思想:当
包裹的元素插入、删除时,在适当的时机插入这些 CSS 样式。
渲染流程:
- 组件的渲染
- 钩子函数的执行
- 模式的应用
/**
* Transition 组件的定义
*/
const Transition = (props, { slots }) => h(BaseTransition, (props), slots)
/**
* Transition 组件的基础配置
*/
const BaseTransition = {
name: `BaseTransition`,
props: {
mode: String,
appear: Boolean,
persisted: Boolean,
// enter
onBeforeEnter: TransitionHookValidator,
onEnter: TransitionHookValidator,
onAfterEnter: TransitionHookValidator,
onEnterCancelled: TransitionHookValidator,
// leave
onBeforeLeave: TransitionHookValidator,
onLeave: TransitionHookValidator,
onAfterLeave: TransitionHookValidator,
onLeaveCancelled: TransitionHookValidator,
// appear
onBeforeAppear: TransitionHookValidator,
onAppear: TransitionHookValidator,
onAfterAppear: TransitionHookValidator,
onAppearCancelled: TransitionHookValidator,
},
setup(props, { slots }) {
const instance = getCurrentInstance()
const state = useTransitionState()
let prevTransitionKey
return () => {
const children = slots.default && getTransitionRawChildren(slots.default(), true)
if (!children || !children.length) return
// Transition 组件只允许一个子元素节点,多个报警告,提示使用 TransitionGroup 组件
if ((process.env.NODE_ENV !== 'production') && children.length > 1) {
warn(/* ... */)
}
// 不需要追踪响应式,所以改成原始值,提升性能
const rawProps = toRaw(props)
const { mode } = rawProps
// 检查 mode 是否合法
if ((process.env.NODE_ENV !== 'production') && mode && !['in-out', 'out-in', 'default'].includes(mode)) {
warn(/* ... */)
}
// 获取第一个子元素节点
const child = children[0]
if (state.isLeaving) return emptyPlaceholder(child)
// 处理 的情况
const innerChild = getKeepAliveChild(child)
if (!innerChild) {
return emptyPlaceholder(child)
}
// 通过 resolveTransitionHooks 定义组件创建和删除阶段的钩子函数对象
const enterHooks = resolveTransitionHooks(innerChild, rawProps, state, instance)
// 通过 setTransitionHooks 把钩子函数对象设置到 vnode.transition 上
setTransitionHooks(innerChild, enterHooks)
const oldChild = instance.subTree
const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
let transitionKeyChanged = false
const { getTransitionKey } = innerChild.type
if (getTransitionKey) {
const key = getTransitionKey()
if (prevTransitionKey === undefined) {
prevTransitionKey = key
} else if (key !== prevTransitionKey) {
prevTransitionKey = key
transitionKeyChanged = true
}
}
if (oldInnerChild && oldInnerChild.type !== Comment && (!isSameVNodeType(innerChild, oldlnnerChild) || transitionKeyChanged)) {
const leavingHooks = resolveTransitionHooks(oldInnerChild, rawProps, state, instance)
// 更新旧树的钩子函数
setTransitionHooks(oldInnerChild, leavingHooks)
// 在两个视图之间切换
if (mode === 'out-in') {
state.isLeaving = true
// 当离开过渡结束后,再重新渲染组件
leavingHooks.afterLeave = () => {
state.isLeaving = false
instance.update()
}
// 返回空的占位符节点
return emptyPlaceholder(children)
} else if (mode === 'in-out') {
leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => {
const leavingVNodesCache = getLeavingNodesForType(state, oldInnerChild)
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
// early removal callback
el._leaveCb = () => {
earlyRemove()
el._leaveCb = undefined
delete enterHooks.delayedLeave
}
enterHooks.delayedLeave = delayedLeave
}
}
}
return child
}
}
}
/**
* 组件的渲染
*/
function resolveTransitionHooks(vnode, props, state, instance) {
const { appear, mode, persisted = false, onBeforeEnter, onEnter, onAfterEnter, onEnterCancelled, onBeforeLeave, onLeave, onAfterLeave, onLeaveCancelled, onBeforeAppear, onAppear, onAfterAppear, onAppearCancelled } = props
const key = String(vnode.key)
const leavingVNodesCache = getLeavingNodesForType(state, vnode)
const callHook = (hook, args) => {
hook && callWithAsyncErrorHandling(hook, instance, 9/* TRANSITION_HOOK */, args)
}
const hooks = {
mode,
persisted,
// 在 patch 阶段的 mountElement 函数中,在插入元素节点前且存在过渡条件下执行
beforeEnter(el) {
// 根据 appear 的值和 DOM 是否挂载判断执行 onBefore 还是 onBeforeEnter
let hook = onBeforeEnter
if (!state.isMounted) {
if (appear) {
hook = onBeforeAppear || onBeforeEnter
} else return
}
// el._leaveCb 存在则执行 leave 钩子函数
if (el._leaveCb) {
el.leaveCb(true/* cancelled */)
}
const leavingVNode = leavingVNodesCache[key]
if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el.leaveCb) {
leavingVNode.el.leaveCb()
}
callHook(hook, [el])
},
enter(el) {
let hook = onEnter
let afterHook = onAfterEnter
let cancelHook = onEnterCancelled
if (!state.isMounted) {
if (appear) {
hook = onAppear || onEnter
afterHook = onAfterAppear || onAfterEnter
cancelHook = onAppearCancelled || onEnterCancelled
} else return
}
let called = false
const done = (el._enterCb = (cancelled) => {
if (called) return
called = true
if (cancelled) {
callHook(cancelHook, [el])
} else {
callHook(afterHook, [el])
}
if (hooks.delayedLeave) {
hooks.delayedLeave()
}
el.enterCb = undefined
})
if (hook) {
hook(el, done)
if (hook.length <= 1) {
done()
}
} else {
done()
}
},
leave(el, remove) {
const key = String(vnode.key)
if (el._enterCb) {
el._enterCb(true/* cancelled */)
}
if (stateisUnmounting) {
return remove()
}
callHook(onBeforeLeave, [el])
let called = false
const done = (el._leaveCb = (cancelled) => {
if (called) return
called = true
remove()
if (cancelled) {
callHook(onLeaveCancelled, [el])
} else {
callHook(onAfterLeave, [el])
}
el._leaveCb = undefined
if (leavingVNodesCache[key] === vnode) {
delete leavingVNodesCache[key]
}
})
leavingVNodesCache[key] = vnode
if (onLeave) {
onLeave(el.done)
if (onLeave.length <= 1) done()
} else {
done()
}
},
clone(vnode) {
return resolveTransitionHooks(vnode, props, state, instance)
}
}
return hooks
}
/**
* 钩子函数的执行 - 对传递的 props 进行封装
*/
function resolveTransitionProps(rawProps) {
let {
name = 'v',
type, css = true,
duration,
enterFromClass = `${name}-enter-from`,
enterActiveClass = `${name}-enter-active`,
enterToClass = `${name}-enter-to`,
appearFromClass = enterFromClass,
appearActiveClass = enterActiveClass,
appearToClass = enterToClass,
leaveFromClass = `${name}-leave-from`,
leaveActiveClass = `${name}-leave-active`,
leaveToClass = `${name}-leave-to`
} = rawProps
const baseProps = {}
for (const key in rawProps) {
if (!(key in DOMTransitionPropsValidators)) {
baseProps[key] = rawProps[key]
}
}
if (!css) return baseProps
const durations = normalizeDuration(duration)
const enterDuration = durations && durations[0]
const leaveDuration = durations && durations[1]
const {
onBeforeEnter,
onEnter,
onEnterCancelled,
onLeave,
onLeaveCancelled,
onBeforeAppear = onBeforeEnter,
onAppear = onEnter,
onAppearCancelled = onEnterCancelled
} = baseProps
// 进入动画结束后执行,移除 DOM 元素的 enterToClass 和 enterActiveClass,然后执行 done 函数进入 onAfterEnter 钩子函数
const finishEnter = (el, isAppear, done) => {
removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
done && done()
}
// 离开动画结束后执行,移除 DOM 元素的 leaveToClass 和 leaveActiveClass
const finishLeave = (el, done) => {
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
done && done()
}
// 制作 enter 钩子
const makeEnterHook = (isAppear) => {
return (el, done) => {
const hook = isAppear ? onAppear : onEnter
const resolve = () => finishEnter(el, isAppear, done)
hook && hook(el, resolve)
nextFrame(() => {
removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
// 添加了 enterToClass 后,浏览器就进入过渡动画了
addTransitionClass(el, isAppear ? appearToClass : enterToClass)
if (!(hook && hook.length > 1)) {
if (enterDuration) {
setTimeout(resolve, enterDuration)
} else {
whenTransitionEnds(el, type, resolve)
}
}
})
}
}
return extend(baseProps, {
onBeforeEnter(el) {
// 执行 onBeforeEnter 钩子函数
onBeforeEnter && onBeforeEnter(el)
// 给 DOM 元素添加 enterActiveClass 和 enterFromClass
addTransitionClass(el, enterActiveClass)
addTransitionClass(el, enterFromClass)
},
onBeforeAppear(el) {
onBeforeAppear && onBeforeAppear(el)
addTransitionClass(el, appearActiveClass)
addTransitionClass(el, appearFromClass)
},
onEnter: makeEnterHook(false),
onAppear: makeEnterHook(true),
onLeave(el, done) {
const resolve = () => finishLeave(el, done)
addTransitionClass(el, leaveActiveClass)
addTransitionClass(el, leaveFromClass)
nextFrame(() => {
removeTransitionClass(el, leaveFromClass)
// 当添加 leaveToClass 时,浏览器就开始执行离开过渡动画了
addTransitionClass(el, leaveToClass)
if (!(onLeave && onLeave.length > 1)) {
if (leaveDuration) {
setTimeout(resolve, leaveDuration)
} else {
whenTransitionEnds(el, type, resolve)
}
}
})
onLeave && onLeave(el, resolve)
},
onEnterCancelled(el) {
finishEnter(el, false)
onEnterCancelled && onEnterCancelled(el)
},
onAppearCancelled(el) {
finishEnter(el, true)
onAppearCancelled && onAppearCancelled(el)
},
onLeaveCancelled(el) {
finishLeave(el)
onLeaveCancelled && onLeaveCancelled(el)
}
})
}