Learning Vue as a React dev

Here are my thoughts

I work daily with React. In recent days I decided to try Vue for the first time.
I enjoyed the experience, so I decided to share my thoughts.

Frontend Framework

Do you know Create React App? Class Components? React Hooks? Next.js?

Great, so you know why their Vue equivalent exists.

To scaffold a project with a lot of DX stuff already configured, React has Create React App, Vue has Vue CLI. Recently, both Create React App and Vue CLI are in maintenance mode. Now is recommended to use Vite for both, unless you use a meta framework (Next.js and Nuxt).

React has Class Components and Vue has Options API. React has React Hooks and Vue Composition API. React has Next.js and Vue Nuxt.js.

Key differences, React vs Vue

The ecosystem

React is a library, Vue is a framework.

This means that Vue has a wider set of official tools, integrated as a whole "framework". React, on the other end, need to be extended by third-party tools.

In Vue, the core team defines the way, in React, the community drives everything.
I'm not saying that Vue doesn't have an active community, but that Vue team guides a lot more than React one.

What do I mean by "ecosystem"?

Think about these common "parts" of a frontend app:

  • Client Side Routing, which handles the navigation between different pages of your app, even if the browser never redirects actually (the core of a SPA, Single Page Application)

  • Global State Management, which handles the share of state between multiple components without passing props down to every level of the app tree.

How do you solve them?

The Vue team authored Vuex and Pinia for Global State Management, and Vue Router for Client Side Routing.

React has no official tools, but this means that a lot of third-party good libraries have emerged over time to fill the gap. Like Redux for Global State Management and React Router or Reach Router for Client Side Routing.

Think about this as ...

React gives the world an essential building block for handling reactivity in a Frontend App and, but has no official way to structure the rest of the app, leaving this duty to the community.
Vue instead gives the reactivity system and also a toolbelt with a solid opinion on how to handle everything, so there is an official package for every common problem.

No best or worst.
Both are great declarative libraries, with a good DX.
Don't be a blind fanboy :)

The render runs only once

In react, a render function or a function component re-runs after every state changes.
React compare the result with the current DOM state and decide if to "reconcile" or not.

Instead, in Vue, the template (the render function equivalent) is run only once.
I don't know much about how internal works in Vue, but if you put a console.log inside the script portion of a template it will run only once.

No need for dependencies array

Vue reactivity system is different from React one.

In React you must optimize your code using useMemo , useCallback and other things to avoid unnecessary computation and bugs.
I'm referring mainly to the dependencies array.

In Vue all these duties are in charge of the library.
Vue takes care of analyzing and understanding when a piece of code, that depends on some reactive values needs to be recalculated.
No need to define dependencies.

Beginners could find this a winning point in favor of Vue.

A brief history of Vue

History

Vue.js was first released in February 2014 by ex-Google engineer Evan You.
It is an open-source progressive JavaScript framework used for building user interfaces (UIs) and single-page applications.

The Options API was the original API for Vue.js.
The Composition API was introduced later in Vue 3.0, which was released in September 2020.

Evolution: Options API and Composition API

Let's be honest, 99,9% of React nowadays is written in hook style. But imagine that in your work you inherit a legacy project written in Class Components... better you don't feel lost.

So a React beginner should spend a little bit of time on Class Components even if the standard way of writing React is with Hooks.
The same with Vue.
Don't you agree? Let me know it.

Vue initial API is now called "Options API" and is considered legacy.
Vue 3 introduced a new style of writing Vue, that is now the standard,and it is known as "Composition API".

React has had the same evolution, with React Hooks as successor of Class Components.

Compare Vue and React

Take this React class component

import React from 'react';

class Counter extends React.Component {
  constructor(props) {
    super(props);

    // decalare local state
    this.state = {
      count: 0
    };

    // bind "this" to event handlers
    this.handleIncrementClick = this.handleIncrementClick.bind(this);
    this.handleDecrementClick = this.handleDecrementClick.bind(this);
  }

