Vue3 对 TS 的适配是全方位的,从组件定义、Props 声明到响应式数据、生命周期等都有专门的 TS 语法,下面我会按开发中最常用的场景逐一讲解:
一、基础:组件的 TS 写法(setup 语法糖)
Vue3 推荐使用<script setup lang="ts">作为 TS 组件的核心写法,这是对 TS 支持最友好的方式,也是官方推荐的最佳实践。
<template> <div>{{ msg }}</div> <button @click="handleClick">点击</button> </template> <script setup lang="ts"> // 1. 响应式数据的 TS 类型标注 import { ref, reactive } from 'vue' // ref 基础类型(TS 可自动推导,也可显式标注) const msg = ref<string>('Hello Vue3 + TS') // 显式标注为字符串类型 const count = ref(0) // 自动推导为 number 类型 // reactive 复杂类型(推荐用 interface/type 定义) interface User { name: string age: number isAdmin?: boolean // 可选属性 } const user = reactive<User>({ name: '张三', age: 20 }) // 2. 函数的 TS 类型标注 const handleClick = (): void => { // 无返回值标注为 void count.value++ console.log(count.value) } </script>二、核心:Props 的 TS 强类型声明
Vue3 提供了defineProps专门适配 TS 的写法,有两种方式(推荐第二种):
方式 1:运行时声明(兼容 Vue2 风格,类型推导)
<script setup lang="ts"> // TS 会自动从 props 定义中推导类型 const props = defineProps({ title: { type: String, required: true }, size: { type: Number, default: 16 }, isShow: Boolean }) // 使用 props 时会有完整的类型提示 console.log(props.title) // 提示为 string 类型 </script>方式 2:纯 TS 类型声明(推荐,更贴合 TS 习惯)
<script setup lang="ts"> // 用 TS 接口定义 Props 类型,配合 withDefaults 设置默认值 interface Props { title: string // 必传 size?: number // 可选 isShow?: boolean // 可选 } // 方式 1:仅声明类型 // const props = defineProps<Props>() // 方式 2:声明类型 + 设置默认值(推荐) const props = withDefaults(defineProps<Props>(), { size: 16, isShow: true }) </script>三、Emits 的 TS 类型声明
defineEmits同样支持 TS 类型标注,明确事件的名称和参数类型:
<script setup lang="ts"> // 方式 1:纯 TS 类型声明(推荐) const emit = defineEmits<{ // 事件名: (参数1类型, 参数2类型) => void change: (value: string) => void confirm: (id: number, name: string) => void }>() // 方式 2:运行时声明(类型推导) // const emit = defineEmits(['change', 'confirm']) // 触发事件时会校验参数类型 const handleConfirm = () => { emit('confirm', 1, '张三') // 正确 // emit('confirm', '1', '张三') // TS 报错:id 应为 number 类型 } </script>四、Ref 获取 DOM 元素的 TS 标注
获取 DOM 元素时,需要给 ref 标注具体的元素类型:
<template> <input ref="inputRef" type="text" /> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' // 标注为 HTMLInputElement 类型,初始值为 null const inputRef = ref<HTMLInputElement | null>(null) onMounted(() => { // 使用时需要判空(避免 null 报错) if (inputRef.value) { inputRef.value.focus() // TS 会提示 input 元素的所有方法/属性 } }) </script>五、组合式函数的 TS 适配
自定义组合式函数时,通过 TS 标注返回值类型,让调用方获得完整提示:
// hooks/useUser.ts import { ref, computed } from 'vue' interface UserInfo { id: number name: string } // 标注函数返回值类型 export function useUser(): { user: Ref<UserInfo | null> userName: ComputedRef<string> updateName: (name: string) => void } { const user = ref<UserInfo | null>(null) const userName = computed(() => { return user.value?.name || '未知用户' }) const updateName = (name: string): void => { if (user.value) { user.value.name = name } } return { user, userName, updateName } }在组件中使用:
<script setup lang="ts"> import { useUser } from '@/hooks/useUser' // 解构时会获得完整的类型提示 const { user, userName, updateName } = useUser() </script>六、全局属性 / 方法的 TS 扩展
如果给 Vue 实例添加了全局属性(如app.config.globalProperties.$api),需要扩展 TS 类型以获得提示:
// src/types/global.d.ts import type { ComponentCustomProperties } from 'vue' import type { Api } from '@/api' // 扩展 ComponentCustomProperties 接口 declare module 'vue' { interface ComponentCustomProperties { $api: Api // 声明全局 $api 的类型 $formatTime: (time: number) => string // 声明全局方法类型 } }总结
Vue3 对 TS 的核心适配语法可以总结为以下 3 个关键点:
- 核心基座:使用
<script setup lang="ts">作为 TS 组件的基础,这是所有适配语法的前提。 - 核心 API:
defineProps<Props>()、withDefaults()、defineEmits<Emits>()是组件层面适配 TS 的核心,能精准约束 Props/Emits 类型。 - 类型标注:响应式数据(ref/reactive)、DOM 引用、组合式函数等场景,通过 TS 的
interface/type+ 显式标注,实现完整的类型校验和代码提示。
这些语法的核心目标是让 Vue 组件的每一个部分(数据、Props、事件、方法)都能被 TS 精准识别,既提升开发效率,也减少运行时错误。
vue3中父子同行
你想了解的是 Vue3 中父子组件在同一行显示的布局实现方法,这本质上是 CSS 布局的问题,Vue3 本身不直接控制布局,但可以结合 Vue 的组件写法来实现这个效果。
下面我会提供几种最常用、兼容性最好的实现方案,你可以根据实际需求选择:
一、核心实现思路(Flex 布局,推荐)
Flex 布局是实现 “父子同行” 最简洁、灵活的方式,也是现代前端布局的首选方案。
1. 基础示例(父组件控制布局)
<!-- 父组件 Parent.vue --> <template> <!-- 父容器设置 display: flex 即可让子元素同行 --> <div class="parent-container"> <!-- 子组件1 --> <Child1 /> <!-- 子组件2 --> <Child2 /> <!-- 普通元素也适用 --> <div class="normal-element">普通元素</div> </div> </template> <script setup lang="ts"> import父子通信
你想了解的是 Vue3 中父子组件之间的通信方式,这是 Vue 开发中最基础也最核心的知识点之一,Vue3 结合 TypeScript 提供了更规范、类型更安全的通信方案。
下面我会按「父传子」「子传父」「父子双向通信」「跨级 / 通用通信(补充)」四个维度,结合 TS 语法详细讲解,所有示例都基于 Vue3 推荐的<script setup lang="ts">语法。
一、父传子(Props)
这是最基础的通信方式,父组件通过 Props 向子组件传递数据,Vue3 + TS 下能通过类型校验保证数据合法性。
1. 子组件(Child.vue):声明 Props 并接收
<template> <div> <h3>子组件接收的父组件数据:</h3> <p>标题:{{ title }}</p> <p>数量:{{ count }}</p> <p>是否显示:{{ isShow }}</p> </div> </template> <script setup lang="ts"> // 1. 定义 Props 类型(TS 专属) interface Props { title: string; // 必传字符串 count?: number; // 可选数字 isShow?: boolean; // 可选布尔值 } // 2. 声明 Props + 设置默认值(withDefaults 是 Vue3 适配 TS 的语法) const props = withDefaults(defineProps<Props>(), { count: 0, // count 默认值 isShow: true // isShow 默认值 }); </script>2. 父组件(Parent.vue):传递 Props
<template> <div class="parent"> <h2>父组件</h2> <!-- 向子组件传递 Props --> <Child title="父组件传递的标题" :count="parentCount" :isShow="parentIsShow" /> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Child from './Child.vue'; // 父组件的响应式数据 const parentCount = ref<number>(10); const parentIsShow = ref<boolean>(true); </script>二、子传父(Emits)
子组件通过触发自定义事件,将数据传递给父组件,Vue3 + TS 可通过类型约束事件名和参数类型。
1. 子组件(Child.vue):触发事件
<template> <div> <h3>子组件</h3> <button @click="handleSendData">向父组件传值</button> <button @click="handleChangeShow">切换显示状态</button> </div> </template> <script setup lang="ts"> // 1. 定义事件类型(TS 专属) const emit = defineEmits<{ // 事件名: (参数类型) => void send-data: (msg: string, id: number) => void; change-show: (status: boolean) => void; }>(); // 2. 触发事件,传递数据 const handleSendData = () => { emit('send-data', '子组件传递的消息', 1001); }; const handleChangeShow = () => { emit('change-show', false); }; </script>2. 父组件(Parent.vue):监听事件
<template> <div class="parent"> <h2>父组件</h2> <!-- 监听子组件的自定义事件 --> <Child @send-data="handleReceiveData" @change-show="handleChangeShow" /> <p>子组件传递的消息:{{ childMsg }}</p> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Child from './Child.vue'; const childMsg = ref<string>(''); // 接收子组件传递的数据 const handleReceiveData = (msg: string, id: number) => { console.log('子组件传递的ID:', id); childMsg.value = msg; }; // 处理子组件的状态变更 const handleChangeShow = (status: boolean) => { console.log('子组件切换显示状态:', status); }; </script>三、父子双向通信(v-model)
Vue3 支持自定义v-model,实现父子组件数据双向绑定,相比 Vue2 更灵活。
1. 子组件(Child.vue):定义 v-model
<template> <div> <h3>子组件</h3> <!-- 绑定内部值,触发 update 事件 --> <input type="text" :value="modelValue" @input="emit('update:modelValue', $event.target.value)" /> <!-- 自定义名称的 v-model --> <input type="number" :value="count" @input="emit('update:count', Number($event.target.value))" /> </div> </template> <script setup lang="ts"> // 定义 v-model 对应的 Props interface Props { modelValue: string; // 默认 v-model 绑定的属性 count: number; // 自定义 v-model:count 绑定的属性 } const props = defineProps<Props>(); // 定义 update 事件(v-model 固定触发 update:xxx 事件) const emit = defineEmits<{ 'update:modelValue': (value: string) => void; 'update:count': (value: number) => void; }>(); </script>2. 父组件(Parent.vue):使用 v-model
<template> <div class="parent"> <h2>父组件</h2> <p>默认 v-model 值:{{ inputValue }}</p> <p>自定义 v-model:count 值:{{ countValue }}</p> <!-- 使用 v-model 双向绑定 --> <Child v-model="inputValue" v-model:count="countValue" /> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Child from './Child.vue'; // 父组件的响应式数据 const inputValue = ref<string>('初始值'); const countValue = ref<number>(0); </script>四、补充:父子通用通信方式(ref /provide/inject)
1. 父组件通过 ref 调用子组件方法 / 访问数据
子组件(Child.vue):暴露方法 / 数据
<script setup lang="ts"> import { ref, defineExpose } from 'vue'; const childCount = ref<number>(0); const addCount = () => { childCount.value++; }; // 显式暴露(setup 语法糖下默认私有,需 defineExpose 暴露) defineExpose({ childCount, addCount }); </script>父组件(Parent.vue):通过 ref 访
<template> <Child ref="childRef" /> <button @click="handleCallChildMethod">调用子组件方法</button> </template> <script setup lang="ts"> import { ref } from 'vue'; import Child from './Child.vue'; // 标注子组件 ref 类型(TS 专属) const childRef = ref<InstanceType<typeof Child> | null>(null); const handleCallChildMethod = () => { if (childRef.value) { childRef.value.addCount(); // 调用子组件方法 console.log('子组件的 count:', childRef.value.childCount); // 访问子组件数据 } }; </script>
2. provide/inject(适合深层嵌套,父子也可用)
父组件:提供数据
<script setup lang="ts"> import { ref, provide } from 'vue'; import Child from './Child.vue'; const theme = ref<string>('dark'); const changeTheme = () => { theme.value = theme.value === 'dark' ? 'light' : 'dark'; }; // 提供数据(TS 可标注类型) provide('theme', theme); provide('changeTheme', changeTheme); </script>子组件:注入数据
<script setup lang="ts"> import { inject } from 'vue'; // 注入数据并标注类型(TS 专属,设置默认值避免 null) const theme = inject<string>('theme', 'light'); const changeTheme = inject<() => void>('changeTheme', () => {}); </script>