Nikhil Verma

Moving from React to Vue? Here's what to expect

This is a mirror of the original post on dev.to. For discussion and comments, please visit the original post.

I moved from a React to a Vue project two years ago from a company change. If you are about to undergo a similar journey, this post will help.

I will not cover the basic differences between them. I will instead attempt to explain the conceptual differences that I found when switching.

Embrace Vue’s reactivity

My favourite part about Vue is that you can simply modify state. Vue will track the changes and update the computed properties and UI automatically. In React this is done explicitly via setters or using libraries which internally do the same.

Before joining the Vue project I had read how mutating state is bad because you can’t track where the changes are coming from. While this is a valid point, in practice a reasonable data organisation and development discipline makes it a non issue.

The productivity gains from modifying a state property and knowing that the UI will update automatically are incredible.

This also integrates well with effects and computed properties. You don’t need to pass dependencies in your effects/computed properties, it “just works”.

Here is an example code that works in Vue out of the box.

/* state.js */
import { ref } from "vue";
export const count = ref(0);

/* App.vue */
<script setup lang="ts">
import { count } from "./state";
</script>

<template>
    <div>The count is {{ count }}</div>
    <button @click="count++">count++</button>
</template>

Doing something similar in React will need a state management library.

Know your Vue version

There is a big difference between Vue 2 and 3 experiences. With the latter being more polished and better supported in the modern web development experience.

I have written about the pain we went through when migrating from Vue 2 to 3.

In React, you wouldn’t have to worry about this as there haven’t been major breaking API changes between version updates.

Vue 2 will reach end of life support on December 31st, 2023. My advice would be to plan a migration to Vue 3 as soon as possible.

Understand the core difference between Vue templates and JSX

API differences aside, you must have a basic understanding of how Vue templates operate internally and how they differ from JSX.

Vue templates are closer to HTML, JSX is closer to Javascript. (You might have heard the phrase “JSX is Javascript”).

In JSX you can refer to local variables, use Javascript expressions and use JSX inline with other JS code.

In Vue you must define them inside a template block. They can only refer to values you have either provided in your component definition (options API) or present in your setup scope (composition API).

You also can’t easily split Vue templates into inline code like JSX. They must be defined in a separate SFC file or a string template inside a render function (which needs runtime compilation).

An example of what I am talking about. Let’s take two bits of Vue and React code. They basically behave in the same way. And let’s compare their code (as you would write it) and their compiled output.

React

import HelloWorld from "./components/HelloWorld";
import Header from "./components/Header";
import HeaderAlt from "./components/HeaderAlt";

const App = () => {
  const isAlternateHeader = Math.random() > 0.5;
  const items = [1, 2, 3];

  return (
    <HelloWorld
      msg="Vite + React"
      header={isAlternateHeader ? <HeaderAlt /> : <Header />}
    >
      {items.map((item) => (
        <div {...{ [isAlternateHeader ? "alt-header" : "header"]: true }}>
          {item}
        </div>
      ))}

      {isAlternateHeader ? "Alt header" : "Header"}
    </HelloWorld>
  );
};

export default App;

React (as compiled by Babel)

import HelloWorld from "./components/HelloWorld";
import Header from "./components/Header";
import HeaderAlt from "./components/HeaderAlt";
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";

const App = () => {
  const isAlternateHeader = Math.random() > 0.5;
  const items = [1, 2, 3];
  return /*#__PURE__*/ _jsxs(HelloWorld, {
    msg: "Vite + React",
    header: isAlternateHeader
      ? /*#__PURE__*/ _jsx(HeaderAlt, {})
      : /*#__PURE__*/ _jsx(Header, {}),
    children: [
      items.map((item) =>
        /*#__PURE__*/ _jsx("div", {
          [isAlternateHeader ? "alt-header" : "header"]: true,
          children: item,
        }),
      ),
      isAlternateHeader ? "Alt header" : "Header",
    ],
  });
};

export default App;

Now let’s look at a similar Vue code

Vue

<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import Header from "./components/Header.vue";
import HeaderAlt from "./components/HeaderAlt.vue";

const isAlternateHeader = Math.random() > 0.5;
const items = [1, 2, 3];
const dynamicAttribute = isAlternateHeader ? "alt-header" : "header";
</script>

<template>
  <HelloWorld msg="Vite + Vue">
    <template #header>
      <HeaderAlt v-if="isAlternateHeader" />
      <Header v-else />
    </template>

    <div :[dynamicAttribute]="true" v-for="item in items" :key="item">
      {{ item }}
    </div>

    {{ isAlternateHeader ? "Alt header" : "Header" }}
  </HelloWorld>
</template>

Vue (compiled by Vue SFC Playground)

import { defineComponent as _defineComponent } from "vue";
import {
  openBlock as _openBlock,
  createBlock as _createBlock,
  renderList as _renderList,
  Fragment as _Fragment,
  createElementBlock as _createElementBlock,
  unref as _unref,
  toDisplayString as _toDisplayString,
  normalizeProps as _normalizeProps,
  createElementVNode as _createElementVNode,
  createTextVNode as _createTextVNode,
  withCtx as _withCtx,
} from "vue";

import HelloWorld from "./components/HelloWorld.vue";
import Header from "./components/Header.vue";
import HeaderAlt from "./components/HeaderAlt.vue";

const __sfc__ = _defineComponent({
  __name: "App",
  setup(__props) {
    const isAlternateHeader = Math.random() > 0.5;
    const items = [1, 2, 3];
    const dynamicAttribute = isAlternateHeader ? "alt-header" : "header";

    return (_ctx, _cache) => {
      return (
        _openBlock(),
        _createBlock(
          HelloWorld,
          { msg: "Vite + Vue" },
          {
            header: _withCtx(() => [
              isAlternateHeader
                ? (_openBlock(), _createBlock(HeaderAlt, { key: 0 }))
                : (_openBlock(), _createBlock(Header, { key: 1 })),
            ]),
            default: _withCtx(() => [
              (_openBlock(),
              _createElementBlock(
                _Fragment,
                null,
                _renderList(items, (item) => {
                  return _createElementVNode(
                    "div",
                    _normalizeProps({
                      [_unref(dynamicAttribute) || ""]: true,
                      key: item,
                    }),
                    _toDisplayString(item),
                    17 /* TEXT, FULL_PROPS */,
                  );
                }),
                64 /* STABLE_FRAGMENT */,
              )),
              _createTextVNode(
                " " +
                  _toDisplayString(isAlternateHeader ? "Alt header" : "Header"),
                1 /* TEXT */,
              ),
            ]),
            _: 1 /* STABLE */,
          },
        )
      );
    };
  },
});
__sfc__.__file = "src/App.vue";
export default __sfc__;

The translation from JSX to JS is simple because tags map to function calls and props map to object properties.

Vue has a more custom DSL where more code is needed to translate it to an executable Javascript code. This also comes into play when using it with Typescript.

Vue’s Typescript support is… fine

If you are on a Vue project, you get scoped styles out of the box:

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>

Final thoughts

I still prefer JSX over Vue templates but I vastly prefer the ref/reactive/computed systems of Vue.

My ideal UI library will offer first class template syntax of JSX and reactivity of Vue. Vue does support JSX but it has it’s own set of gotchas.

Vue was designed with a certain set of tradeoffs, and it does them really well. If you have a Vue 3 codebase you will not find it too difficult to switch from React.

If you have a Vue 2 codebase however… Read my post here.