DEV Community

Sibasish Mohanty
Sibasish Mohanty

Posted on

From React to Vue: My Weekend with Reactive Magic

I spent this weekend poking around Vue for the first time—no hand-holding tutorials, just me, a blank editor, and the official docs. After six years of React, I expected some déjà vu 😰. Instead, I found fresh ergonomics, surprising simplicity, and a mental model that feels more… declarative 😇.

Templates: HTML First

Opening App.vue, I stared at plain HTML in a block. Interpolation uses {{ count }} instead of embedding JSX inside JavaScript. Control flow? Directives like v-if and v-for keep loops and branches in markup, not scattered across ternaries and map() in JSX. It felt like my linters and Prettier could finally focus on real HTML 😅.

<ul>
  <li
    v-for="item in items"
    :key="item.id"
    v-show="item.visible"
    :class="{ active: item.isActive }"
  >
    {{ item.label }}
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Compare with React:

<ul>
  {items.map(item =>
    item.visible ? (
      <li
        key={item.id}
        className={item.isActive ? 'active' : ''}
      >
        {item.label}
      </li>
    ) : null
  )}
</ul>
Enter fullscreen mode Exit fullscreen mode

Scoped Styles: Zero Boilerplate

Drop a <style scoped> block into your SFC and—bam—PostCSS tags your selectors with a unique ID. No CSS modules, no styled-components, no runtime style libs. Your .my-button { … } only affects this component.

<style scoped>
  .my-button { background: hotpink }
</style>
Enter fullscreen mode Exit fullscreen mode

In React, I’d import a CSS module or reach for Styled components (RIP 🥲). Vue treats scoped styles as first-class citizens.

Reactive State: ref() & reactive()

This is where Vue’s mental model clicked for me 😍. Instead of juggling multiple useState hooks, Vue treats your state as plain variables wrapped in reactive containers:

<script setup>
import { ref, reactive } from 'vue'

// Primitive state
const count = ref(0)

// Object/array state
const state = reactive({ items: [] })

// Mutations happen in place
function addItem(item) {
  state.items.push(item)
  count.value++
}
</script>
<template>
  <button @click="addItem('🍎')">
    Added {{ count }} item{{ count > 1 ? 's' : '' }}
  </button>
</template>
Enter fullscreen mode Exit fullscreen mode
  • ref() wraps a primitive so Vue can track .value changes and trigger updates—no setter functions needed.
  • reactive() uses a Proxy under the hood, so mutating state.items.push(...) or state.items[0] = 'x' instantly reflects in the DOM.

Vue’s mental model treats state like plain JS objects and arrays—you read and write them directly. The reactivity system and compiler-informed virtual DOM handle dependency tracking and efficient updates for you. It turns state management into “just JavaScript,” without boilerplate or indirection.

What's next?

That’s a wrap for today. Vue’s “just write HTML and JS” vibe is growing on me. The reactive system feels intuitive, the separation of concerns is clean, and I haven’t written a single custom hook yet 😉.

Next up, I’ll go deeper into component communication—passing props, emitting events, and managing local state across a small UI tree. Basically, how Vue handles what React devs usually reach for useContext or lifting state up to solve. Spoiler: It’s kinda elegant 😌.

Stay tuned, this is getting fun.

Top comments (3)

Collapse
 
thejaredwilcurt profile image
The Jared Wilcurt • Edited

Don't skip out on Options API. The main feature of using Vue's atomic reactivity functions (ref, computed, watch, etc) is they allow you to extend reactivity to outside of the component. But the vast majority of the components you'll write will not require that. So you should take advantage of the built in organizational structure the framework gives you. Also use ESLint-Plugin-Vue with all the settings maxed out. The best thing about Vue is being able to go to any component, in any codebase, written by any dev, and know instantly where everything is. No other framework offers that (even though they all should). But the only way to fully get this ability is with Options API and the linter plugin. All the teams at my company do this, and as I jump between their repos constantly as part of my job, it is so nice to just know where everything is without having to study every component to "figure out" whatever that person was thinking when they wrote it. Or to have to have 50 discussions a week to figure out the right way to organize code for this specific repo. What a waste of time, so happy the framework solves this for us.

Other things to compare:

  • Vue-Router to React-Router
  • Pinia 🍍 to the 900 bad state management options in the React ecosystem 🤮
  • Vue-Doxen 🐶 to Storybook 📙
  • (Vitest + HappyDOM + Vue-Test-Utils + Vue3-Snapshot-Serializer) vs (Jest + JSDOM + React Testing Library)

Also you can use <style module="$s"> to enable CSS modules in Vue components. Scoped styles are better for apps, CSS Modules are better for component libraries (for style scoping). But Vue makes all CSS stuff super easy (See: v-bind() in CSS).

Here's an Options API boilerplate with Vue-Router and Pinia already setup:

Collapse
 
enniovisco profile image
Ennio Visconti

There is nothing wrong in some self advertisement, but the Options API is slowly being abandoned for a reason, it is objectively verbose and hard to read. When I moved to Vue it helped me better understand what was happening under the hood, but after that I'd strongly discourage it: it needs a lot of memory and gets messy pretty quickly.

The switch to nuxt 3 has the benefit of making you really get rid of irrelevant details and having comments that just describe the essence of the presentation logic in a very clean way (ad the author correctly figured). I'd recommend following on with the series and giving this new approach a try :)

Collapse
 
sibasishm profile image
Sibasish Mohanty

Thank you so much, this is insightful