name: vue-development description: 使用组合式API、响应式系统、组件模式、TypeScript集成和最佳实践进行Vue 3开发 allowed-tools: 读取, 写入, 编辑, Bash, Glob, Grep
Vue开发技能
为使用组合式API和现代模式构建Vue 3应用程序提供专家级协助。
能力
- 使用组合式API创建Vue 3组件
- 使用ref和reactive实现响应式状态
- 构建可复用逻辑的组合式函数
- 配置Vue与TypeScript
- 设置Vue Router和导航守卫
- 实现provide/inject进行依赖注入
使用场景
在以下情况时调用此技能:
- 创建Vue 3组件
- 构建共享逻辑的组合式函数
- 设置Vue项目结构
- 实现响应式模式
- 配置Vue与TypeScript
输入参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| componentName | 字符串 | 是 | 组件名称(帕斯卡命名法) |
| compositionApi | 布尔值 | 否 | 使用组合式API(默认:true) |
| typescript | 布尔值 | 否 | 使用TypeScript(默认:true) |
| scriptSetup | 布尔值 | 否 | 使用script setup语法(默认:true) |
配置示例
{
"componentName": "UserProfile",
"compositionApi": true,
"typescript": true,
"scriptSetup": true,
"features": ["props", "emits", "slots"]
}
组件模式
Script Setup组件
<!-- components/UserProfile.vue -->
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
interface User {
id: string;
name: string;
email: string;
avatar: string;
}
// 带默认值的Props
const props = withDefaults(defineProps<{
user: User;
editable?: boolean;
}>(), {
editable: false,
});
// 类型化的Emits
const emit = defineEmits<{
update: [user: User];
delete: [id: string];
}>();
// 响应式状态
const isEditing = ref(false);
const editedName = ref(props.user.name);
// 计算属性
const initials = computed(() => {
return props.user.name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase();
});
// 方法
function saveChanges() {
emit('update', { ...props.user, name: editedName.value });
isEditing.value = false;
}
// 生命周期
onMounted(() => {
console.log('UserProfile mounted');
});
// 向父组件暴露
const resetForm = () => {
editedName.value = props.user.name;
isEditing.value = false;
};
defineExpose({
resetForm,
});
</script>
<template>
<div class="user-profile">
<div class="avatar">
<img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
<span v-else class="initials">{{ initials }}</span>
</div>
<div class="info">
<template v-if="isEditing">
<input v-model="editedName" @keyup.enter="saveChanges" />
<button @click="saveChanges">保存</button>
<button @click="isEditing = false">取消</button>
</template>
<template v-else>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button v-if="editable" @click="isEditing = true">编辑</button>
</template>
</div>
<slot name="actions" :user="user" />
</div>
</template>
<style scoped>
.user-profile {
display: flex;
gap: 1rem;
padding: 1rem;
}
.avatar {
width: 64px;
height: 64px;
border-radius: 50%;
overflow: hidden;
}
.initials {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #e5e7eb;
font-weight: bold;
}
</style>
组合式函数模式
// composables/useUser.ts
import { ref, computed, readonly } from 'vue';
interface User {
id: string;
name: string;
email: string;
}
export function useUser(userId: string) {
const user = ref<User | null>(null);
const loading = ref(false);
const error = ref<Error | null>(null);
const isAuthenticated = computed(() => !!user.value);
async function fetchUser() {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('获取用户失败');
user.value = await response.json();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
}
async function updateUser(data: Partial<User>) {
if (!user.value) return;
loading.value = true;
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
user.value = await response.json();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
}
return {
user: readonly(user),
loading: readonly(loading),
error: readonly(error),
isAuthenticated,
fetchUser,
updateUser,
};
}
// composables/useLocalStorage.ts
import { ref, watch } from 'vue';
export function useLocalStorage<T>(key: string, defaultValue: T) {
const stored = localStorage.getItem(key);
const data = ref<T>(stored ? JSON.parse(stored) : defaultValue);
watch(
data,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
return data;
}
Provide/Inject模式
// context/theme.ts
import { provide, inject, ref, type Ref, type InjectionKey } from 'vue';
type Theme = 'light' | 'dark';
interface ThemeContext {
theme: Ref<Theme>;
toggleTheme: () => void;
}
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
export function provideTheme() {
const theme = ref<Theme>('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
provide(ThemeKey, { theme, toggleTheme });
return { theme, toggleTheme };
}
export function useTheme() {
const context = inject(ThemeKey);
if (!context) {
throw new Error('useTheme必须在theme provider内部使用');
}
return context;
}
Vue Router设置
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
{
path: '',
name: 'home',
component: () => import('@/views/Home.vue'),
},
{
path: 'dashboard',
name: 'dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true },
},
],
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login.vue'),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// 导航守卫
router.beforeEach((to, from, next) => {
const isAuthenticated = !!localStorage.getItem('token');
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'login', query: { redirect: to.fullPath } });
} else {
next();
}
});
export default router;
最佳实践
- 使用script setup语法以获得更简洁的组件
- 创建组合式函数用于可复用逻辑
- 正确类型化props和emits
- 使用readonly修饰暴露的响应式状态
- 利用provide/inject进行深层依赖传递
目标流程
- vue应用开发
- vue组件库
- nuxt全栈开发
- 前端架构