Nuxt.js组件通信 #
一、组件通信概述 #
在 Vue 应用中,组件间的通信是构建复杂应用的关键。Nuxt.js 继承了 Vue 的所有通信方式,并提供了一些额外的工具。
1.1 通信方式一览 #
| 方式 | 方向 | 适用场景 |
|---|---|---|
| Props | 父→子 | 简单数据传递 |
| Events | 子→父 | 子组件通知父组件 |
| v-model | 双向 | 表单控件 |
| Provide/Inject | 跨层级 | 深层嵌套组件 |
| 状态管理 | 全局 | 复杂应用状态 |
| 组合式函数 | 共享 | 复用逻辑 |
二、Props与Events #
2.1 Props传递数据 #
父组件:
vue
<template>
<ChildComponent
:title="pageTitle"
:items="listItems"
:config="componentConfig"
/>
</template>
<script setup lang="ts">
const pageTitle = ref('页面标题')
const listItems = ref(['item1', 'item2'])
const componentConfig = reactive({
theme: 'dark',
size: 'large'
})
</script>
子组件:
vue
<script setup lang="ts">
interface Props {
title: string
items: string[]
config: {
theme: string
size: string
}
}
defineProps<Props>()
</script>
2.2 Events传递事件 #
子组件:
vue
<script setup lang="ts">
interface Emits {
(e: 'update', value: string): void
(e: 'delete', id: number): void
}
const emit = defineEmits<Emits>()
const handleUpdate = () => {
emit('update', 'new value')
}
const handleDelete = (id: number) => {
emit('delete', id)
}
</script>
父组件:
vue
<template>
<ChildComponent
@update="handleUpdate"
@delete="handleDelete"
/>
</template>
<script setup lang="ts">
const handleUpdate = (value: string) => {
console.log('更新:', value)
}
const handleDelete = (id: number) => {
console.log('删除:', id)
}
</script>
2.3 v-model双向绑定 #
子组件:
vue
<script setup lang="ts">
interface Props {
modelValue: string
}
interface Emits {
(e: 'update:modelValue', value: string): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
const updateValue = (event: Event) => {
emit('update:modelValue', (event.target as HTMLInputElement).value)
}
</script>
<template>
<input
:value="modelValue"
@input="updateValue"
/>
</template>
父组件:
vue
<template>
<CustomInput v-model="inputValue" />
</template>
<script setup lang="ts">
const inputValue = ref('')
</script>
三、Provide/Inject #
3.1 基本用法 #
祖先组件:
vue
<script setup lang="ts">
const theme = ref('dark')
const user = reactive({
name: '张三',
role: 'admin'
})
provide('theme', theme)
provide('user', user)
</script>
后代组件:
vue
<script setup lang="ts">
const theme = inject('theme')
const user = inject('user')
console.log(theme.value)
console.log(user.name)
</script>
3.2 类型安全的Provide/Inject #
typescript
import type { InjectionKey } from 'vue'
interface ThemeConfig {
mode: 'light' | 'dark'
primaryColor: string
}
const ThemeKey: InjectionKey<ThemeConfig> = Symbol('theme')
provide(ThemeKey, {
mode: 'dark',
primaryColor: '#3498db'
})
vue
<script setup lang="ts">
const theme = inject(ThemeKey)
if (theme) {
console.log(theme.mode)
}
</script>
3.3 响应式Provide #
vue
<script setup lang="ts">
const state = reactive({
count: 0,
increment() {
this.count++
}
})
provide('state', readonly(state))
provide('actions', {
increment: state.increment
})
</script>
四、组合式函数共享状态 #
4.1 共享状态模式 #
composables/useCounter.ts:
typescript
const globalCount = ref(0)
export const useCounter = () => {
const increment = () => globalCount.value++
const decrement = () => globalCount.value--
return {
count: globalCount,
increment,
decrement
}
}
组件A:
vue
<script setup lang="ts">
const { count, increment } = useCounter()
</script>
组件B:
vue
<script setup lang="ts">
const { count, decrement } = useCounter()
</script>
4.2 认证状态共享 #
composables/useAuth.ts:
typescript
interface User {
id: number
name: string
email: string
role: string
}
const user = useState<User | null>('user', () => null)
const token = useCookie('token')
export const useAuth = () => {
const isAuthenticated = computed(() => !!user.value && !!token.value)
const login = async (credentials: { email: string; password: string }) => {
const { data } = await useFetch('/api/auth/login', {
method: 'POST',
body: credentials
})
if (data.value) {
user.value = data.value.user
token.value = data.value.token
}
}
const logout = async () => {
await useFetch('/api/auth/logout')
user.value = null
token.value = null
}
const hasRole = (role: string) => {
return user.value?.role === role
}
return {
user,
token,
isAuthenticated,
login,
logout,
hasRole
}
}
五、事件总线 #
5.1 使用mitt创建事件总线 #
bash
pnpm add mitt
composables/useEventBus.ts:
typescript
import mitt from 'mitt'
const emitter = mitt()
export const useEventBus = () => {
return {
on: emitter.on,
off: emitter.off,
emit: emitter.emit
}
}
5.2 使用事件总线 #
组件A(发送事件):
vue
<script setup lang="ts">
const bus = useEventBus()
const sendMessage = () => {
bus.emit('message', { text: 'Hello!' })
}
</script>
组件B(接收事件):
vue
<script setup lang="ts">
const bus = useEventBus()
const message = ref('')
onMounted(() => {
bus.on('message', (data: any) => {
message.value = data.text
})
})
onUnmounted(() => {
bus.off('message')
})
</script>
六、组件引用 #
6.1 使用ref获取组件实例 #
父组件:
vue
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup lang="ts">
const childRef = ref()
const callChildMethod = () => {
childRef.value?.doSomething()
}
</script>
子组件:
vue
<script setup lang="ts">
const doSomething = () => {
console.log('子组件方法被调用')
}
defineExpose({
doSomething
})
</script>
6.2 模板引用与组合式函数 #
vue
<script setup lang="ts">
const inputRef = ref<HTMLInputElement>()
const focus = () => {
inputRef.value?.focus()
}
defineExpose({
focus
})
</script>
<template>
<input ref="inputRef" />
</template>
七、父子组件完整示例 #
7.1 父组件 #
vue
<template>
<div class="parent">
<h1>用户管理</h1>
<UserForm
v-model="newUser"
@submit="addUser"
/>
<UserList
:users="users"
@edit="editUser"
@delete="deleteUser"
>
<template #empty>
<p>暂无用户数据</p>
</template>
</UserList>
</div>
</template>
<script setup lang="ts">
interface User {
id: number
name: string
email: string
}
const users = ref<User[]>([])
const newUser = ref({ name: '', email: '' })
const addUser = () => {
users.value.push({
id: Date.now(),
...newUser.value
})
newUser.value = { name: '', email: '' }
}
const editUser = (user: User) => {
console.log('编辑用户:', user)
}
const deleteUser = (id: number) => {
users.value = users.value.filter(u => u.id !== id)
}
</script>
7.2 子组件 - UserForm #
vue
<template>
<form @submit.prevent="$emit('submit')">
<div>
<label>姓名</label>
<input v-model="localUser.name" />
</div>
<div>
<label>邮箱</label>
<input v-model="localUser.email" type="email" />
</div>
<button type="submit">添加</button>
</form>
</template>
<script setup lang="ts">
interface User {
name: string
email: string
}
interface Props {
modelValue: User
}
interface Emits {
(e: 'update:modelValue', value: User): void
(e: 'submit'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const localUser = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
</script>
7.3 子组件 - UserList #
vue
<template>
<div class="user-list">
<slot v-if="users.length === 0" name="empty" />
<ul v-else>
<li v-for="user in users" :key="user.id">
<span>{{ user.name }} - {{ user.email }}</span>
<button @click="$emit('edit', user)">编辑</button>
<button @click="$emit('delete', user.id)">删除</button>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
interface User {
id: number
name: string
email: string
}
defineProps<{
users: User[]
}>()
defineEmits<{
(e: 'edit', user: User): void
(e: 'delete', id: number): void
}>()
</script>
八、跨层级组件通信 #
8.1 使用Provide/Inject #
祖先组件:
vue
<script setup lang="ts">
const selectedUser = ref<User | null>(null)
const selectUser = (user: User) => {
selectedUser.value = user
}
provide('userContext', {
selected: selectedUser,
select: selectUser
})
</script>
后代组件:
vue
<script setup lang="ts">
const { selected, select } = inject('userContext')
</script>
<template>
<div @click="select(user)">
{{ user.name }}
</div>
</template>
8.2 使用组合式函数 #
composables/useUserSelection.ts:
typescript
const selectedUser = ref<User | null>(null)
export const useUserSelection = () => {
const select = (user: User) => {
selectedUser.value = user
}
const clear = () => {
selectedUser.value = null
}
return {
selected: selectedUser,
select,
clear
}
}
九、最佳实践 #
9.1 选择合适的通信方式 #
| 场景 | 推荐方式 |
|---|---|
| 父子简单数据 | Props |
| 子通知父 | Events |
| 表单双向绑定 | v-model |
| 深层嵌套 | Provide/Inject |
| 全局状态 | useState/Pinia |
| 跨组件事件 | 事件总线 |
9.2 避免过度使用 #
- 不要滥用全局状态
- 保持组件职责单一
- 优先使用 Props/Events
- 组合式函数优于混入
十、总结 #
本章介绍了 Nuxt.js 组件通信:
- Props 和 Events 实现父子通信
- v-model 实现双向绑定
- Provide/Inject 实现跨层级通信
- 组合式函数共享状态
- 事件总线实现任意组件通信
- 组件引用调用子组件方法
合理选择通信方式是构建可维护应用的关键,下一章我们将学习元数据与 SEO。
最后更新:2026-03-28