Claude
Skills
Sign in
Back

vue

Included with Lifetime
$97 forever

Vue 3 - Progressive JavaScript framework with Composition API, reactivity system, single-file components, Vite integration, TypeScript support

toolchainvuevue3composition-apireactivitysfcpiniaroutervite

What this skill does


# Vue 3 - Progressive JavaScript Framework

## Overview

Vue 3 is a **progressive framework** for building user interfaces with emphasis on approachability, performance, and flexibility. It features the **Composition API** for better logic reuse, a powerful **reactivity system**, and **single-file components** (.vue files).

**Key Features**:
- **Composition API**: setup() with ref, reactive, computed, watch
- **Reactivity System**: Fine-grained reactive data tracking
- **Single-File Components**: Template, script, style in one file
- **Vue Router**: Official routing for SPAs
- **Pinia**: Modern state management (Vuex successor)
- **TypeScript**: First-class TypeScript support
- **Vite**: Lightning-fast development with HMR

**Installation**:
```bash
# Create new Vue 3 project (recommended)
npm create vue@latest my-app
cd my-app
npm install
npm run dev

# Or with Vite template
npm create vite@latest my-app -- --template vue-ts
```

## Composition API Fundamentals

### setup() Function

```vue
<script setup lang="ts">
// Modern <script setup> syntax (recommended)
import { ref, computed, onMounted } from 'vue';

// Reactive state
const count = ref(0);
const message = ref('Hello Vue 3');

// Computed values
const doubled = computed(() => count.value * 2);

// Methods
function increment() {
  count.value++;
}

// Lifecycle hooks
onMounted(() => {
  console.log('Component mounted');
});
</script>

<template>
  <div>
    <p>Count: {{ count }} (Doubled: {{ doubled }})</p>
    <button @click="increment">Increment</button>
  </div>
</template>
```

### Reactive State with ref() and reactive()

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

// ref() - for primitives and objects (needs .value in script)
const count = ref(0);
const user = ref({ name: 'Alice', age: 30 });

console.log(count.value); // 0
console.log(user.value.name); // 'Alice'

// reactive() - for objects only (no .value needed)
const state = reactive({
  todos: [] as Todo[],
  filter: 'all',
  error: null as string | null
});

console.log(state.todos); // []
state.todos.push({ id: 1, text: 'Learn Vue', done: false });
</script>

<template>
  <!-- In template, .value is automatic for refs -->
  <p>Count: {{ count }}</p>
  <p>User: {{ user.name }}</p>
  <p>Todos: {{ state.todos.length }}</p>
</template>
```

### Computed Properties

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

const firstName = ref('John');
const lastName = ref('Doe');

// Read-only computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`);

// Writable computed
const fullNameWritable = computed({
  get() {
    return `${firstName.value} ${lastName.value}`;
  },
  set(value: string) {
    const parts = value.split(' ');
    firstName.value = parts[0];
    lastName.value = parts[1];
  }
});

// Complex computations
interface Todo {
  id: number;
  text: string;
  done: boolean;
}

const todos = ref<Todo[]>([
  { id: 1, text: 'Learn Vue', done: true },
  { id: 2, text: 'Build app', done: false }
]);

const completedTodos = computed(() =>
  todos.value.filter(t => t.done)
);

const activeTodos = computed(() =>
  todos.value.filter(t => !t.done)
);

const progress = computed(() =>
  todos.value.length > 0
    ? (completedTodos.value.length / todos.value.length) * 100
    : 0
);
</script>

<template>
  <div>
    <p>Full Name: {{ fullName }}</p>
    <p>Progress: {{ progress.toFixed(1) }}%</p>
    <p>Active: {{ activeTodos.length }} | Done: {{ completedTodos.length }}</p>
  </div>
</template>
```

### Watchers and Side Effects

```vue
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue';

const count = ref(0);
const user = ref({ name: 'Alice', age: 30 });