  // define event handlers
  handleIncrementClick() {
    this.setState({ count: this.state.count + 1 });
  }
  handleDecrementClick() {
    this.setState({ count: this.state.count - 1 });
  }

  // render the template
  render() {
    return (
      <div>
        <h1>Counter: {this.state.count}</h1>
        <button onClick={this.handleIncrementClick}>+</button>
        <button onClick={this.handleDecrementClick}>-</button>
      </div>
    );
  }
}

In this class component, we define a state object that includes a count property, which will hold the current value of the counter. We also define two methods, handleIncrementClick and handleDecrementClick, which increment and decrement the count property of the state object, respectively.

In the render method, we display the current value of count in an h1 element, and we include two buttons that call the handleIncrementClick and handleDecrementClick methods, respectively, when clicked. When either button is clicked, the count property of the state object is updated, which triggers a re-render of the component and updates the displayed value of the counter.

This is the Vue counterpart in Options API

<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <button @click="incrementCount">+</button>
    <button @click="decrementCount">-</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    incrementCount() {
      this.count++;
    },
    decrementCount() {
      this.count--;
    }
  }
}
</script>

The data function must return an object that is the local state.
Under methods, you defined incrementCount and decrementCount. Vue under the hood takes care of "binding" the this instance to "methods" handlers, so we don't have to. In addition, updating the local state is done in a mutable fashion, instead of React's immutable.
Vue uses Proxy under the hood, so, in reality, you are not mutating the "real" state object but the proxied one. But from a developer's point of vue view it's less code.

But the overall component code is almost identical.

Ok, this was Class Components vs Options API.
Now let's take a look to React Hook vs Composition API.

React

const Counter = () => {
  const [count, setCount] = React.useState(0);
  const incrementCount = () => setCount(prev => prev+1);
  const decrementCount = () => setCount(prev => prev-1);

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={incrementCount}>+</button>
      <button onClick={decrementCount}>-</button>
    </div>
  )
}

Vue Composition API

<script setup>
import {ref} from 'vue';

const count = ref(0);
const incrementCount = () => count.value++;
const decrementCount = () => count.value--;
</script>

<template>
  <div>
    <h1>Counter: {{count}}</h1>
    <button @click="incrementCount">+</button>
    <button @click="decrementCount">-</button>
  </div>
</template>

They look similar.

For a good description of the entire reactivity system of Vue read Template Syntax and Reactivity Fundamentals. This is not a tutorial, so take a look there if something is not clear...

The Vue way

It's time to explore some Vue distinctive stuff, that makes it different from React.

v-model

Take this React code that renders an input

const InputComponent = () => {
  const [inputValue, setInputValue] = useState('');
  const handleChange = (e) => setInputValue(e.target.value)

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleChange}
      />
      <p>You entered: {inputValue}</p>
    </div>
  )
}

... and look it in Vue with Composition API

<script setup>
import {ref} from 'vue';
const inputValue = ref('');
</script>

<template>
  <div>
    <input type="text" v-model="inputValue" />
    <p>You entered: {{ inputValue }}</p>
  </div>
</template>

... and Options API

<template>
  <div>
    <input type="text" v-model="inputValue" />
    <p>You entered: {{ inputValue }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      inputValue: ''
    }
  },
}
</script>

A lot less code.
Great, don't you think???

v-model does all the boring stuff of the Two-Way Data Binding between template and component instance.

This is a great DX improvement of Vue, when compared to React.

Can I use the React way? Yes, in Vue you can use the React "way" if you want.

<script setup>
  const inputValue = ref('');
</script>

<input
  :value="inputValue"
  @input="(event) => inputValue.value = event.target.value"
/>

computed

Computed data, is data that is derived from the state, and should be automatically recalculated on every state update.

Let's augment the Counter example with a doubledCount derived value.

