mrkeyoor.com_
Wed 05 Aug 05:05 UTC
npmWeb Frontendupdated 05 Aug 2026

vue

Vue is a frontend framework for building web UIs with HTML-based templates and a fine-grained reactivity system. You write single-file components (.vue files) that combine template, script and scoped styles, and Vue tracks exactly which state each piece of the DOM depends on, so updates are automatic and precise. It scales down to a script tag progressively enhancing a server-rendered page and up to full SPAs with the official router, Pinia for state, and Nuxt for SSR. MIT licensed, led by Evan You with a paid core team.

Verdict

Technically excellent, stable, and easier to learn than React, with first-party tooling that removes whole categories of decisions. The honest tradeoff is ecosystem and job-market size, not the framework itself.

API stability5/5Vue 3 has been the stable line since 2020 and minors are additive (defineModel in 3.4, useTemplateRef in 3.5). The 2-to-3 break was painful but is long past; v2 reached end of life at the end of 2023.
Docs5/5vuejs.org is one of the best-written doc sites in frontend: a toggle renders every example in Options or Composition style, and the guide explains the reactivity caveats honestly.
Maintenance5/5Pushed the same day as this review, active 3.6 alpha/beta line in progress, sponsor-funded full-time team led by Evan You.
Ecosystem4/5Router, Pinia, Nuxt, Vite and Vitest are first-party or sibling projects and component libraries like Vuetify, PrimeVue and Element Plus are mature, but the total pool is clearly smaller than React's.

Use it if

  • You want HTML-first templates with directives (v-if, v-for, v-model) instead of JSX, which designers and backend devs pick up noticeably faster
  • You need to sprinkle interactivity into an existing server-rendered app (Rails, Laravel, Django) without rewriting it as a SPA
  • You want an officially maintained full stack: vue-router, Pinia and Vite tooling are first-party, so the pieces are designed together
  • Fine-grained reactivity matters: Vue updates only what changed without the memoization discipline React requires
Skip it if

Setup reality

npm create vue@latest scaffolds a Vite project with checkbox choices for TypeScript, router, Pinia and testing, and it is fast and pleasant. The friction is conceptual: refs need .value in script but not in templates, and destructuring a reactive object silently kills reactivity, which bites every newcomer once. You must also pick Composition versus Options API up front (pick Composition with script setup for anything new). SSR is not in the box; that is Nuxt or manual @vue/server-renderer wiring. Editor support means installing the Vue extension (Volar); without it .vue files are inert text.

Patterns

Single-file component with script setupsfc-component

<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">Count: {{ count }}</button>
</template>

<style scoped>
button { font-weight: bold; }
</style>

script setup is the modern default; everything declared top-level is available in the template with no return statement.

ref vs reactivereactive-state

import { ref, reactive } from 'vue'

const count = ref(0)
count.value++ // .value needed in script, not in templates

const state = reactive({ user: { name: 'Ada' } })
state.user.name = 'Grace' // deep reactivity, no .value

Destructuring reactive() breaks reactivity: const { user } = state gives a plain snapshot. Default to ref for everything if in doubt.

Derived state with computedcomputed

import { ref, computed } from 'vue'

const items = ref([{ price: 10 }, { price: 5 }])
const total = computed(() =>
  items.value.reduce((sum, i) => sum + i.price, 0)
)

Computeds cache until a dependency changes; never mutate state inside one.

React to state changeswatchers

import { ref, watch, watchEffect } from 'vue'

const query = ref('')

watch(query, async (q, oldQ) => {
  results.value = await search(q)
})

watchEffect(() => {
  document.title = `Search: ${query.value}`
})

watch takes an explicit source and gives old/new values; watchEffect auto-tracks whatever it reads and runs immediately.

Typed props and eventsprops-emits

<script setup lang="ts">
const props = defineProps<{ title: string; count?: number }>()
const emit = defineEmits<{ (e: 'save', id: number): void }>()

function onClick() {
  emit('save', 42)
}
</script>

defineProps and defineEmits are compiler macros, no import needed; use withDefaults() for prop defaults with the type-only syntax.

v-model on a custom componenttwo-way-binding

<!-- Child.vue -->
<script setup lang="ts">
const model = defineModel<string>()
</script>
<template>
  <input v-model="model" />
</template>

<!-- Parent.vue -->
<!-- <Child v-model="name" /> -->

defineModel (stable since 3.4) replaces the old modelValue prop plus update:modelValue emit boilerplate.

Load data when a component mountsfetch-on-mount

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const posts = ref<Post[]>([])
const loading = ref(true)

onMounted(async () => {
  posts.value = await fetch('/api/posts').then((r) => r.json())
  loading.value = false
})
</script>

Top-level await in script setup also works but makes the component async, requiring a <Suspense> boundary in the parent.

Access a DOM elementtemplate-ref

<script setup>
import { useTemplateRef, onMounted } from 'vue'

const input = useTemplateRef('search')
onMounted(() => input.value?.focus())
</script>

<template>
  <input ref="search" />
</template>

useTemplateRef arrived in 3.5; the ref is null until mount, so touch it only in onMounted or a watcher.

Extract reusable logic into a composablecomposable

// composables/useLocalStorage.ts
import { ref, watch } from 'vue'

export function useLocalStorage(key: string, initial: string) {
  const value = ref(localStorage.getItem(key) ?? initial)
  watch(value, (v) => localStorage.setItem(key, v))
  return value
}

Composables are just functions using reactivity APIs; the use prefix is convention. Check VueUse before writing your own, it probably exists.

Pass state down without prop drillingprovide-inject

// ancestor
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)

// any descendant
import { inject } from 'vue'
const theme = inject('theme', ref('light')) // second arg = default

Fine for a plugin or theme; for real app state use Pinia, which is easier to test and inspect in devtools.

Render lists and conditionalslist-rendering

<template>
  <p v-if="items.length === 0">Nothing here</p>
  <ul v-else>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

Always bind :key to a stable id, never the array index, or reorders will recycle DOM state; avoid v-if and v-for on the same element.

Alternatives

PackageRegistryPick it when
reactnpmYou need the biggest ecosystem, React Native, or your hiring market demands it
sveltenpmYou want even less boilerplate and compiled output with no runtime framework overhead on small widgets
solid-jsnpmYou want Vue-style fine-grained reactivity with JSX syntax and top-tier update performance