网站首页 > 技术文章 正文
从样式功能来看,整体不是很复杂,alert 组件主要包括了主题色,title,关闭按钮,关闭事件,居中,加粗等
源码
- template
<template>
<!-- 显示隐藏有动画效果 -->
<!-- 开发没用过,不是很理解为什么使用v-show判断显示 -->
<transition name="d-alert-fade">
<div
class="d-alert"
:class="[typeClass, center ? 'is-center' : '', 'is-' + effect]"
v-show="visible"
role="alert"
>
<!-- 左侧图标 -->
<i
class="d-alert__icon"
:class="[iconClass, isBigIcon]"
v-if="showIcon"
></i>
<!-- title 和 描述 -->
<div class="d-alert__content">
<span
class="d-alert__title"
:class="[isBoldTitle]"
v-if="title || $slots.title"
>
<slot name="title">{{ title }}</slot>
</span>
<p v-if="$slots.default && !description" class="d-alert__description">
<slot></slot>
</p>
<p v-if="description && !$slots.default" class="d-alert__description">
{{ description }}
</p>
<i
class="d-alert__closebtn"
:class="{
'is-customed': closeText !== '',
'd-icon-close': closeText === ''
}"
v-show="closable"
@click="close"
>{{ closeText }}</i
>
</div>
</div>
</transition>
</template>
使用 role 属性告诉辅助设备(如屏幕阅读器)这个元素所扮演的角色。本质上是增强语义性,当现有的 HTML标签不能充分表达语义性的时候,就可以借助 role 来说明。
这里不是很理解为什么 title 和 description 使用了属性和 slot 判断,有清楚的朋友可以帮忙解答
- props 属性比较常规,这里就不介绍了哈
setup(props, { emit, slots }) {
// 接受的属性转为响应式
const { description, type } = toRefs(props)
// 使用 v-show 显示隐藏
const visible = ref(true)
// 关闭事件
const close = () => {
visible.value = false
emit('close')
}
const typeClass = computed(() => {
return `d-alert--${type.value}`
})
const iconClass = computed(() => {
return TYPE_CLASSES_MAP[type.value] || 'd-icon-info'
})
const isBigIcon = computed(() => {
return description.value || slots.default ? 'is-big' : ''
})
const isBoldTitle = computed(() => {
return description.value || slots.default ? 'is-bold' : ''
})
return {
close,
visible,
typeClass,
iconClass,
isBigIcon,
isBoldTitle
}
}
组件介绍到这里就结束了,比较简单。为了凑字呢,这里在介绍下 transition 组件
transition
大部分朋友都了解这是设置组件动画的内置动画组件。通常有三种使用方式:
- CSS 过渡
- CSS 动画
- Javascript 钩子
CSS 过渡
我们通常使用的方法,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>
CSS 动画
<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>
js 钩子
监听 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 接口就没法被调用了。
动画触发条件
- 条件渲染(v-if)
- 条件展示(v-show)
- 动态组件
- 组件根节点
执行原理
实例
<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 组件的学习。如有不对欢迎指正。
猜你喜欢
- 2024-10-24 初探animation中steps()属性(animation steps属性)
- 2024-10-24 HTML5(九)——超强的 SVG 动画(htmlsvg动画代码)
- 2024-10-24 自定义日历(二)(自定义日历控件)
- 2024-10-24 Flutter简单动画Animation运用(flutter 视频教程)
- 2024-10-24 css3中动画animation中的steps()函数
- 2024-10-24 移动端渲染原理浅析(移动端渲染原理浅析设计)
- 2024-10-24 iOS 事件处理机制与图像渲染过程(简述ios中的事件响应机制)
- 2024-10-24 Android 开机问题分析(android无法开机)
- 2024-10-24 GoogleCTF + zer0ptsCTF + ImaginaryCTF 2023 笔记
- 2024-10-24 决战“金三银四”,中高级Web前端大厂面试秘籍:CSS篇
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)