跳到主要内容

在 Cursor 中进行 Vue.js 开发:完整指南

Vue.js development in Cursor

Vue.js 已成为构建用户界面最流行的渐进式 JavaScript 框架之一。随着 Vue 3 引入了 Composition API、<script setup> 以及重写的响应式系统,开发者需要一个能够深度理解现代 Vue 生态系统的编辑器。Cursor 将 VS Code 兼容的基础与一流的 AI 辅助相结合,使其成为 Vue.js 开发的出色环境。

本指南将带你了解如何在 Cursor 中搭建高效的 Vue.js 工作流,涵盖从项目脚手架到状态管理、路由以及 AI 辅助组件生成的全过程。无论你是在将 Options API 代码库迁移到 Vue 3,还是在启动一个全新的 SFC(单文件组件)项目,以下技巧都能帮助你更快地推进开发,同时保持代码整洁且可维护。

在 Cursor 中搭建 Vue.js 环境​

前置条件与 Node.js 配置​

在编写任何 Vue 代码之前,请确保你的系统具备正确的基础环境。Cursor 会继承你机器上安装的 Node.js,因此请先验证你的版本。

node --version
npm --version

Vue 3 和 Volar 工具链要求 Node.js 18 或更高版本。如果你使用的是旧版本,请使用版本管理器(如 nvm 或 fnm)进行升级:

# 使用 fnm(推荐,速度更快)
fnm install 20
fnm use 20

# 使用 nvm
nvm install 20
nvm use 20
提示

Cursor 的集成终端支持所有常用的 Shell 工具。你可以使用 Ctrl+`(Windows/Linux)或 Cmd+`(macOS)打开它,并在不离开编辑器的情况下运行任何 CLI 命令。

安装 Volar 扩展​

在 Cursor 中进行 Vue.js 开发最重要的扩展是 Volar(官方 Vue.js 语言支持,现在以 Vue - Official 扩展的形式分发)。它为 .vue 文件提供类型检查、IntelliSense、格式化和诊断功能。

安装步骤:

  1. 在 Cursor 中使用 Ctrl+Shift+X(或在 macOS 上使用 Cmd+Shift+X)打开扩展视图。
  2. 搜索 "Vue - Official"。
  3. 点击 Install。
注意

避免同时启用旧版 Vetur 扩展和 Volar。Vetur 是为 Vue 2 设计的,与现代工具链冲突。如果你已安装它,请禁用或卸载它,以防止重复诊断和错误的 IntelliSense 建议。

你还可以选择安装以下补充扩展来完善工作流:

  • ESLint — 用于对 .vue 和 .js/.ts 文件进行代码检查。
  • Prettier — 用于保持一致的格式化。
  • TypeScript Vue Plugin (Volar) — 改善模板内的类型解析。

脚手架创建新的 Vue 3 项目​

创建新 Vue 项目的推荐方式是使用官方的 create-vue 脚手架工具,它由 Vue 核心团队维护,可选择性地集成 Vite、TypeScript、Router 和 Pinia。

npm create vue@latest my-vue-app

交互式提示会让你选择功能:

Vue.js - The Progressive JavaScript Framework

? Project name: my-vue-app
? Add TypeScript? Yes
? Add JSX Support? No
? Add Vue Router for Single Page Application development? Yes
? Add Pinia for state management? Yes
? Add Vitest for Unit Testing? Yes
? Add ESLint for code quality? Yes
? Add Prettier for code formatting? Yes

脚手架创建完成后,在 Cursor 中打开项目:

cd my-vue-app
cursor .

安装依赖并启动开发服务器:

npm install
npm run dev
提示

