Categories
Vue 3

Vue 3 — Refs and Edge Cases

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use template refs and edge cases with Vue 3.

Template refs

Sometimes we may need to directly access the DOM.

To let us do that, we can assign a ref to the DOM element we want to access and then we can access it in our component code.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <input ref="input" />
    </div>
    <script>
      const app = Vue.createApp({
        methods: {
          focusInput() {
            this.$refs.input.focus();
          }
        },
        mounted() {
          this.focusInput();
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

to focus an input when the Vue instance mounts.

We assigned the input ref to our input.

Then we called focus on it to focus on the input.

We can also add a ref to component itself and use that to call focusInput .

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <custom-input ref="customInput"></custom-input>
    </div>
    <script>
      const app = Vue.createApp({
        mounted() {
          this.$refs.customInput.focusInput();
        }
      });

      app.component("custom-input", {
        methods: {
          focusInput() {
            this.$refs.input.focus();
          }
        },
        template: `<input ref='input' />`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We put our input into the custom-input component.

And we have the focusInput method that calls focus on the input.

We assigned a ref to the custom-input component so that we can call the focusInput method of it.

And so we get the same result as we did before.

$refs are only populated after the component is rendered.

It’s the last resort for direct child component manipulation.

We should avoid accessing refs from templates and computed properties.

Edge Cases

There’re edge cases in Vue 3 that aren’t addressed by the usual constructs.

Controlling Updates

Usually, we rely on Vue’s reactivity system to update our component.

But sometimes, we may need to control how components are updated.

Vue provides ways to do that.

Forcing Updates

We can force updates by using the $forceUpdate method on a component.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <button @click="forceUpdate">update</button>
      <p>{{new Date()}}</p>
    </div>
    <script>
      const app = Vue.createApp({
        methods: {
          forceUpdate() {
            this.$forceUpdate();
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

to do that.

We have a non-reactive expression in our template, so we can call $forceUpdate to update the component with a new date.

Create Static Components with v-once

We can create static component with the v-once directive.

It makes the content inside the element with the directive only update once.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <div v-once>
        hello world
      </div>
    </div>
    <script>
      const app = Vue.createApp({});

      app.mount("#app");
    </script>
  </body>
</html>

then Vue will never try to update the component once it’s rendered once.

Conclusion

Template refs let us access component methods and DOM elements directly.

We can also force updates with the $forceUpdate method and the v-once directive to display static content.

Categories
Vue 3

Vue 3 — JavaScript Transitions

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at setting transition durations and creating JavaScript transitions.

Transition Durations

We can set transition durations with the duration prop.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <style>
      .bounce-enter-active {
        animation: bounce-in 1.5s;
      }
      .bounce-leave-active {
        animation: bounce-in 1.5s reverse;
      }
      @keyframes bounce-in {
        0% {
          transform: scale(0);
        }
        50% {
          transform: scale(1.5);
        }
        100% {
          transform: scale(1);
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>

      <transition name="bounce" :duration="1000">
        <p v-if="show">hello</p>
      </transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            show: false
          };
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

We just add the duration prop to the transition component to set the duration.

The duration is in milliseconds.

We can also set separate values for enter and leave transitions:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <style>
      .bounce-enter-active {
        animation: bounce-in 1.5s;
      }
      .bounce-leave-active {
        animation: bounce-in 1.5s reverse;
      }
      [@keyframes](http://twitter.com/keyframes "Twitter profile for @keyframes") bounce-in {
        0% {
          transform: scale(0);
        }
        50% {
          transform: scale(1.5);
        }
        100% {
          transform: scale(1);
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>

      <transition name="bounce" :duration="{ enter: 500, leave: 1000 }">
        <p v-if="show">hello</p>
      </transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            show: false
          };
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

We have the enter and leave properties again in milliseconds to set the duration of each effect.

JavaScript Hooks

There’re JavaScript hooks for each stage of a transition.

They include:

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
  :css="false"
>
  <!-- ... -->
</transition>

css set to false means we use JavaScript to apply the transition effects.

And the hooks are in the methods property of the component as usual.

The signatures of the are:

methods: {
  beforeEnter(el) {
    // ...
  },

  enter(el, done) {
    // ...
    done()
  },

  afterEnter(el) {
    // ...
  },

  enterCancelled(el) {
    // ...
  },

  beforeLeave(el) {
    // ...
  },

  leave(el, done) {
    // ...
    done()
  },

  afterLeave(el) {
    // ...
  },

  leaveCancelled(el) {
    // ...
  }
}

el is the element and done is a function to indicate that the transition is done.

We can use the Greensock library to make JavaScript transitions.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>

      <transition
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
        :css="false"
      >
        <p v-if="show">
          hello world
        </p>
      </transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            show: false
          };
        },
        methods: {
          beforeEnter(el) {
            gsap.set(el, {
              scaleX: 0.9,
              scaleY: 1.6
            });
          },
          enter(el, done) {
            gsap.to(el, {
              duration: 1,
              scaleX: 1.2,
              scaleY: 0.9,
              opacity: 1,
              x: 150,
              ease: "elastic.inOut(2.8, 1)",
              onComplete: done
            });
          },
          leave(el, done) {
            gsap.to(el, {
              duration: 0.5,
              scaleX: 1,
              scaleY: 1,
              x: 300,
              ease: "elastic.inOut(2.5, 1)"
            });
            gsap.to(el, {
              duration: 0.2,
              delay: 0.5,
              opacity: 0,
              onComplete: done
            });
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

to create our animation with Greensock.

We added Greensock library with a script tag.

Then we call the gsap.set method to set the initial scale of our element before we start the animation.

The enter method has our enter transition effect.

The duration is set to 1 for 1 second

scaleX and scaleY change the sizing of our p element.

opacity changes the opacity.

x is the translation distance to the right in pixels.

ease is the easing effect.

onComplete is a callback which we indicate that the transition is done.

We have similar things in the leave method.

We have 2 transition effects instead of one for the leave transition effect.

The methods are set as the values of the corresponding directives in the transition component so we can use it.

Conclusion

We can use JavaScript to create our transitions instead of CSS.

A library like Greensock makes this an easy process.

Categories
Vue 3

Vue 3 — Dynamic and Async Components

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to add dynamic and async components with Vue 3.

Dynamic Components with keep-alive

The keep-alive component lets us keep the state of the component even if we switched out of them.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <button @click='currentComponent = "tab-home"'>home</button>
      <button @click='currentComponent = "tab-post"'>post</button>
      <button @click='currentComponent = "tab-archive"'>archive</button>
      <keep-alive>
        <component :is="currentComponent"></component>
      </keep-alive>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            currentComponent: "tab-home"
          };
        }
      });

      app.component("tab-home", {
        template: `
          <div>
            home
          </div>
        `
      });

      app.component("tab-post", {
        data() {
          return {
            on: false
          };
        },
        template: `
          <div>
            <button @click='on = !on'>toggle</button>
            <p v-if='on'>post</p>
          </div>
        `
      });

      app.component("tab-archive", {
        data() {
          return {
            on: false
          };
        },
        template: `
          <div>
            <button @click='on = !on'>toggle</button>
            <p v-if='on'>archive</p>
          </div>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the keep-alive component to cache the state of dynamic components so that when we display it again after leaving it, we keep the same state as before.

So if the on state is true in tab-post or tab-archive , then on will stay true even if we switch back and forth between any component.

Without it, the on state would be reset to the initial state.

Async Components

In larger apps, we may need to divide our app int smaller chunks and only load a component from the server when it’s needed.

We can do that with the Vue.defineAsyncComponent method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <async-comp></async-comp>
    </div>
    <script>
      const app = Vue.createApp({});

      const AsyncComp = Vue.defineAsyncComponent(
        () =>
          new Promise((resolve, reject) => {
            resolve({
              template: "<div>async component</div>"
            });
          })
      );

      app.component("async-comp", AsyncComp);

      app.mount("#app");
    </script>
  </body>
</html>

to define an async component and use it.

We called Vue.defineAsyncComponent with a callback that returns a promise that resolves to a component object.

We can pass the async component returned to the app.component method with the name as the first argument and the async component object as the 2nd argument.

Then we can use it in our parent Vue instance’s template.

We can also import the component from somewhere else.

For example, we can write:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

app.component('async-component', AsyncComp)

import returns a promise that resolves to the Vue component so we achieve the same result.

We can also register the component locally by putting our async component in the components property:

import { createApp, defineAsyncComponent } from 'vue'

createApp({
  // ...
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    )
  }
})

Use with Suspense

Async components are suspensible by default.

That means we can pass it into the Suspenbse component and use it.

To disable suspension, we can set suspensible to false in the async component.

Conclusion

We can define async components to load them asynchronously.

The keep-alive component lets us keep the component’s state while switching between them.

Categories
Vue 3

Vue 3 — Basic Transitions

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to add transitions with Vue 3.

Transitions

We can add transitions to our Vue app with various components

The transition component helps us add enter and leave transitions which are triggered by v-if .

Transition modes let us change the ordering of a transition.

Transitions with multiple components can be added with the transition-group component.

We can transition to different states in an app with watchers .

Class-based Animations & Transitions

We can add transitions by binding to a class.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <style>
      .shake {
        animation: shake 1s cubic-bezier(0.31, 0.07, 0.39, 0.97) both;
        transform: translate3d(0, 0, 0);
        backface-visibility: hidden;
        perspective: 1000px;
      }

      @keyframes shake {
        10%,
        90% {
          transform: translate3d(-1px, 0, 0);
        }

        20%,
        80% {
          transform: translate3d(2px, 0, 0);
        }

        30%,
        50%,
        70% {
          transform: translate3d(-4px, 0, 0);
        }

        40%,
        60% {
          transform: translate3d(4px, 0, 0);
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div :class="{ shake: on }">
        <button @click="on = !on">toggle</button>
        <p v-if="on">hello world!</p>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            on: false
          };
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

to add the shaking transition effect to our div.

We have the translate3d to add the shaking left and right,

Negative is left and positive is right.

The shake class has the shake transition.

cubic-bezier is the easing function, which makes the transition non-linear.

transform keeps the div in place.

We apply the shake class when on is true , so when we click the toggle button to set on to true , we’ll see the elements shake.

Transitions with Style Bindings

We can also add transitions with style bindings.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <div
        @mousemove="onMouseMove"
        :style="{ backgroundColor: `rgb(${x}, 80, 50)` }"
        class="movearea"
      >
        <p>x: {{x}}</p>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            x: 0
          };
        },
        methods: {
          onMouseMove(e) {
            this.x = e.clientX;
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

When our mouse moves, then the onMouseMove method is called.

This gets the x coordinate of the mouse and set it to the this.x state.

Then we used x to set the background color by changing the first value of rgb .

Therefore, when we move our mouse, the background color will change.

Performance

CSS animations has better performance than using JavaScript to change the position of our elements.

Every redraw with JavaScript is an expensive operation, so we try to eliminate them if we can.

CSS animations is also faster because browsers have hardware acceleration capabilities.

Properties like perspective, backface-visibility, and transform: translateZ(x) will trigger a browser’s hardware acceleration capabilities.

Conclusion

Vue lets us animate our elements with CSS and class or style bindings.

Categories
Vue 3

Vue 3 — Slot Shorthands

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at slot shorthands with Vue 3.

Shorthand Syntax for Default Slots

If we have one slot, then we don’t need the template element.

We can put the v-slot directive right on the opening component tag.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items v-slot="slotProps">
        <b>{{ slotProps.item.name }}</b>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the v-slot directive on the component tag, which is only allowed when we have one slot in our component.

Destructuring Slot Props

Slot props can be destructured.

Since we have an object as the value of v-slot , we can destructure its properties.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items v-slot="{ item: { name } }">
        <b>{{ name }}</b>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We destructured the properties of the slotProps object, so instead of writing:

<items v-slot="slotProps">
  <b>{{ slotProps.item.name }}</b>
</items>

we have:

<items v-slot="{ item: { name } }">
  <b>{{ name }}</b>
</items>

This can make templates much cleaner especially if we have many slots.

It’s also easy for us to provide fallbacks.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items v-slot="{ item: { name = 'placeholder' } }">
        <b>{{ name }}</b>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, {}]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We set the default value of the name property to 'placeholder' so that we can display that if nothing is set for the name property.

Dynamic Slot Names

Slot names can be dynamic.

So we can write:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

where dynamicSlotName is the variable with the slot name.

Named Slots Shorthand

Named slots can be shorted with the # sign.

For example, instead of writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post>
        <template v-slot:header>
          <h1>header</h1>
        </template>
        <template v-slot:default>
          <p>lorem ipsum.</p>
        </template>
        <template v-slot:footer>
          <p>footer</p>
        </template>
      </blog-post>
    </div>
    <script>
      const app = Vue.createApp({});
      app.component("blog-post", {
        template: `
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        `
      });
      app.mount("#app");
    </script>
  </body>
</html>

We can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post>
        <template #header>
          <h1>header</h1>
        </template>
        <template #default>
          <p>lorem ipsum.</p>
        </template>
        <template #footer>
          <p>footer</p>
        </template>
      </blog-post>
    </div>
    <script>
      const app = Vue.createApp({});
      app.component("blog-post", {
        template: `
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        `
      });
      app.mount("#app");
    </script>
  </body>
</html>

# is short for v-slot: .

Conclusion

There’s a shorthand for named slots and also there’s a shorthand for default slots.