Vue3 对 TS 的适配是全方位的,从组件定义、Props 声明到响应式数据、生命周期等都有专门的 TS 语法,下面我会按开发中最常用的场景逐一讲解:
一、基础:组件的 TS 写法(setup 语法糖)
Vue3 推荐使用 <script setup lang="ts"> 作为 TS 组件的核心写法,这是对 TS 支持最友好的方式,也是官方推荐的最佳实践。
javascript
<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 标注返回值类型,让调用方获得完整提示:
TypeScript
// 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 类型以获得提示:
TypeScript
// 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>