Cursor 会自动识别 Vite 开发服务器。当你运行 npm run dev 时,Cursor 的终端会显示本地 URL(通常是 http://localhost:5173)。你可以 Ctrl+Click 该 URL,在默认浏览器中打开运行中的应用。

掌握 Vue 3 Composition API​

理解 <script setup> 语法​

<script setup> 语法是编写 Vue 3 组件的推荐方式。它是一种编译时语法糖,与 Options API 相比大幅减少了样板代码。

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

interface Props {
title: string
count?: number
}

const props = withDefaults(defineProps<Props>(), {
count: 0,
})

const emit = defineEmits<{
(e: 'update', value: number): void
}>()

const doubled = computed(() => props.count * 2)

function increment() {
const next = props.count + 1
emit('update', next)
}
</script>

<template>
<div class="counter">
<h2>{{ title }}</h2>
<p>Count: {{ count }} (doubled: {{ doubled }})</p>
<button @click="increment">Increment</button>
</div>
</template>

<style scoped>
.counter {
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
</style>
注意

defineProps 和 defineEmits 是编译器宏。它们不需要导入,且仅在 <script setup> 中可用。在没有 setup 的普通 <script> 块中尝试使用它们会抛出编译错误。

使用 ref 和 reactive 管理响应式状态​

Vue 3 提供了两种声明响应式状态的主要方式。在两者之间做选择是新手常见的困惑来源。

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

// ref 适用于原始值和对象
const count = ref(0)
const user = ref({ name: 'Ada', age: 36 })

// reactive 仅适用于对象/数组
const settings = reactive({
theme: 'light',
notifications: true,
})

function toggleTheme() {
settings.theme = settings.theme === 'light' ? 'dark' : 'light'
}
</script>

<template>
<button @click="count++">{{ count }}</button>
<p>{{ user.name }} — theme: {{ settings.theme }}</p>
<button @click="toggleTheme">Toggle Theme</button>
</template>
提示

一个实用的经验法则:对原始值以及可能需要整体重新赋值的对象使用 ref,对需要原地修改的分组状态对象使用 reactive。在模板中,ref 会自动解包,因此你写 {{ count }} 而不是 {{ count.value }}。

可复用逻辑的 Composables​

Composables 是封装可复用响应式逻辑的函数。它们遵循 useSomething 的命名约定。Cursor 的 AI 助手擅长从自然语言描述生成 composables。

// composables/useFetch.ts
import { ref, watchEffect, type Ref } from 'vue'

export function useFetch<T>(url: Ref<string>) {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<string | null>(null)
const loading = ref(false)

watchEffect(async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url.value)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = (await response.json()) as T
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error'
} finally {
loading.value = false
}
})

return { data, error, loading }
}

在组件中使用:

<script setup lang="ts">
import { ref } from 'vue'
import { useFetch } from '../composables/useFetch'

interface Post {
id: number
title: string
body: string
}

const endpoint = ref('https://jsonplaceholder.typicode.com/posts/1')
const { data: post, error, loading } = useFetch<Post>(endpoint)
</script>

<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<article v-else-if="post">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</article>
</template>

使用 Volar 扩展​

类型检查模板​

Volar 最强大的功能之一是在 <template> 块内进行类型检查。这能在编译时而非运行时捕获组件 props、事件名称和表达式中的拼写错误。

<script setup lang="ts">
import Counter from './Counter.vue'
</script>

<template>
<!-- 如果 prop 不存在,Volar 会标记此处 -->
<Counter title="Clicks" :count="5" @update="handleChange" />
</template>

如果 Counter 未声明 count prop,Volar 会在该属性下显示红色波浪线,并在问题面板中显示诊断信息。

提示

将 vue-tsc --noEmit 作为 CI 流水线的一部分运行,以在整个项目中强制执行模板类型检查。将其添加到你的 package.json 脚本中:

{
"scripts": {
"type-check": "vue-tsc --noEmit"
}
}

工作区配置​

创建 .vscode/settings.json 文件(Cursor 共享此文件)来为整个团队锁定 Volar 偏好设置:

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "Vue.volar",
"vue.codeActions.askNewComponentName": false,
"vue.inlayHints.missingProps": true,
"vue.inlayHints.inlineHandlerLeading": true,
"typescript.tsdk": "node_modules/typescript/lib"
}

接管模式(独立 TypeScript)​

Volar 可以"接管"内置的 TypeScript 扩展,为 .ts 和 .vue 文件提供更好的 Vue 感知 IntelliSense。在工作区中,创建 .vscode/settings.json:

{
"typescript.preferences.importModuleSpecifier": "relative",
"vue.server.includeLanguages": ["vue"]
}
注意

接管模式会为该工作区禁用标准 TS 扩展。如果你在包含非 Vue TypeScript 库的混合项目中工作,请仔细测试以确保外部类型解析仍然按预期工作。

使用 Pinia 进行状态管理​

为什么选择 Pinia 而非 Vuex​

Pinia 是 Vue 3 的官方状态管理库。它是 Vuex 的继任者,具有以下优势:

  • 更简单、类型安全的 API,无需 mutations(你可以直接修改 state)。
  • 一流的 TypeScript 支持,提供完整的自动补全。
  • 模块化 — 每个 store 独立定义。
  • 出色的 DevTools 集成。

定义 Store​

// stores/counter.ts
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubled = computed(() => count.value * 2)

