news 2026/9/27 20:37:41

Vue3 + ts 实战:选项式 API 与组合式 API 的配置骨架与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue3 + ts 实战:选项式 API 与组合式 API 的配置骨架与验证

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_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite,登录后新建一个 Key,复制出来存到本地环境变量里,别硬编码进代码。

项目初始化用 Vite 官方模板:

npm create vite@latest 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_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite,它面向的是持续编码场景,和本篇的组件复用思路能对上。

3. 可复制配置:tsconfig.json 与 vite.config.ts

3.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 types="vite/client" /> declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component }

到这里配置层就绪。接下来分别看两套 API 的组件骨架。

4. 两套组件骨架与类型校验动作

4.1 选项式 API:defineComponent + 类型推导

选项式 API 在 Vue3 里通过defineComponent保留,类型推导主要靠data、props、emits的显式声明。

<template> <div> <p>计数:{{ count.toFixed(2) }}</p> <button @click="increment">加一</button> <p>模型回复:{{ reply }}</p> </div> </template> <script lang="ts"> 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 initial="1" />,Vue 会在控制台给出类型不匹配的警告;把modelName传成数字,同样会报。emits用对象形式声明后,this.$emit('change', 'abc')会触发类型错误,因为校验函数要求number。

4.2 组合式 API:script setup + 泛型 props

组合式 API 的<script setup>写法更紧凑,类型推导也更直接。

<template> <div> <p>计数:{{ count.toFixed(2) }}</p> <button @click="increment">加一</button> <p>模型回复:{{ reply }}</p> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' interface Props { initial: number modelName?: string } const props = withDefaults(defineProps<Props>(), { 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>

验证动作:defineProps<Props>()里把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,直接写defineProps<Props>()不会应用默认值。

报错四:emits类型不校验。选项式要用对象形式声明,数组形式emits: ['change']不做类型检查。组合式用元组语法defineEmits<{ change: [value: number] }>()。

报错五:请求 401。检查VITE_TAOTOKEN_KEY是否写进了.env.local,且变量名以VITE_开头,否则 Vite 不会注入。Key 可以在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite重新生成。

报错六:代理不生效。vite.config.ts改动后要重启 dev server,热更新不会重载配置。

6. 继续验证与接入文档

配置和组件骨架跑通后,建议做两件事:一是用模型对话页面手动发一条请求,确认 Key 和模型名都对得上,地址是https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite;二是把接口参数、错误码对照接入文档过一遍,地址是https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面列了请求体和响应字段的完整说明。

如果你在迁移过程中遇到类型推导对不上的情况,优先怀疑tsconfig的strict和moduleResolution两项,这两个是 Vue3 + TS 项目里最容易踩的配置坑。把这两项对齐官方模板,剩下的组件写法差异就只是风格选择了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 20:36:06

GM-ID设计方法:Cadence+Matlab实现模拟IC高效设计工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 20:35:51

Android GPS HAL层架构解析与U-blox模块移植实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 20:35:37

XU9261高功率同步升压芯片深度实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 20:33:42

Trae 里 Maven 打包跳过 TEST 的配置骨架:TaoToken 统一 Key 接入与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华