Antecedents feed

Vue.js has a powerful built-in transition and animation aid system that allows you to transition applications in a variety of ways when inserting, updating, or removing the DOM. Vue provides
and
wrapped components that wrap around the elements and components we need to add to the in/out transition. If we want to reuse some of the most common transition animations on our components, we have to reconfigure the transition and transition-group again and again.

No, even if we wanted to, Vue didn’t want us to, as described in the “Transitions & Animation > In/out & List Transitions > Reusable Transitions” section of the official documentation:

To create a reusable transition component, all you need to do is make
or
the root component, and then place any child components in it.

Functional components are better suited for this task.

Ok, that’s our topic today, how to make a reusable
functional component.

Huh? Isn’t it on the official website? There is, but that is the direct use of function components, pure code to create components for people to look at actually not very friendly, can have SFC (single file component) look intuitive? So what we’re doing today is using SFC to make reusable
functional components

Note: this article uses Vue 2.x version, later partners can migrate to 3.x ~~

Functional component

Why functional components

First, to clarify the necessity, why is it better for functional components to do
? First of all, from a business perspective, our transition component does not need to care about its subelement, nor does it need to pass any attributes to the child element, it is a component that single-mindedly provides the transition to the child element: a child element appears, triggers the transition hook, and presents the transition effect of entry; There are child element changes that provide a transition animation effect from the old position to the new position; A child element has disappeared, rendering an exit effect. So it doesn’t need any state. If a component doesn’t need state, we can consider writing it as a stateless functional component. In Vue 2.x, there is some performance optimization for functional components, which can be initialized much faster than stateful components. This is the main reason why we write the reusable
as a functional component. But next you’ll see another reason for writing functional components.

How to write functional components

In the official document, in fact, has been written very clearly, pure code will not say, this is not what we are going to focus on today, we will skip; Writing functional components on single-file components is as simple as writing functional on the template tag:

<template functional>
</template>
Copy the code

Very simple!

In addition, all of the props passed in can be accessed directly on the template, without having to be declared on the object on the script tag; And if you don’t have anything to define or configure, you don’t even need to write script tags! Something like this:

<template functional>
	<h2>{{props.title}}</h2>
</template>
Copy the code

Above is a simple function component, it is too easy to write!

Of course, you can also write script tags when you need to evaluate properties in props, for example, if you need to convert text:

<template functional>
	<h2>{{$options.coverTitle(props.title)}}</h2>
</template>
<script>
export default {
	coverTitle(str) {
    return str.toLowerCase();
  }
}
</script>
Copy the code

At this point, you can use the $options call to ask for the object you defined in the script tag.

Or maybe you want to bind some simple events:

<template functional> <h2 @click="$option.clickTitle">{{props.title}}</h2> </template> <script> export default { ClickTitle (title) {alert(' This is the title '); } } </script>Copy the code

It’s all ok.

Note, however, that code inside a script object does not have this, nor can it access fields in the document that refer to the context.

So that’s why we use functional components, because it’s so easy!

Began to work

Get to work!

First, we need to confirm the requirements. We need to make a
component with various transition effects. Then we can customize the effects we need according to external parameters passed in.

It’s that simple!

Let’s add
to our functional component!

<template functional>
    <transition-group
          :name="props.effectName"            
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
Copy the code

The externally passed effectName property is bound directly to the
. Finished!

Ah, and so on! That’s not true! Let me explain! This is just an example! Don’t close the browser page!

Of course we want to reuse the transition effect, of course it’s not that simple, so we need to do something complicated to make it reusable, so we want to do this effect:

The effect of entering in sequence does not look very complicated! But using
is very simple! Let’s keep writing!

The
JavaScript hook is used, and the data attribute is used to obtain the index of the node, and then the corresponding delay rendering is performed. The animation is written using velocity.js. We could try it.

As mentioned above, we can define some methods in the script tag, which can then be accessed on the template using $options, so we can write:

<template functional>
    <transition-group
          v-on:before-enter="$options.beforeEnter"
          v-on:enter="$options.enter"
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
<script>
export default {
	beforeEnter(el) {
    // ...
  },
  enter(el, done) {
    // ...
  },
}
</script>
Copy the code

We’ll put the entry effect right there, and the same goes for all the other effects. You can then write effects in these two functions, first write beforeEnter:

<template functional>
    <transition-group
          v-on:before-enter="$options.beforeEnter"
          v-on:enter="$options.enter"
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
<script>
export default {
	beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'translateX(-50%)';
      el.style.transition = 'all 1s';
  },
  enter(el, done) {
    // ...
  },
}
</script>
Copy the code

BeforeEnter Sets the style state of the element before it enters, then remember to set the Transition property to have a transition effect.

Next, enter, we want to enter a state like this:

<template functional>
    <transition-group
          v-on:before-enter="$options.beforeEnter"
          v-on:enter="$options.enter"
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
<script>
export default {
	beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'translateX(-50%)';
      el.style.transition = 'all 1s';
  },
  enter(el, done) {
       dom.style.opacity = 1;
       dom.style.transform = 'translateX(0%)';
       dom.addEventListener('transitionend', done);
  },
}
</script>
Copy the code

