第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'

📝 最佳实践

  1. 始终使用类型注解

    • 为函数参数和返回值添加类型
    • 为复杂对象定义接口或类型别名
    • 避免过度使用any类型
  2. 使用接口定义组件Props和Events

    • 清晰的组件接口定义
    • 更好的类型检查和提示
  3. 利用TypeScript的高级特性

    • 泛型
    • 联合类型
    • 交叉类型
    • 条件类型
  4. 配置严格的TypeScript规则

    • 开启strict模式
    • 启用noUnusedLocals和noUnusedParameters
    • 配置eslint-plugin-vue和@typescript-eslint/eslint-plugin
  5. 使用最新的Vue 3 TypeScript语法

    • &lt;script setup lang=&quot;ts&quot;&gt;
    • definePropsdefineEmits
    • defineComponent

💡 常见问题与解决方案

  1. 找不到模块'*.vue'或其相应的类型声明

    • 确保已创建env.d.ts文件
    • 检查tsconfig.json中的include配置
    • 确保已安装@vitejs/plugin-vue
  2. TypeScript编译错误但运行正常

    • 检查tsconfig.json配置
    • 确保所有依赖都已正确安装
    • 尝试删除node_modules并重新安装
  3. Props类型不生效

    • 确保使用了definePropsprops选项
    • 检查Props接口定义是否正确
    • 确保lang=&quot;ts&quot;已添加到script标签
  4. 事件类型不生效

    • 使用defineEmits定义事件类型
    • 检查事件名称和参数类型是否匹配
  5. 类型断言过度使用

    • 尝试使用更精确的类型定义
    • 利用TypeScript的类型推导
    • 考虑使用类型守卫

📚 进一步学习资源

🎯 课后练习

  1. 基础练习

    • 使用Vite创建一个Vue 3 + TypeScript项目
    • 配置路径别名
    • 创建一个简单的组件,使用TypeScript定义Props和Events
  2. 进阶练习

    • 创建一个包含多个组件的Vue 3 + TypeScript应用
    • 使用接口定义组件间传递的数据结构
    • 实现一个简单的状态管理(使用ref和reactive)
  3. 实战练习

    • 创建一个用户管理系统
    • 实现用户列表、添加用户、编辑用户等功能
    • 使用TypeScript定义所有数据结构和组件接口

通过本集的学习,你已经掌握了Vue 3 + TypeScript项目的创建和基本配置。TypeScript为Vue开发带来了更好的类型安全和开发体验,是构建大型Vue应用的最佳选择。下一集我们将深入学习TypeScript的类型推断和类型注解。

« 上一篇 性能优化的动画技巧 下一篇 » 类型推断与类型注解