# Vue Cheat Sheet

Course: [Vue.js](https://www.devtutorials.ai/learn/vue) · Section: Reference
Source: https://www.devtutorials.ai/learn/vue/reference/vue-cheat-sheet

> A quick reference for the whole Vue course: templates and directives, reactivity, rendering, events and forms, components, the Options API, composables, directives and plugins, routing, Pinia, HTTP, built-in components, transitions, rendering modes, testing, performance, errors and deployment.

## How to Use This Cheat Sheet

- This page is a quick reference for the whole course, in course order.
- Each block shows the common patterns as short code you can copy.
- Each block names the lesson that explains it in full.
- Code samples follow the TypeScript / JavaScript picker in the top bar.

### A Real-World Example

- A developer is adding a booking form to Meera's site.
- She forgot the exact `defineModel()` syntax and the `.number` modifier.
- She finds both here in seconds, instead of reading two lessons again.

const dishes = ref([
    { id: 1, name: "Masala Dosa", price: 90 },
    { id: 2, name: "Idli", price: 50 },
]);
const plates = ref<number>(1);
const picked = ref<number>(1);
const total = computed(() => (dishes.value.find((d) => d.id === picked.value)?.price ?? 0) * plates.value);
</script>

<template>
    <select v-model.number="picked">
      <option v-for="d in dishes" :key="d.id" :value="d.id">{{ d.name }}</option>
    </select>
    <p :class="{ big: total > 200 }">Total: ₹{{ total }}</p>
</template>

<style>
.big { color: crimson; font-weight: bold; }
</style>
`,
    "QuantityPicker.vue": `<script setup lang="ts">
const count = defineModel<number>({ default: 1 });
</script>

<template>
    <button @click="count = Math.max(1, count - 1)">−</button> {{ count }} <button @click="count++">+</button>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

## Start an App

**Introduction to Vue**

```ts
// main.ts
import { createApp } from "vue";
import App from "./App.vue";

createApp(App).mount("#app");
```

```bash
npm create vue@latest   # new project
npm install             # packages
npm run dev             # dev server with HMR
npm run build           # production files in dist/
```

## Templates and Directives

**Templates and Directives**

```vue
<template>
  <p>{{ price * plates }}</p>                    <!-- one expression -->
  <img :src="photo" :alt="dish" />              <!-- v-bind shorthand -->
  <button :disabled="soldOut">Order</button>    <!-- false removes it -->
  <input v-bind="attrs" />                      <!-- many attributes -->
  <p v-html="trustedHtml"></p>                  <!-- only trusted HTML -->
  <p v-once>{{ firstSpecial }}</p>              <!-- render once -->
</template>
```

<div className="tscroll">

| Directive part | Example |
|---|---|
| Name | `v-on` |
| Argument | `:submit` |
| Dynamic argument | `:[eventName]` |
| Modifier | `.prevent` |
| Value | `="save"` |

</div>

## Reactivity

**Reactive State**, **Computed Properties and Watchers**, **Advanced Reactivity**

```vue
<script setup lang="ts">
import { ref, reactive, computed, watch, watchEffect, nextTick, toRefs } from "vue";

const count = ref<number>(0);              // count.value in script
const order = reactive({ plates: 1 });     // no .value, do not destructure
const { plates } = toRefs(order);          // safe destructure
const double = computed(() => count.value * 2);

watch(count, (now, before) => console.log(before, "→", now), { immediate: true });
watch(() => order.plates, (p) => console.log("plates", p));
watchEffect(() => console.log("count is", count.value));

async function add(): Promise<void> {
  count.value++;
  await nextTick();                        // DOM is updated now
}
</script>
```

<div className="tscroll">

| Need | Use |
|---|---|
| A value from state | `computed` |
| A job when state changes | `watch` / `watchEffect` |
| Big data replaced as a whole | `shallowRef` + `triggerRef` |
| Read-only view | `readonly()` |
| A debounced ref | `customRef()` |

</div>

## Dynamic and List Rendering

**Dynamic Rendering**, **List Rendering**

```vue
<template>
  <p :class="{ active: isOn, 'sold-out': soldOut }" :style="{ fontSize: size + 'px' }">Dosa</p>
  <p v-if="hour < 11">Breakfast</p>
  <p v-else-if="hour < 16">Lunch</p>
  <p v-else>Dinner</p>
  <ul v-show="open">
    <li v-for="(dish, i) in shown" :key="dish.id">{{ i + 1 }}. {{ dish.name }}</li>
  </ul>
  <span v-for="n in 3" :key="n">{{ n }}</span>   <!-- 1, 2, 3 -->
</template>
```

- Filter and sort in a `computed`, never `v-if` with `v-for` on one element.
- `filter` and `map` return new arrays: assign them.

## Events and Forms

**Events and Forms**

```vue
<template>
  <button @click="add(2, $event)">Add 2</button>
  <input @keyup.enter="save" @keyup.esc="clear" />
  <div @click="open"><button @click.stop="like">Like</button></div>
  <form @submit.prevent="submit">
    <input v-model.trim="name" :aria-invalid="!!error" :aria-describedby="errorId" />
    <input v-model.number="guests" type="number" />
    <input type="checkbox" v-model="extras" value="chutney" />
    <select v-model="time"><option>19:00</option></select>
    <p :id="errorId">{{ error }}</p>
    <button :disabled="!!error">Book</button>
  </form>
</template>
```

## Components

**Components and Single-File Components**, **Props and Events**, **Slots, Attributes and Provide/Inject**

```vue
<script setup lang="ts">
const props = defineProps<{ name: string; price?: number }>();
const emit = defineEmits<{ add: [name: string] }>();
const qty = defineModel<number>({ default: 1 });
</script>

<template>
  <div class="card">
    <slot name="header" />
    <slot :dish="props.name">{{ props.name }}</slot>
    <button @click="emit('add', props.name)">Add</button>
  </div>
</template>

<style scoped>
.card :deep(.price) { color: crimson; }
</style>
```

```vue
<template>
  <DishCard name="Idli" :price="50" v-model="plates" @add="cart.push($event)">
    <template #header><h3>Breakfast</h3></template>
    <template #default="{ dish }">⭐ {{ dish }}</template>
  </DishCard>
</template>
```

- Props down, events up. Never change a prop.
- Runtime props in JavaScript: `defineProps({ name: { type: String, required: true } })`.

## Attributes, Provide/Inject and Lifecycle

**Slots, Attributes and Provide/Inject**, **Lifecycle and Template Refs**

```ts
defineOptions({ inheritAttrs: false });          // then v-bind="$attrs"

provide(cartKey, cart);                          // ancestor
const cart = inject(cartKey, ref([]));           // any descendant

onMounted(() => { /* DOM ready: start timers, listeners */ });
onUnmounted(() => { /* clean everything up */ });

const box = useTemplateRef<HTMLInputElement>("box");   // ref="box"
defineExpose({ reset });                         // what a parent ref sees
```

## Options API

**Options API**

```ts
export default defineComponent({
  props: { price: { type: Number, required: true } },
  emits: ["add"],
  data() { return { plates: 1 }; },
  computed: { total(): number { return this.price * this.plates; } },
  watch: { plates(now: number) { console.log(now); } },
  methods: { addPlate() { this.plates++; } },
  mounted() { console.log("on the page"); },
});
```

## Reuse: Composables, Directives and Plugins

**Composables, Directives and Plugins**

```ts
// composables/useCounter.ts
export function useCounter(start: MaybeRefOrGetter<number> = 0) {
  const count = ref(toValue(start));
  const add = () => count.value++;
  return { count, add };                         // refs: safe to destructure
}

// a directive (in <script setup>, vFocus = v-focus)
const vFocus: Directive<HTMLElement> = { mounted: (el) => el.focus() };

// a plugin
export const kitchenPlugin = {
  install(app: App, options: { shop: string }) {
    app.provide("shop", options.shop);
    app.directive("focus", vFocus);
  },
};
```

## TypeScript

**TypeScript with Vue**

```ts
const props = defineProps<{ dish: Dish; plates?: number }>();
const emit = defineEmits<{ add: [name: string] }>();
const qty = defineModel<number>({ default: 1 });
const chosen = ref<Dish | null>(null);
const box = useTemplateRef<HTMLInputElement>("box");
export const themeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
// <script setup lang="ts" generic="T extends { id: number }">
// npx vue-tsc --noEmit    (Vite does not check types)
```

## Routing

**Vue Router Basics**, **Navigation Guards and Advanced Routing**

```ts
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: "/", component: HomePage },
    { path: "/dish/:id", name: "dish", component: DishPage, props: true },
    { path: "/admin", component: () => import("./views/Admin.vue"), meta: { requiresAuth: true } },
    { path: "/:pathMatch(.*)*", component: NotFound },
  ],
});

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !loggedIn.value) return { path: "/login", query: { redirect: to.fullPath } };
});
```

```vue
<template>
  <RouterLink :to="{ name: 'dish', params: { id: 2 } }">Dish 2</RouterLink>
  <RouterView />
</template>
```

- `useRoute()` reads `params` and `query`. `useRouter()` does `push`, `replace` and `go`.

## Pinia

**State Management with Pinia**

```ts
export const useCartStore = defineStore("cart", () => {
  const items = ref<string[]>([]);
  const count = computed(() => items.value.length);
  function add(dish: string): void { items.value.push(dish); }
  return { items, count, add };
});

const cart = useCartStore();
const { count } = storeToRefs(cart);             // state and getters
const { add } = cart;                            // actions directly
```

## HTTP

**HTTP Requests**, **Authentication and Real-Time Data**

```ts
const res = await fetch(`${import.meta.env.VITE_API_URL}/dishes`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Pongal" }),
  signal: controller.signal,                     // AbortController
});
if (!res.ok) throw new Error(`Request failed (${res.status})`);
const dish = await res.json();

// auth: send the token on every request, refresh once on 401
api.interceptors.request.use((c) => (c.headers.set("Authorization", `Bearer ${auth.token}`), c));

// live data: close on unmount
const socket = new WebSocket("wss://api.example.com/live");
onUnmounted(() => socket.close());
```

## Advanced Components and Transitions

**Dynamic and Built-In Components**, **Transitions and Animations**, **Render Functions and Component Patterns**

```vue
<template>
  <KeepAlive><component :is="current" /></KeepAlive>
  <Teleport to="body"><div v-if="open" class="modal" role="dialog">…</div></Teleport>
  <Transition name="fade" mode="out-in"><p :key="text">{{ text }}</p></Transition>
  <TransitionGroup name="list" tag="ul"><li v-for="o in orders" :key="o.id">{{ o.dish }}</li></TransitionGroup>
</template>
```

```ts
const Chart = defineAsyncComponent(() => import("./Chart.vue"));
const render = () => h("button", { onClick: add }, "Add");
```

## Rendering Modes, Testing and Production

**How Vue Renders**, **Server and Static Rendering**, **Nuxt: Full-Stack Vue**, **Testing Vue Apps**, **Performance**, **Errors, Security and Deployment**, **Capstone: Build Meera's Kitchen**

```ts
const html = await renderToString(createSSRApp(App));     // server
createSSRApp(App).mount("#app");                          // hydrate

it("adds a dish", async () => {                           // Vitest + VTU
  const wrapper = mount(DishCard, { props: { name: "Idli" } });
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("add")).toEqual([["Idli"]]);
});

app.config.errorHandler = (err) => report(err);           // global errors
onErrorCaptured(() => { failed.value = true; return false; });
```

<div className="tscroll">

| Mode | HTML made | Good for |
|---|---|---|
| CSR | In the browser | Apps behind a login |
| SSR | On the server, per request | Fresh, shareable pages |
| SSR + SWR/ISR | On the server, then cached | Menus, product pages |
| SSG | At build time | Docs, blogs, marketing |
| Hybrid | Per route (route rules) | Most real sites |

</div>

## Nuxt

**Nuxt: Full-Stack Vue**

```ts
// app/pages/dish/[id].vue → /dish/:id
const { data: dish, status } = await useFetch(`/api/dishes/${useRoute().params.id}`);
useSeoMeta({ title: () => `${dish.value?.name} | Meera's Kitchen` });
definePageMeta({ layout: "default", middleware: "auth" });

// server/api/dishes/[id].get.ts
export default defineEventHandler((event) => findDish(Number(getRouterParam(event, "id"))));

// nuxt.config.ts
export default defineNuxtConfig({ routeRules: { "/": { prerender: true }, "/menu": { swr: 60 } } });
```

Stuck on a pattern? Each heading above names the lesson that explains it with runnable boxes and exercises.

## TL;DR

- **Start** = `createApp(App).mount("#app")`, `npm create vue@latest`.
- **State** = `ref`, `reactive`, `computed`, `watch`.
- **Templates** = `{{ }}`, `:attr`, `@event`, `v-if`, `v-for` + `:key`, `v-model`.
- **Components** = `defineProps`, `defineEmits`, `defineModel`, slots.
- **Reuse** = composables, directives, plugins.
- **App-wide** = Vue Router and Pinia.
- **Production** = tests, performance, error handling, safe deployment.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No `.value` in the script | Refs work like plain variables | `count.value++` | Nothing updates |
| Destructuring reactive or a store | Values stay linked | `toRefs` / `storeToRefs` | Frozen values |
| Index as `:key` | Positions are names | Use an id | State jumps between rows |
| Changing a prop | Props are local | Emit an event or copy it | Warning and overwritten data |
| `v-html` with user text | It is just formatting | `{{ }}` | XSS |
| Secrets in `VITE_` variables | `.env` is private | Keep them on the server | Anyone can read them |

</div>

## Check Yourself

## What to Remember

- State: `ref` first, `computed` for values, `watch` for jobs.
- Templates: `:` binds, `@` listens, `v-model` does both, `:key` on every list item.
- Components: props down, events up, slots for content, composables for logic.
- Apps: Vue Router for pages, Pinia for shared state, a service module for HTTP.
- Production: test, measure, handle errors kindly, keep secrets on the server.