Good, easy! However, this is not effective.

For some reason (no documentation, I don’t know), the done callback is mandatory when using only JavaScript hooks, and it can’t be called synchronously. Synchronous calls lose their effect and must be done asynchronously, so we add a slight delay to execute this code:

<template functional>
    <transition-group
          v-on:before-enter="$options.beforeEnter"
          v-on:enter="$options.enter"
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
<script>
export default {
	beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'translateX(-50%)';
      el.style.transition = 'all 1s';
  },
  enter(el, done) {
       setTimeout(() => {
        	el.style.opacity = 1;
        	el.style.transform = 'translateX(0%)';
        	el.addEventListener('transitionend', done);
      }, 100);
  },
}
</script>
Copy the code

Then the transition effect takes effect

But that’s not what we want, it’s simultaneous entry, not sequential entry, so here’s the problem: The transition group will contain a number of child elements, and how do we know their order in the set? The transition group will contain a number of child elements.

We might not know if we think of them as vUE components, but what if we think of them as DOM nodes?

As long as we know how many sibling nodes are in front of the child node, we can know our rank.

So a very simple auxiliary function is written:

function getDomNodeIndex(el) {
  let node = el;
  let index = 0;
  while (node.previousSibling) { // Continue to visit the previous sibling node
    index += 1;
    node = node.previousSibling;
  }
  return index;
}
Copy the code

Our component could then be written like this:

<template functional> <transition-group v-on:before-enter="$options.beforeEnter" v-on:enter="$options.enter" tag="div" class="transition-group-container" > <slot></slot> </transition-group> </template> <script> function getDomNodeIndex(el)  { let node = el; let index = 0; While (node.previoussibling) {// continue to access the previousSibling node index += 1; node = node.previousSibling; } return index; } export default { beforeEnter(el) { el.style.opacity = 0; el.style.transform = 'translateX(-50%)'; el.style.transition = 'all 1s'; }, enter(el, done) { const delay = getDomNodeIndex(el) * 200; setTimeout(() => { el.style.opacity = 1; el.style.transform = 'translateX(0%)'; el.addEventListener('transitionend', done); }, delay); }, } </script>Copy the code

That completes one of our effects!

Further improve reusability

Ok, so we have our first effect, but we can’t stop there, if we can slide in from the left, can we slide in from the right, from the top, from the bottom? So if we can slide in one by one, can we also slide in from the left, from the right, from the top, from the bottom? Now that I can slide it in can I write it in a different way?

Take your time, and here are some tips to further improve the reusability of this component.

As stated above, we are functional components, so creating different transition effects based on external incoming properties is the next challenge we will tackle.

$options () {$options () {$options () {$options () {$options () {$options () {$options () {$options () {$options () {$options () {

<template functional>
    <transition-group
          v-on:before-enter="$options[props.animateName].beforeEnter"
          v-on:enter="$options[props.animateName].enter"           
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
Copy the code

An attempt was made to dynamically bind the incoming JavaScript hook using the animateName property passed in through props, but it didn’t work. So, we’re going to have to take a detour.

How do you get around this limitation? As we all know, Vue template v-bind and V-on directives can write some simple JS code to implement simple functions, so we can do as follows:

<template functional>
    <transition-group
          v-on:before-enter="(el) => $options[props.animateType].beforeEnter(el)"
          v-on:enter="(el, done) => $options[props.animateType].enter(el, done)"          
          tag="div"
          class="transition-group-container"
    >
      <slot></slot>
    </transition-group>
</template>
Copy the code

This allows us to circumvent the constraint that we cannot bind methods dynamically. Now we can define more transition effects in the component:

<template functional> <transition-group v-on:before-enter="(el) => $options[props.animateType].beforeEnter(el)" v-on:enter="(el, done) => $options[props.animateType].enter(el, done)" tag="div" class="transition-group-container" > <slot></slot> </transition-group> </template> <script> function getDomNodeIndex(el) { let node = el; let index = 0; While (node.previoussibling) {// continue to access the previousSibling node index += 1; node = node.previousSibling; } return index; } export default { slideLeftInOrder: { beforeEnter(el) { el.style.opacity = 0; el.style.transform = 'translateX(-50%)'; el.style.transition = 'all 1s'; }, enter(el, done) { const delay = getDomNodeIndex(el) * 200; setTimeout(() => { el.style.opacity = 1; el.style.transform = 'translateX(0%)'; el.addEventListener('transitionend', done); }, delay); }, }, slideRightInOrder: { beforeEnter(el) { el.style.opacity = 0; el.style.transform = 'translateX(50%)'; el.style.transition = 'all 1s'; }, enter(el, done) { const delay = getDomNodeIndex(el) * 200; setTimeout(() => { el.style.opacity = 1; el.style.transform = 'translateX(0%)'; el.addEventListener('transitionend', done); }, delay); }}, / /... More transition effects} </script>Copy the code

Transition-group functional reuse component transition-group functional reuse component transition-group functional reuse component transition-group functional reuse component

Reference: the Vue. Js

This article is original and cannot be reproduced without authorization