主题
Vue3 + TypeScript 与 Pinia(一)
本页对应 Vue3 + TypeScript 与 Pinia,共 7 段视频,约 1 小时 1 分。
学习目标
- 能够使用
npm create vue@latest -- --template vue-ts创建 Vue3+TS 项目,并说明typescript、vue-tsc、Vue Language Tools(Volar)、TypeScript Vue Plugin 在开发阶段与打包阶段各自的作用。 - 能够为
ref区分简单类型与复杂类型两种场景完成类型标注:简单数据依赖类型推导,复杂数据使用泛型参数,并验证赋值校验与模板中的属性提示均生效。 - 能够为
reactive对象选择类型推导或类型别名显式注解(含可选属性),并说明显式注解对初始参数和后续属性赋值的双向约束。 - 能够说明
computed无需手动标注类型的原因,并基于已标注的响应式数据推导出过滤后计算属性的元素类型,在模板遍历时获得属性提示。 - 能够为事件处理函数参数标注
Event类型,并通过as HTMLInputElement/as HTMLButtonElement断言获得精确的 DOM 类型提示。 - 能够为模板引用标注
Ref<HTMLxxxElement | null>类型,并分别用可选链、条件判断、非空断言三种方式处理空值,说明非空断言在运行时无实际判空逻辑的风险。
知识点
创建 Vue3 + TS 开发环境
为什么需要:基础阶段用的是纯 TS 环境,进入 Vue3 项目后要同时支持 .vue 文件和 TS 类型检查,所以要用 Vite 的 Vue + TS 模板。怎么想:创建命令与纯 TS 模板相似,只把模板参数改成 vue-ts。怎么做:
bash
npm create vite@latest vue-ts-pro -- --template vue-ts
cd vue-ts-pro
npm install
npm run dev打开 package.json,重点看 devDependencies:typescript 负责 TS 编译能力,vue-tsc 是 Vue 官方基于 TypeScript 封装的类型检查工具。scripts.build 通常是先执行 vue-tsc,再执行 vite build。入口 main.ts 仍然完成 App 组件渲染并挂载到 #app;App.vue 的 <script> 要写 lang="ts",否则不能正常写 TS。
开发阶段有两个关键工具:Volar 对 .vue 文件提供实时类型错误反馈;TypeScript Vue Plugin 支持在 .ts 中 import .vue 文件。调试时禁用后者,main.ts 里导入 App.vue 会报找不到模块;重新启用后恢复正常。打包阶段由 vue-tsc 做最终类型检查:在 App.vue 写一个类型错误,开发时 Volar 会提示;即使漏掉,执行 npm run build 也会报错。这样设计是为了开发时把类型提示交给编辑器,保证 Vite 运行速度;打包时再兜底校验。
易错点:命令里的模板名是 vue-ts;lang="ts" 不能丢;不要以为开发时没红波浪线就一定没问题,打包还会检查。
为 ref 标注类型
为什么需要:给 ref 标注类型,本质是约束 ref 对象的 value 属性:赋值时校验,使用时提示。怎么想:简单类型用类型推导;复杂类型用泛型。怎么做:
ts
import { ref } from 'vue'
const count = ref(100) // 推导为 Ref<number>
count.value = 200
// count.value = '100' // 报错
const id = ref<string | number>('2028')
id.value = 2030复杂对象数组可写:
ts
type Item = { id: string; name: string; price: number }
const list = ref<Item[]>([])
list.value = [{ id: '1001', name: '男士鞋子', price: 200 }]在模板中 v-for 遍历时,输入 item. 会自动提示 id/name/price。易错点:泛型不仅约束 value,也约束 ref 的初始值;简单类型不要过度手写泛型。
为 reactive 标注类型
为什么需要:修改响应式对象属性时要类型约束,访问属性时要代码提示。怎么想:能根据默认对象推导出所需类型就用推导;推导不出,比如有可选字段,就用类型别名显式标注。怎么做:
ts
import { reactive } from 'vue'
type Form = { username: string; password: string; age?: number }
const form = reactive<Form>({ username: 'tom', password: '123' })
form.username = 'jerry'
// form.username = 100 // 报错易错点:显式标注后,reactive 的初始值也必须满足类型;可选属性写 ?,但一旦传入仍要类型正确。
为 computed 标注类型
为什么需要:计算属性通常由已知响应式数据计算得到,类型可由依赖自动推导;多数计算属性只读,主要收益是代码提示。怎么想:优先依赖自动推导,不手动标注。怎么做:
ts
const count = ref(100)
const doubleCount = computed(() => count.value * 2)
// doubleCount.value 推导为 number
const list = ref<Item[]>([])
const filterList = computed(() => list.value.filter(i => i.price > 500))模板里遍历 filterList 时仍能提示 id/name/price。易错点:不要脱离依赖数据凭空标注;计算属性一般不做赋值。
为事件处理函数标注类型
为什么需要:原生事件处理函数的参数默认容易落到 any,访问 e 没有提示。怎么想:先给事件对象标注 Event,需要更精确的 DOM 类型时再断言 target。怎么做:
ts
function inputChange(e: Event) {
const target = e.target as HTMLInputElement
console.log(target.value)
}按钮点击同理:e.target as HTMLButtonElement。易错点:不断言时 e.target 类型较宽,直接访问 value 会报错;断言要基于真实 DOM 元素。
为模板引用标注类型
为什么需要:模板引用拿到真实 DOM 后,希望访问其属性和方法时有提示。怎么想:ref 泛型写“具体 DOM 类型 | null”,因为挂载前是 null。怎么做:
ts
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value?.focus()
})模板:<input ref="inputRef" />。易错点:必须处理 null,所以常用可选链;onMounted 之后才访问。
非空值处理
为什么需要:对象属性可能为 null 或 undefined,访问其属性会报类型错误。怎么想:有三种方案:可选链、条件判断、非空断言。怎么做:
ts
inputRef.value?.focus() // 可选链
if (inputRef.value) inputRef.value.focus() // 条件判断
inputRef.value!.focus() // 非空断言易错点:非空断言只是骗过 TS,没有运行时判断;无法确保非空时不要用 !,否则真实运行可能报错。
创建 Vue + TS 项目
为什么需要:基础阶段的纯 TS 环境只能练习 TS 语法,不能直接开发 Vue 单文件组件;Vue + TS 项目需要模板、类型检查工具和编辑器支持一起配合。
怎么想:Vite 已支持快速创建 Vue + TS 模板,核心是把模板参数改成 vue-ts。
怎么做:在目标目录打开命令行,执行:
bash
npm create vite@latest vue-ts-pro -- --template vue-ts说明:npm create vite@latest 表示基于最新版 Vite 创建项目;vue-ts-pro 是项目名,可自定义;-- --template vue-ts 表示选择 Vue + TS 模板。创建完成后进入项目,安装依赖并启动:
bash
npm install
npm run dev看到 Vite 本地地址和 Vue 计数器页面,说明环境跑通。
易错点:项目名不要与已有目录冲突;模板参数不要漏写;启动前先安装依赖。
认识 Vue + TS 相关工具
为什么需要:.vue 文件不是普通 .ts 文件,开发时的类型提示、.vue 导入、打包前类型检查需要不同工具配合。
怎么想:开发阶段优先保证编辑器和 Vite 速度,打包阶段再做兜底类型检查。
怎么做:在 package.json 的 devDependencies 中重点看 typescript、vue-tsc。开发阶段常见两个工具:Volar 对 .vue 文件提供实时类型错误反馈;TypeScript Vue Plugin 支持在 .ts 中 import .vue 文件。打包阶段 vue-tsc 做最终类型检查,npm run build 中通常先执行 vue-tsc,再执行 vite build。
易错点:不要以为开发时没报错就万事大吉;vue-tsc 会在打包前兜底检查,遗漏的类型错误仍可能让构建失败。
为 ref 标注类型
为什么需要:给 ref 标注类型,本质是约束 ref 对象 value 属性的类型;赋值时能校验,使用时能提示。
怎么想:简单类型交给类型推导;复杂类型用泛型。
怎么做:
ts
import { ref } from 'vue'
const count = ref(100) // 推导为 Ref<number>
const value = ref<string | number>('2028')
value.value = 2030复杂对象数组可先定义类型:
ts
type Item = { id: string; name: string; price: number }
const list = ref<Item[]>([])
list.value = [{ id: '1001', name: '男士鞋子', price: 200 }]模板中遍历时,item. 会自动联想 id/name/price。
易错点:使用泛型后,初始值也受约束;例如 ref<string | number>('2028') 中初始值必须满足联合类型。
为 reactive 标注类型
为什么需要:reactive 对象修改属性时要有类型约束,访问属性时要有代码提示。
怎么想:默认对象能推导出所需类型时直接用推导;推导不出或需要可选字段时,用类型别名显式注解。
怎么做:
ts
import { reactive } from 'vue'
type Form = { userName: string; password: string; gender?: string }
const form = reactive<Form>({ userName: '张三', password: '123456' })
form.userName = '李四'易错点:显式注解后,传入 reactive 的初始对象也要满足类型;可选字段可省略,但写了就必须类型正确。
为计算属性标注类型
为什么需要:计算属性通常由已知响应式数据计算得到,类型可由依赖自动推导;使用时需要提示。
怎么想:计算属性多数只读,不需要手动赋值,因此优先依赖自动推导。
怎么做:
ts
const count = ref(100)
const doubleCount = computed(() => count.value * 2)
const list = ref<Item[]>([])
const filterList = computed(() => list.value.filter(item => item.price > 500))doubleCount.value 会提示 number 的方法;filterList 中每项仍能提示 id/name/price。
易错点:不要为计算属性硬写不必要类型;先确认依赖数据类型是否已明确。
为事件处理函数标注类型
为什么需要:原生 DOM 事件回调参数默认可能是 any,访问 event.target 时提示很少。
怎么想:先给事件对象标 Event,需要具体 DOM 属性时再断言 target 的具体元素类型。
怎么做:
ts
function inputChange(e: Event) {
const target = e.target as HTMLInputElement
console.log(target.value)
}按钮点击可断言为 HTMLButtonElement。
易错点:e.target 默认较宽泛,不断言就访问 value 等属性容易报错;断言要基于真实元素类型。
为模板引用标注类型
为什么需要:通过 ref 获取真实 DOM 后,需要明确 value 中存放的元素类型,才能获得准确提示。
怎么想:用“具体元素类型 | null”作为泛型,因为组件渲染完成前 value 可能是 null。
怎么做:
ts
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value?.focus()
})模板中绑定 ref="inputRef"。
易错点:访问时加可选链 ?.,否则 value 可能为 null 导致类型错误。
对象非空值处理
为什么需要:对象属性可能为 null 或 undefined,直接访问其属性或方法会报类型错误。
怎么想:常用三种方案:可选链、逻辑判断、非空断言。
怎么做:
ts
inputRef.value?.focus()
if (inputRef.value) {
inputRef.value.focus()
}
inputRef.value!.focus()易错点:非空断言 ! 只是让 TS 通过校验,没有真实运行时判断;若实际仍为空,浏览器运行时会出错。可选链和 if 判断有真实运行时保护,更稳妥。
先看整体:开发阶段与打包阶段的分工
这一节的核心不是某个 API,而是理解 Vue + TypeScript 项目里“谁来检查 .vue 文件类型”。转写稿里先创建了 vue-ts-pro 项目,打开 package.json 的 devDependencies,能看到 typescript 和 vue-tsc;随后运行项目,并在 App.vue 的 <script> 上保留 lang="ts"。这说明:.vue 文件要写 TS,必须开启 lang="ts",否则 TS 支持不会生效。
整体分工可以记成一句话:开发阶段由 IDE 相关工具做实时提示,打包阶段由 vue-tsc 做最终兜底。这样设计的原因是:实时类型检查如果全交给打包工具,会影响 Vite 的开发运行速度;而只在 IDE 里提示又可能被开发者忽略,所以打包前再统一校验一次。