function increment() {
count.value++
}

async function incrementAsync() {
await new Promise((resolve) => setTimeout(resolve, 200))
count.value++
}

return { count, doubled, increment, incrementAsync }
})

在组件中使用 Store​

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '../stores/counter'

const counter = useCounterStore()

// 安全地解构响应式状态
const { count, doubled } = storeToRefs(counter)
// actions 可以直接解构
const { increment, incrementAsync } = counter
</script>

<template>
<div class="counter">
<p>Count: {{ count }} / Doubled: {{ doubled }}</p>
<button @click="increment">+1</button>
<button @click="incrementAsync">+1 (async)</button>
</div>
</template>
注意

切勿使用普通的 const { count } = counter 来解构 store 状态 — 你会失去响应性。始终对 state 和 getters 使用 storeToRefs,而 actions 是普通函数,可以直接解构。

组织多个 Store​

对于较大的应用,按领域拆分 store:

src/stores/
├── auth.ts
├── cart.ts
├── products.ts
└── ui.ts

跨 store 模式允许一个 store 调用另一个:

// stores/cart.ts
import { defineStore } from 'pinia'
import { useProductsStore } from './products'

export const useCartStore = defineStore('cart', () => {
const items = ref<{ id: number; qty: number }[]>([])

function addToCart(productId: number) {
const products = useProductsStore()
if (!products.exists(productId)) {
throw new Error('Product not found')
}
items.value.push({ id: productId, qty: 1 })
}

return { items, addToCart }
})

使用 Vue Router 进行路由​

配置路由​

// router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes: RouteRecordRaw[] = [
{ path: '/', name: 'home', component: HomeView },
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
{
path: '/users/:id',
name: 'user-detail',
component: () => import('../views/UserDetailView.vue'),
props: true,
},
]

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})

export default router

导航与路由参数​

<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { computed } from 'vue'

const route = useRoute()
const router = useRouter()

const userId = computed(() => route.params.id as string)

function goHome() {
router.push({ name: 'home' })
}
</script>

<template>
<div>
<p>Viewing user #{{ userId }}</p>
<button @click="goHome">Back to Home</button>
</div>
</template>
提示

对不需要立即加载的路由段使用懒加载组件(() => import(...))。这能产生更小的初始包和更快的首次渲染,在移动网络上尤为重要。

导航守卫​

router.beforeEach((to, from) => {
const isAuthenticated = localStorage.getItem('token') !== null
if (to.meta.requiresAuth && !isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } }
}
})

按路由应用 meta 字段:

{
path: '/dashboard',
component: () => import('../views/DashboardView.vue'),
meta: { requiresAuth: true },
}

在 Cursor 中进行 AI 辅助组件生成​

从提示词生成 SFC​

Cursor 的 AI 助手可以从纯英文描述生成完整的单文件组件。打开命令面板(Ctrl+Shift+P)并选择 Cursor: New Chat,或使用 Ctrl+K 内联编辑。

示例提示词:

Create a Vue 3 component using script setup and TypeScript for a paginated
data table. It should accept a columns prop (array of { key, label }),
a rows prop (array of records), and emit a page-change event with the new
page number. Include scoped styles and show empty-state messaging.

Cursor 会生成一个包含所有三个块的脚手架 .vue 文件。审查输出并迭代优化 — 按需要求它添加加载骨架屏、排序或粘性表头。

提示

在提示词中明确指定 Vue 版本和语法风格。指定"Vue 3 with <script setup lang="ts"> and Composition API"可以防止模型输出 Options API 代码或 Vue 2 模式。

重构现有组件​

Cursor 可以将旧版 Options API 组件重构为 Composition API 等效代码。选中组件,按 Ctrl+K,并提示:

Convert this Options API component to script setup using the Composition API.
Keep all existing props, emits, and methods. Use composables for any logic
that could be reused elsewhere.

AI 会在保留行为的同时重写文件。之后务必运行测试套件以确认行为一致。

使用 AI 生成测试​

将 Vue 组件与 Cursor 生成的 Vitest 测试配对:

// Counter.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

describe('Counter.vue', () => {
it('renders the title prop', () => {
const wrapper = mount(Counter, { props: { title: 'Clicks', count: 0 } })
expect(wrapper.text()).toContain('Clicks')
})

it('emits update with incremented value on click', async () => {
const wrapper = mount(Counter, { props: { title: 'Clicks', count: 5 } })
await wrapper.find('button').trigger('click')
expect(wrapper.emitted('update')?.[0]).toEqual([6])
})
})

