第51集 Vue 3 + TypeScript项目创建
📖 概述
TypeScript为Vue 3带来了类型安全和更好的开发体验,已成为Vue生态中的主流选择。本集将详细介绍如何创建Vue 3 + TypeScript项目,包括环境配置、项目结构、基础配置等,帮助你快速上手Vue 3与TypeScript的结合开发。
✨ 核心知识点
1. TypeScript与Vue 3的结合优势
类型安全
- 编译时类型检查,减少运行时错误
- 更好的代码提示和自动补全
- 提高代码可维护性和可读性
更好的IDE支持
- 智能代码补全
- 类型错误实时提示
- 重构支持
更好的团队协作
- 明确的接口定义
- 减少沟通成本
- 代码文档化
2. 创建Vue 3 + TypeScript项目
使用Vite创建
# 使用npm
npm create vite@latest my-vue-ts-app -- --template vue-ts
# 使用yarn
yarn create vite my-vue-ts-app --template vue-ts
# 使用pnpm
pnpm create vite my-vue-ts-app --template vue-ts项目结构
my-vue-ts-app/
├── public/
│ └── vite.svg
├── src/
│ ├── assets/
│ │ └── vue.svg
│ ├── components/
│ │ └── HelloWorld.vue
│ ├── App.vue
│ ├── main.ts # TypeScript入口文件
│ ├── env.d.ts # 环境变量类型声明
│ └── vite-env.d.ts # Vite类型声明
├── .gitignore
├── index.html
├── package.json
├── tsconfig.json # TypeScript配置
├── tsconfig.node.json # Node环境TypeScript配置
└── vite.config.ts # Vite配置3. TypeScript配置详解
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}关键配置项说明
target: 编译目标ES版本module: 模块系统strict: 开启严格类型检查jsx: JSX支持include: 需要编译的文件references: 项目引用
4. Vue组件中的TypeScript使用
基本组件示例
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script setup lang="ts">
// 使用lang="ts"开启TypeScript支持
import { ref } from 'vue'
// 类型注解
const msg: string = 'Hello Vue 3 + TypeScript'
// ref类型推导
const count = ref<number>(0)
</script>
<style scoped>
.hello {
color: #42b883;
}
</style>组件定义方式
<!-- 使用defineComponent -->
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'HelloWorld',
props: {
msg: {
type: String,
required: true
}
},
setup(props) {
// props.msg 自动推导为string类型
return {
// 组件逻辑
}
}
})
</script>5. 环境变量类型声明
env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}自定义环境变量
// src/env.d.ts
env.d.ts
/// <reference types="vite/client" />
type ImportMetaEnv = {
readonly VITE_APP_TITLE: string
readonly VITE_APP_API_URL: string
// 更多环境变量...
}
type ImportMeta = {
readonly env: ImportMetaEnv
}6. 常用TypeScript类型
基础类型
// 基本类型
const str: string = 'hello'
const num: number = 42
const bool: boolean = true
const arr: number[] = [1, 2, 3]
const tuple: [string, number] = ['hello', 42]
const obj: { name: string; age: number } = { name: 'vue', age: 3 }
const nil: null = null
const undef: undefined = undefined
const anyVal: any = 'any type'
const voidVal: void = undefined
const neverVal: never = (() => { throw new Error() })()高级类型
// 联合类型
const union: string | number = 'hello'
// 交叉类型
interface A { a: string }
interface B { b: number }
const intersection: A & B = { a: 'hello', b: 42 }
// 类型别名
type User = {
id: number
name: string
email: string
}
// 泛型
function identity<T>(arg: T): T {
return arg
}🚀 实战案例
1. 创建一个完整的Vue 3 + TypeScript组件
UserCard.vue
<template>
<div class="user-card">
<img :src="user.avatar" :alt="user.name" class="avatar" />
<div class="user-info">
<h3>{{ user.name }}</h3>
<p class="email">{{ user.email }}</p>
<p class="role">{{ user.role }}</p>
<button @click="onEdit">编辑用户</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
// 定义接口
interface User {
id: number
name: string
email: string
avatar: string
role: 'admin' | 'user' | 'guest'
}
// Props类型定义
interface Props {
user: User
}
// 事件类型定义
const emit = defineEmits<{
(e: 'edit', userId: number): void
}>()
// 接收Props
const props = defineProps<Props>()
// 计算属性
const isAdmin = computed(() => props.user.role === 'admin')
// 方法
const onEdit = () => {
emit('edit', props.user.id)
}
</script>
<style scoped>
.user-card {
display: flex;
align-items: center;
padding: 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin: 16px 0;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin-right: 16px;
}
.user-info {
flex: 1;
}
.email {
color: #666;
margin: 4px 0;
}
.role {
font-weight: bold;
margin: 4px 0;
}
button {
background-color: #42b883;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 8px;
}
</style>使用组件
<template>
<div class="app">
<h1>用户列表</h1>
<UserCard
v-for="user in users"
:key="user.id"
:user="user"
@edit="handleEdit"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import UserCard from './components/UserCard.vue'
// 导入User接口
import type { User } from './components/UserCard.vue'
// 模拟用户数据
const users = ref<User[]>([
{
id: 1,
name: '张三',
email: 'zhangsan@example.com',
avatar: 'https://via.placeholder.com/80',
role: 'admin'
},
{
id: 2,
name: '李四',
email: 'lisi@example.com',
avatar: 'https://via.placeholder.com/80',
role: 'user'
}
])
// 处理编辑事件
const handleEdit = (userId: number) => {
console.log('编辑用户:', userId)
// 编辑逻辑
}
</script>2. 配置路径别名
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})tsconfig.json
{
"compilerOptions": {
// ...其他配置
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
// ...其他配置
}使用路径别名
// 之前的导入
import UserCard from './components/UserCard.vue'
// 使用别名后的导入
import UserCard from '@/components/UserCard.vue'📝 最佳实践
始终使用类型注解
- 为函数参数和返回值添加类型
- 为复杂对象定义接口或类型别名
- 避免过度使用any类型
使用接口定义组件Props和Events
- 清晰的组件接口定义
- 更好的类型检查和提示
利用TypeScript的高级特性
- 泛型
- 联合类型
- 交叉类型
- 条件类型
配置严格的TypeScript规则
- 开启strict模式
- 启用noUnusedLocals和noUnusedParameters
- 配置eslint-plugin-vue和@typescript-eslint/eslint-plugin
使用最新的Vue 3 TypeScript语法
<script setup lang="ts">defineProps和defineEmitsdefineComponent
💡 常见问题与解决方案
找不到模块'*.vue'或其相应的类型声明
- 确保已创建
env.d.ts文件 - 检查
tsconfig.json中的include配置 - 确保已安装
@vitejs/plugin-vue
- 确保已创建
TypeScript编译错误但运行正常
- 检查
tsconfig.json配置 - 确保所有依赖都已正确安装
- 尝试删除
node_modules并重新安装
- 检查
Props类型不生效
- 确保使用了
defineProps或props选项 - 检查Props接口定义是否正确
- 确保
lang="ts"已添加到script标签
- 确保使用了
事件类型不生效
- 使用
defineEmits定义事件类型 - 检查事件名称和参数类型是否匹配
- 使用
类型断言过度使用
- 尝试使用更精确的类型定义
- 利用TypeScript的类型推导
- 考虑使用类型守卫
📚 进一步学习资源
🎯 课后练习
基础练习
- 使用Vite创建一个Vue 3 + TypeScript项目
- 配置路径别名
- 创建一个简单的组件,使用TypeScript定义Props和Events
进阶练习
- 创建一个包含多个组件的Vue 3 + TypeScript应用
- 使用接口定义组件间传递的数据结构
- 实现一个简单的状态管理(使用ref和reactive)
实战练习
- 创建一个用户管理系统
- 实现用户列表、添加用户、编辑用户等功能
- 使用TypeScript定义所有数据结构和组件接口
通过本集的学习,你已经掌握了Vue 3 + TypeScript项目的创建和基本配置。TypeScript为Vue开发带来了更好的类型安全和开发体验,是构建大型Vue应用的最佳选择。下一集我们将深入学习TypeScript的类型推断和类型注解。