Vue 3源码深度解析之:`transition`组件:它如何管理`CSS`过渡和动画。

各位观众老爷们,大家好!今天咱们来聊聊 Vue 3 源码里一个神奇的家伙:transition 组件。这玩意儿能让你的 Vue 应用动起来,告别生硬的页面切换,让用户体验蹭蹭往上涨。

准备好了吗?系好安全带,我们要发车了!

一、transition 组件:表面光鲜,内里乾坤

首先,transition 组件是 Vue 内置的一个抽象组件,它本身不会渲染任何实际的 DOM 元素。它的作用是包裹需要过渡或动画的元素,并根据元素的状态变化添加/移除特定的 CSS 类名,从而触发 CSS 过渡或动画效果。

简单来说,transition 组件就像一个导演,它指挥着你的元素在特定的时间点“表演”特定的动画。

二、transition 组件的使用方法:手把手教学

使用 transition 组件非常简单,只需要将需要过渡或动画的元素包裹在 transition 标签里即可。

<template>
  <div>
    <button @click="show = !show">Toggle</button>
    <transition name="fade">
      <p v-if="show">Hello, world!</p>
    </transition>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const show = ref(false);
    return {
      show,
    };
  },
};
</script>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

在上面的例子中,我们使用 transition 组件包裹了一个 p 元素,并指定了 name 属性为 "fade"。这意味着 Vue 会自动为元素添加/移除以下 CSS 类名:

  • fade-enter-from: 进入过渡的起始状态
  • fade-enter-active: 进入过渡的激活状态
  • fade-enter-to: 进入过渡的结束状态
  • fade-leave-from: 离开过渡的起始状态
  • fade-leave-active: 离开过渡的激活状态
  • fade-leave-to: 离开过渡的结束状态

通过 CSS,我们定义了这些类名对应的样式,从而实现了淡入淡出的效果。

三、transition 组件的属性:十八般武艺

transition 组件有很多属性,可以用来控制过渡和动画的行为。下面是一些常用的属性:

属性 类型 描述
name string 用于生成 CSS 类名的前缀。
appear boolean 是否在初始渲染时应用过渡。
mode string 过渡模式,可选值有 in-outout-inin-out 表示新元素先进入,然后当前元素离开。out-in 表示当前元素先离开,然后新元素进入。
type string 指定过渡类型,可选值有 transitionanimation。如果未指定,Vue 会自动检测。
enter-class string 指定进入过渡的起始类名。
enter-to-class string 指定进入过渡的结束类名。
enter-active-class string 指定进入过渡的激活类名。
leave-class string 指定离开过渡的起始类名。
leave-to-class string 指定离开过渡的结束类名。
leave-active-class string 指定离开过渡的激活类名。
css boolean 是否使用 CSS 过渡和动画。如果设置为 false,则只触发 JavaScript 钩子。
@before-enter Function 进入过渡之前调用的 JavaScript 钩子。
@enter Function 进入过渡期间调用的 JavaScript 钩子。可以与 done 回调结合使用,手动控制过渡的完成。
@after-enter Function 进入过渡之后调用的 JavaScript 钩子。
@enter-cancelled Function 进入过渡被取消时调用的 JavaScript 钩子。
@before-leave Function 离开过渡之前调用的 JavaScript 钩子。
@leave Function 离开过渡期间调用的 JavaScript 钩子。可以与 done 回调结合使用,手动控制过渡的完成。
@after-leave Function 离开过渡之后调用的 JavaScript 钩子。
@leave-cancelled Function 离开过渡被取消时调用的 JavaScript 钩子。

四、transition 组件的源码解析:深入虎穴

