Vue3 + ts 实战:选项式 API 与组合式 API 的配置骨架与验证
发布时间:2026/9/27 20:37:44来源:尧图网络
1. 从 Vue2 迁移到 Vue3两种 API 到底怎么选Vue3 同时保留了选项式 API 和组合式 API这件事对从 Vue2 过来的开发者其实挺友好——你不用一次性把老代码全推翻。但真正落到 TypeScript 项目里问题就来了tsconfig.json该怎么配vite.config.ts里要不要加额外插件defineComponent和script setup的类型推导为什么表现不一样props 和 emits 的校验写法差在哪这篇就围绕 Vue3 TypeScript 的工程化落地把两套 API 的配置骨架、组件模板、类型校验动作完整走一遍。适合两类人一是手上有一批 Vue2 项目准备渐进迁移二是新起项目想直接上组合式 API 但不确定配置细节。读完之后你能拿到可直接复制的tsconfig.json、vite.config.ts以及选项式和组合式两套组件骨架并且知道怎么用类型报错来验证配置是否真的生效。我试过在同一个项目里混用两种风格结论是配置层完全共用差异只在组件写法。所以下面先讲公共配置再分别给两套模板。2. 前置准备TaoToken 接入与项目初始化在开始写组件之前先把模型调用这条链路打通因为后面验证类型推导时我会用一个真实的接口请求来演示 props 和 emits 的类型校验。这里用 TaoToken 作为模型服务入口它的 API 地址是https://taotoken.net/api兼容常见的对话补全格式接入成本低。你需要先去控制台创建一个 API Key。打开https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite登录后新建一个 Key复制出来存到本地环境变量里别硬编码进代码。项目初始化用 Vite 官方模板npm create vitelatest vue3-ts-demo -- --template vue-ts cd vue3-ts-demo npm install模板自带 TypeScript 支持但默认的tsconfig.json比较宽松类型检查不够严格。下面我会替换成更工程化的版本。如果你打算长期用组合式 API 写业务组件、甚至接 Agent 类工具可以顺带了解下 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它面向的是持续编码场景和本篇的组件复用思路能对上。3. 可复制配置tsconfig.json 与 vite.config.ts3.1 tsconfig.json 严格模式骨架Vue3 项目推荐用「项目引用」结构把应用代码和 Node 侧配置分开。根目录tsconfig.json{ files: [], references: [ { path: ./tsconfig.app.json }, { path: ./tsconfig.node.json } ] }tsconfig.app.json负责src下的业务代码{ compilerOptions: { target: ES2020, useDefineForClassFields: true, module: ESNext, moduleResolution: Bundler, strict: true, noUnusedLocals: true, noUnusedParameters: true, noFallthroughCasesInSwitch: true, jsx: preserve, resolveJsonModule: true, isolatedModules: true, esModuleInterop: true, lib: [ES2020, DOM, DOM.Iterable], skipLibCheck: true, baseUrl: ., paths: { /*: [src/*] } }, include: [src/**/*.ts, src/**/*.d.ts, src/**/*.tsx, src/**/*.vue] }tsconfig.node.json负责vite.config.ts{ compilerOptions: { composite: true, module: ESNext, moduleResolution: Bundler, allowSyntheticDefaultImports: true, strict: true, types: [node] }, include: [vite.config.ts] }关键点有三个strict: true打开全部严格检查moduleResolution: Bundler适配 Vite 的解析方式paths里配了/*别名后面组件里可以直接用。3.2 vite.config.ts 与类型声明import { defineConfig } from vite import vue from vitejs/plugin-vue import { fileURLToPath, URL } from node:url export default defineConfig({ plugins: [vue()], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } }, server: { port: 5173, proxy: { /api: { target: https://taotoken.net, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, /api) } } } })代理这段是为了让前端请求走同源避免本地调试时的跨域问题。src/env.d.ts里补上 Vue 单文件组件的类型声明/// reference typesvite/client / declare module *.vue { import type { DefineComponent } from vue const component: DefineComponent{}, {}, any export default component }到这里配置层就绪。接下来分别看两套 API 的组件骨架。4. 两套组件骨架与类型校验动作4.1 选项式 APIdefineComponent 类型推导选项式 API 在 Vue3 里通过defineComponent保留类型推导主要靠data、props、emits的显式声明。template div p计数{{ count.toFixed(2) }}/p button clickincrement加一/button p模型回复{{ reply }}/p /div /template script langts import { defineComponent } from vue interface Props { initial: number modelName?: string } export default defineComponent({ name: CounterOptions, props: { initial: { type: Number, required: true }, modelName: { type: String, default: default-model } }, emits: { change: (value: number) typeof value number }, data() { return { count: this.initial, reply: } }, mounted() { this.fetchReply() }, methods: { increment() { this.count 1 this.$emit(change, this.count) }, async fetchReply() { const res await fetch(/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${import.meta.env.VITE_TAOTOKEN_KEY} }, body: JSON.stringify({ model: this.modelName, messages: [{ role: user, content: 用一句话介绍 Vue3 }] }) }) const data await res.json() this.reply data.choices?.[0]?.message?.content ?? } } }) /script验证动作把initial传成字符串比如CounterOptions initial1 /Vue 会在控制台给出类型不匹配的警告把modelName传成数字同样会报。emits用对象形式声明后this.$emit(change, abc)会触发类型错误因为校验函数要求number。4.2 组合式 APIscript setup 泛型 props组合式 API 的script setup写法更紧凑类型推导也更直接。template div p计数{{ count.toFixed(2) }}/p button clickincrement加一/button p模型回复{{ reply }}/p /div /template script setup langts import { ref, onMounted } from vue interface Props { initial: number modelName?: string } const props withDefaults(definePropsProps(), { modelName: default-model }) const emit defineEmits{ change: [value: number] }() const count ref(props.initial) const reply ref() function increment() { count.value 1 emit(change, count.value) } async function fetchReply() { const res await fetch(/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${import.meta.env.VITE_TAOTOKEN_KEY} }, body: JSON.stringify({ model: props.modelName, messages: [{ role: user, content: 用一句话介绍 Vue3 }] }) }) const data await res.json() reply.value data.choices?.[0]?.message?.content ?? } onMounted(fetchReply) /script验证动作definePropsProps()里把initial改成string父组件传数字就会报错defineEmits用元组语法后emit(change, abc)直接编译不过。这就是组合式 API 在类型上的优势——校验发生在编译期而不是运行时警告。4.3 组合式函数复用把请求逻辑抽出来组合式 API 真正的价值在逻辑复用。把上面的请求逻辑抽成useChat// src/composables/useChat.ts import { ref } from vue export function useChat(modelName: string) { const reply ref() const loading ref(false) async function send(content: string) { loading.value true try { const res await fetch(/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${import.meta.env.VITE_TAOTOKEN_KEY} }, body: JSON.stringify({ model: modelName, messages: [{ role: user, content }] }) }) const data await res.json() reply.value data.choices?.[0]?.message?.content ?? } finally { loading.value false } } return { reply, loading, send } }组件里直接const { reply, loading, send } useChat(default-model)类型全部自动推导。选项式 API 想达到同样效果得靠 mixin但 mixin 的类型推导一直是痛点这也是新项目更推荐组合式的原因。5. 本篇常见错排查报错一Cannot find module /xxx。检查tsconfig.app.json的paths和vite.config.ts的resolve.alias是否都配了两边缺一不可。IDE 里如果还飘红重启 TS 服务。报错二Property xxx does not exist on type。选项式 API 里常见于this推断失败确认用了defineComponent而不是裸对象导出。组合式里常见于ref忘了.value。报错三props 默认值不生效。组合式必须用withDefaults包裹defineProps直接写definePropsProps()不会应用默认值。报错四emits类型不校验。选项式要用对象形式声明数组形式emits: [change]不做类型检查。组合式用元组语法defineEmits{ change: [value: number] }()。报错五请求 401。检查VITE_TAOTOKEN_KEY是否写进了.env.local且变量名以VITE_开头否则 Vite 不会注入。Key 可以在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite重新生成。报错六代理不生效。vite.config.ts改动后要重启 dev server热更新不会重载配置。6. 继续验证与接入文档配置和组件骨架跑通后建议做两件事一是用模型对话页面手动发一条请求确认 Key 和模型名都对得上地址是https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite二是把接口参数、错误码对照接入文档过一遍地址是https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面列了请求体和响应字段的完整说明。如果你在迁移过程中遇到类型推导对不上的情况优先怀疑tsconfig的strict和moduleResolution两项这两个是 Vue3 TS 项目里最容易踩的配置坑。把这两项对齐官方模板剩下的组件写法差异就只是风格选择了。
网站建设高端定制企业官网