Volar:开发时的实时类型反馈
为什么需要:在 .vue 文件里写 TS 时,如果编辑器不能实时提示类型错误,只能等打包才发现问题,反馈太慢。
怎么想:把“实时反馈”交给 VS Code 的语言服务插件,让错误在写代码时就以红色波浪线出现。转写稿中在 App.vue 里写了一个 count.value 的错误赋值:原本是 number,却赋了字符串,Volar 立刻提示“不能将类型 string 分配给类型 number”。
怎么做:在 VS Code 扩展中安装并启用 Volar;打开 .vue 文件,在 <script lang="ts"> 中写 TS 代码,观察类型提示。示意代码:
ts
const count = ref(100)
count.value = '100' // Volar 实时提示:不能将 string 分配给 number易错点:一是忘记写 lang="ts",导致 TS 语法支持没开启;二是扩展被禁用或工作区未启用,实时提示消失;三是把 Volar 的红色提示误认为项目一定跑不起来——开发服务器可能仍在运行,真正阻断打包的是后面的 vue-tsc。
TypeScript Vue Plugin:让 TS 能 import *.vue
为什么需要:TypeScript 本身不认识 .vue 文件。入口 main.ts 里通常会写 import App from './App.vue',如果没有相应插件支持,TS 会认为找不到这个模块。
怎么想:Volar 负责 .vue 内部的类型反馈,TypeScript Vue Plugin 负责让 TS 语境识别 .vue 模块导入,两者配合使用。
怎么做:在 VS Code 中安装并启用 TypeScript Vue Plugin。转写稿的调试过程很直接:先禁用该插件,回到 main.ts,import App from './App.vue' 立刻报错;重新启用后,这条导入恢复正常。示意代码:
ts
import App from './App.vue'易错点:只启用 Volar 而忽略 TypeScript Vue Plugin,可能出现 .vue 导入报“找不到模块”;排查时先看扩展是否启用,再看是否在正确的工作区打开项目。
vue-tsc:打包前的兜底类型检查
为什么需要:开发时可能漏看 Volar 的提示,或者错误被暂时忽略。打包阶段需要一次统一、最终的类型检查,防止错误代码进入构建产物。
怎么想:vue-tsc 是 Vue 官方基于 tsc 做的一层封装,专门处理 Vue 项目中的类型检查。它不负责开发服务器的实时反馈,而是放在 build 脚本里做“兜底”。
怎么做:在 package.json 中确认 vue-tsc 依赖,并观察构建脚本,通常是先执行 vue-tsc,再执行 vite build:
json
{
"scripts": {
"build": "vue-tsc && vite build"
},
"devDependencies": {
"typescript": "...",
"vue-tsc": "..."
}
}转写稿中故意在 App.vue 留下 count.value 的类型错误,然后执行 npm run build,vue-tsc 在打包前报错,并指出错误发生在 src/App.vue 的 count.value 处。
易错点:不要把 vue-tsc 当成开发服务器实时检查工具;构建脚本顺序不要写反,先 vue-tsc 后 vite build,类型检查失败就不应继续打包;转写稿口述里的“view 杠 TSC”“wa build”按实际包名和脚本应写为 vue-tsc、vite build。
从创建到验证:把三个工具串起来
创建项目可使用与转写稿一致的 Vite 模板命令,项目名可自定义:
bash
npm create vite@latest vue-ts-pro -- --template vue-ts随后安装依赖并启动:
bash
npm install
npm run dev验证顺序建议是:打开 App.vue 确认 <script lang="ts">;写一个错误赋值看 Volar 是否实时提示;在 main.ts 看 import App from './App.vue' 是否正常;最后执行 npm run build,看 vue-tsc 是否在打包前报出同一类错误。这样就能把“开发实时反馈”和“打包兜底校验”串成完整链路。
课件图示








