从样式功能来看,整体不是很复杂,alert
组件主要包括了主题色,title
,关闭按钮,关闭事件,居中,加粗等
使用
role
属性告诉辅助设备(如屏幕阅读器)这个元素所扮演的角色。本质上是增强语义性,当现有的HTML
标签不能充分表达语义性的时候,就可以借助role
来说明。这里不是很理解为什么
title
和description
使用了属性和slot
判断,有清楚的朋友可以帮忙解答
组件介绍到这里就结束了,比较简单。为了凑字呢,这里在介绍下 transition
组件
大部分朋友都了解这是设置组件动画的内置动画组件。通常有三种使用方式:
我们通常使用的方法,css
配置 enter
和 leave
<template>
<div class="app">
<button @click="show = !show">
Toggle render
</button>
<transition name="fade">
<p v-if="show">我是测试</p>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
}
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<template>
<div class="app">
<button @click="show = !show">Toggle show</button>
<transition name="bounce">
<p v-if="show">我是测试</p>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
}
</script>
<style>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
// reverse 很关键
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
</style>
监听 transition
组件的内置方法,js
控制动画
<template>
<div class="app">
<button @click="show = !show">
Toggle render
</button>
<transition
@before-enter="beforeEnter"
@enter="enter"
@before-leave="beforeLeave"
@leave="leave"
css="false"
>
<p v-if="show">hello</p>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0
el.style.transition = 'opacity 0.5s ease'
},
enter(el) {
this.$el.offsetHeight
el.style.opacity = 1
},
beforeLeave(el) {
el.style.opacity = 1
},
leave(el) {
el.style.transition = 'opacity 0.5s ease'
el.style.opacity = 0
}
}
}
</script>
如果形参不指定
done
,则表明用户不手动控制动画的结束,而转由节点的transition
或者animationEnd
来标识动画结束,开始回调afterEnter
。
钩子函数的形参的个数大于1,表示形参中有 done
, 也就是说用户必须手动控制动画何时结束。所以一旦你配置了 done
形参,则转由你告诉框架,动画何时结束。需要在合适的时机调用 done
,否则 afterEnter
接口就没法被调用了。
实例
<template>
<div class="app">
<button @click="show = !show">
Toggle render
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</template>
编译生成的 render
函数(不是使用的模板组件)
import {
createVNode as _createVNode,
openBlock as _openBlock,
createBlock as _createBlock,
createCommentVNode as _createCommentVNode,
Transition as _Transition,
withCtx as _withCtx,
} from "vue";
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (
// 收集动态节点 如 v-if v-for
_openBlock(),
_createBlock("template", null, [
_createVNode("div", { class: "app" }, [
_createVNode(
"button",
{
onClick: ($event) => (_ctx.show = !_ctx.show),
},
" Toggle render ",
8 /* PROPS */,
["onClick"]
),
_createVNode(
_Transition,
{ name: "fade" },
{
// transition 只有一个子节点,默认插槽。多个子节点报错
default: _withCtx(() => [
_ctx.show
? (_openBlock(), _createBlock("p", { key: 0 }, "hello"))
: _createCommentVNode("v-if", true),
]),
_: 1,
}
),
]),
])
);
}
那么如何在组建创建和销毁的时候执行事件呢?————创建钩子函数
transition
组件返回的是处理过的第一个子节点
Transition
组件内部嵌套的是 KeepAlive
组件,那么它会继续查找 KeepAlive
组件嵌套的第一个子元素节点,来作为渲染的元素节点。Transition
组件内部没有嵌套任何子节点,那么它会渲染空的注释节点。trantion
组件定义
const Transition = (props, { slots }) =>
//esolveTransitionProps 函数主要作用是,在我们给 Transition 传递的 Props 基础上做一层封装,然后返回一个新的 Props 对象,由于它包含了所有的 Props 处理
h(BaseTransition, resolveTransitionProps(props), slots);
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(
"<transition> can only be used on a single element or component. Use " +
"<transition-group> for lists."
);
}
// 不需要追踪响应式,所以改成原始值,提升性能
const rawProps = toRaw(props);
const { mode } = rawProps;
// 检查 mode 是否合法
if (
process.env.NODE_ENV !== "production" &&
mode &&
!["in-out", "out-in", "default"].includes(mode)
) {
warn(`invalid <transition> mode: ${mode}`);
}
// 获取第一个子元素节点
const child = children[0];
if (state.isLeaving) {
return emptyPlaceholder(child);
}
// 处理 <transition><keep-alive/></transition> 的情况
const innerChild = getKeepAliveChild(child);
if (!innerChild) {
return emptyPlaceholder(child);
}
const enterHooks = resolveTransitionHooks(
innerChild,
rawProps,
state,
instance
);
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, oldInnerChild) || 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(child);
} 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;
};
},
};
在渲染的过程中,Transition
组件还会通过 resolveTransitionHooks
去定义组件创建和删除阶段的钩子函数对象,然后再通过 setTransitionHooks
函数去把这个钩子函数对象设置到 vnode.transition
上。
hooks定义
const hooks = {
mode,
persisted,
beforeEnter(el) {
let hook = onBeforeEnter;
if (!state.isMounted) {
if (appear) {
hook = onBeforeAppear || onBeforeEnter;
} else {
return;
}
}
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 (state.isUnmounting) {
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);
},
};
钩子函数对象定义了 4 个钩子函数,分别是 beforeEnter
,enter
,leave
和 clone
。在节点 patch
阶段的 mountElement
函数中,在插入节点前且存在过度会执行 vnode.transition
中的 beforeEnter
函数
//beforeEnter 钩子函数主要做的事情就是根据 appear 的值和 DOM 是否挂载,来执行 onBeforeEnter 函数或者是 onBeforeAppear 函数。appear 是否节点现实的时候执行动画
beforeEnter(el) {
let hook = onBeforeEnter
if (!state.isMounted) {
if (appear) {
hook = onBeforeAppear || onBeforeEnter
}
else {
return
}
}
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])
}
resolveTransitionProps 函数
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;
const finishEnter = (el, isAppear, done) => {
removeTransitionClass(el, isAppear ? appearToClass : enterToClass);
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);
done && done();
};
const finishLeave = (el, done) => {
removeTransitionClass(el, leaveToClass);
removeTransitionClass(el, leaveActiveClass);
done && done();
};
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);
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(el);
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);
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);
},
});
}
我们来看 onBeforeEnter
函数,它的内部执行了基础 props
传入的 onBeforeEnter
钩子函数,并且给 DOM 元素 el
添加了 enterActiveClass
和 enterFromClass
样式。
其中,props
传入的 onBeforeEnter
函数就是我们写 Transition
组件时添加的 beforeEnter
钩子函数。enterActiveClass
默认值是 v-enter-active
,enterFromClass
默认值是 v-enter-from
,如果给 Transition
组件传入了 name
的 prop
,比如 fade
,那么 enterActiveClass
的值就是 fade-enter-active
,enterFromClass
的值就是 fade-enter-from
。(onBeforeAppear
和 onBeforeEnter
的逻辑类似,就不赘述了,它是在我们给 Transition
组件传入 appear
的 Prop
,且首次挂载的时候执行的。执行完 beforeEnter
钩子函数,接着插入元素到页面,然后会执行 vnode.transition
中的 enter
钩子函数,上面的 hooks
中)
在 enter
函数内部,首先执行基础 props
传入的 onEnter
钩子函数,然后在下一帧给 DOM
元素 el
移除了 enterFromClass
,同时添加了 enterToClass
样式(动画也就是所谓的样式交替改变)
Transition
组件允许我们传入enterDuration
这个prop
,它会指定进入过渡的动画时长,当然如果你不指定,Vue.js
内部会监听动画结束事件,然后在动画结束后,执行finishEnter
函数
来看它的实现
const finishEnter = (el, isAppear, done) => {
removeTransitionClass(el, isAppear ? appearToClass : enterToClass);
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);
done && done();
};
其实就是给 DOM 元素移除 enterToClass 以及 enterActiveClass,同时执行 done 函数,进而执行 onAfterEnter 钩子函数
leave
钩子主要功能和 enter
相反。小伙伴们可自行查阅。
以上就是对 alert
组件的学习, 如有不对欢迎指正。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。