原文筆記
11 TypeScript 和生产级代码
你可以先用 JavaScript 学 Vue,再逐步引入 TypeScript。TypeScript 的价值不是“让代码更复杂”,而是让错误更早暴露,尤其适合多人协作和长期维护的项目。
本章目标:你理解 TypeScript 能解决什么问题,能给 Vue props、emits、ref、API 数据加基础类型,并知道生产级代码应该具备哪些习惯。
1. JavaScript 中的问题
JavaScript 很灵活:
function formatMinutes(minutes) {
return `${minutes} 分钟`
}
formatMinutes(30)
formatMinutes('abc')
第二次调用语法上没问题,但业务上可能错了。TypeScript 可以提前提醒:这里应该传数字。
function formatMinutes(minutes: number) {
return `${minutes} 分钟`
}
formatMinutes('abc') // 类型错误
2. 基础类型
const title: string = 'Vue'
const minutes: number = 45
const done: boolean = false
数组:
const tags: string[] = ['vue', 'router']
const scores: number[] = [80, 90]
对象类型:
type Lesson = {
id: string
title: string
minutes: number
done: boolean
}
使用:
const lesson: Lesson = {
id: 'core',
title: 'Vue 核心语法',
minutes: 90,
done: false
}
3. 可选属性和联合类型
可选属性:
type User = {
id: string
name: string
avatarUrl?: string
}
avatarUrl? 表示可能存在,也可能没有。
联合类型:
type Priority = 'low' | 'normal' | 'high'
这样可以限制优先级只能是这三个值。
const priority: Priority = 'urgent' // 报错
4. Vue 中给 props 加类型
<script setup lang="ts">
type Lesson = {
id: string
title: string
minutes: number
done: boolean
}
defineProps<{
lesson: Lesson
completed: boolean
}>()
</script>
这样父组件传错类型时,编辑器会提示。
例如 completed 应该是布尔值:
<LessonCard :completed="true" />
如果写成:
<LessonCard completed="true" />
这里传的是字符串 "true",TypeScript 和 Vue 工具会更容易发现问题。
5. 给 emits 加类型
const emit = defineEmits<{
toggle: [id: string]
remove: [id: string]
}>()
调用:
emit('toggle', props.lesson.id)
如果写错事件名:
emit('toggel', props.lesson.id)
会提示错误。
如果参数类型错:
emit('toggle', 123)
也会提示错误。
6. 给 ref 加类型
多数时候 TypeScript 能推断:
const count = ref(0) // Ref<number>
const title = ref('') // Ref<string>
需要手动写类型的情况:
type Lesson = {
id: string
title: string
}
const lesson = ref<Lesson | null>(null)
为什么要 Lesson | null?
因为一开始可能还没请求到课程详情,所以是 null。请求成功后才是 Lesson。
模板中要处理:
<p v-if="!lesson">加载中...</p>
<article v-else>
<h1>{{ lesson.title }}</h1>
</article>
7. API 数据类型
type ApiLesson = {
id: string
title: string
completed: boolean
}
async function getLessons(): Promise<ApiLesson[]> {
const response = await fetch('/api/lessons')
if (!response.ok) {
throw new Error('课程加载失败')
}
return response.json()
}
Promise<ApiLesson[]> 表示这个异步函数最终返回课程数组。
注意:TypeScript 只能检查你写的代码,不能保证后端真实返回一定符合类型。后端返回脏数据时,TypeScript 不会自动拦截。真实项目如果需要运行时校验,可以使用 zod 等库。
8. 类型放在哪里
小项目可以写在组件里:
type Lesson = { ... }
多个文件共用时,可以放:
src/types/lesson.ts
// src/types/lesson.ts
export type Lesson = {
id: string
title: string
minutes: number
}
导入:
import type { Lesson } from '../types/lesson'
import type 表示只导入类型,不导入运行时代码。
9. 生产级代码习惯
生产级不是指代码很复杂,而是指它能被维护。
好习惯:
- 文件名和组件名清晰。
- 组件职责单一。
- 状态放在合适位置。
- API 请求有错误处理。
- 表单有校验。
- 列表有空状态。
- 关键逻辑有测试。
- 不把所有逻辑塞进一个巨大组件。
- 不依赖“我记得这里不会出错”。
10. 目录建议
src/
assets/
components/
composables/
data/
router/
services/
stores/
types/
views/
职责:
components:可复用组件。views:页面组件。composables:组合式函数。stores:Pinia。services:请求封装。types:共享类型。
11. 初学者引入 TypeScript 的节奏
不要一开始就被类型吓住。推荐顺序:
- 先用 JavaScript 学会 Vue 核心。
- 给数据模型写类型,例如
Lesson、Todo。 - 给 props 和 emits 加类型。
- 给 API 函数返回值加类型。
- 给复杂组合式函数加类型。
TypeScript 是工具,不是学习 Vue 的前置障碍。
12. 常见错误
错误一:把类型当运行时变量用
type Lesson = { title: string }
console.log(Lesson) // 错误,Lesson 是类型,不是值
类型只在开发和编译时存在,运行时没有。
错误二:过度使用 any
const lesson: any = {}
any 会关闭类型检查。初学时可以偶尔救急,但不要大量使用。
错误三:忽略 null
const lesson = ref<Lesson | null>(null)
console.log(lesson.value.title) // 可能报错,因为 lesson.value 可能是 null
修复:
if (lesson.value) {
console.log(lesson.value.title)
}
本章练习
- 为
Lesson和Todo写类型。 - 把
LessonCard的 props 改成 TypeScript 写法。 - 为
toggle和removeemits 加类型。 - 写
getLessons(): Promise<Lesson[]>。 - 回答:TypeScript 能防止哪些错误?不能防止哪些错误?