Understand the extend and $mount principle and implement an imperative Confirm popover component

Animation has always been a tricky point in the front end, which is easy to be neglected but so important. Writing pleasant and natural interactive experience can really add a lot to the project. After all, it is easy to get started, so it is necessary to explore the realization principle of transition component of Vue. The Transition component can be animated in two ways, using Css class names and JavaScript hooks, as described next.

Transition Component

This is an abstract component, meaning that after the component is rendered, it does not appear as any Dom, but as a slot for controlling the internal child nodes. It is used to add/remove Css class names or execute JavaScript hooks when appropriate for animation purposes.

The transition to VNode

Since it is a component, when generating into a real Dom, you need to first convert to a VNode, and then take the VNode and convert to a real Dom. So let’s first take a look at what the Transition component will become as a VNode.

exportConst transitionProps = {// The component accepts the props attribute appear: Boolean, // Whether to render CSS for the first time: Boolean, // Whether to unanimate CSS mode: String, //inEither out-out or out-intype: String, // Display declaration listens to animation or transition name: String, // default v enterClass: String, // default '${name}-Enter 'leaveClass: String, // Default'${name}-leave 'enterToClass: String, // default'${name}-enter to 'leaveToClass: String, // Default'${name}-leave-to 'enterActiveClass: String, // Default'${name}-enter-active 'leaveActiveClass: String, // Default'${name}-leave-active 'appearClass: String, // If the first rendering takes place, they always appear. Duration: [Number, String, Object] // Animation duration}export default {
  name: 'transition',
  props: transitionProps,
  abstract: trueRender (h) {// Render (h) {// Render (h) {// Render (h) {// Render (h) {//let children = this.$slotsDefault // Obtain the node in the default slotif(! children) {return
    }
    if(! children.length) {return
    }
    if(children.length > 1) { ... A slot can have only one child} const mode = this.modeif(mode && mode ! = ='in-out'&& mode ! = ='out-in') {... Mode can only bein-out or out-in} const child = children[0] // VNode const id = '__transition-${this._uid}- 'child.key = child.key == null // Add the key attribute to the VNode of the child node? Child.iscomment // Comment node? id +'comment': id + child.tag: isPrimitive(child.key) // primitive? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key) : Child. The child. The key (data | | (child) data = {})). The transition = extractTransitionData (this) / / core! Assign the props and hook functions to the transition property of the child node to indicate that it is a VNode rendered by the Transition componentreturn child
  }
}

export functionExtractTransitionData (comp) {// Assign function const data = {} const options = comp.$options
  for (const key inOptions.propsdata) {// Process parameters data[key] = comp[key]} const listeners = options._parentlisteners // Register hook methods on the Transition componentfor (const key in listeners) {
    data[key] = listeners[key]
  }
  return data
}
Copy the code

The transition component does two things. First, it adds a key attribute to the VNode rendering its child node. Then, it adds a transition attribute to its data attribute. This will be dealt with later when the path creates the real Dom.

Implementation principle of Css class names

We first focus on the principle of Css class name implementation, now that we have got the corresponding VNode, now we need to create a real Dom, in the process of path, Dom style, Css, attr and other properties are divided into modules to create, these modules have their own hook functions. For example, there are created, update, and INSERT functions. Some modules have different functions, which indicate that a certain thing is done in a certain period of time. Transition is no exception, executing an Created hook first. The transition component is split into enter and leave states.

export functionEnter (vnode) {// The parameter is vnode in the component slot const el = vnode.elm // the real node const data = resolveTransition(vnode.data.transition) // Extended properties // data contains the props passed in and the extended six class propertiesif(isUndef(data)) {// If not transition render vnode, byereturn}... }export functionResolveTransition (def) {/ / extended attributes const res = {} the extend (res, autoCssTransition (def. Name | |'v'Extend (res, def) // Extend attributes on def to resreturnRes} const autoCssTransition (name) {// Generate 6 class objects that need to be usedreturn {
    enterClass: `${name}-enter`,
    enterToClass: `${name}-enter-to`,
    enterActiveClass: `${name}-enter-active`,
    leaveClass: `${name}-leave`,
    leaveToClass: `${name}-leave-to`,
    leaveActiveClass: `${name}-leave-active`
  }
})
Copy the code

