Vue3 源码解读系列(十四)——内置组件

内置组件

问题:内置组件为什么不需要引入?

答:内置组件默认是全局引入的。

定义

/**
 * 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)
  }
}

问题: 组件是如何注册 activateddeactivated 钩子函数?

答: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 样式。

渲染流程:

  1. 组件的渲染
  2. 钩子函数的执行
  3. 模式的应用

定义

/**
 * 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)
    }
  })
}

你可能感兴趣的:(Vue,javascript,前端,vue.js)