一键导入
vuejs
Expert knowledge for working with VueJS, the preferred frontend framework we use along with Vite as a bundler
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Expert knowledge for working with VueJS, the preferred frontend framework we use along with Vite as a bundler
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Expert knowledge for developing Obsidian plugins and themes using TypeScript, CodeMirror 6, and CSS variables - covers plugin lifecycle, Vault API, Workspace API, editor extensions, styling patterns, and the official Obsidian CLI (obsidian command, plugin:reload, eval, dev tools)
Expert knowledge for modern browser technologies including HTML5, CSS (Grid, Flexbox, Container Queries, Scroll-Driven Animations, Anchor Positioning), Progressive Web Apps (PWA, Service Workers, Web Push, File System Access), and Browser APIs (Canvas, Geolocation, Web Storage, Web Share, Drag and Drop). Use when working with web standards, client-side features, responsive design, offline capabilities, or native-like web experiences.
Expert knowledge for building cross-platform desktop applications with Electron using Chromium and Node.js, including IPC patterns, security best practices, and deployment considerations
Expert knowledge for runtime validation in TypeScript using ArkType, a syntax-first validation library with TypeScript-like definitions, JIT compilation for 10x-100x performance over Zod, native recursion support, morphs for data transformation, and Standard Schema compatibility
基于 SOC 职业分类
| name | vuejs |
| description | Expert knowledge for working with VueJS, the preferred frontend framework we use along with Vite as a bundler |
VueJS is the preferred frontend framework we use along with Vite as a bundler.
<script setup> syntax over plain <script> blocksThe Composition API organizes component logic by feature rather than by option type. It enables better code reuse through composables and provides superior TypeScript support.
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// Reactive state
const count = ref(0)
// Computed property
const doubled = computed(() => count.value * 2)
// Methods
function increment() {
count.value++
}
// Lifecycle
onMounted(() => {
console.log('Component mounted')
})
</script>
<template>
<button @click="increment">Count: {{ count }} (doubled: {{ doubled }})</button>
</template>
<script setup> is syntactic sugar that provides:
These macros are globally available in <script setup> without imports:
// Runtime declaration
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 }
})
// Type-based declaration (preferred)
const props = defineProps<{
title: string
count?: number
}>()
// With defaults (type-based)
const props = withDefaults(defineProps<{
title: string
count?: number
}>(), {
count: 0
})
// Destructuring with defaults (3.5+)
const { title, count = 0 } = defineProps<{
title: string
count?: number
}>()
// Runtime declaration
const emit = defineEmits(['update', 'delete'])
// Type-based declaration (preferred)
const emit = defineEmits<{
update: [id: number, value: string]
delete: [id: number]
}>()
// Usage
emit('update', 1, 'new value')
Creates two-way binding props for v-model:
// Default model (v-model)
const modelValue = defineModel<string>()
modelValue.value = 'hello' // emits 'update:modelValue'
// Named model (v-model:count)
const count = defineModel<number>('count', { default: 0 })
// With options
const title = defineModel<string>('title', {
required: true,
default: 'Untitled'
})
Explicitly expose properties to parent components via template refs:
const internalState = ref(0)
const publicMethod = () => { /* ... */ }
// Only publicMethod is accessible via ref
defineExpose({
publicMethod
})
Declare component options (Vue 3.3+):
defineOptions({
inheritAttrs: false,
name: 'CustomComponent'
})
Type slot props (Vue 3.3+):
const slots = defineSlots<{
default(props: { item: Item }): any
header(): any
}>()
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
Async operations are supported (requires <Suspense> wrapper):
<script setup>
const data = await fetch('/api/data').then(r => r.json())
</script>
Wraps values in a reactive container. Preferred for most cases.
import { ref } from 'vue'
const count = ref(0)
const user = ref<User | null>(null)
// Access/modify via .value
count.value++
user.value = { name: 'Ken' }
// Auto-unwraps in templates
// <template>{{ count }}</template> - no .value needed
Advantages:
.value makes reactivity visibleMakes objects deeply reactive without wrapping:
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: { name: 'Ken' }
})
// Direct property access
state.count++
state.user.name = 'Kenneth'
Limitations (prefer ref() instead):
state = { ... } breaks reactivityconst { count } = state is not reactiveCreates cached derived values:
import { ref, computed } from 'vue'
const firstName = ref('Ken')
const lastName = ref('Snyder')
// Read-only computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// Writable computed (rare)
const fullNameWritable = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value: string) => {
const [first, last] = value.split(' ')
firstName.value = first
lastName.value = last
}
})
Best practices:
Watches specific reactive sources:
import { ref, watch } from 'vue'
const count = ref(0)
const user = ref<User | null>(null)
// Watch single ref
watch(count, (newValue, oldValue) => {
console.log(`Count changed: ${oldValue} -> ${newValue}`)
})
// Watch multiple sources
watch([count, user], ([newCount, newUser], [oldCount, oldUser]) => {
// ...
})
// Watch getter function
watch(
() => user.value?.name,
(newName) => console.log(`Name: ${newName}`)
)
// Options
watch(count, callback, {
immediate: true, // Run immediately on creation
deep: true, // Deep watch nested objects (use sparingly)
once: true, // Run only once (3.4+)
flush: 'post' // Run after DOM updates
})
// Stop watching
const stop = watch(count, callback)
stop() // Stop the watcher
Automatically tracks all reactive dependencies accessed during execution:
import { ref, watchEffect } from 'vue'
const count = ref(0)
const name = ref('Ken')
// Automatically tracks count and name
watchEffect(() => {
console.log(`Count: ${count.value}, Name: ${name.value}`)
})
// With cleanup
watchEffect((onCleanup) => {
const controller = new AbortController()
fetch('/api/data', { signal: controller.signal })
onCleanup(() => controller.abort())
})
When to use which:
watch(): When you need old/new values, or want explicit control over dependencieswatchEffect(): When callback uses the same values it needs to watchWait for DOM updates after state changes:
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
await nextTick()
// DOM is now updated
}
All hooks must be called synchronously during setup():
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated
} from 'vue'
// Most commonly used
onMounted(() => {
// Component is mounted, DOM is available
console.log('Mounted')
})
onUnmounted(() => {
// Cleanup: remove event listeners, cancel timers
console.log('Unmounted')
})
onUpdated(() => {
// DOM has been updated after reactive state change
console.log('Updated')
})
// Less common
onBeforeMount(() => { /* Before initial render */ })
onBeforeUpdate(() => { /* Before DOM update */ })
onBeforeUnmount(() => { /* Before unmounting */ })
// Error handling
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err)
return false // Prevent propagation
})
// Keep-alive specific
onActivated(() => { /* Component activated from cache */ })
onDeactivated(() => { /* Component deactivated to cache */ })
// Type-based declaration (preferred)
interface Props {
title: string
count?: number
items: Item[]
onUpdate?: (value: string) => void
}
const props = defineProps<Props>()
// With defaults
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => [] // Factory function for objects/arrays
})
// Destructuring with defaults
const { title, count = 0 } = defineProps<Props>()
One-way data flow: Props flow down, events flow up. Never mutate props directly.
// BAD - mutating prop
props.count++
// GOOD - emit event to parent
const emit = defineEmits<{ update: [count: number] }>()
emit('update', props.count + 1)
// Type-based declaration
const emit = defineEmits<{
change: [id: number]
update: [id: number, value: string]
'update:modelValue': [value: string]
}>()
// Usage
emit('change', 1)
emit('update', 1, 'new value')
Parent component:
<template>
<MyComponent>
<!-- Default slot -->
<p>Default content</p>
<!-- Named slot -->
<template #header>
<h1>Header</h1>
</template>
<!-- Scoped slot -->
<template #item="{ item, index }">
<li>{{ index }}: {{ item.name }}</li>
</template>
</MyComponent>
</template>
Child component:
<script setup lang="ts">
interface Item {
name: string
}
defineSlots<{
default(): any
header(): any
item(props: { item: Item; index: number }): any
}>()
</script>
<template>
<div>
<header>
<slot name="header" />
</header>
<main>
<slot /> <!-- default slot -->
</main>
<ul>
<li v-for="(item, index) in items" :key="item.id">
<slot name="item" :item="item" :index="index" />
</li>
</ul>
</div>
</template>
Dependency injection for deeply nested components:
// Parent component
import { provide, ref } from 'vue'
import type { InjectionKey } from 'vue'
// Type-safe injection key
const ThemeKey: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme')
const theme = ref<'light' | 'dark'>('light')
provide(ThemeKey, theme)
// Also provide mutation function
provide('setTheme', (newTheme: 'light' | 'dark') => {
theme.value = newTheme
})
// Child component (any depth)
import { inject } from 'vue'
const theme = inject(ThemeKey) // Ref<'light' | 'dark'> | undefined
const theme = inject(ThemeKey, ref('light')) // With default
const setTheme = inject<(theme: 'light' | 'dark') => void>('setTheme')
import { ref, type Ref } from 'vue'
// Inferred
const count = ref(0) // Ref<number>
// Explicit
const user = ref<User | null>(null)
// Complex types
interface State {
items: Item[]
loading: boolean
}
const state = ref<State>({ items: [], loading: false })
import { reactive } from 'vue'
interface State {
count: number
user: User | null
}
// Use interface annotation
const state: State = reactive({
count: 0,
user: null
})
import { computed, type ComputedRef } from 'vue'
// Usually inferred
const doubled = computed(() => count.value * 2) // ComputedRef<number>
// Explicit when needed
const user: ComputedRef<User | undefined> = computed(() =>
users.value.find(u => u.id === selectedId.value)
)
function handleInput(event: Event) {
const target = event.target as HTMLInputElement
console.log(target.value)
}
function handleClick(event: MouseEvent) {
console.log(event.clientX, event.clientY)
}
import { ref, onMounted } from 'vue'
const inputRef = ref<HTMLInputElement | null>(null)
const componentRef = ref<InstanceType<typeof MyComponent> | null>(null)
onMounted(() => {
inputRef.value?.focus()
componentRef.value?.someMethod()
})
<template>
<input ref="inputRef" />
<MyComponent ref="componentRef" />
</template>
Composables are functions that encapsulate and reuse stateful logic using Composition API.
Always prefix with use: useMouse, useFetch, useAuth
// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event: MouseEvent) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
// Usage in component
import { useMouse } from '@/composables/useMouse'
const { x, y } = useMouse()
// composables/useFetch.ts
import { ref, watchEffect, toValue, type MaybeRefOrGetter } from 'vue'
export function useFetch<T>(url: MaybeRefOrGetter<string>) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(false)
async function fetchData() {
loading.value = true
error.value = null
try {
const response = await fetch(toValue(url))
data.value = await response.json()
} catch (e) {
error.value = e instanceof Error ? e : new Error('Unknown error')
} finally {
loading.value = false
}
}
watchEffect(() => {
fetchData()
})
return { data, error, loading, refetch: fetchData }
}
MaybeRefOrGetter - Use toValue() to handle refs, getters, or plain valuesonUnmounted or cleanup callbacks// Preferred
const count = ref(0)
const user = ref<User | null>(null)
// Avoid unless you have a specific reason
const state = reactive({ count: 0, user: null })
// BAD
const doubled = computed(() => {
fetch('/api/log') // Side effect!
return count.value * 2
})
// GOOD
const doubled = computed(() => count.value * 2)
// BAD
const d = ref<User[]>([])
// GOOD
const users = ref<User[]>([])
const isLoading = ref(false)
const hasError = ref(false)
// BAD
props.items.push(newItem)
// GOOD
emit('add-item', newItem)
// BAD
const props = defineProps(['title', 'count'])
// GOOD
const props = defineProps<{
title: string
count?: number
}>()
// Group related state, computed, and methods together
// Feature 1: User management
const user = ref<User | null>(null)
const isLoggedIn = computed(() => user.value !== null)
async function login(credentials: Credentials) { /* ... */ }
async function logout() { /* ... */ }
// Feature 2: Theme
const theme = ref<'light' | 'dark'>('light')
const isDarkMode = computed(() => theme.value === 'dark')
function toggleTheme() { /* ... */ }
import { defineAsyncComponent } from 'vue'
const AsyncModal = defineAsyncComponent(() =>
import('./components/Modal.vue')
)
const AsyncModalWithOptions = defineAsyncComponent({
loader: () => import('./components/Modal.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 3000
})
<!-- Parent -->
<CustomInput v-model="searchQuery" />
<CustomInput v-model:title="title" v-model:content="content" />
<!-- CustomInput.vue -->
<script setup lang="ts">
const model = defineModel<string>({ required: true })
</script>
<template>
<input :value="model" @input="model = ($event.target as HTMLInputElement).value" />
</template>
<template>
<!-- v-if for conditional rendering (removes from DOM) -->
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else>{{ data }}</div>
<!-- v-show for frequent toggling (CSS display: none) -->
<div v-show="isVisible">Toggleable content</div>
</template>
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- With index -->
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
<!-- Object iteration -->
<div v-for="(value, key, index) in object" :key="key">
{{ key }}: {{ value }}
</div>
</template>
<template>
<!-- Method handler -->
<button @click="handleClick">Click</button>
<!-- Inline handler -->
<button @click="count++">Increment</button>
<!-- With event -->
<button @click="handleClick($event)">Click</button>
<!-- Modifiers -->
<button @click.stop="handleClick">Stop propagation</button>
<button @click.prevent="handleSubmit">Prevent default</button>
<input @keyup.enter="submit" />
<input @keyup.ctrl.enter="submitWithCtrl" />
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
const form = reactive({
email: '',
password: '',
remember: false
})
const errors = ref<Record<string, string>>({})
async function handleSubmit() {
errors.value = {}
if (!form.email) {
errors.value.email = 'Email is required'
}
if (Object.keys(errors.value).length === 0) {
await submitForm(form)
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.email" type="email" />
<span v-if="errors.email">{{ errors.email }}</span>
<input v-model="form.password" type="password" />
<label>
<input v-model="form.remember" type="checkbox" />
Remember me
</label>
<button type="submit">Submit</button>
</form>
</template>