Cover

Did Vue.js Just Blink?

There is a lot of buzz around the internet about Vue.js 3.0’s announcement about a new composition model. There are a lot of questions about it and I think much of it is ‘they moved my cheese’ more than ‘they’re breaking everything’.

So let’s talk about it…

Evan You’s announcement in London last week scared a bunch of people, including me.

For clarity’s sake, I’m going to keep saying “he and his” but I’m aware that a bigger team works on Vue.js and that he’s not the only voice on the team.

I’ve invested pretty heavily in my belief in Vue.js. <shamelessPlug>Vue Course</shamelessPlug> Watching the evolution of any platform is a risk. Better ideas come in, bottlenecks are found, bad ideas are shelved. It’s the nature of the beast. What I keep hearing is “Is this like Angular 2 where I have to re-write everything”? Try and calm down.

You see this announcement is actually for a RFC (Request for Comments). He got what he asked for. A lot of comments. But let’s see what he’s actually suggesting.

New Syntax is Not Replacing Existing Syntax

I want to get this out of the way first. As of the comment’s he’s received, they’ve backtracked on making this new syntax the only syntax for creating components. All the old code will continue to work, though I am sure they want you to start moving to it when you start greenfield Vue 3.0 apps. So this clearly isn’t like the Angular 2. Angular 2 came with a lot of new concepts that changed the entire ecosystem (requiring several build steps, introducing advanced JavaScript/TypeScript features that weren’t approved yet, etc.). This isn’t that.

New Component Syntax

So the new syntax isn’t that revolutionary. In fact, I think it is simpler in some ways than the old system. Let’s look at an example straight out of the RFC:

import { value, computed, watch, onMounted } from 'vue'

export default {
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}

First you notice that it’s an object syntax with a setup function. This is easier to read (and teach) I think. Instead of these nested objects, everything at the same level. It also shows that we don’t want to instantiate the setup until it needs this component. The object syntax always had an async way to do it, but I think this is clearer and better.

Note that reactive state has a wrapper function value(0), while this is being proposed for primitive values to get around passing by value, it also guarantees that child state for complex objects is also made reactive (which is a pain point for many). I’m not in love with this, but I get why this is a laudable way to do it.

You can see the computed and methods are all first class citizens too. I think this is clearer than the magic of the data property in the old syntax.

You can also notice that the lifecycle methods are imported so it should allow for better linting for those of us that misname the functions at times ; ).

Lastly, the setup then returns an object that contains all the public interface of the component (see how the lifecycle hooks aren’t required). I think this is clearer.

This still feels terse, but with this syntax you have the power to compose your components instead (closures are fun, huh):

import { value, watch, onMounted, onUnmounted } from 'vue'
import { fetchPost } from './api'

function useFetch(props) {
  const isLoading = value(true)
  const post = value(null)

  watch(() => props.id, async (id) => {
    isLoading.value = true
    post.value = await fetchPost(id)
    isLoading.value = false
  })

  return {
    isLoading,
    post
  }
}

function useMouse() {
  const x = value(0)
  const y = value(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}

export default {
  setup(props) {
    return {
      ...useFetch(props),
      ...useMouse()
    }
  }
}

You can see better in this example how you could compose your components from different parts of your code. It could lead to more spaghetti code in the wrong hands, but I think overall it’s a good proposal. Yes, it means learning…but you know what i think. Your job as a developer is to continuously learn.

What about TypeScript

A lot of the ideas here are to support TypeScript more efficiently. But just because you’re using TypeScript doesn’t mean you should only be creating classes. Unfortunately this is an approach that a lot of Java/C# dev’s bring to TypeScript. Object and Function syntaxes are first-class citizens here.

The nature of the Vue API has always been about composing at runtime and that’s actually easier with objects and functional APIs than with class-based APIs. Anyway, Classes are just syntax sugar. Stretch your legs, it’s fine in here.

So, Did They Blink?

Sure they did (emphasis mine):

From the RFC

They backed away from deprecating the old APIs and I think this is the right approach. But I’m not quite on board with it. I think they should depreciate them, but not remove them. If this is the future, they should keep the APIs but show warnings in development that it will likely go away in 5.0 (yeah two full versions). But now that they scared a lot of dev’s, I doubt they’ll ever go away.

Here’s a link to the RFC so you can share your own thoughts:

Vue.js API RFC

As long as the framework is growing, i’ll be happy. Don’t be afraid, your cheese is safe…for now.