代码示例
① 创建 Vue3 + TS 开发环境(终端命令)
bash
# 基于最新版 vite 创建项目,项目名 vue-ts-pro,模板选 vue-ts
npm create vite@latest vue-ts-pro -- --template vue-ts
cd vue-ts-pro
npm install # 安装所有依赖(含 typescript、vue-tsc)
npm run dev # 启动开发服务器,看到 Vue + 计数器页面即成功要点解释 / 易错点:
-- --template vue-ts中间那个--不能丢,它的作用是把参数透传给 vite,丢了就会变成交互式询问模板。vue-ts-pro是项目名,可自定义,但必须小写、不能有中文。- 创建后先看
package.json的devDependencies:多了typescript(编译器)和vue-tsc(Vue 官方的类型检查工具,打包时兜底)。 build脚本通常是vue-tsc && vite build:打包前先做一次全量类型检查,开发阶段的类型提示交给 IDE(Volar + TypeScript Vue Plugin)保证 vite 运行速度。
② ref 标注类型 + computed 自动推导(App.vue)
vue
<!-- App.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
type Item = {
id: string
name: string
price: number
}
// 复杂类型:用泛型指定,同时约束了 value 的赋值和初始值
const list = ref<Item[]>([
{ id: '1001', name: '男士鞋子', price: 200 },
{ id: '1002', name: '女士背包', price: 600 }
])
// 简单类型:直接交给类型推导,不写任何 TS 代码
const count = ref(100)
// computed 依赖已知响应式数据,类型自动推导,无需手动标注
const filterList = computed(() =>
list.value.filter((item) => item.price > 500)
)
</script>
<template>
<ul>
<li v-for="item in filterList" :key="item.id">
{{ item.id }} — {{ item.name }} — {{ item.price }}
</li>
</ul>
<p>{{ count.toFixed(2) }}</p>
</template>要点解释 / 易错点:
<script setup lang="ts">里的lang="ts"不能丢,它是开启 TS 支持的语法,丢了就写不了 TS 代码。- ref 标注类型的本质是给
value加类型约束:list.value = [{...}]时少写price会立刻报「缺少属性 price」。 ref<Item[]>的泛型同样约束了初始值,初始值里的对象也必须满足Item。- 简单类型(number / string / boolean)不要画蛇添足写
ref<number>(100),推导即可;复杂结构才用泛型。 filterList是ComputedRef<Item[]>,模板里item.会自动联想出id / name / price,这就是 computed 不用手写类型的原因。
③ reactive 显式标注 + 事件处理函数 + 模板引用 + 非空值处理(App.vue)
vue
<!-- App.vue -->
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
// isAgree 是可选属性,reactive 推导不出来 → 用类型别名显式标注
type Form = {
userName: string
password: string
isAgree?: boolean
}
const form = reactive<Form>({
userName: 'zhangsan',
password: '123456'
})
// 模板引用:具体 DOM 类型 联合 null,null 是为了兼容初始值
const inputRef = ref<HTMLInputElement | null>(null)
const btnRef = ref<HTMLButtonElement | null>(null)
// 事件对象默认推导为 any,手动标注 Event 才有提示
const inputChange = (e: Event) => {
const target = e.target as HTMLInputElement // 断言成具体 DOM 类型才能拿到 value
console.log(target.value)
}
const btnClick = (e: MouseEvent) => {
console.log((e.target as HTMLButtonElement).innerText)
}
onMounted(() => {
inputRef.value?.focus() // 方案一:可选链,前面是 null 就停止运算
if (btnRef.value) { // 方案二:条件判断,有值才访问
btnRef.value.innerText = '已点击'
}
// inputRef.value!.focus() // 方案三:非空断言,必须确保不是 null,慎用
})
</script>
<template>
<input ref="inputRef" @change="inputChange" />
<button ref="btnRef" @click="btnClick">按钮</button>
<input v-model="form.userName" />
<p>{{ form.userName }}</p>
</template>要点解释 / 易错点:
reactive能推导出就用推导,只有像isAgree?: boolean这种可选字段才需要type别名 +reactive<Form>显式标注;标注后初始值和后续赋值都会校验。- 事件处理函数的
e不写类型会被推成any,IDE 里没有提示;先标Event,再用e.target as HTMLInputElement断言,才能点出value。 - 模板引用必须写
ref<HTMLInputElement | null>(null):少了| null会与初始值null冲突;ref变量名要和模板上ref="inputRef"完全一致。 - DOM 只能在
onMounted之后访问,此时inputRef.value仍可能是null,所以要用可选链?.或if判断。 - 非空断言
!只在 TS 编译期生效,运行时没有保护,一旦真的为 null 会直接报错,前两种方案更安全。
常见错误
创建项目时模板没生效,装出来的是纯 TS 或默认模板 现象:执行
npm create vite@latest vue-ts-pro --template vue-ts后没有App.vue,或仍被交互式提问。 原因:--template是传给 create-vite 的参数,npm 需要额外的--才能透传。 修法:写成npm create vite@latest vue-ts-pro -- --template vue-ts,创建后检查package.json的 devDependencies 中是否有vue-tsc。.vue文件里写 TS 语法直接报错,或类型完全不生效 现象:<script setup>中写ref<number>(0)报语法错误;模板里点不出属性提示。 原因:漏写lang="ts",SFC 仍按 JS 解析。 修法:<script setup lang="ts">;确认编辑器启用的是 Volar(Vue - Official)而非 Vetur。TS 文件里
import App from './App.vue'报"找不到模块" 现象:入口main.ts里 import.vue出现红色波浪线,但项目能跑。 原因:缺少 TypeScript Vue Plugin(或 Volar 插件被禁用、TS 服务未刷新)。 修法:安装并启用 Volar + TypeScript Vue Plugin,然后在命令面板执行Restart TS Server;同时卸载/禁用 Vetur。开发时一路绿灯,
npm run build才爆类型错误 现象:IDE 没提示,打包时 vue-tsc 报出若干类型错误并中断。 原因:开发阶段类型检查由 IDE 承担,Vite 只做转译不做类型校验;vue-tsc 在 build 前做兜底检查。 修法:把npm run build或单独的vue-tsc --noEmit纳入提交前/CI 检查,不要以"页面能跑"作为通过标准。ref 初始值给了
null或空数组,后续赋值全报错 现象:const list = ref([])后list.value = data报"不能将 Item[] 分配给 never[]";ref(null)后访问属性报错。 原因:初始值过弱,推导出的类型被收窄成never[]/null。 修法:复杂类型一律显式传泛型:ref<Item[]>([])、ref<HTMLInputElement | null>(null)。reactive 对象整体重新赋值,响应式和类型双双出问题 现象:
form = { ...newForm }后页面不再更新,或提示不能赋值。 原因:reactive返回的是代理引用,替换变量本身会丢掉响应式连接。 修法:用Object.assign(form, newForm)或逐字段赋值;确需整体替换时改用ref+.value。模板引用直接
xxxRef.value.focus()报"对象可能为 null" 现象:onMounted中访问模板引用提示可能为空。 原因:ref的 value 在挂载前是null,类型是联合类型。 修法:优先用可选链xxxRef.value?.focus(),或先if (xxxRef.value)判断;非空断言!只在能确定已挂载时使用。滥用非空断言
!掩盖空值 现象:编译通过,运行时浏览器报Cannot read properties of null。 原因:!只在 TS 层消除告警,不产生任何运行时判断。 修法:能用?.或if判断就不要用!;!仅限"逻辑上不可能为空"的场景,并加注释说明。
练习
用一条命令创建名为
vue-ts-pro的 Vue3 + TS 项目,并逐段解释命令各部分含义;创建完成后打开package.json,指出与类型检查相关的两个依赖,以及build脚本中两条命令的执行顺序和各自职责。定义商品类型
Item { id: string; name: string; price: number },用ref声明一个初始为空的商品列表并保证赋值时类型受约束;再声明一个"既可存 string 也可存 number"的 ref,并说明为什么第一种情况不能用类型推导、第二种必须用泛型。用
reactive定义用户表单,字段包含userName、password和可选的gender。分别说明什么情况下用类型推导、什么情况下必须用类型别名显式注解,并各写出一段代码。为
<input>的 change 事件和<button>的 click 事件编写处理函数并标注类型,要求能在函数内访问target.value时获得精确提示;说明标注过程中两个必要步骤分别解决了什么问题。用模板引用获取页面上的
<input>元素并在挂载后使其聚焦。写出三种规避"可能为 null"的写法,并比较三者在运行时行为上的差异。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | Vue3+TS环境创建 | 10:49 |
| 2 | 为ref标注类型 | 09:54 |
| 3 | 为reactive标注类型修复 | 12:49 |
| 4 | 为计算属性标注类型 | 07:22 |
| 5 | 为事件处理函数标注类型 | 06:50 |
| 6 | 为模版引用标注类型 | 07:41 |
| 7 | 对象的非空值处理 | 06:24 |
小结
- 环境创建的关键是
-- --template vue-ts,多一个--决定模板是否生效。 - 三个工具分工明确:Volar 负责开发期实时类型反馈,TypeScript Vue Plugin 负责 TS 中导入
.vue,vue-tsc 负责打包期兜底校验。 - "IDE 报错"和"打包报错"不是同一件事,能跑起来不等于类型通过,CI 中必须有 vue-tsc 这一关。
- ref 的类型标注本质是约束
.value:简单类型靠推导,复杂类型靠泛型,初始值过弱时必须显式泛型。 - reactive 优先用类型推导;只有出现可选字段、推导不出目标类型时才用类型别名显式注解,且注解同时约束初始值和后续属性赋值。
- computed 依赖响应式数据自动推导,通常无需手写类型,收益主要体现在模板中的代码提示。
- 事件处理函数默认
e为any(或宽松类型),需两步:先标注事件类型拿到基础提示,再用断言收窄target拿到精确 DOM 提示。 - 模板引用用
具体元素类型 | null的泛型声明;空值处理三种方案中,?.和if有真实运行时保护,非空断言!没有,须谨慎。