From the perspective of style function, the whole is not very complicated. The Alert component mainly includes theme color, title, close button, close event, center, bold and so on

The source code

  • template
<template> <! -- Show hide has animation effect --> <! -- Never used in development. <transition name="d-alert-fade"> <div class="d-alert" :class="[typeClass, center ? 'is-center' : '', 'is-' + effect]" v-show="visible" role="alert" > <! - on the left side of the icon - > < I class = "d - alert__icon:" class = "[iconClass isBigIcon]" v - if = "showIcon" > < / I > <! - the title and description - > < 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>Copy the code

Use the role attribute to tell auxiliary devices (such as screen readers) what role the element plays. The essence is to enhance semantics, when the existing HTML tags can not fully express semantics, you can use role to explain.

I don’t understand why title and description use attribute and slot judgment

  • The props property is generic, so I won’t cover it here
Setup (props, {emit, slots}) {// Accept attributes to respond const {description, Type} = toRefs(props) // Use v-show to hide const visible = ref(true) // Close events 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 } }Copy the code

This concludes the component introduction, which is relatively simple. And just to make things right, here’s the transition component

transition

Most of you know that this is the built-in animation component that sets up component animation. There are usually three ways to use it:

  1. CSS transition
  2. CSS animations
  3. Javascript hooks

CSS transition

The method we usually use is CSS to configure Enter and leave

<template> <div class="app"> <button @click="show = ! <transition name="fade"> <p v-if="show" </p> </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>Copy the code

CSS animations

<template> <div class="app"> <button @click="show = ! Show ">Toggle show</button> <transition name="bounce"> <p v-if="show" </p> </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>Copy the code

Js hooks

Listen to the transition component’s built-in method, js controls the animation

<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>Copy the code

If done is not specified, the user does not manually control the end of the animation. Instead, the transition or animationEnd of the node signals the end of the animation, and the afterEnter callback begins.

The number of parameters in the hook function is greater than 1, indicating done, which means the user must manually control when the animation ends. So once you configure the done parameter, it’s up to you to tell the frame when the animation ends. Done needs to be called at the right time, otherwise the afterEnter interface will not be called.

Animation trigger condition

  • Conditional Rendering (V-IF)
  • Conditional Display (V-show)
  • Dynamic components
  • Component root node

All works

The instance

<template> <div class="app"> <button @click="show = ! show"> Toggle render </button> <transition name="fade"> <p v-if="show">hello</p> </transition> </div> </template>Copy the code

Compile the generated render function (not the template component used)

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 has only one child, default slot. _withCtx(() => [ _ctx.show ? (_openBlock(), _createBlock("p", { key: 0 }, "hello")) : _createCommentVNode("v-if", true), ]), _: 1, } ), ]), ]) ); }Copy the code

So how do you execute events when a build is created and destroyed? The ———— create hook function transition component returns the first child node processed

  • If the Transition component is nested internally with a KeepAlive component, it continues to look for the first child of the KeepAlive component to use as the element node for rendering.

  • If the Transition component does not have any child nodes nested inside it, it renders an empty comment node.

Trantion component definition

Const Transition = (props, {slots}) => //esolveTransitionProps Then return a new Props object, since it contains all the Props processing 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; } // The Transition component allows only one child node, and multiple alerts are generated to remind the TransitionGroup component 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; // Check whether mode is valid if (process.env.node_env! == "production" && mode && ! ["in-out", "out-in", "default"].includes(mode) ) { warn(`invalid <transition> mode: ${mode}`); } // Get the first child const child = children[0]; if (state.isLeaving) { return emptyPlaceholder(child); } // handle <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 ); // Update the old tree hook function setTransitionHooks(oldInnerChild, leavingHooks); // Switch between two views if (mode === "out-of-in ") {state. IsLeaving = true; 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; }; }};Copy the code

During rendering, the Transition component also uses resolveTransitionHooks to define hook function objects for the component creation and deletion phases. Then set this hook object to vNode. transition using the setTransitionHooks function.

Hooks to define

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); }};Copy the code

The hook function object defines four hook functions, which are beforeEnter, Enter, Leave, and Clone. In the mountElement function in the patch phase of a node, the beforeEnter function in vNode. transition is executed before a node is inserted and there is an excess

The beforeEnter hook function mainly does onBeforeEnter or onBeforeEnter depending on the value of appear and whether the DOM is mounted. Appear whether the node is realistic when executing the animation 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]) }Copy the code

ResolveTransitionProps function

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); }}); }Copy the code

Let’s look at the onBeforeEnter function, which internally implements the onBeforeEnter hook function passed to the base props and adds the enterActiveClass and enterFromClass styles to the DOM element EL.

The onBeforeEnter function passed to props is the beforeEnter hook function we added when we wrote the Transition component. EnterActiveClass defaults to V-enter-active and enterFromClass defaults to V-enter-from. If you pass a prop of name to the Transition component, for example, fade, So the enterActiveClass value is fade-enter-active, and the enterFromClass value is fade-enter-from. The logic of onBeforeAppear and onBeforeEnter is similar, without further explanation, when we pass the Appear Prop to the Transition component and first mount it. Execute beforeEnter hook function, then insert elements into the page, then execute Enter hook function in vNode. transition, in hooks above)

Inside the Enter function, we execute the onEnter hook function passed in by the underlying props. On the next frame, we remove enterFromClass from the DOM element EL and add an enterToClass style.

The Transition component allows us to pass in an enterDuration prop, which specifies the animation duration to enter the Transition. Of course, if you don’t specify it, vue.js will listen internally for the animation end event, and then execute the finishEnter function when the animation ends

Let’s look at the implementation

const finishEnter = (el, isAppear, done) => {
  removeTransitionClass(el, isAppear ? appearToClass : enterToClass);
  removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);
  done && done();
};
Copy the code

This removes enterToClass and enterActiveClass from the DOM element, executes the done function, and executes the onAfterEnter hook function

The leave hook does the opposite of Enter. Friends can look it up by themselves.

So that’s the Alert component. You are welcome to correct me.