1. 项目背景与核心价值
Vue3和TypeScript的组合已经成为现代前端开发的黄金搭档。作为一个从零开始的学习项目,这个练手项目能帮助开发者快速掌握Vue3的Composition API、响应式系统等核心特性,同时体验TypeScript带来的类型安全和更好的开发体验。
我在实际项目中多次使用这套技术栈,发现它特别适合构建中小型应用。Vue3的性能优化和更小的包体积,加上TypeScript的静态类型检查,能显著提升代码质量和开发效率。这个练手项目将带你从环境搭建到核心功能实现,完整走一遍开发流程。
2. 环境准备与项目初始化
2.1 开发工具选择
对于Vue3 + TypeScript开发,我推荐以下工具组合:
- VS Code:轻量且对TypeScript支持极佳
- Volar:专为Vue3设计的VS Code插件
- Vue DevTools:调试Vue应用的浏览器插件
安装完基础工具后,打开终端运行:
npm init vue@latest vue3-ts-demo在创建向导中,确保勾选TypeScript支持。这个命令会使用Vue官方的脚手架工具创建项目骨架。
2.2 关键依赖解析
创建完成后,查看package.json中的核心依赖:
vue@3.x:Vue3核心库typescript:TypeScript编译器@vitejs/plugin-vue:Vite的Vue插件vue-tsc:Vue的TypeScript类型检查工具
提示:建议将Node.js升级到最新LTS版本,避免兼容性问题。我在实际项目中遇到过旧版Node导致Vite热更新失效的情况。
3. 项目结构深度解析
3.1 初始目录说明
生成的典型项目结构如下:
├── src │ ├── assets/ # 静态资源 │ ├── components/ # 公共组件 │ ├── App.vue # 根组件 │ └── main.ts # 应用入口 ├── tsconfig.json # TS配置 └── vite.config.ts # Vite配置3.2 TypeScript配置要点
打开tsconfig.json,有几个关键配置需要注意:
{ "compilerOptions": { "target": "ESNext", "module": "ESNext", "strict": true, "jsx": "preserve", "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] }注意:
strict: true会启用所有严格类型检查,这对学习TypeScript很有帮助,但初期可能会遇到较多类型错误。
4. Vue3核心特性实践
4.1 Composition API基础
在src/components下创建Counter.vue:
<script setup lang="ts"> import { ref } from 'vue' const count = ref(0) function increment() { count.value++ } </script> <template> <button @click="increment"> Count is: {{ count }} </button> </template>这是最基础的Composition API用法:
ref创建响应式变量setup语法糖简化代码- 完整的TypeScript类型支持
4.2 组件Props类型定义
改进Counter组件,添加props类型检查:
<script setup lang="ts"> interface Props { initialValue?: number step?: number } const props = withDefaults(defineProps<Props>(), { initialValue: 0, step: 1 }) const count = ref(props.initialValue) function increment() { count.value += props.step } </script>这里展示了:
- 使用interface定义Props类型
withDefaults设置默认值- 类型安全的props访问
5. 状态管理进阶
5.1 Pinia基础使用
安装Pinia:
npm install pinia创建store/counter.ts:
import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), actions: { increment() { this.count++ } } })在组件中使用:
<script setup lang="ts"> import { useCounterStore } from '@/stores/counter' const counter = useCounterStore() </script> <template> <button @click="counter.increment"> {{ counter.count }} </button> </template>5.2 类型化的Store
Pinia天然支持TypeScript,我们可以进一步强化类型:
interface CounterState { count: number history: number[] } export const useCounterStore = defineStore('counter', { state: (): CounterState => ({ count: 0, history: [] }), actions: { increment() { this.count++ this.history.push(this.count) } } })6. 路由配置与类型安全
6.1 Vue Router安装
npm install vue-router@4创建router/index.ts:
import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: () => import('../views/AboutView.vue') } ] }) export default router6.2 路由类型增强
在src目录下创建types/router.d.ts:
import 'vue-router' declare module 'vue-router' { interface RouteMeta { requiresAuth?: boolean title?: string } }这样可以在路由配置中使用自定义的meta字段,并保持类型安全。
7. 实战:Todo应用开发
7.1 数据结构设计
创建types/todo.ts:
export interface TodoItem { id: string title: string completed: boolean createdAt: Date } export type FilterType = 'all' | 'active' | 'completed'7.2 核心组件实现
TodoList.vue的部分代码:
<script setup lang="ts"> import { ref, computed } from 'vue' import type { TodoItem, FilterType } from '@/types/todo' const todos = ref<TodoItem[]>([]) const filter = ref<FilterType>('all') const filteredTodos = computed(() => { switch (filter.value) { case 'active': return todos.value.filter(t => !t.completed) case 'completed': return todos.value.filter(t => t.completed) default: return todos.value } }) function addTodo(title: string) { todos.value.push({ id: crypto.randomUUID(), title, completed: false, createdAt: new Date() }) } </script>8. 测试与调试技巧
8.1 组件单元测试
安装测试依赖:
npm install -D vitest @vue/test-utils happy-dom创建tests/Counter.spec.ts:
import { mount } from '@vue/test-utils' import Counter from '@/components/Counter.vue' describe('Counter', () => { it('renders properly', () => { const wrapper = mount(Counter) expect(wrapper.text()).toContain('Count is: 0') }) it('increments count on click', async () => { const wrapper = mount(Counter) await wrapper.find('button').trigger('click') expect(wrapper.text()).toContain('Count is: 1') }) })8.2 调试技巧
- 使用
debugger语句配合浏览器开发者工具 - Vue DevTools的Composition API面板
- 在VS Code中设置断点调试
经验分享:遇到类型错误时,先阅读完整的错误信息。Vue3+TS的错误提示通常很详细,能直接定位问题所在。
9. 项目优化与部署
9.1 代码分割优化
在vite.config.ts中添加:
export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vendor: ['vue', 'pinia', 'vue-router'] } } } } })9.2 部署准备
构建生产版本:
npm run build生成的dist目录可以直接部署到静态托管服务,如Vercel、Netlify等。
10. 常见问题解决
10.1 类型导入问题
遇到"无法找到模块"错误时,检查tsconfig.json中的paths配置:
{ "compilerOptions": { "paths": { "@/*": ["src/*"] } } }10.2 Vue文件类型支持
确保在env.d.ts中有:
declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component }10.3 响应式数据更新
如果发现数据更新但视图不更新:
- 确保使用了ref或reactive
- 检查是否直接修改了数组的length属性
- 对于复杂对象,考虑使用
toRefs
我在实际项目中积累的一个技巧是:当遇到难以理解的类型错误时,可以临时使用// @ts-ignore注释掉错误,但一定要后续回来解决这些类型问题,这是学习TypeScript的好机会。