现在,我们要深入 transition 组件的源码,看看它到底是怎么实现的。

  1. resolveTransitionHooks 函数:寻找 JavaScript 钩子

    首先,Vue 会调用 resolveTransitionHooks 函数来查找组件中定义的 JavaScript 钩子函数。这些钩子函数包括 @before-enter@enter@after-enter@enter-cancelled@before-leave@leave@after-leave@leave-cancelled

    function resolveTransitionHooks(
      instance: ComponentInternalInstance
    ): TransitionHooks {
      const { vnode } = instance
      const props = vnode.props || EMPTY_OBJ
      const hooks: TransitionHooks = {}
    
      for (const key in props) {
        if (isOn(key)) {
          const hook = props[key]
          if (isFunction(hook)) {
            hooks[key.slice(3).toLowerCase()] = hook
          }
        }
      }
    
      return hooks
    }

    这个函数遍历组件的 props,找到以 on 开头的属性,并且属性值是一个函数。然后,它将属性名去掉 on 前缀,并转换为小写,作为钩子函数的名称。

  2. useTransition 函数:核心逻辑

    useTransition 函数是 transition 组件的核心逻辑。它负责处理过渡的开始、结束和取消。

    function useTransition(
      instance: ComponentInternalInstance
    ): TransitionProps {
      const props = instance.props as TransitionProps
      const isCss = props.css !== false
      const type = props.type
      const { name, appear, mode } = props
      const isMultiMode = mode === 'in-out' || mode === 'out-in'
      const leavingVNodes = new Map<VNode, Element>()
      const transitionHooks = resolveTransitionHooks(instance)
    
      // ... 省略大量代码 ...
    
      return props
    }

    这个函数首先获取 transition 组件的属性,例如 csstypenameappearmode。然后,它创建一个 leavingVNodes Map,用于存储正在离开的 VNode 节点。最后,它调用 resolveTransitionHooks 函数来获取 JavaScript 钩子函数。

  3. transition 组件的渲染函数:渲染 VNode

    transition 组件的渲染函数负责渲染 VNode 节点。它首先获取 transition 组件的子节点,然后根据子节点的状态变化添加/移除 CSS 类名,并触发 JavaScript 钩子函数。

    const Transition: ComponentOptions = {
      name: 'Transition',
      props: TransitionPropsValidators,
      setup(props, { slots }) {
        const instance = currentRenderingInstance!
        const isHMRUpdating = useDevTools() ? () => instance.isHMRUpdating : () => false
    
        if (__DEV__ && !(__FEATURE_SUSPENSE__ || instance.parent)) {
          warn(
            `<transition> can only be used as a direct child of a component ` +
              `or another <transition>.`
          )
        }
    
        const children = () => {
          if (!slots.default) {
            return null
          }
    
          const children = slots.default()
          if (!children.length) {
            return null
          }
    
          // ... 省略大量代码 ...
    
          return children
        }
    
        return () => {
          const child = children()
          if (!child) {
            return createVNode(Text, null, '')
          }
    
          // ... 省略大量代码 ...
    
          return child[0]
        }
      }
    }

    这个渲染函数首先获取 transition 组件的子节点,然后判断子节点是否需要过渡。如果需要过渡,则根据子节点的状态变化添加/移除 CSS 类名,并触发 JavaScript 钩子函数。

五、transition-group 组件:群体表演

transition-group 组件是 transition 组件的兄弟组件,它可以用来处理多个元素的过渡和动画。与 transition 组件不同的是,transition-group 组件会渲染一个真实的 DOM 元素,默认情况下是一个 span 元素。你可以通过 tag 属性来指定渲染的 DOM 元素。

<template>
  <div>
    <button @click="addItem">Add Item</button>
    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item.id">{{ item.text }}</li>
    </transition-group>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const items = ref([
      { id: 1, text: 'Item 1' },
      { id: 2, text: 'Item 2' },
      { id: 3, text: 'Item 3' },
    ]);

    let nextId = 4;

    const addItem = () => {
      items.value.push({ id: nextId++, text: `Item ${nextId - 1}` });
    };

    return {
      items,
      addItem,
    };
  },
};
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 1s;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
</style>

在上面的例子中,我们使用 transition-group 组件包裹了一个 ul 元素,并指定了 name 属性为 "list" 和 tag 属性为 "ul"。这意味着 Vue 会自动为 ul 元素的子元素(li 元素)添加/移除以下 CSS 类名:

  • list-enter-from: 进入过渡的起始状态
  • list-enter-active: 进入过渡的激活状态
  • list-enter-to: 进入过渡的结束状态
  • list-leave-from: 离开过渡的起始状态
  • list-leave-active: 离开过渡的激活状态
  • list-leave-to: 离开过渡的结束状态

通过 CSS,我们定义了这些类名对应的样式,从而实现了列表项的淡入淡出和位移动画。

六、transitiontransition-group 的区别:兄弟齐心,其利断金

特性 transition transition-group
适用场景 单个元素的过渡和动画。 多个元素的过渡和动画,例如列表项的添加、删除和排序。
渲染 DOM 元素 不渲染任何实际的 DOM 元素,只是一个抽象组件。 渲染一个真实的 DOM 元素,默认情况下是一个 span 元素,可以通过 tag 属性来指定渲染的 DOM 元素。
key 属性 不需要 key 属性。 必须为每个子元素指定 key 属性,以便 Vue 能够正确地跟踪元素的状态变化。
特殊类名 支持 v-move 类名,用于处理元素的移动动画。
适用元素 只能包裹单个元素。如果需要包裹多个元素,需要使用 template 标签。 可以包裹多个元素。

七、总结:掌握 transition,玩转动画

transition 组件是 Vue 中非常重要的一个组件,它可以让你轻松地为你的应用添加过渡和动画效果。通过掌握 transition 组件的属性和使用方法,你可以创建出各种各样的动画效果,让你的应用更加生动有趣。

今天就讲到这里,希望大家有所收获!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注