We're live-coding on Twitch! Join us!
Class Components in Vue are No Longer Happening

Class Components in Vue are No Longer Happening

An upcoming Vue update was set to have classes implemented. In React and Angular, we can create components using JavaScript classes. Some people prefer this way of component creation as it can lead to better readability. It can be a confusing tool though since people start to think of JavaScript classes as classes in other languages that have inheritance. JavaScript classes are just syntactical sugar over JavaScript functions however and classes can lead to a bit of confusion.

In Vue, we create components using objects like so:

// standalone
new Vue({ })

// using the CLI
<script>
export default { }
</script>

There was a proposal started on February 26, 2019 on GitHub that would allow us to create components with classes in addition to objects. This was targeted for the Vue 3.0 release.

What would Vue classes have looked like?

Here were the initially proposed classes:

In Browser

class App extends Vue {
  // options declared via static properties (stage 3)
  // more details below
  static template = `<div @click="increment">
      {{ count }} {{ plusOne }}
    </div>`

  // reactive data declared via class fields (stage 3)
  // more details below
  count = 0

  // lifecycle
  created() {
    console.log(this.count)
  }

  // getters are converted to computed properties
  get plusOne() {
    return this.count + 1
  }

  // a method
  increment() {
    this.count++
  }
}

In Single File Components

<template>
  <div @click="increment">
    {{ count }} {{ plusOne }}
    <Foo />
  </div>
</template>

<script>
import Vue from 'vue'
import Foo from './Foo.vue'

export default class App extends Vue {
  static components = {
    Foo
  }

  count = 0

  created() {
    console.log(this.count)
  }

  get plusOne() {
    return this.count + 1
  }

  increment() {
    this.count++
  }
}
</script>

Why classes for Vue components?

Pulled directly from the RFC on GitHub:

Vue's current object-based component API has created some challenges when it comes to type inference. As a result, most users opting into using Vue with TypeScript end up using vue-class-component. This approach works, but with some drawbacks:

  • Internally, Vue 2.x already represents each component instance with an underlying "class". We are using quotes here because it's not using the native ES2015 syntax but the ES5-style constructor/prototype function. Nevertheless, conceptually components are already handled as classes internally.
  • vue-class-component had to implement some inefficient workarounds in order to provide the desired API without altering Vue internals.
  • vue-class-component has to maintain typing compatibility with Vue core, and the maintenance overhead can be eliminated by exposing the class directly from Vue core.

The primary motivation of native class support is to provide a built-in and more efficient replacement for vue-class-component. The affected target audience are most likely also TypeScript users.

The API is also designed to not rely on anything TypeScript specific: it should work equally well in plain ES, for users who prefer using native ES classes.

Note we are not pushing this as a replacement for the existing object-based API - the object-based API will continue to work in 3.0.

Classes are abandoned. What now?

There are two major reasons why the Class API proposal was dropped:

Composition functions and Classes and Objects would allow us to make the same component 3 different ways. Vue has always focused on developer experience so it's comforting to see them try to simplify the developer experience again. They feel that 3 ways to do the same thing is not the best.

Composition Functions

With the coming composition functions, TypeScript support is one of the main benefits. Support is better in this approach than in the classes approach.

With the two new APIs #22 Advanced Reactivity API and #23 Dynamic Lifecycle Injection, we have a new way of declaring component logic: using function calls.. These are inspired by React Hooks.

In composition functions, a component's logic will happen in a new setup() method. It is pretty much data() but gives us more flexibility using function calls inside of it.

// everything tree-shakable
import {
  value,
  computed,
  watch,
  onMounted,
  inject
} from 'vue'

const App = {
  // same as before
  props: {
    a: String,
    b: Number
  },

  // same as before
  components: {
    // ...
  },

  setup(props) {
    // data
    const count = value(1)

    // computed
    const plusOne = computed(() => count.value + 1)

    // methods
    function inc() {
      count.value++
    }

    // watch
    watch(() => props.b + count.value, val => {
      console.log('changed: ', val)
    })

    // lifecycle
    onMounted(() => {
      console.log('mounted!')
    })

    // dependency injection
    const injected = inject(SomeSymbol)

    // other options like el, extends and mixins are no longer necessary

    // expose bindings on render context
    // any value containers will be unwrapped when exposed
    // any non-containers will be exposed as-is, including functions
    return {
      count,
      plusOne,
      inc,
      injected
    }
  },

  // template: `same as before`,

  render({ state, props, slots }) {
    // `this` points to the render context and works same as before (exposes everything)
    // `state` exposes bindings returned from `setup()` (with value wrappers unwrapped)
  }
}

Conclusion

I'm excited to see where the Vue team goes with these "composition functions". I like the idea of thinking of our components more as composed parts since that's more in line with a JavaScript way of thinking. Classes lead to people thinking in a more object-oriented way.

This also leans towards a thought process that is similar to how React is moving with React Hooks. The "composition functions" also allow for better TypeScript support which in turn leads to a better developer experience and tooling.

I'm looking forward to seeing where Vue goes next. I don't mind Vue's way of declaring components with objects and it's looking good with the way they are sticking with it.

What are your thoughts on the dropping of the classes proposal?

Like this article? Follow @chrisoncode on Twitter