// watch() - explicit dependencies
watch(count, (newVal, oldVal) => {
  console.log(`Count changed from ${oldVal} to ${newVal}`);
});

// Watch multiple sources
watch([count, user], ([newCount, newUser], [oldCount, oldUser]) => {
  console.log('Count or user changed');
});

// Watch object property (needs getter)
watch(
  () => user.value.name,
  (newName, oldName) => {
    console.log(`Name changed from ${oldName} to ${newName}`);
  }
);

// Deep watch for nested objects
watch(
  user,
  (newUser) => {
    console.log('User object changed deeply');
  },
  { deep: true }
);

// watchEffect() - automatic dependency tracking
watchEffect(() => {
  // Automatically watches count and user
  console.log(`Count: ${count.value}, User: ${user.value.name}`);
});

// Cleanup function
watchEffect((onCleanup) => {
  const timer = setTimeout(() => {
    console.log('Delayed effect');
  }, 1000);

  onCleanup(() => {
    clearTimeout(timer);
  });
});
</script>
```

## Component Props and Events

### Defining Props (TypeScript)

```vue
<script setup lang="ts">
// Type-safe props with defineProps
interface Props {
  title: string;
  count?: number;
  tags?: string[];
  user: {
    name: string;
    email: string;
  };
  disabled?: boolean;
}

// With defaults
const props = withDefaults(defineProps<Props>(), {
  count: 0,
  tags: () => [],
  disabled: false
});

// Access props
console.log(props.title);
console.log(props.count);
</script>

<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
    <p>Tags: {{ tags.join(', ') }}</p>
  </div>
</template>
```

### Emitting Events

```vue
<script setup lang="ts">
// Define emitted events with types
const emit = defineEmits<{
  update: [value: number];
  submit: [data: { name: string; email: string }];
  delete: [id: number];
}>();

function handleClick() {
  emit('update', 42);
}

function handleSubmit() {
  emit('submit', { name: 'Alice', email: '[email protected]' });
}
</script>

<template>
  <button @click="handleClick">Update</button>
  <button @click="handleSubmit">Submit</button>
</template>
```

### v-model for Two-Way Binding

```vue
<!-- Child: CustomInput.vue -->
<script setup lang="ts">
// v-model creates 'modelValue' prop and 'update:modelValue' event
const props = defineProps<{
  modelValue: string;
  placeholder?: string;
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement;
  emit('update:modelValue', target.value);
}
</script>

<template>
  <input
    :value="modelValue"
    @input="handleInput"
    :placeholder="placeholder"
  />
</template>

<!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

const searchQuery = ref('');
</script>

<template>
  <CustomInput v-model="searchQuery" placeholder="Search..." />
  <p>Searching for: {{ searchQuery }}</p>
</template>
```

### Multiple v-model Bindings

```vue
<!-- Child: UserForm.vue -->
<script setup lang="ts">
defineProps<{
  firstName: string;
  lastName: string;
}>();

const emit = defineEmits<{
  'update:firstName': [value: string];
  'update:lastName': [value: string];
}>();
</script>

<template>
  <div>
    <input
      :value="firstName"
      @input="emit('update:firstName', ($event.target as HTMLInputElement).value)"
    />
    <input
      :value="lastName"
      @input="emit('update:lastName', ($event.target as HTMLInputElement).value)"
    />
  </div>
</template>

<!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import UserForm from './UserForm.vue';

const first = ref('John');
const last = ref('Doe');
</script>

<template>
  <UserForm v-model:first-name="first" v-model:last-name="last" />
  <p>Full name: {{ first }} {{ last }}</p>
</template>
```

## Template Syntax

### Directives

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

const message = ref('Hello Vue');
const isActive = ref(true);
const hasError = ref(false);
const items = ref(['Apple', 'Banana', 'Cherry']);
const user = ref({ name: 'Alice', email: '[email protected]' });

const formData = reactive({
  username: '',
  agree: false,
  gender: 'male',
  interests: [] as string[]
});
</script>

Related in toolchain