All Articles

First Impressions of Vue: A React Developer's Perspective

Image from Unsplash by Ben Dutton
Image from Unsplash by Ben Dutton

Time flies! I took a few months off from the site because I didn’t have a personal Macbook and wasn’t keen on using my company-issued laptop. The M1 Air I ordered some time ago just arrived, so I’m back at it again.

Over the last few weeks, I’ve been putting together screens using Vue components (for better or worse, with Vue 2.x, not Vue 3). This post outlines my initial impressions of some of Vue’s most basic features.

Coming from a React background, I’ve been quite pleased with what I’ve seen so far, but there are a few minor quirks that I still haven’t quite gotten used to.

Template Readability

Vue templates aren’t terribly different from React ones — we’re pretty much still dealing with representations of HTML.

To control the dynamic behaviors of templates, Vue developers use directives. Directives can be thought of as special attributes that allow us to “bind” pieces of data and rendering logic. For example, we can use v-if and v-else to define control flows in our rendering codepaths:

// 1. Template
<template>
  <div>
    <my-title>{{ title }}</my-title>
    <my-subtitle v-if="showSubtitle" class="foo">
      {{ subtitle }}
    </my-subtitle>
  </div>
</template>

// 2. Script
// ...

// 3. Styles
// ...

In Single File Components, our presentational layer (HTML) sits by itself and isn’t mixed up with the interactivity (JavaScript) or styling (CSS) layers. Compare this with how the same thing might be written in React:

return (
  <div>
    <myTitle>{title}</myTitle>
    {showSubtitle && <mySubtitle class="foo">{subtitle}</mySubtitle>}
  </div>
);

I do feel React has a tiny edge over Vue in readability. In the React snippet, you almost immediately notice that <mySubtitle /> is being conditionally rendered. In the Vue snippet, you don’t really know that until your eyes go over the attributes inside <my-subtitle />. It’s an extremely small difference that can compound itself in older, bloated templates.

Choice of Syntax

One thing that might throw a React developer off is that showSubtitle, in the example, is meant to reference a JavaScript variable, but in Vue, it almost looks like we’re passing an arbitrary string to an attribute. Some templating directives, such as v-for, can make this practice of writing code in quotes feel rather odd:

<template>
  <div>
    <my-item v-for="item in items" :key="item.id">
      {{ item.name }}
    </my-item>
  </div>
</template>

Here, Vue’s v-for="item in items" allows us to iteratively render items in a list. It reads a bit like Python, and doesn’t quite work the same way JavaScript does (in JavaScript you’d write item of items to cycle through elements in a list, and Vue accepts this too to return you the same output).

We’re always going to need some kind of syntax that stitches both aspects of our code together. To me, React’s JSX feels like the more natural approach, because most experienced web developers already understand what it means to work with JavaScript, a native tool, in a vanilla environment.

To be clear, Vue does allow you to write JSX if you wish to. That said, the impression I get from the docs is that it’s more of an alternative approach to templating than the default go-to one.

Reactivity

In Vue, state changes and re-renders can easily be triggered with just a property reassignment.

// Vue component
// ...
methods: {
  onClick() {
    this.showSubtitle = !this.showSubtitle;
  }
}

No fancy updating of state with setState() as we do in the React world. I found this a slightly odd at first, but it grew on me. The Vue docs has a section covering Reactivity in Depth which provides great insight into how this works with JavaScript Proxies.

For new web developers, both React and Vue present learning curves that are somewhat analogous to one another:

// Vue state manipulation
this.foo = ["bar", ...this.foo.slice(1)] // ✅
this.foo[0] = "bar"; // 🚫

I suspect it’ll be easier for beginners to remember to use setState() for all state changes in React, than it’ll be for them to remember the caveats associated with modifying arrays and objects in Vue. For those of us familiar with either framework, adapting shouldn’t be too much of a problem given how often we work with non-primitives in state.

Computed Properties

A Vue feature I appreciate is the ability to define computed properties, which automatically update themselves in response to changes to props or internal state:

// Vue component
// ...
props: {
  name: {
    type: String,
  } 
},
data() {
  return {
    hobby: ''
  }
},
computed: {
  blurb() {
    return `Name: ${this.name}, Hobby: ${this.hobby}`
  }
}

If the dependencies (i.e. this.name, this.hobby) don’t change, then Vue simply references the cached computed value. I particularly like how Vue clearly demarcates a space for defining these pieces of derived data. To be fair, achieving the same thing with React hooks is easy too:

// React functional component
// ...
const { name } = props;
const [hobby, setHobby] = useState('');
// Caching as a premature optimization 
const blurb = useMemo(() => `Name: ${name}, Hobby: ${hobby}`, [
  name, hobby
]);

On the other hand, in a class-based React component, you’d have to compute the blurb somewhere within your render functions, which can be a little awkward if the same computation is done across many of these:

// React class component
// ...
renderHeader() {
  const blurb = getBlurb(this.props.name, this.state.hobby)
  // ...
}

renderFooter() {
  const blurb = getBlurb(this.props.name, this.state.hobby)
  // ...
}

Watchers

In the same spirit, Vue offers watchers for running expensive / async operations in response to data changes.

It’s worth noting that data is here a broad term that may refer to any field on your component. This means that you can explicitly define operations to run even when your computed properties (which are themselves the product of computations over other pieces of state) are updated:

// Vue Component
// ...
watch: {
  // When this.blurb is updated
  blurb() {
    // Run this.executeAsync()
    this.executeAsync();
  }
}

This is pretty neat. With React’s useEffect(), we can easily put together something similar as well. Conversely, you can imagine how trying doing the same in class-based React components might be somewhat clunky:

// React class component
// ...
componentDidUpdate(prevProps, prevState) {
  if (
    getBlurb(prevProps.name, prevState.hobby) !==
    getBlurb(this.props.name, this.props.hobby)
  ) {
    this.executeAsync();
  }
}

Concluding Thoughts

To crudely summarize: I prefer Vue 2’s approach over React classes, but I also find functional React components a joy to write. All in all, both frameworks measure up pretty well against each other so far and I have no intention of dwelling solely in one camp.

We’ve barely scratched the surface here. There’s more I’d cover but this post is running a little longer than I’d hoped. As I invest more time in Vue, I’ll pay attention to broader differences between the framework and React (in areas such as developer tooling, performance, related libraries, etc).

Also, a lot of the differences I’ve called out are negligible to an extent. As I work with more developers from different backgrounds, it’s become clear that consistent team-wide standards (enforced with static analysis tools and well-documented guidelines) are as, if not more important than the choice of tools.

There’s still plenty I can learn from the Vue ecosystem — I’ve not even fiddled with Vue 3 yet! Who knows, I might come back in a few weeks singing a different tune.