In React

const Counter = () => {
  const [count, setCount] = React.useState(0);

  const doubledCount = count * 2;
  // here a useMemo version if computation is expensive
  // const doubledCount = useMemo(() => count * 2, [count]);

  const incrementCount = () => setCount(prev => prev+1);
  const decrementCount = () => setCount(prev => prev-1);

  return (
    <div>
      <h1>Count: {count}</h1>
      <h1>Doubled Count: {doubledCount}</h1>
      <button onClick={incrementCount}>Increment</button>
      <button onClick={decrementCount}>Decrement</button>
    </div>
  );
}

...in Vue Composition API

<script setup>
import {ref, computed} from 'vue';
const count = ref(0);
const doubledCount = computed(() => count.value * 2); 
</script>

<template>
  <div>
    <h1>Count: {{ count }}</h1>
    <h1>Doubled Count: {{ doubledCount }}</h1>
    <button @click="incrementCount">Increment</button>
    <button @click="decrementCount">Decrement</button>
  </div>
</template>

... and Options API

<template>
  <div>
    <h1>Count: {{ count }}</h1>
    <h1>Doubled Count: {{ doubledCount }}</h1>
    <button @click="incrementCount">Increment</button>
    <button @click="decrementCount">Decrement</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2;
    }
  },
  methods: {
    incrementCount() {
      this.count++;
    },
    decrementCount() {
      this.count--;
    }
  }
}
</script>

You can think of "computed" as useMemo without dependency.
Not so much difference between React and Vue here.

Emitting events

Think about a parent component, that renders a child component and wants to be notified when the child is clicked.

In React you "pass" down a function via props.

import React from 'react';

const ChildComponent = ({ onClick }) => (
  <button onClick={onClick}>Click me!</button>
);

const ParentComponent = () => {
  const handleChildClick = () => {
    console.log('Child is clicked!!!!!');
  }

  return (
    <div>
      <h1>Hello, world!</h1>
      <ChildComponent onClick={handleChildClick} />
    </div>
  );
}

in Vue instead you "emit" an event (like in native DOM elements) from the child component and "listen" to that event in the parent component.

This is Vue Composition API

<script setup>
import { defineEmits } from 'vue';

// define all event that this component can emit...
const emits = defineEmits(['clicked']);

const handleClick = () => {
  emits('clicked');
}
</script>

<template>
    <button @click="handleClick">Click me!</button>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';

const handleChildClick = () => {
  console.log('Child is clicked!!!!!');
}
</script>

<template>
 <div>
    <h1>Hello, world!</h1>
    <ChildComponent @clicked="handleChildClicked" />
  </div>
</template>

... and Options API

<template>
  <div>
    <h1>Hello, world!</h1>
    <ChildComponent @clicked="handleChildClicked" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  name: "ParentComponent",
  // 
  // this registration of "components" is required 
  // before using a child component with Options API.
  components: {
    ChildComponent
  },
  methods: {
    handleChildClicked() {
      console.log('Hello');
    }
  }
}
</script>
<template>
  <button @click="handleClick">Click me!</button>
</template>

<script>
export default {
  name: "ChildComponent",
  methods: {
    handleClick() {
      this.$emit('clicked');
    }
  }
}
</script>

The idea of emitting an event has the advantage that from the ChildComponent code there is no need to check if his parent has provided a function or not (like you would do in react when the "onClick" prop is optional).
The child emits the event anyway, and if someone is listening to it will be notified.

Conclusion

I started learning Vue with Options API, founding it a bit verbose compared to React Hooks.
But when tried Composition API I felt at home.
The code is readable and without a "class-like" boilerplate, and the overall experience is great.

Vue has some "magic", that makes code easier to read.
The only downside is that it feels weird to write js code inside quotes like it was a string as happens in HTML attributes.


And you?
What do you think about Vue?

Do you prefer it over React?
Why?