向 Cursor 提示:"Generate Vitest unit tests for Counter.vue covering the increment behavior and empty-state rendering."

在 Cursor 中调试和分析 Vue 应用​

Vue DevTools 集成​

安装 Vue.js devtools 浏览器扩展,或使用独立的 Electron 应用。虽然 Cursor 本身不托管 DevTools UI,但 Volar 扩展会以内联方式显示组件诊断信息。

要调试 Pinia store 状态,Vue DevTools 的"Pinia"标签页会显示每个 store、其当前状态以及 actions 时间线 — 这对于追踪过期状态 bug 非常有价值。

Source Map 与 Vite​

Vite 默认为开发环境生成 source map。确保你的 vite.config.ts 保持开启:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
plugins: [vue()],
build: {
sourcemap: true,
},
})
注意

将 source map 发布到生产环境可能会暴露你的原始源代码。对于生产构建,请设置 sourcemap: 'hidden' 或使用托管的 source map 服务,并且绝不要将 .map 文件提交到公共仓库。

性能分析​

结合使用 Chrome 性能标签页和 Cursor。vite-plugin-vue-inspector(或 unplugin-vue-source)让你可以点击浏览器中的任何元素直接跳转到 Cursor 中的源代码行 — 对于导航大型代码库来说是一个巨大的生产力提升。

npm install -D vite-plugin-vue-devtools
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueDevTools from 'vite-plugin-vue-devtools'

export default defineConfig({
plugins: [vue(), VueDevTools()],
})

Cursor 中 Vue.js 的最佳实践​

项目结构约定​

可扩展的目录结构如下:

src/
├── assets/
├── components/
│ ├── common/
│ └── forms/
├── composables/
├── layouts/
├── router/
├── stores/
├── views/
├── App.vue
└── main.ts

代码检查与格式化​

在 package.json 中固定 ESLint 和 Prettier 版本,确保整个团队共享相同的规则:

{
"devDependencies": {
"eslint": "^9.0.0",
"eslint-plugin-vue": "^9.0.0",
"prettier": "^3.0.0",
"@vue/eslint-config-typescript": "^13.0.0"
}
}

创建 .eslintrc.cjs:

module.exports = {
root: true,
env: { browser: true, node: true },
extends: [
'plugin:vue/vue3-recommended',
'@vue/eslint-config-typescript',
],
rules: {
'vue/multi-word-component-names': 'off',
'vue/component-tags-order': ['error', { order: ['script', 'template', 'style'] }],
},
}
提示

通过在 settings.json 中添加以下配置,让 Cursor 在保存时自动修复 lint 错误:

{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}

Vue 工作流的键盘快捷键​

  • Ctrl+K — 内联 AI 编辑/重构。
  • Ctrl+Shift+P → "Cursor: New Chat" — 打开 AI 聊天以生成组件。
  • Alt+Click 组件标签 — 跳转到定义(由 Volar 驱动)。
  • Ctrl+. — Volar 诊断的快速修复菜单。

常见陷阱及避免方法​

解构后失去响应性​

Vue 3 中最常见的错误是直接解构响应式对象或 ref:

// 错误 — 会失去响应性
const { count } = useCounterStore()

// 正确
const { count } = storeToRefs(useCounterStore())

修改 Props​

Props 是只读的。如果子组件需要修改一个值,请改为触发事件:

<script setup lang="ts">
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ (e: 'update:modelValue', v: string): void }>()

// 使用 computed setter 实现 v-model 透传
import { computed } from 'vue'
const proxy = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
})
</script>

在 v-for 中忘记 :key​

始终绑定唯一的 :key,以便 Vue 能高效地追踪节点:

<template>
<li v-for="item in items" :key="item.id">{{ item.label }}</li>
</template>
注意

使用数组索引作为 :key 可能会在列表重新排序或过滤时导致难以察觉的 bug。优先使用稳定的唯一标识符,如数据库 ID。

总结​

Cursor 为 Vue.js 开发者提供了一个现代的、AI 增强的工作空间,与官方 Vue 工具链完美配合。通过结合 Volar 进行类型安全编写、Pinia 进行清晰的状态管理、Vue Router 进行导航,以及 Cursor 的 AI 进行组件生成和重构,你可以更快地交付 Vue 3 应用并减少 bug。从使用 create-vue 脚手架创建项目开始,固定你的 ESLint 和格式化规则,然后让 Cursor 的 AI 处理重复的脚手架工作,而你专注于产品逻辑和用户体验。