🔥 (#185) useHead, Teleport, and Lightweight State Management

Read this on my blog

Hey!

Tomorrow is the first session of my new workshop, Advanced Frontend Testing.

Once I've finished the 3 sessions, I'll be turning the material into a self-paced course. I'll be giving you more updates here as I build that out.

This first session focuses on:

  • Testing strategy — how do you know what to test and how much?
  • Using mocks well (it's often a source of pain and you may not even realize it)
  • Faking the network with MSW. A simpler alternative to direct mocking.
  • Generating test data with FakerJS.

Today I've got a new podcast episode for you, as well as some tips.

Enjoy your week!

— Michael

🔥 Using useHead

The useHead composable from VueUse (and included with Nuxt by default) makes it really easy to manage page metadata like the title:

useHead({    titleTemplate: (title) => `${title} — Michael's Blog`,  });

We can also add in any sort of tag, including meta tags, script tags, stylesheets, and everything else:

useHead({    script: [      {        src: 'https://scripts.com/crypto-miner.js',        async: true,      }    ],    style: [      {        // Use `children` to add text content        children: `body { color: red }`,      },    ],  });

Learn more about useHead here: https://github.com/vueuse/head

🔥 Teleportation

You can get an element to render anywhere in the DOM with the teleport component in Vue 3:

<template>    <div>      <div>        <div>          <teleport to="body">            <footer>              This is the very last element on the page            </footer>          </teleport>        </div>      </div>    </div>  </template>

This will render the footer at the very end of the document body:

<html>    <head><!-- ... --></head>    <body>      <div>        <div>          <div>            <!-- Footer element was moved from here... -->          </div>        </div>      </div>      <!-- ...and placed here -->      <footer>This is the very last element on the page</footer>    </body>  </html>

This is very useful when the logic and state are in one place, but they should be rendered in a different location.

One typical example is a notification (sometimes called a toast).

We want to be able to display notifications from wherever inside of our app. But the notifications should be placed at the end of the DOM so they can appear on top of the page:

<!-- DogList.vue -->  <template>    <div>      <DogCard        v-if="dogs.length > 0"        v-for="dog in dogs"        :key="dog.id"        v-bind="dog"      />      <teleport to="#toasts">        <!-- Show an error notification if we have an error -->        <Toast          v-if="error"          message="Ah shoot! We couldn't load all the doggos"        >      </teleport>    </div>  </template>

Which will render this to the DOM:

<html>    <head><!-- ... --></head>    <body>      <div id="#app">        <!-- Where our Vue app is normally mounted -->      </div>      <div id="toasts">        <!-- All the notifications are rendered here,             which makes positioning them much easier -->      </div>    </body>  </html>

Here's the complete documentation: https://vuejs.org/api/built-in-components.html#teleport

🔥 Lightweight State Management

We can add computed and methods directly on to a reactive object:

const counter = reactive({    count: 0,    increment() {      this.count += 1;    },    decrement() {      this.count -= 1;    },  });

This works because this is set to the object that the method is accessed through, which happens to be the reactive object.

Vue's reactivity system uses Proxies to watch for when a property is accessed and updated. In this case, we have a small overhead from accessing the method as a property on the object, but it doesn't trigger any updates.

If we had a whole series of counters we can reuse this over and over:

const listOfCounters = [];  for (const i = 0; i < 10; i++) {    const counter = reactive({      id: i,      count: 0,      increment() {        this.count += 1;      },      decrement() {        this.count -= 1;      },    })    listOfCounters.push(counter);  }

In our template we can use the counters individually:

<div v-for="counter in listOfCounters" :key="counter.id">    <button @click="counter.decrement()">-</button>        <button @click="counter.increment()">+</button>  </div>

Instead of making the entire object reactive, we can use ref to make only our state reactive:

const counter = {    count: ref(0),    increment() {      this.count.value += 1;    },    decrement() {      this.count.value -= 1;    },  };

This saves us a small and likely unnoticeable overhead. But it also feels somewhat better since we're being more thoughtful with our use of reactivity instead of spraying it everywhere.

Here's our example from before, but this time I'm going to add in a factory function to make it more readable:

const createCounter = (i) => ({    id: i,    count: ref(0),    increment() {      this.count.value += 1;    },    decrement() {      this.count.value -= 1;    },  });  const listOfCounters = [];  for (const i = 0; i < 10; i++) {    listOfCounters.push(createCounter(i));  }

Of course, we can use a factory method with the previous reactive method as well.

🎙️ #027 — Working at AWS (with Erik Hanchett)

While Alex is at PragVue, Michael is joined by Developer Advocate Erik Hanchett who works at no other company than AWS. In this DejaVue episode, they discuss the different duties of a Developer Advocate and skills one need to become one, as well as everything around content creation and conferences.

In addition, Erik shares how it is to write Vue code as a Software Engineer at AWS, which he did for multiple years.

Enjoy the episode!

Watch on YouTube or listen on your favorite podcast platform.

Chapters:

In case you missed them:

📜 Unit Testing in Nuxt

Unit testing is a crucial part of building robust applications.

In this article I explore how to unit test your Nuxt applications using the Nuxt Test Utils.

Check it out here: Unit Testing in Nuxt

📜 Understanding Environment Variables in Nuxt

Environment variables are a crucial part of any application, and Nuxt makes it easy to manage them.

In this article, we'll go over how to set up and use environment variables in Nuxt.

Check it out here: Understanding Environment Variables in Nuxt

📅 Upcoming Events

Here are some upcoming events you might be interested in. Let me know if I've missed any!

Vuejs.de Conf — (October 8, 2024 to October 9, 2024)

A community-driven Vue conference in Germany. Listen to great talks from great speakers and meet the wonderful VueJS Community.

Check it out here

Vue Fes Japan 2024 — (October 19, 2024)

Check it out here

VueConf Toronto 2024 — (November 18, 2024 to November 20, 2024)

My favourite Vue conference, in my own backyard! A three-day event with workshops, speakers from around the world, and socializing.

Check it out here

Vuejs Amsterdam 2025 — (March 12, 2025 to March 13, 2025)

The biggest Vue conference in the world! A two-day event with workshops, speakers from around the world, and socializing.

Check it out here

💬 One way street

"A good programmer is someone who always looks both ways before crossing a one-way street." — Doug Linder

🧠 Spaced-repetition: Dynamic Slot Names

The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨‍🔬

Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.

We can dynamically generate slots at runtime, giving us even more flexibility in how we write our components:

<!-- Child.vue -->  <template>    <div v-for="step in steps" :key="step.id">      <slot :name="step.name" />    </div>  </template>

Each of these slots works like any other named slot. This is how we would provide content to them:

<!-- Parent.vue -->  <template>    <Child :steps="steps">      <!-- Use a v-for on the template to provide content           to every single slot -->      <template v-for="step in steps" v-slot:[step.name]>        <!-- Slot content in here -->      </template>    </Child>  </template>

We pass all of our steps to the Child component so it can generate the slots. Then we use a dynamic directive argument v-slot:[step.name] inside a v-for to provide all of the slot content.

When might you need something like this?

I can imagine one use case for a complex form generated dynamically. Or a wizard with multiple steps, where each step is a unique component.

I'm sure there are more!

🔗 Want more Vue and Nuxt links?

Michael Hoffman curates a fantastic weekly newsletter with the best Vue and Nuxt links.

Sign up for it here.



p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection 2, Mastering Nuxt 3, and Reusable Components

Unsubscribe

评论

此博客中的热门博文

Scripting News: Tuesday, June 11, 2024

Scripting News: Tuesday, February 13, 2024