Docs menu

Learning Logs · · 3 min read

Learning Vue 3: installation to component patterns

Vue 3 from scratch: scaffolding with create-vue, Single File Component anatomy, ref/computed, v-model, and the golden props-down-events-up rule — with a simple shop example.

Vue 3ViteComposition API

Vue 3 is the friendliest framework for moving from plain HTML/JS to reactive apps — its templates still feel like HTML. This tutorial goes from installation to the component patterns I use on real projects.

1. Installation

Prerequisite: Node.js 20+. Scaffold the official project (Vite under the hood):

npm create vue@latest shop-app
# answer the prompts (TypeScript? Router? Pinia?) — while learning, answer No to everything
cd shop-app
npm install
npm run dev          # open http://localhost:5173

2. Anatomy of a Single File Component (SFC)

One .vue file = logic + template + style in one place:

<script setup>
import { ref, computed } from "vue";

const cart = ref([]);                        // reactive state
const total = computed(() =>                 // derived value — recomputes automatically
  cart.value.reduce((s, item) => s + item.price, 0)
);

function add(item) {
  cart.value.push(item);
}
</script>

<template>
  <button @click="add({ name: 'Coffee', price: 25000 })">Add Coffee</button>
  <p>{{ cart.length }} items — total {{ total.toLocaleString() }}</p>
</template>

Three things to memorize from this example:

  • ref() wraps a value to make it reactive — in <script> you access cart.value, in the template just cart.
  • computed() for derived values — never store total as separate state (the same anti-pattern as in React).
  • @click is shorthand for v-on:click.

3. Two-way forms with v-model

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

<template>
  <input v-model="name" placeholder="Product name" />
  <p>Preview: {{ name || "(empty)" }}</p>
</template>

v-model = two-way binding — typing in the input changes name directly, and vice versa. This is why forms in Vue feel so much leaner than onChange + setState.

4. Components: props down, events up

<!-- ProductCard.vue -->
<script setup>
defineProps({ name: String, price: Number });
const emit = defineEmits(["buy"]);
</script>

<template>
  <article class="card">
    <h3>{{ name }}</h3>
    <p>{{ price.toLocaleString() }}</p>
    <button @click="emit('buy')">Buy</button>
  </article>
</template>
<!-- Used in the parent -->
<ProductCard v-for="p in products" :key="p.id" :name="p.name" :price="p.price" @buy="add(p)" />

The golden rule is the same in every framework: data flows down through props, changes flow up through events. Never mutate props from a child.

Tips from experience

  • Always give v-for a unique :key — without it Vue recycles DOM in ways that cause subtle bugs.
  • Start without Pinia/Router; add them when you genuinely need them. Many small apps are fine with ref + props.
  • <script setup> is the modern way — if another tutorial uses export default { data() ... }, that’s the older Options API; both work, but learn the Composition API first.

Want something like this built for your business?