format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
This commit is contained in:
@@ -3,7 +3,7 @@ name: vue
|
||||
description: Vue 3 Composition API, script setup macros, reactivity system, and built-in components. Use when writing Vue SFCs, defineProps/defineEmits/defineModel, watchers, or using Transition/Teleport/Suspense/KeepAlive.
|
||||
metadata:
|
||||
author: Anthony Fu
|
||||
version: "2026.1.31"
|
||||
version: '2026.1.31'
|
||||
source: Generated from https://github.com/vuejs/docs, scripts at https://github.com/antfu/skills
|
||||
---
|
||||
|
||||
@@ -21,15 +21,15 @@ metadata:
|
||||
|
||||
## Core
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| Script Setup & Macros | `<script setup>`, defineProps, defineEmits, defineModel, defineExpose, defineOptions, defineSlots, generics | [script-setup-macros](references/script-setup-macros.md) |
|
||||
| Reactivity & Lifecycle | ref, shallowRef, computed, watch, watchEffect, effectScope, lifecycle hooks, composables | [core-new-apis](references/core-new-apis.md) |
|
||||
| Topic | Description | Reference |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| Script Setup & Macros | `<script setup>`, defineProps, defineEmits, defineModel, defineExpose, defineOptions, defineSlots, generics | [script-setup-macros](references/script-setup-macros.md) |
|
||||
| Reactivity & Lifecycle | ref, shallowRef, computed, watch, watchEffect, effectScope, lifecycle hooks, composables | [core-new-apis](references/core-new-apis.md) |
|
||||
|
||||
## Features
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| Topic | Description | Reference |
|
||||
| -------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| Built-in Components & Directives | Transition, Teleport, Suspense, KeepAlive, v-memo, custom directives | [advanced-patterns](references/advanced-patterns.md) |
|
||||
|
||||
## Quick Reference
|
||||
@@ -38,28 +38,31 @@ metadata:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
count?: number
|
||||
}>()
|
||||
title: string;
|
||||
count?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [value: string]
|
||||
}>()
|
||||
update: [value: string];
|
||||
}>();
|
||||
|
||||
const model = defineModel<string>()
|
||||
const model = defineModel<string>();
|
||||
|
||||
const doubled = computed(() => (props.count ?? 0) * 2)
|
||||
const doubled = computed(() => (props.count ?? 0) * 2);
|
||||
|
||||
watch(() => props.title, (newVal) => {
|
||||
console.log('Title changed:', newVal)
|
||||
})
|
||||
watch(
|
||||
() => props.title,
|
||||
(newVal) => {
|
||||
console.log('Title changed:', newVal);
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
console.log('Component mounted')
|
||||
})
|
||||
console.log('Component mounted');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,14 +74,21 @@ onMounted(() => {
|
||||
|
||||
```ts
|
||||
// Reactivity
|
||||
import { ref, shallowRef, computed, reactive, readonly, toRef, toRefs, toValue } from 'vue'
|
||||
import { ref, shallowRef, computed, reactive, readonly, toRef, toRefs, toValue } from 'vue';
|
||||
|
||||
// Watchers
|
||||
import { watch, watchEffect, watchPostEffect, onWatcherCleanup } from 'vue'
|
||||
import { watch, watchEffect, watchPostEffect, onWatcherCleanup } from 'vue';
|
||||
|
||||
// Lifecycle
|
||||
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'
|
||||
import {
|
||||
onMounted,
|
||||
onUpdated,
|
||||
onUnmounted,
|
||||
onBeforeMount,
|
||||
onBeforeUpdate,
|
||||
onBeforeUnmount,
|
||||
} from 'vue';
|
||||
|
||||
// Utilities
|
||||
import { nextTick, defineComponent, defineAsyncComponent } from 'vue'
|
||||
import { nextTick, defineComponent, defineAsyncComponent } from 'vue';
|
||||
```
|
||||
|
||||
@@ -17,10 +17,12 @@ Animate enter/leave of a single element or component.
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from, .fade-leave-to {
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -28,14 +30,14 @@ Animate enter/leave of a single element or component.
|
||||
|
||||
### CSS Classes
|
||||
|
||||
| Class | When |
|
||||
|-------|------|
|
||||
| `{name}-enter-from` | Start state for enter |
|
||||
| Class | When |
|
||||
| --------------------- | --------------------------------------------- |
|
||||
| `{name}-enter-from` | Start state for enter |
|
||||
| `{name}-enter-active` | Active state for enter (add transitions here) |
|
||||
| `{name}-enter-to` | End state for enter |
|
||||
| `{name}-leave-from` | Start state for leave |
|
||||
| `{name}-leave-active` | Active state for leave |
|
||||
| `{name}-leave-to` | End state for leave |
|
||||
| `{name}-enter-to` | End state for enter |
|
||||
| `{name}-leave-from` | Start state for leave |
|
||||
| `{name}-leave-active` | Active state for leave |
|
||||
| `{name}-leave-to` | End state for leave |
|
||||
|
||||
### Transition Modes
|
||||
|
||||
@@ -62,7 +64,7 @@ Animate enter/leave of a single element or component.
|
||||
<script setup lang="ts">
|
||||
function onEnter(el: Element, done: () => void) {
|
||||
// Animate with JS library
|
||||
gsap.to(el, { opacity: 1, onComplete: done })
|
||||
gsap.to(el, { opacity: 1, onComplete: done });
|
||||
}
|
||||
</script>
|
||||
```
|
||||
@@ -89,10 +91,12 @@ Animate list items. Each child must have a unique `key`.
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.list-enter-active, .list-leave-active {
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.list-enter-from, .list-leave-to {
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
@@ -110,11 +114,9 @@ Render content to a different DOM location.
|
||||
```vue
|
||||
<template>
|
||||
<button @click="open = true">Open Modal</button>
|
||||
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="open" class="modal">
|
||||
Modal content rendered at body
|
||||
</div>
|
||||
<div v-if="open" class="modal">Modal content rendered at body</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
```
|
||||
@@ -155,6 +157,7 @@ Handle async dependencies with loading states. **Experimental feature.**
|
||||
### Async Dependencies
|
||||
|
||||
Suspense waits for:
|
||||
|
||||
- Components with `async setup()`
|
||||
- Components using top-level `await` in `<script setup>`
|
||||
- Async components created with `defineAsyncComponent`
|
||||
@@ -162,18 +165,14 @@ Suspense waits for:
|
||||
```vue
|
||||
<!-- AsyncComponent.vue -->
|
||||
<script setup lang="ts">
|
||||
const data = await fetch('/api/data').then(r => r.json())
|
||||
const data = await fetch('/api/data').then((r) => r.json());
|
||||
</script>
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
```vue
|
||||
<Suspense
|
||||
@pending="onPending"
|
||||
@resolve="onResolve"
|
||||
@fallback="onFallback"
|
||||
>
|
||||
<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
|
||||
...
|
||||
</Suspense>
|
||||
```
|
||||
@@ -208,17 +207,17 @@ Cache component instances when toggled.
|
||||
### Lifecycle Hooks
|
||||
|
||||
```ts
|
||||
import { onActivated, onDeactivated } from 'vue'
|
||||
import { onActivated, onDeactivated } from 'vue';
|
||||
|
||||
onActivated(() => {
|
||||
// Called when component is inserted from cache
|
||||
fetchLatestData()
|
||||
})
|
||||
fetchLatestData();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
// Called when component is removed to cache
|
||||
pauseTimers()
|
||||
})
|
||||
pauseTimers();
|
||||
});
|
||||
```
|
||||
|
||||
## v-memo
|
||||
@@ -235,6 +234,7 @@ Skip re-renders when dependencies unchanged. Use for performance optimization.
|
||||
```
|
||||
|
||||
Equivalent to `v-once` when empty:
|
||||
|
||||
```vue
|
||||
<div v-memo="[]">Never updates</div>
|
||||
```
|
||||
@@ -254,23 +254,23 @@ Create reusable DOM manipulations.
|
||||
```ts
|
||||
// Directive definition
|
||||
const vFocus: Directive<HTMLElement> = {
|
||||
mounted: (el) => el.focus()
|
||||
}
|
||||
mounted: (el) => el.focus(),
|
||||
};
|
||||
|
||||
// Full hooks
|
||||
const vColor: Directive<HTMLElement, string> = {
|
||||
created(el, binding, vnode, prevVnode) {},
|
||||
beforeMount(el, binding) {},
|
||||
mounted(el, binding) {
|
||||
el.style.color = binding.value
|
||||
el.style.color = binding.value;
|
||||
},
|
||||
beforeUpdate(el, binding) {},
|
||||
updated(el, binding) {
|
||||
el.style.color = binding.value
|
||||
el.style.color = binding.value;
|
||||
},
|
||||
beforeUnmount(el, binding) {},
|
||||
unmounted(el, binding) {}
|
||||
}
|
||||
unmounted(el, binding) {},
|
||||
};
|
||||
```
|
||||
|
||||
### Directive Arguments & Modifiers
|
||||
@@ -298,8 +298,8 @@ const vColor: Directive<HTMLElement, string> = {
|
||||
```ts
|
||||
// main.ts
|
||||
app.directive('focus', {
|
||||
mounted: (el) => el.focus()
|
||||
})
|
||||
mounted: (el) => el.focus(),
|
||||
});
|
||||
```
|
||||
|
||||
<!--
|
||||
|
||||
@@ -10,16 +10,16 @@ description: Vue 3 reactivity system, lifecycle hooks, and composable patterns
|
||||
### ref vs shallowRef
|
||||
|
||||
```ts
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { ref, shallowRef } from 'vue';
|
||||
|
||||
// ref - deep reactivity (tracks nested changes)
|
||||
const user = ref({ name: 'John', profile: { age: 30 } })
|
||||
user.value.profile.age = 31 // Triggers reactivity
|
||||
const user = ref({ name: 'John', profile: { age: 30 } });
|
||||
user.value.profile.age = 31; // Triggers reactivity
|
||||
|
||||
// shallowRef - only .value assignment triggers reactivity (better performance)
|
||||
const data = shallowRef({ items: [] })
|
||||
data.value.items.push('new') // Does NOT trigger reactivity
|
||||
data.value = { items: ['new'] } // Triggers reactivity
|
||||
const data = shallowRef({ items: [] });
|
||||
data.value.items.push('new'); // Does NOT trigger reactivity
|
||||
data.value = { items: ['new'] }; // Triggers reactivity
|
||||
```
|
||||
|
||||
**Prefer `shallowRef`** for large data structures or when deep reactivity is unnecessary.
|
||||
@@ -27,30 +27,32 @@ data.value = { items: ['new'] } // Triggers reactivity
|
||||
### computed
|
||||
|
||||
```ts
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const count = ref(0)
|
||||
const count = ref(0);
|
||||
|
||||
// Read-only computed
|
||||
const doubled = computed(() => count.value * 2)
|
||||
const doubled = computed(() => count.value * 2);
|
||||
|
||||
// Writable computed
|
||||
const plusOne = computed({
|
||||
get: () => count.value + 1,
|
||||
set: (val) => { count.value = val - 1 }
|
||||
})
|
||||
set: (val) => {
|
||||
count.value = val - 1;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### reactive & readonly
|
||||
|
||||
```ts
|
||||
import { reactive, readonly } from 'vue'
|
||||
import { reactive, readonly } from 'vue';
|
||||
|
||||
const state = reactive({ count: 0, nested: { value: 1 } })
|
||||
state.count++ // Reactive
|
||||
const state = reactive({ count: 0, nested: { value: 1 } });
|
||||
state.count++; // Reactive
|
||||
|
||||
const readonlyState = readonly(state)
|
||||
readonlyState.count++ // Warning, mutation blocked
|
||||
const readonlyState = readonly(state);
|
||||
readonlyState.count++; // Warning, mutation blocked
|
||||
```
|
||||
|
||||
Note: `reactive()` loses reactivity on destructuring. Use `ref()` or `toRefs()`.
|
||||
@@ -60,32 +62,32 @@ Note: `reactive()` loses reactivity on destructuring. Use `ref()` or `toRefs()`.
|
||||
### watch
|
||||
|
||||
```ts
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const count = ref(0)
|
||||
const count = ref(0);
|
||||
|
||||
// Watch single ref
|
||||
watch(count, (newVal, oldVal) => {
|
||||
console.log(`Changed from ${oldVal} to ${newVal}`)
|
||||
})
|
||||
console.log(`Changed from ${oldVal} to ${newVal}`);
|
||||
});
|
||||
|
||||
// Watch getter
|
||||
watch(
|
||||
() => props.id,
|
||||
(id) => fetchData(id),
|
||||
{ immediate: true }
|
||||
)
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// Watch multiple sources
|
||||
watch([firstName, lastName], ([first, last]) => {
|
||||
fullName.value = `${first} ${last}`
|
||||
})
|
||||
fullName.value = `${first} ${last}`;
|
||||
});
|
||||
|
||||
// Deep watch with depth limit (Vue 3.5+)
|
||||
watch(state, callback, { deep: 2 })
|
||||
watch(state, callback, { deep: 2 });
|
||||
|
||||
// Once (Vue 3.4+)
|
||||
watch(source, callback, { once: true })
|
||||
watch(source, callback, { once: true });
|
||||
```
|
||||
|
||||
### watchEffect
|
||||
@@ -93,25 +95,25 @@ watch(source, callback, { once: true })
|
||||
Runs immediately and auto-tracks dependencies.
|
||||
|
||||
```ts
|
||||
import { ref, watchEffect, onWatcherCleanup } from 'vue'
|
||||
import { ref, watchEffect, onWatcherCleanup } from 'vue';
|
||||
|
||||
const id = ref(1)
|
||||
const id = ref(1);
|
||||
|
||||
watchEffect(async () => {
|
||||
const controller = new AbortController()
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
// Cleanup on re-run or unmount (Vue 3.5+)
|
||||
onWatcherCleanup(() => controller.abort())
|
||||
|
||||
const res = await fetch(`/api/${id.value}`, { signal: controller.signal })
|
||||
data.value = await res.json()
|
||||
})
|
||||
onWatcherCleanup(() => controller.abort());
|
||||
|
||||
const res = await fetch(`/api/${id.value}`, { signal: controller.signal });
|
||||
data.value = await res.json();
|
||||
});
|
||||
|
||||
// Pause/resume (Vue 3.5+)
|
||||
const { pause, resume, stop } = watchEffect(() => {})
|
||||
pause()
|
||||
resume()
|
||||
stop()
|
||||
const { pause, resume, stop } = watchEffect(() => {});
|
||||
pause();
|
||||
resume();
|
||||
stop();
|
||||
```
|
||||
|
||||
### Flush Timing
|
||||
@@ -121,8 +123,8 @@ stop()
|
||||
// 'post' - after component update (access updated DOM)
|
||||
// 'sync' - immediate, use with caution
|
||||
|
||||
watch(source, callback, { flush: 'post' })
|
||||
watchPostEffect(() => {}) // Alias for flush: 'post'
|
||||
watch(source, callback, { flush: 'post' });
|
||||
watchPostEffect(() => {}); // Alias for flush: 'post'
|
||||
```
|
||||
|
||||
## Lifecycle Hooks
|
||||
@@ -136,24 +138,24 @@ import {
|
||||
onBeforeUnmount,
|
||||
onUnmounted,
|
||||
onErrorCaptured,
|
||||
onActivated, // KeepAlive
|
||||
onDeactivated, // KeepAlive
|
||||
onServerPrefetch // SSR only
|
||||
} from 'vue'
|
||||
onActivated, // KeepAlive
|
||||
onDeactivated, // KeepAlive
|
||||
onServerPrefetch, // SSR only
|
||||
} from 'vue';
|
||||
|
||||
onMounted(() => {
|
||||
console.log('DOM is ready')
|
||||
})
|
||||
console.log('DOM is ready');
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// Cleanup timers, listeners, etc.
|
||||
})
|
||||
});
|
||||
|
||||
// Error boundary
|
||||
onErrorCaptured((err, instance, info) => {
|
||||
console.error(err)
|
||||
return false // Stop propagation
|
||||
})
|
||||
console.error(err);
|
||||
return false; // Stop propagation
|
||||
});
|
||||
```
|
||||
|
||||
## Effect Scope
|
||||
@@ -161,24 +163,24 @@ onErrorCaptured((err, instance, info) => {
|
||||
Group reactive effects for batch disposal.
|
||||
|
||||
```ts
|
||||
import { effectScope, onScopeDispose } from 'vue'
|
||||
import { effectScope, onScopeDispose } from 'vue';
|
||||
|
||||
const scope = effectScope()
|
||||
const scope = effectScope();
|
||||
|
||||
scope.run(() => {
|
||||
const count = ref(0)
|
||||
const doubled = computed(() => count.value * 2)
|
||||
|
||||
watch(count, () => console.log(count.value))
|
||||
|
||||
const count = ref(0);
|
||||
const doubled = computed(() => count.value * 2);
|
||||
|
||||
watch(count, () => console.log(count.value));
|
||||
|
||||
// Cleanup when scope stops
|
||||
onScopeDispose(() => {
|
||||
console.log('Scope disposed')
|
||||
})
|
||||
})
|
||||
console.log('Scope disposed');
|
||||
});
|
||||
});
|
||||
|
||||
// Dispose all effects
|
||||
scope.stop()
|
||||
scope.stop();
|
||||
```
|
||||
|
||||
## Composables
|
||||
@@ -193,21 +195,21 @@ Composables are functions that encapsulate stateful logic using Composition API.
|
||||
|
||||
```ts
|
||||
// composables/useMouse.ts
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
export function useMouse() {
|
||||
const x = ref(0)
|
||||
const y = ref(0)
|
||||
const x = ref(0);
|
||||
const y = ref(0);
|
||||
|
||||
const update = (e: MouseEvent) => {
|
||||
x.value = e.pageX
|
||||
y.value = e.pageY
|
||||
}
|
||||
x.value = e.pageX;
|
||||
y.value = e.pageY;
|
||||
};
|
||||
|
||||
onMounted(() => window.addEventListener('mousemove', update))
|
||||
onUnmounted(() => window.removeEventListener('mousemove', update))
|
||||
onMounted(() => window.addEventListener('mousemove', update));
|
||||
onUnmounted(() => window.removeEventListener('mousemove', update));
|
||||
|
||||
return { x, y }
|
||||
return { x, y };
|
||||
}
|
||||
```
|
||||
|
||||
@@ -216,31 +218,31 @@ export function useMouse() {
|
||||
Use `toValue()` (Vue 3.3+) to normalize refs, getters, or plain values.
|
||||
|
||||
```ts
|
||||
import { ref, watchEffect, toValue, type MaybeRefOrGetter } from 'vue'
|
||||
import { ref, watchEffect, toValue, type MaybeRefOrGetter } from 'vue';
|
||||
|
||||
export function useFetch(url: MaybeRefOrGetter<string>) {
|
||||
const data = ref(null)
|
||||
const error = ref(null)
|
||||
const data = ref(null);
|
||||
const error = ref(null);
|
||||
|
||||
watchEffect(async () => {
|
||||
data.value = null
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await fetch(toValue(url))
|
||||
data.value = await res.json()
|
||||
} catch (e) {
|
||||
error.value = e
|
||||
}
|
||||
})
|
||||
data.value = null;
|
||||
error.value = null;
|
||||
|
||||
return { data, error }
|
||||
try {
|
||||
const res = await fetch(toValue(url));
|
||||
data.value = await res.json();
|
||||
} catch (e) {
|
||||
error.value = e;
|
||||
}
|
||||
});
|
||||
|
||||
return { data, error };
|
||||
}
|
||||
|
||||
// Usage - all work:
|
||||
useFetch('/api/users')
|
||||
useFetch(urlRef)
|
||||
useFetch(() => `/api/users/${props.id}`)
|
||||
useFetch('/api/users');
|
||||
useFetch(urlRef);
|
||||
useFetch(() => `/api/users/${props.id}`);
|
||||
```
|
||||
|
||||
### Return Refs (Not Reactive)
|
||||
@@ -249,10 +251,10 @@ Always return plain object with refs for destructuring compatibility.
|
||||
|
||||
```ts
|
||||
// Good - preserves reactivity when destructured
|
||||
return { x, y }
|
||||
return { x, y };
|
||||
|
||||
// Bad - loses reactivity when destructured
|
||||
return reactive({ x, y })
|
||||
return reactive({ x, y });
|
||||
```
|
||||
|
||||
<!--
|
||||
|
||||
@@ -12,11 +12,11 @@ description: Vue 3 script setup syntax and compiler macros for defining props, e
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Top-level bindings are exposed to template
|
||||
import { ref } from 'vue'
|
||||
import MyComponent from './MyComponent.vue'
|
||||
import { ref } from 'vue';
|
||||
import MyComponent from './MyComponent.vue';
|
||||
|
||||
const count = ref(0)
|
||||
const increment = () => count.value++
|
||||
const count = ref(0);
|
||||
const increment = () => count.value++;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -32,24 +32,27 @@ Declare component props with full TypeScript support.
|
||||
```ts
|
||||
// Type-based declaration (recommended)
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
count?: number
|
||||
items: string[]
|
||||
}>()
|
||||
title: string;
|
||||
count?: number;
|
||||
items: string[];
|
||||
}>();
|
||||
|
||||
// With defaults (Vue 3.5+)
|
||||
const { title, count = 0 } = defineProps<{
|
||||
title: string
|
||||
count?: number
|
||||
}>()
|
||||
title: string;
|
||||
count?: number;
|
||||
}>();
|
||||
|
||||
// With defaults (Vue 3.4 and below)
|
||||
const props = withDefaults(defineProps<{
|
||||
title: string
|
||||
items?: string[]
|
||||
}>(), {
|
||||
items: () => [] // Use factory for arrays/objects
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string;
|
||||
items?: string[];
|
||||
}>(),
|
||||
{
|
||||
items: () => [], // Use factory for arrays/objects
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## defineEmits
|
||||
@@ -59,14 +62,14 @@ Declare emitted events with typed payloads.
|
||||
```ts
|
||||
// Named tuple syntax (recommended)
|
||||
const emit = defineEmits<{
|
||||
update: [value: string]
|
||||
change: [id: number, name: string]
|
||||
close: []
|
||||
}>()
|
||||
update: [value: string];
|
||||
change: [id: number, name: string];
|
||||
close: [];
|
||||
}>();
|
||||
|
||||
emit('update', 'new value')
|
||||
emit('change', 1, 'name')
|
||||
emit('close')
|
||||
emit('update', 'new value');
|
||||
emit('change', 1, 'name');
|
||||
emit('close');
|
||||
```
|
||||
|
||||
## defineModel
|
||||
@@ -75,26 +78,31 @@ Two-way binding prop consumed via `v-model`. Available in Vue 3.4+.
|
||||
|
||||
```ts
|
||||
// Basic usage - creates "modelValue" prop
|
||||
const model = defineModel<string>()
|
||||
model.value = 'hello' // Emits "update:modelValue"
|
||||
const model = defineModel<string>();
|
||||
model.value = 'hello'; // Emits "update:modelValue"
|
||||
|
||||
// Named model - consumed via v-model:name
|
||||
const count = defineModel<number>('count', { default: 0 })
|
||||
const count = defineModel<number>('count', { default: 0 });
|
||||
|
||||
// With modifiers
|
||||
const [value, modifiers] = defineModel<string>()
|
||||
const [value, modifiers] = defineModel<string>();
|
||||
if (modifiers.trim) {
|
||||
// Handle trim modifier
|
||||
}
|
||||
|
||||
// With transformers
|
||||
const [value, modifiers] = defineModel({
|
||||
get(val) { return val?.toLowerCase() },
|
||||
set(val) { return modifiers.trim ? val?.trim() : val }
|
||||
})
|
||||
get(val) {
|
||||
return val?.toLowerCase();
|
||||
},
|
||||
set(val) {
|
||||
return modifiers.trim ? val?.trim() : val;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Parent usage:
|
||||
|
||||
```vue
|
||||
<Child v-model="name" />
|
||||
<Child v-model:count="total" />
|
||||
@@ -106,21 +114,24 @@ Parent usage:
|
||||
Explicitly expose properties to parent via template refs. Components are closed by default.
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
import { ref } from 'vue';
|
||||
|
||||
const count = ref(0)
|
||||
const reset = () => { count.value = 0 }
|
||||
const count = ref(0);
|
||||
const reset = () => {
|
||||
count.value = 0;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
count,
|
||||
reset
|
||||
})
|
||||
reset,
|
||||
});
|
||||
```
|
||||
|
||||
Parent access:
|
||||
|
||||
```ts
|
||||
const childRef = ref<{ count: number; reset: () => void }>()
|
||||
childRef.value?.reset()
|
||||
const childRef = ref<{ count: number; reset: () => void }>();
|
||||
childRef.value?.reset();
|
||||
```
|
||||
|
||||
## defineOptions
|
||||
@@ -130,8 +141,8 @@ Declare component options without a separate `<script>` block. Available in Vue
|
||||
```ts
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
name: 'CustomName'
|
||||
})
|
||||
name: 'CustomName',
|
||||
});
|
||||
```
|
||||
|
||||
## defineSlots
|
||||
@@ -140,9 +151,9 @@ Provide type hints for slot props. Available in Vue 3.3+.
|
||||
|
||||
```ts
|
||||
const slots = defineSlots<{
|
||||
default(props: { item: string; index: number }): any
|
||||
header(props: { title: string }): any
|
||||
}>()
|
||||
default(props: { item: string; index: number }): any;
|
||||
header(props: { title: string }): any;
|
||||
}>();
|
||||
```
|
||||
|
||||
## Generic Components
|
||||
@@ -152,20 +163,21 @@ Declare generic type parameters using the `generic` attribute.
|
||||
```vue
|
||||
<script setup lang="ts" generic="T extends string | number">
|
||||
defineProps<{
|
||||
items: T[]
|
||||
selected: T
|
||||
}>()
|
||||
items: T[];
|
||||
selected: T;
|
||||
}>();
|
||||
</script>
|
||||
```
|
||||
|
||||
Multiple generics with constraints:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts" generic="T, U extends Record<string, T>">
|
||||
import type { Item } from './types'
|
||||
import type { Item } from './types';
|
||||
defineProps<{
|
||||
data: U
|
||||
key: keyof U
|
||||
}>()
|
||||
data: U;
|
||||
key: keyof U;
|
||||
}>();
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -175,11 +187,11 @@ Use `vNameOfDirective` naming convention.
|
||||
|
||||
```ts
|
||||
const vFocus = {
|
||||
mounted: (el: HTMLElement) => el.focus()
|
||||
}
|
||||
mounted: (el: HTMLElement) => el.focus(),
|
||||
};
|
||||
|
||||
// Or import and rename
|
||||
import { myDirective as vMyDirective } from './directives'
|
||||
import { myDirective as vMyDirective } from './directives';
|
||||
```
|
||||
|
||||
```vue
|
||||
@@ -194,7 +206,7 @@ Use `await` directly in `<script setup>`. The component becomes async and must b
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const data = await fetch('/api/data').then(r => r.json())
|
||||
const data = await fetch('/api/data').then((r) => r.json());
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user