Enter to extend the transition property with six more class names that will be used later. Let’s move on:

export functionEnter (vnode) {// The parameter is vnode inside the component slot... EnterClass, enterToClass, enterActiveClass, appearClass, appearToClass, CSS,type/ /... } = data const isAppear =! context._isMounted || ! IsRootInsert // _isMounted Specifies whether the component is mounted. // isRootInsert specifies whether the root node is insertedif(isAppear && ! appear && appear ! = =' ') {// If the appear attribute is not configured, it is the first time it was renderedreturn} const startClass = isAppear && appearClass AppearClass // implement the definition of appearClass: enterClass // otherwise, enterClass const activeClass = isAppear && appearActiveClass? appearActiveClass : enterActiveClass const toClass = isAppear && appearToClass ? appearToClass : enterToClass ... }Copy the code

The next step is to get the props and extended class values for later use. AppearClass is a complete function of enter state if it is not mounted or the root node is not inserted and the appearClass attribute is defined. Otherwise, no animation will be performed directly. Next comes the core implementation process.

export functionenter (vnode) { ... const expectsCSS = css ! = =false&&! Const cb = once(() => {// Defines a CB function that executes only once, except that it does not executeif(expectsCSS) {removeTransitionClass(el, toClass) // Remove activeClass}})if(expectsCSS) {addTransitionClass(el, startClass) // Add startClass addTransitionClass(el, ActiveClass nextFrame(() => {// requestAnimationFrame wrapper, RemoveTransitionClass (el, startClass) // Remove startClass addTransitionClass(el, ToClass) // toClass whenTransitionEnds(el,typeCb) remove toClass and activeClass by executing cb after transitionEnd or AnimationEnd})}}Copy the code

First, we define a cb function, which is wrapped in the once function. The cb function is defined only once, but does not execute. Next, add startClass and activeClass synchronously for the current real node, that is, we are familiar with v-Enter and V-Enter -active; Then remove startClass in requestAnimationFrame (the next frame rendered by the browser) and add toClass (v-enter-to); Finally, execute the whenTransitionEnds method, which listens for the browser’s end of animation event, i.e. transitionEnd or AnimationEnd, indicating that the animation or transition defined in v-Enter-active has ended. Remove toClass and activeClass from this function.

It is not hard to find that the main function of the Enter state is to manage the addition and deletion of the three classes v-Enter/V-enter -active/ V-Enter -to. The specific animation is user-defined.

It is natural to think that the function of the leave state is to manage the addition and deletion of the other three classes.

export functionleave (vnode) { const cb = once(() => { removeTransitionClass(el, LeaveToClass) // Remove v-leave-to removeTransitionClass(el, leaveActiveClass) // Remove V-leave-active}) addTransitionClass(el, LeaveClass) // Add v-leave addTransitionClass(el, LeaveActiveClass) // Add V-leave-active nextFrame(() => {removeTransitionClass(el, LeaveClass) // Remove v-leave addTransitionClass(el, leaveToClass) // Add V-leave-to whenTransitionEnds(el,typeCb) // Execute cb function after the end of animation event})}Copy the code

There are many boundary cases in the source code, such as the transition package is an abstract component, enter is not executed when leave is executed, enter is executed before the last Enter is completed, etc. Interested in you can go to see the complete source code implementation, here is only the core implementation principle of the analysis. Let’s look at how JavaScript hooks are implemented.

JavaScript hook implementation principles

Once you know how Css class names work, it’s easy to understand how JavaScript hooks work. Hook implementation is also divided into two states enter and leave, and the code is also in these two functions, but the previous introduction of Css ignored, now let’s look at these two state functions from the perspective of hook implementation. First, enter:

export function enter(vnode) {

  if(isDef(el._leavecb)) {// If _leaveCb is not cancelled =, execute el._leavecbtrueEl._leavecb () // cb._leavecb will be null} // el._leavecb is the cb function defined in the leave state, BeforeEnter, enter, afterEnter, enterCancelled, duration... const {beforeEnter, enter, afterEnter, enterCancelled, duration... } = data const userWantsControl = getHookArgumentsLength(Enter)doneFunction, which means that the user wants to control it for himselfdoneConst cb = el._entercb = once(() => {// El._entercb = once() => {// El._entercb = once() => {//if(cb. Cancelled) {// If the cb function in leave is cancelled enterCancelled hook enterCancelled && enterCancelled(el)}else{afterEnter && afterEnter(el)} el._entercb = null Omit CSS logic correlation}) mergeVNodeHook(vnode,'insert', () => {// Insert the function body into the insert hook, which executes after the created module in the path... Enter && Enter (el, cb) // Execute the Enter hook and pass in cb, which corresponds to the enter hookdoneFunction}) beforeEnter && beforeEnter(el) nextFrame(() => {if(! UserWantsControl) {// If the user does not want to controlif(duration) {// If a valid transition time parameter is specifiedsetTimeout(cb, duration) // setCb} after Timeoutelse {
        whenTransitionEnds(el, typeCb) // Execute after the event after the browser transition ends}}})}Copy the code

This code is how JavaScript hooks work, and it is important to note the order in which they are executed:

  1. Executed firstbeforeEnterHook, because this is synchronized,cbIt just defines,insertIs in thecreatedAnd then execute,nextFrameInside is the next frame of the browser, which is asynchronous.
  2. Perform insert toinsertThe body of the function in the hook, which also belongs to synchronization, is just increatedAfter that, execute the insideenterHook.
  3. If the user does not want to control the end of the animation, executenextFrameThe body of the function.
  4. If the user wants control, it’s calleddoneDelta function, straight upcbFunction, which normally executes insideafterEnterHook.

The leave state will only post the core code, so that you can compare it with the Enter state. The difference is not very big:

export functionleave(vnode) { const { beforeLeave, leave, afterLeave, duration ... } = data const cb = once(() => {afterLeave && afterLeave(el)... }) beforeLeave && beforeLeave(el) nextFrame(() => {if(! UserWantsControl) {// The user does not want to controlif (isValidDuration(duration)) {
        setTimeout(cb, duration)
      } else {
        whenTransitionEnds(el, type, cb)}}}) leave && leave(el, cb) // The user wants to control execution heredone
}
Copy the code

The order of hook execution in the leave state is beforeLeave, leave, afterLeave.

At this point, the two implementation principles of the Transition built-in component are resolved. Source code to consider the boundary situation will be a lot more, need a more comprehensive understanding, you need to see the source code.

The author finished reading thistransitionA little disappointed after the principle, the original can not let me become animation master, the most important isCssOf those animation knowledge, visible foundation plays the importance of the firm!

I’m going to end with a question that the interviewer might ask me, because I’ve actually been asked.

The interviewer smiled politely and asked,

  • Please specify thetransitionHow does the component work?

Dui back:

  • transitionThe component is an abstract component and does not render anyDomIt is mainly to help us write animations more easily. In the form of slots for the internal single child node animation management, during the rendering phase will be to the child node virtualDomMount onetransitionProperty that represents one of its substransitionComponent wrapped nodes inpathPhases are executedtransitionComponent internal hook, hook is divided intoenterandleaveState, used on the wrapped child nodev-iforv-showTo switch the status. You can useCssYou can also useJavaScriptHook, usingCssWill be in theenter/leaveIn-stateclassTo add or delete a class name, the user only needs to write the animation corresponding to the class name. If you are usingJavaScriptHooks, on the other hand, execute specified functions sequentially, and these functions need to be defined by the user. Components only control the flow of this function.

Next: Buried in writing…

Easy to click a like or follow bai, also easy to find ~

Reference:

Vue. Js source code comprehensive in-depth analysis

Share a component library written by the author, who knows which day to use the ~ ↓

A library of vUE functional components that you might want to use.