Эта статья является третьей статьей в серии vue.vueвстроенные компонентыtransition. Предыдущие ссылки выглядят следующим образом
Прежде чем мы начнем, давайте взглянем на официальныйtransitionОпределение
- Автоматически определяет, использует ли целевой элемент переходы или анимацию CSS, и если да, то добавляет/удаляет классы перехода CSS в соответствующее время.
- Если компонент перехода установленХук-функция JavaScript, эти функции ловушек будут вызываться в нужное время.
- Если переходы/анимации CSS не обнаружены и не установлены хуки JavaScript, вставка и/или удаление DOM выполняется немедленно в следующем кадре.
1. Абстрактные компоненты
существуетvueв, дляoptionsЕсть свойство под названиемabstract, типboolean, который показывает, является ли компонент абстрактным компонентом. но искалoptions, но нашел только такой
static options: Object;
Но по факту,vueвсе еще правabstractсвойства обрабатываются, вlifecycle.jsизinitLifecycle()Есть такая логика в методе
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
Из этой логики мы видим, чтоvueОн будет подниматься слой за слоем, чтобы узнать, есть ли он у родительского узла.abstractатрибут, и после того, как он будет найден, он будет непосредственноvmупал на родительский узел$children, его собственные отношения родитель-потомок напрямую игнорируются
затем вcreate-component.jsизcreateComponent()Это обрабатывается в методе следующим образом
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
2. Приведите пример
Прежде чем анализировать, посмотрите официальнуюtransitionпример
<template>
<div id="example">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
</template>
<script>
export default {
data () {
return {
show: true
}
}
}
</script>
<style lang="scss">
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
</style>
При нажатии кнопки для переключения состояния отображенияtransitionОбернутый элемент будет иметь переход css. Далее мы проанализируем, как он производит этот эффект.
Три, реализация перехода
Из приведенного выше использования мы должны быть в состоянии понять, что для<transition>,vueОн предоставляет нам полный набор хуков css и js.Сами хуки были определены для меня и привязаны к экземпляру.Остальное нужно переписать функцию хуков css или js самим. На самом деле это иvueсебяhooksРеализация очень похожа и поможет нам делать разные вещи в разных хуках.
Это мышление программирования очень похоже на мышление в функциональном программировании, или так оно и есть.
Функциональное программирование связано с отображением данных, и лямбда в функциональном программировании может рассматриваться как отношение между двумя типами, типом ввода и типом вывода. Вы даете ему значение типа ввода, и вы получаете значение типа вывода.
Собственно, это и отражено в этом куске, и разные результаты вывода можно получить, переписывая в разной степени в разных хуках.Конечно, вывод здесь - это собственно эффект перехода, и этот метод не совсем тот输入 => 输出способ? Далее речь пойдет о конкретной реализации этого блока.
<transtion>Реализация компонентаexportИз объекта, отсюда мы можем видеть, что он будет предварительно установленpropsсвязываются сtransition, а затем нам нужно только ввести в него точки, чтобы получить соответствующий вывод стиля Нам не нужно думать о конкретном процессе.
export default {
name: 'transition',
props: transitionProps,
abstract: true,
render (h: Function) {
// 具体 render 等会再看
}
}
который поддерживаетpropsКак следует, вы можетеenterа такжеleaveстиль для произвольных перезаписей
export const transitionProps = {
name: String,
appear: Boolean,
css: Boolean,
mode: String,
type: String,
enterClass: String,
leaveClass: String,
enterToClass: String,
leaveToClass: String,
enterActiveClass: String,
leaveActiveClass: String,
appearClass: String,
appearActiveClass: String,
appearToClass: String,
duration: [Number, String, Object]
}
Далее посмотримrenderСодержимое внутри — это то, как делать то, что у нас есть заранее输入 => 输出преобразованный
-
childrenЛогика обработки: сначала получить из слота по умолчанию<transition>обернутые дочерние узлы, которые затем обрабатываютсяfilterФильтровать, отфильтровывать текстовые узлы и пробелы. Если нет дочернего узла, то сразуreturn, если есть несколько дочерних узлов, сообщается об ошибке, потому что<transition>Компонент может иметь только один дочерний узел
let children: any = this.$slots.default
if (!children) {
return
}
children = children.filter(isNotTextNode)
if (!children.length) {
return
}
if (process.env.NODE_ENV !== 'production' && children.length > 1) {
warn(
'<transition> can only be used on a single element. Use ' +
'<transition-group> for lists.',
this.$parent
)
}
-
modelЛогика обработки: суждениеmodeЭтоin-outа такжеout-inДва режима, если нет, сообщать об ошибке напрямую
const mode: string = this.mode
if (
process.env.NODE_ENV !== 'production' &&
mode && mode !== 'in-out' && mode !== 'out-in'
) {
warn(
'invalid <transition> mode: ' + mode,
this.$parent
)
}
-
rawChild & childЛогика обработки:rawChildдля<transition>первый компонент пакетаvnodeдочерний узел, затем определите, является ли его родительский контейнер такжеtransition, если да, вернитесь напрямуюrawChild; затем выполнитьgetRealChild()Метод получает реальный дочерний узел компонента и возвращает значение, если он не существует.rawChild
const rawChild: VNode = children[0]
if (hasParentTransition(this.$vnode)) {
return rawChild
}
const child: ?VNode = getRealChild(rawChild)
if (!child) {
return rawChild
}
-
hasParentTransition(): Поднимитесь на один слой вверх, чтобы узнать, есть ли у родительского узлаtransitionАтрибуты
function hasParentTransition (vnode: VNode): ?boolean {
while ((vnode = vnode.parent)) {
if (vnode.data.transition) {
return true
}
}
}
-
getRealChild(): рекурсивно получить первый неабстрактный дочерний узел и вернуть
function getRealChild (vnode: ?VNode): ?VNode {
const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (compOptions && compOptions.Ctor.options.abstract) {
return getRealChild(getFirstComponentChild(compOptions.children))
} else {
return vnode
}
}
- Далее правильно
child.keyа такжеdataобработанный. сначала черезidи ряд условных парchild.keyвыполнить операцию присваивания, затем использоватьextractTransitionDataПолучите необходимые переходы из экземпляра компонентаdataданные
const id: string = `__transition-${this._uid}-` // 使用 this._uid 进行拼接
child.key = child.key == null // 若 child.key 为 null
? child.isComment // child 为注释节点
? id + 'comment'
: id + child.tag
: isPrimitive(child.key) // 若 child.key 为 原始值
? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
: child.key
// 获取过渡需要的数据
const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)
if (child.data.directives && child.data.directives.some(isVShowDirective)) {
child.data.show = true
}
-
extractTransitionData(): Это целое является входным значениемpropsи внутриeventsПроцесс преобразования , параметр методаthis, текущий компонент. Сначала пройдите текущий компонентoptions.propsDataи назначитьdata, затем перебирает события родительского компонента и присваивает ихdata.
export function extractTransitionData (comp: Component): Object {
const data = {}
const options: ComponentOptions = comp.$options
// props
for (const key in options.propsData) {
data[key] = comp[key]
}
// events.
// extract listeners and pass them directly to the transition methods
const listeners: ?Object = options._parentListeners
for (const key in listeners) {
data[camelize(key)] = listeners[key]
}
return data
}
Согласно текущей логике, пример, который я привел выше, полученныйchild.dataследующим образом
{
transition: {
name: 'slide-fade'
}
}
- Наконец, старый и новый
childСравните и выполните некоторые функции ловушекhook mergeждать
const oldRawChild: VNode = this._vnode
const oldChild: VNode = getRealChild(oldRawChild)
if (
oldChild &&
oldChild.data &&
!isSameChild(child, oldChild) &&
!isAsyncPlaceholder(oldChild) &&
!(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
) {
// 将 oldData 更为为当前的 data
const oldData: Object = oldChild.data.transition = extend({}, data)
// 当 transition mode 为 out-in 的情况,返回一个 vnode 占位,执行完 watch 进行更新
if (mode === 'out-in') {
this._leaving = true
mergeVNodeHook(oldData, 'afterLeave', () => {
this._leaving = false
this.$forceUpdate()
})
return placeholder(h, rawChild)
// 当 transition mode 为 in-out 的情况
} else if (mode === 'in-out') {
if (isAsyncPlaceholder(child)) {
return oldRawChild
}
let delayedLeave
const performLeave = () => { delayedLeave() }
mergeVNodeHook(data, 'afterEnter', performLeave)
mergeVNodeHook(data, 'enterCancelled', performLeave)
mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })
}
}
Проверьте это здесьmergeVNodeHook(),placeholder()а также$forceUpdate()Логика, упомянутая здесьmergeVNodeHookлогика: будетhookфункция объединена вdef.data.hook[hookKey], создать новыйinvoker
// mergeVNodeHook
export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {
if (def instanceof VNode) {
def = def.data.hook || (def.data.hook = {})
}
let invoker
// 获取已有 hook 赋值给 oldHook
const oldHook = def[hookKey]
function wrappedHook () {
hook.apply(this, arguments)
// 删除合并的钩子确保其只被调用一次,这样能防止内存泄漏
remove(invoker.fns, wrappedHook)
}
// 如果 oldHook 不存在,则直接创建一个 invoker
if (isUndef(oldHook)) {
invoker = createFnInvoker([wrappedHook])
} else {
// oldHook 已经存在,则将 invoker 赋值为 oldHook
if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
invoker = oldHook
invoker.fns.push(wrappedHook)
} else {
// 现有的普通钩子
invoker = createFnInvoker([oldHook, wrappedHook])
}
}
invoker.merged = true
def[hookKey] = invoker
}
// placeholder
function placeholder (h: Function, rawChild: VNode): ?VNode {
if (/\d-keep-alive$/.test(rawChild.tag)) {
return h('keep-alive', {
props: rawChild.componentOptions.propsData
})
}
}
// $forceUpdate
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Четыре, войти и выйти
Введение<transition>Внедрение компонента, мы узнали, что его вrenderсцена для输入值 propsи некоторые js-хуки для输出процесс обработки.
Или мы можем понять это так,<transition>существуетrenderЭтап получит данные об узле в разныхmodeСоответствующая функция ловушки привязана к соответствующей функции ловушки, и необходимо использовать каждую ловушку.dataданные, а также возвращаетrawChild vnodeузел.
Однако до сих пор он не делал никакого дизайна в анимации. Итак, далее мы поговорим об этом подробноtransitionсуществуетenterтак же какleaveКак работает эта штука输入 => 输出из.
первый вsrc/platforms/web/modules/transition.js, есть такой кусок кода
function _enter (_: any, vnode: VNodeWithData) {
if (vnode.data.show !== true) {
enter(vnode)
}
}
export default inBrowser ? {
create: _enter,
activate: _enter,
remove (vnode: VNode, rm: Function) {
if (vnode.data.show !== true) {
leave(vnode, rm)
} else {
rm()
}
}
} : {}
Из приведенного выше кода видно, что при обработке анимации он устанавливает два тайминга, а именно:
- существует
createа такжеactivateвыполнить, когдаenter() -
removeвыполнить, когдаleave()
1. введите
Прежде чем анализировать, давайте посмотрим наenter()контекст дизайна
export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
const el: any = vnode.elm
// 如果 el 中存在 _leaveCb,则立即执行 _leaveCb()
if (isDef(el._leaveCb)) {
el._leaveCb.cancelled = true
el._leaveCb()
}
// 一系列处理,这里忽略,后面会具体分析
}
- Сначала давайте посмотрим на
enterЧто делать с переходными данными: они будутvnode.data.transitionвходящий вresolveTransition()Когда параметр, и проанализируйте его, чтобы получитьdataданные
const data = resolveTransition(vnode.data.transition)
// 如果 data 不存在,则直接返回
if (isUndef(data)) {
return
}
// 如果 el 中存在 _enterCb 或者 el 不是元素节点,则直接返回
if (isDef(el._enterCb) || el.nodeType !== 1) {
return
}
const {
css,
type,
enterClass,
enterToClass,
enterActiveClass,
appearClass,
appearToClass,
appearActiveClass,
beforeEnter,
enter,
afterEnter,
enterCancelled,
beforeAppear,
appear,
afterAppear,
appearCancelled,
duration
} = data
Тогда мы смотрим наresolveTransition(): метод имеет один параметрvnode.data.transition, он проходит черезautoCssTransition()метод обработкиnameсвойств и распространяется наvnode.data.transitionвверх и назад
export function resolveTransition (def?: string | Object): ?Object {
if (!def) {
return
}
if (typeof def === 'object') {
const res = {}
if (def.css !== false) {
extend(res, autoCssTransition(def.name || 'v'))
}
extend(res, def)
return res
} else if (typeof def === 'string') {
return autoCssTransition(def)
}
}
вautoCssTransition()Конкретная логика такова: получить параметрыnameпосле возвращения сnameСвязанныйcss class
const autoCssTransition: (name: string) => Object = cached(name => {
return {
enterClass: `${name}-enter`,
enterToClass: `${name}-enter-to`,
enterActiveClass: `${name}-enter-active`,
leaveClass: `${name}-leave`,
leaveToClass: `${name}-leave-to`,
leaveActiveClass: `${name}-leave-active`
}
})
- Сразу, да
<transition>Это граничный случай обработки корневого узла дочернего компонента, нам нужно обработать его родительский компонентappear check
let context = activeInstance
let transitionNode = activeInstance.$vnode
// 往上查找出 <transition> 是子组件的根节点的边界情况,进行赋值
while (transitionNode && transitionNode.parent) {
context = transitionNode.context
transitionNode = transitionNode.parent
}
// 上下文实例没有 mounted 或者 vnode 不是根节点插入的
const isAppear = !context._isMounted || !vnode.isRootInsert
if (isAppear && !appear && appear !== '') {
return
}
- для перехода
class, функция ловушки для обработки
// 过渡 class 处理
const startClass = isAppear && appearClass
? appearClass
: enterClass
const activeClass = isAppear && appearActiveClass
? appearActiveClass
: enterActiveClass
const toClass = isAppear && appearToClass
? appearToClass
: enterToClass
// 钩子函数处理
const beforeEnterHook = isAppear
? (beforeAppear || beforeEnter)
: beforeEnter
const enterHook = isAppear
? (typeof appear === 'function' ? appear : enter)
: enter
const afterEnterHook = isAppear
? (afterAppear || afterEnter)
: afterEnter
const enterCancelledHook = isAppear
? (appearCancelled || enterCancelled)
: enterCancelled
- получить другую конфигурацию
const explicitEnterDuration: any = toNumber(
isObject(duration)
? duration.enter
: duration
) // 获取 enter 动画执行时间
if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {
checkDuration(explicitEnterDuration, 'enter', vnode)
}
// 过渡动画是否受 css 影响
const expectsCSS = css !== false && !isIE9
// 用户是否想介入控制 css 动画
const userWantsControl = getHookArgumentsLength(enterHook)
- правильно
insertХук-функция для слияния
if (!vnode.data.show) {
// remove pending leave element on enter by injecting an insert hook
mergeVNodeHook(vnode, 'insert', () => {
const parent = el.parentNode
const pendingNode = parent && parent._pending && parent._pending[vnode.key]
if (pendingNode &&
pendingNode.tag === vnode.tag &&
pendingNode.elm._leaveCb
) {
pendingNode.elm._leaveCb()
}
enterHook && enterHook(el, cb)
})
}
- Время выполнения функции хука анимации перехода: сначала выполнить
beforeEnterHookкрючок и поместите узел DOMelСдайте, а потом рассудите, хотите ли вы управлять анимацией через css, если даtrueзатем выполнитеaddTransitionClass()Метод добавляет к узлуstartClassа такжеactiveClass. - затем выполнить
nextFrame()Введите следующий кадр, следующий кадр в основном для удаления предыдущего кадра и добавления хороших.class;Сразу определяем, отменен ли переход, если нет, добавляемtoClassКласс перехода; позже, если пользователь не пройдетenterHookФункция хука для управления анимацией, если пользователь указываетdurationвремя, выполнитьsetTimeoutпровестиdurationзадержка, иначе выполнитьwhenTransitionEndsПринимать решениеcbвремя исполнения
beforeEnterHook && beforeEnterHook(el)
if (expectsCSS) {
addTransitionClass(el, startClass)
addTransitionClass(el, activeClass)
nextFrame(() => {
removeTransitionClass(el, startClass)
if (!cb.cancelled) {
addTransitionClass(el, toClass)
if (!userWantsControl) {
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration)
} else {
whenTransitionEnds(el, type, cb)
}
}
}
})
}
- Окончательное исполнение определяется заранее
cbФункция: если на анимацию влияетcssконтроль, удалениеtoClassа такжеactiveClass; Сразу определить, отменен ли переход, если да, то удалить его напрямуюstartClassи выполнитьenterCancelledHook, иначе продолжить выполнениеafterEnterHook
// enter 执行后不同动机对应不同的 cb 回调处理
const cb = el._enterCb = once(() => {
if (expectsCSS) {
removeTransitionClass(el, toClass)
removeTransitionClass(el, activeClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass)
}
enterCancelledHook && enterCancelledHook(el)
} else {
afterEnterHook && afterEnterHook(el)
}
el._enterCb = null
})
Код задействованной выше функции выглядит следующим образом:
-
nextFrame: простая версияrequestAnimationFrame, параметрfn, то есть метод, который нужно выполнить в следующем кадре -
addTransitionClass: для текущего элементаelувеличить указанныйclass -
removeTransitionClass: удалить текущий элементelУказанныйclass -
whenTransitionEnds:пройти черезgetTransitionInfoполучатьtransitionнекоторую информацию, напримерtype,timeout,propCount, и дляelпривязка элементаonEnd. Затем продолжайте выполнение следующего кадра, обновитеendedзначение, пока анимация не закончится. будуelэлементальonEndудалить и выполнитьcbфункция
// nextFrame
const raf = inBrowser
? window.requestAnimationFrame
? window.requestAnimationFrame.bind(window)
: setTimeout
: fn => fn()
export function nextFrame (fn: Function) {
raf(() => {
raf(fn)
})
}
// addTransitionClass
export function addTransitionClass (el: any, cls: string) {
const transitionClasses = el._transitionClasses || (el._transitionClasses = [])
if (transitionClasses.indexOf(cls) < 0) {
transitionClasses.push(cls)
addClass(el, cls)
}
}
// removeTransitionClass
export function removeTransitionClass (el: any, cls: string) {
if (el._transitionClasses) {
remove(el._transitionClasses, cls)
}
removeClass(el, cls)
}
// whenTransitionEnds
export function whenTransitionEnds (
el: Element,
expectedType: ?string,
cb: Function
) {
const { type, timeout, propCount } = getTransitionInfo(el, expectedType)
if (!type) return cb()
const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent
let ended = 0
const end = () => {
el.removeEventListener(event, onEnd)
cb()
}
const onEnd = e => {
if (e.target === el) {
if (++ended >= propCount) {
end()
}
}
}
setTimeout(() => {
if (ended < propCount) {
end()
}
}, timeout + 1)
el.addEventListener(event, onEnd)
}
2. уйти
Мы закончилиenterэтот кусок输入 => 输出обработка, которая в основном происходит после вставки компонента. Далее идет соответствующийleaveсцена输入 => 输出в основном это происходит до того, как компонент будет уничтожен.leaveа такжеenterОбработка этапов очень похожа, поэтому я не буду их повторять здесь, вы можете прочитать их сами. Я немного расскажу о них нижеdelayLeaveДля отложенного исполненияleaveКак создаются анимации перехода
export function leave (vnode: VNodeWithData, rm: Function) {
const el: any = vnode.elm
// call enter callback now
if (isDef(el._enterCb)) {
el._enterCb.cancelled = true
el._enterCb()
}
const data = resolveTransition(vnode.data.transition)
if (isUndef(data) || el.nodeType !== 1) {
return rm()
}
if (isDef(el._leaveCb)) {
return
}
const {
css,
type,
leaveClass,
leaveToClass,
leaveActiveClass,
beforeLeave,
leave,
afterLeave,
leaveCancelled,
delayLeave,
duration
} = data
const expectsCSS = css !== false && !isIE9
const userWantsControl = getHookArgumentsLength(leave)
const explicitLeaveDuration: any = toNumber(
isObject(duration)
? duration.leave
: duration
)
if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {
checkDuration(explicitLeaveDuration, 'leave', vnode)
}
const cb = el._leaveCb = once(() => {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null
}
if (expectsCSS) {
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, leaveClass)
}
leaveCancelled && leaveCancelled(el)
} else {
rm()
afterLeave && afterLeave(el)
}
el._leaveCb = null
})
if (delayLeave) {
delayLeave(performLeave)
} else {
performLeave()
}
function performLeave () {
// the delayed leave may have already been cancelled
if (cb.cancelled) {
return
}
// record leaving element
if (!vnode.data.show && el.parentNode) {
(el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode
}
beforeLeave && beforeLeave(el)
if (expectsCSS) {
addTransitionClass(el, leaveClass)
addTransitionClass(el, leaveActiveClass)
nextFrame(() => {
removeTransitionClass(el, leaveClass)
if (!cb.cancelled) {
addTransitionClass(el, leaveToClass)
if (!userWantsControl) {
if (isValidDuration(explicitLeaveDuration)) {
setTimeout(cb, explicitLeaveDuration)
} else {
whenTransitionEnds(el, type, cb)
}
}
}
})
}
leave && leave(el, cb)
if (!expectsCSS && !userWantsControl) {
cb()
}
}
}
delayLeaveчерезresolveTransition(vnode.data.transition)Получить функцию, если она существует, выполнитьdelayLeave, иначе выполнить напрямуюperformLeave
if (delayLeave) {
delayLeave(performLeave)
} else {
performLeave()
}
может видетьdelayLeaveэто функция, которая сама по себе ничего не делает, единственное, что нужно сделать, этоperformLeaveОн предоставляется как параметр обратного вызова, который пользователь может вызвать самостоятельно.
В соответствии с этой идеей мы просто преобразуем приведенный выше официальный пример следующим образом
<template>
<div id="example">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade" @delay-leave="handleDelay">
<p v-if="show">hello</p>
</transition>
</div>
</template>
<script>
export default {
data () {
return {
show: true
}
},
methods: {
handleDelay (done) {
setTimeout(() => {
done()
}, 2000)
}
}
}
</script>
<style lang="scss">
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
</style>
Конечным результатом является то, что мойleaveЭффект перехода будет выполнен через 2S
Суммировать
статью сюда, для встроенного<transition>Представлен компонентный анализ. От начала до конца статьи я вставил输入 => 输出понятие в.<transition>компонент илиvueИменно таким и является сам дизайн, он делается для нас внутри输入 => 输出обработки, так что нам не нужно заботиться о его внутренностях, просто сосредоточьтесь на нашей собственной бизнес-логике.
Итак, на данный момент кажется, что<transition>Используй его, ты нужен себеcssошибиться первым
В конце концов, я построил фронтальную группу связи: 731175396.
Приглашаем всех сестринских газет (забудьте о китайской бумаге~) присоединиться, давайте хвастаться, говорить о жизни и технологиях.