当前位置: 首页 > news >正文

Vue组件通信完整教程

1. Props 和 Events(父子通信)

基本概念

  • Props:父组件向子组件传递数据

  • Events:子组件向父组件传递数据

代码示例

父组件:

<template><div><h2>父组件</h2><p>接收到的数据:{{ childData }}</p><ChildComponent :message="parentMessage" :user-info="userInfo"@update-data="handleChildData"@custom-event="handleCustomEvent"/></div>
</template>
​
<script>
import ChildComponent from './ChildComponent.vue'
​
export default {name: 'ParentComponent',components: {ChildComponent},data() {return {parentMessage: '来自父组件的消息',userInfo: {name: '张三',age: 25},childData: ''}},methods: {handleChildData(data) {this.childData = data},handleCustomEvent(payload) {console.log('自定义事件:', payload)}}
}
</script>

子组件:

<template><div><h3>子组件</h3><p>父组件传递的消息:{{ message }}</p><p>用户信息:{{ userInfo.name }} - {{ userInfo.age }}岁</p><button @click="sendDataToParent">发送数据给父组件</button><button @click="emitCustomEvent">触发自定义事件</button></div>
</template><script>
export default {name: 'ChildComponent',props: {message: {type: String,required: true,default: '默认消息'},userInfo: {type: Object,default: () => ({})}},methods: {sendDataToParent() {this.$emit('update-data', '来自子组件的数据')},emitCustomEvent() {this.$emit('custom-event', {timestamp: Date.now(),message: '自定义事件触发'})}}
}
</script>

面试要点

  • Props 是单向数据流,子组件不能直接修改 props

  • Props 类型验证:String、Number、Boolean、Array、Object、Function

  • 事件名推荐使用 kebab-case 格式


2. this.$parent 和 this.$children

基本概念

  • $parent:访问父组件实例

  • $children:访问子组件实例数组

代码示例

父组件:

<template><div><h2>父组件数据:{{ parentData }}</h2><ChildComponent ref="child1" /><ChildComponent ref="child2" /><button @click="accessChildren">访问所有子组件</button></div>
</template><script>
import ChildComponent from './ChildComponent.vue'export default {name: 'ParentComponent',components: {ChildComponent},data() {return {parentData: '父组件的数据'}},methods: {parentMethod() {console.log('父组件方法被调用')},accessChildren() {// 访问所有子组件this.$children.forEach((child, index) => {console.log(`子组件${index}:`, child.childData)child.childMethod()})}}
}
</script>

子组件:

<template><div><h3>子组件</h3><p>子组件数据:{{ childData }}</p><button @click="accessParent">访问父组件</button></div>
</template><script>
export default {name: 'ChildComponent',data() {return {childData: '子组件的数据'}},methods: {childMethod() {console.log('子组件方法被调用')},accessParent() {// 访问父组件数据console.log('父组件数据:', this.$parent.parentData)// 调用父组件方法this.$parent.parentMethod()// 修改父组件数据(不推荐)this.$parent.parentData = '被子组件修改的数据'}}
}
</script>

面试要点

  • 这种方式会造成组件间强耦合,不推荐在生产环境使用

  • $children 不保证顺序,且不是响应式的

  • 主要用于开发组件库或特殊场景


3. ref 引用

基本概念

通过 ref 特性为子组件赋予一个 ID 引用,父组件可以直接访问子组件

代码示例

父组件:

<template><div><h2>父组件</h2><ChildComponent ref="childRef" :initial-count="10" /><button @click="accessChildByRef">通过ref访问子组件</button><button @click="callChildMethod">调用子组件方法</button></div>
</template><script>
import ChildComponent from './ChildComponent.vue'export default {name: 'ParentComponent',components: {ChildComponent},methods: {accessChildByRef() {// 访问子组件数据console.log('子组件计数:', this.$refs.childRef.count)// 修改子组件数据this.$refs.childRef.count = 100},callChildMethod() {// 调用子组件方法this.$refs.childRef.increment()this.$refs.childRef.reset()}}
}
</script>

子组件:

<template><div><h3>子组件</h3><p>计数:{{ count }}</p><button @click="increment">增加</button></div>
</template><script>
export default {name: 'ChildComponent',props: {initialCount: {type: Number,default: 0}},data() {return {count: this.initialCount}},methods: {increment() {this.count++},reset() {this.count = 0}}
}
</script>

面试要点

  • ref 在组件上时,引用的是组件实例

  • ref 在普通DOM元素上时,引用的是DOM元素

  • $refs 只在组件渲染完成后才填充,不是响应式的


4. 依赖注入(provide/inject)

基本概念

  • provide:祖先组件提供数据

  • inject:后代组件注入数据

代码示例

祖先组件:

<template><div><h2>祖先组件</h2><p>主题:{{ theme }}</p><button @click="toggleTheme">切换主题</button><ParentComponent /></div>
</template><script>
import ParentComponent from './ParentComponent.vue'export default {name: 'GrandParentComponent',components: {ParentComponent},data() {return {theme: 'light',userInfo: {name: '管理员',role: 'admin'}}},provide() {return {theme: this.theme,userInfo: this.userInfo,changeTheme: this.changeTheme,// 响应式注入getTheme: () => this.theme,getUserInfo: () => this.userInfo}},methods: {toggleTheme() {this.theme = this.theme === 'light' ? 'dark' : 'light'},changeTheme(newTheme) {this.theme = newTheme}}
}
</script>

中间组件:

<template><div><h3>中间组件(父组件)</h3><ChildComponent /></div>
</template><script>
import ChildComponent from './ChildComponent.vue'export default {name: 'ParentComponent',components: {ChildComponent}
}
</script>

后代组件:

<template><div :class="themeClass"><h4>后代组件(子组件)</h4><p>当前主题:{{ currentTheme }}</p><p>用户信息:{{ userInfo.name }} ({{ userInfo.role }})</p><button @click="switchTheme">切换到深色主题</button></div>
</template><script>
export default {name: 'ChildComponent',inject: {// 基本注入theme: {default: 'light'},userInfo: {default: () => ({})},// 方法注入changeTheme: {default: () => {}},// 响应式注入getTheme: {default: () => () => 'light'},getUserInfo: {default: () => () => ({})}},computed: {currentTheme() {return this.getTheme()},themeClass() {return `theme-${this.currentTheme}`}},methods: {switchTheme() {this.changeTheme('dark')}}
}
</script><style scoped>
.theme-light {background-color: #fff;color: #333;
}.theme-dark {background-color: #333;color: #fff;
}
</style>

面试要点

  • provide/inject 不是响应式的,除非传入响应式对象

  • 主要用于高阶组件库开发

  • 可以跨越多层组件传递数据


5. 插槽通信(Slots)

基本概念

  • 默认插槽:基本内容分发

  • 具名插槽:多个插槽分发

  • 作用域插槽:子组件向父组件传递数据

代码示例

父组件:

<template><div><h2>父组件</h2><!-- 默认插槽 --><ChildComponent><p>这是默认插槽内容</p></ChildComponent><!-- 具名插槽 --><ChildComponent><template v-slot:header><h3>自定义头部</h3></template><p>默认内容</p><template v-slot:footer><p>自定义底部</p></template></ChildComponent><!-- 作用域插槽 --><ChildComponent><template v-slot:default="slotProps"><div><h4>用户列表</h4><ul><li v-for="user in slotProps.users" :key="user.id">{{ user.name }} - {{ user.age }}岁<button @click="slotProps.deleteUser(user.id)">删除</button></li></ul></div></template></ChildComponent><!-- 多个作用域插槽 --><ChildComponent><template v-slot:header="{ title }"><h3>{{ title }}</h3></template><template v-slot:default="{ users, deleteUser }"><div v-for="user in users" :key="user.id" class="user-card"><span>{{ user.name }}</span><button @click="deleteUser(user.id)">删除</button></div></template><template v-slot:footer="{ total }"><p>总计:{{ total }} 个用户</p></template></ChildComponent></div>
</template><script>
import ChildComponent from './ChildComponent.vue'export default {name: 'ParentComponent',components: {ChildComponent}
}
</script>

子组件:

<template><div class="child-component"><!-- 具名插槽:头部 --><header><slot name="header" :title="title"><h3>默认头部</h3></slot></header><!-- 默认插槽:主要内容 --><main><slot :users="users" :total="users.length":deleteUser="deleteUser":addUser="addUser"><p>默认内容</p></slot></main><!-- 具名插槽:底部 --><footer><slot name="footer" :total="users.length"><p>默认底部</p></slot></footer><div class="controls"><button @click="addRandomUser">添加随机用户</button></div></div>
</template><script>
export default {name: 'ChildComponent',data() {return {title: '用户管理系统',users: [{ id: 1, name: '张三', age: 25 },{ id: 2, name: '李四', age: 30 },{ id: 3, name: '王五', age: 28 }]}},methods: {deleteUser(id) {this.users = this.users.filter(user => user.id !== id)},addUser(user) {this.users.push({id: Date.now(),...user})},addRandomUser() {const names = ['赵六', '钱七', '孙八', '李九']const randomName = names[Math.floor(Math.random() * names.length)]this.addUser({name: randomName,age: Math.floor(Math.random() * 40) + 20})}}
}
</script><style scoped>
.child-component {border: 1px solid #ccc;padding: 20px;margin: 10px 0;
}.user-card {display: flex;justify-content: space-between;align-items: center;padding: 10px;border: 1px solid #eee;margin: 5px 0;
}.controls {margin-top: 20px;text-align: center;
}
</style>

面试要点

  • 插槽是内容分发机制,实现组件的高度复用

  • 作用域插槽可以让父组件访问子组件的数据

  • v-slot 只能添加在 <template> 上(除了独占默认插槽)


6. 事件总线(Event Bus)

基本概念

创建一个空的 Vue 实例作为事件总线,用于任意组件间通信

代码示例

事件总线:

// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()// 或者在 main.js 中
Vue.prototype.$eventBus = new Vue()

组件A:

<template><div><h3>组件A</h3><p>接收到的消息:{{ receivedMessage }}</p><button @click="sendMessage">发送消息给组件B</button><button @click="sendComplexData">发送复杂数据</button></div>
</template><script>
import { EventBus } from './eventBus.js'export default {name: 'ComponentA',data() {return {receivedMessage: '',messageCount: 0}},created() {// 监听事件EventBus.$on('message-from-b', this.handleMessageFromB)EventBus.$on('complex-data', this.handleComplexData)},beforeDestroy() {// 移除事件监听EventBus.$off('message-from-b', this.handleMessageFromB)EventBus.$off('complex-data', this.handleComplexData)},methods: {sendMessage() {EventBus.$emit('message-from-a', `来自组件A的消息 ${++this.messageCount}`)},sendComplexData() {EventBus.$emit('user-data', {id: Date.now(),name: '用户' + this.messageCount,timestamp: new Date().toLocaleString(),data: {score: Math.floor(Math.random() * 100),level: 'advanced'}})},handleMessageFromB(message) {this.receivedMessage = message},handleComplexData(data) {console.log('组件A接收到复杂数据:', data)}}
}
</script>

组件B:

<template><div><h3>组件B</h3><p>接收到的消息:{{ receivedMessage }}</p><p>用户数据:{{ userData.name }}</p><button @click="sendMessage">发送消息给组件A</button><button @click="broadcastMessage">广播消息</button></div>
</template><script>
import { EventBus } from './eventBus.js'export default {name: 'ComponentB',data() {return {receivedMessage: '',userData: {}}},created() {// 监听多个事件EventBus.$on('message-from-a', this.handleMessageFromA)EventBus.$on('user-data', this.handleUserData)EventBus.$on('broadcast', this.handleBroadcast)},beforeDestroy() {// 移除所有事件监听EventBus.$off('message-from-a')EventBus.$off('user-data')EventBus.$off('broadcast')},methods: {sendMessage() {EventBus.$emit('message-from-b', '来自组件B的回复消息')},broadcastMessage() {EventBus.$emit('broadcast', {type: 'announcement',message: '这是一条广播消息',sender: 'ComponentB',timestamp: Date.now()})},handleMessageFromA(message) {this.receivedMessage = message},handleUserData(data) {this.userData = data// 可以进一步处理数据EventBus.$emit('complex-data', {processed: true,originalData: data,processedAt: Date.now()})},handleBroadcast(data) {console.log('组件B接收到广播:', data)}}
}
</script>

面试要点

  • 适用于任意组件间通信,但会增加维护难度

  • 必须在组件销毁时移除事件监听,避免内存泄漏

  • 大型项目推荐使用 Vuex 替代


7. Vuex 状态管理

基本概念

  • State:状态数据

  • Getters:计算属性

  • Mutations:同步修改状态

  • Actions:异步操作

代码示例

Store 配置:

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import userModule from './modules/user'Vue.use(Vuex)export default new Vuex.Store({state: {count: 0,message: 'Hello Vuex',todos: []},getters: {doubleCount: state => state.count * 2,completedTodos: state => state.todos.filter(todo => todo.completed),todoCount: state => state.todos.length,completedCount: (state, getters) => getters.completedTodos.length},mutations: {INCREMENT(state) {state.count++},DECREMENT(state) {state.count--},SET_COUNT(state, payload) {state.count = payload},ADD_TODO(state, todo) {state.todos.push({id: Date.now(),text: todo.text,completed: false,...todo})},TOGGLE_TODO(state, id) {const todo = state.todos.find(t => t.id === id)if (todo) {todo.completed = !todo.completed}},DELETE_TODO(state, id) {state.todos = state.todos.filter(t => t.id !== id)}},actions: {incrementAsync({ commit }, delay = 1000) {return new Promise(resolve => {setTimeout(() => {commit('INCREMENT')resolve()}, delay)})},addTodoAsync({ commit }, todoText) {return new Promise((resolve, reject) => {// 模拟API调用setTimeout(() => {if (todoText.trim()) {commit('ADD_TODO', { text: todoText })resolve()} else {reject(new Error('Todo text cannot be empty'))}}, 500)})}},modules: {user: userModule}
})

用户模块:

// store/modules/user.js
export default {namespaced: true,state: {currentUser: null,users: [],loading: false},getters: {isLoggedIn: state => !!state.currentUser,userCount: state => state.users.length,adminUsers: state => state.users.filter(user => user.role === 'admin')},mutations: {SET_LOADING(state, loading) {state.loading = loading},SET_CURRENT_USER(state, user) {state.currentUser = user},SET_USERS(state, users) {state.users = users},ADD_USER(state, user) {state.users.push(user)}},actions: {async login({ commit }, credentials) {commit('SET_LOADING', true)try {// 模拟API调用const response = await new Promise(resolve => {setTimeout(() => {resolve({id: 1,name: credentials.username,role: 'user'})}, 1000)})commit('SET_CURRENT_USER', response)return response} finally {commit('SET_LOADING', false)}},logout({ commit }) {commit('SET_CURRENT_USER', null)}}
}

组件使用:

<template><div><h3>Vuex 示例组件</h3><!-- 基本状态 --><div><p>计数:{{ count }}</p><p>双倍计数:{{ doubleCount }}</p><button @click="increment">增加</button><button @click="decrement">减少</button><button @click="incrementAsync">异步增加</button></div><!-- Todo 列表 --><div><h4>Todo 列表 ({{ todoCount }}个,已完成{{ completedCount }}个)</h4><input v-model="newTodo" @keyup.enter="addTodo" placeholder="添加新任务"><ul><li v-for="todo in todos" :key="todo.id"><input type="checkbox" :checked="todo.completed"@change="toggleTodo(todo.id)"><span :class="{ completed: todo.completed }">{{ todo.text }}</span><button @click="deleteTodo(todo.id)">删除</button></li></ul></div><!-- 用户模块 --><div><h4>用户信息</h4><div v-if="!isLoggedIn"><input v-model="username" placeholder="用户名"><button @click="login" :disabled="userLoading">{{ userLoading ? '登录中...' : '登录' }}</button></div><div v-else><p>欢迎,{{ currentUser.name }}!</p><button @click="logout">退出登录</button></div></div></div>
</template><script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'export default {name: 'VuexExample',data() {return {newTodo: '',username: ''}},computed: {// 映射状态...mapState(['count', 'todos']),...mapState('user', {currentUser: 'currentUser',userLoading: 'loading'}),// 映射 getters...mapGetters(['doubleCount', 'todoCount', 'completedCount']),...mapGetters('user', ['isLoggedIn'])},methods: {// 映射 mutations...mapMutations(['INCREMENT', 'DECREMENT', 'ADD_TODO', 'TOGGLE_TODO', 'DELETE_TODO']),...mapMutations('user', ['SET_CURRENT_USER']),// 映射 actions...mapActions(['incrementAsync', 'addTodoAsync']),...mapActions('user', ['login', 'logout']),// 自定义方法increment() {this.INCREMENT()},decrement() {this.DECREMENT()},async addTodo() {if (this.newTodo.trim()) {try {await this.addTodoAsync(this.newTodo)this.newTodo = ''} catch (error) {alert(error.message)}}},toggleTodo(id) {this.TOGGLE_TODO(id)},deleteTodo(id) {this.DELETE_TODO(id)},async login() {if (this.username.trim()) {try {await this.login({ username: this.username })this.username = ''} catch (error) {alert('登录失败')}}}}
}
</script><style scoped>
.completed {text-decoration: line-through;color: #999;
}
</style>

面试要点

  • 单一状态树,所有状态存储在一个对象中

  • 只能通过 mutation 修改状态,且必须是同步的

  • Action 可以包含异步操作,通过 commit 触发 mutation

  • 模块化可以避免状态树过于臃肿


8. $attrs 和 $listeners

基本概念

  • $attrs:包含父作用域中不作为 prop 被识别的特性绑定

  • $listeners:包含父作用域中的 v-on 事件监听器

代码示例

祖父组件:

<template><div><h2>祖父组件</h2><ParentComponent :title="title":subtitle="subtitle":theme="theme"class="custom-class"data-id="parent-1"@custom-event="handleCustomEvent"@click="handleClick"@input="handleInput"/></div>
</template><script>
import ParentComponent from './ParentComponent.vue'export default {name: 'GrandParentComponent',components: {ParentComponent},data() {return {title: '主标题',subtitle: '副标题',theme: 'dark'}},methods: {handleCustomEvent(data) {console.log('祖父组件接收到自定义事件:', data)},handleClick(event) {console.log('祖父组件接收到点击事件:', event)},handleInput(value) {console.log('祖父组件接收到输入事件:', value)}}
}
</script>

父组件(中间层):

<template><div><h3>父组件</h3><p>接收到的 title prop: {{ title }}</p><!-- 使用 v-bind="$attrs" 传递所有非 prop 特性 --><!-- 使用 v-on="$listeners" 传递所有事件监听器 --><ChildComponent :title="title"v-bind="$attrs"v-on="$listeners"@child-event="handleChildEvent"/><div><h4>$attrs 内容:</h4><pre>{{ $attrs }}</pre><h4>$listeners 内容:</h4><pre>{{ Object.keys($listeners) }}</pre></div></div>
</template><script>
import ChildComponent from './ChildComponent.vue'export default {name: 'ParentComponent',// 设置 inheritAttrs 为 false,防止根元素继承特性inheritAttrs: false,components: {ChildComponent},props: {title: String},methods: {handleChildEvent(data) {console.log('父组件接收到子组件事件:', data)// 可以进一步向上传递this.$emit('custom-event', {from: 'parent',childData: data})}}
}
</script>

子组件:

<template><div :class="['child-component', themeClass]"v-bind="$attrs"v-on="$listeners"><h4>子组件</h4><p>标题:{{ title }}</p><p>副标题:{{ subtitle }}</p><input v-model="inputValue"@input="handleInput"placeholder="输入内容"><button @click="emitChildEvent">触发子组件事件</button><button @click="triggerClick">触发点击事件</button><div><h5>子组件接收到的 $attrs:</h5><pre>{{ $attrs }}</pre><h5>子组件接收到的 $listeners:</h5><pre>{{ Object.keys($listeners) }}</pre></div></div>
</template><script>
export default {name: 'ChildComponent',inheritAttrs: false,props: {title: String,subtitle: String},data() {return {inputValue: ''}},computed: {themeClass() {return this.$attrs.theme ? `theme-${this.$attrs.theme}` : ''}},methods: {emitChildEvent() {this.$emit('child-event', {message: '来自子组件的数据',timestamp: Date.now(),inputValue: this.inputValue})},triggerClick() {// 触发从祖父组件传递下来的点击事件this.$emit('click', {source: 'child-component',data: this.inputValue})},handleInput() {// 触发从祖父组件传递下来的输入事件this.$emit('input', this.inputValue)}}
}
</script><style scoped>
.child-component {border: 2px solid #ccc;padding: 15px;margin: 10px 0;
}.theme-dark {background-color: #333;color: #fff;border-color: #666;
}.theme-light {background-color: #fff;color: #333;border-color: #ccc;
}pre {background-color: #f5f5f5;padding: 10px;border-radius: 4px;font-size: 12px;
}
</style>

面试要点

  • $attrs 和 $listeners 主要用于高阶组件(HOC)开发

  • inheritAttrs: false 可以避免根元素自动继承特性

  • 适用于组件库开发,实现属性透传


9. v-model 双向绑定

基本概念

v-model 是语法糖,本质上是 props + events 的组合

代码示例

自定义输入组件:

<template><div class="custom-input"><label v-if="label">{{ label }}</label><input:value="value":type="type":placeholder="placeholder":disabled="disabled"@input="handleInput"@focus="handleFocus"@blur="handleBlur"><span v-if="error" class="error">{{ error }}</span></div>
</template><script>
export default {name: 'CustomInput',props: {value: {type: [String, Number],default: ''},label: String,type: {type: String,default: 'text'},placeholder: String,disabled: Boolean,error: String},methods: {handleInput(event) {// 触发 input 事件,更新 v-modelthis.$emit('input', event.target.value)},handleFocus(event) {this.$emit('focus', event)},handleBlur(event) {this.$emit('blur', event)}}
}
</script><style scoped>
.custom-input {margin: 10px 0;
}.error {color: red;font-size: 12px;
}
</style>

自定义复选框组件:

<template><div class="custom-checkbox"><label><inputtype="checkbox":checked="value"@change="handleChange"><span class="checkmark"></span>{{ label }}</label></div>
</template><script>
export default {name: 'CustomCheckbox',props: {value: Boolean,label: String},methods: {handleChange(event) {this.$emit('input', event.target.checked)}}
}
</script>

自定义选择器组件:

<template><div class="custom-select"><select :value="value" @change="handleChange"><option value="">{{ placeholder }}</option><option v-for="option in options" :key="option.value":value="option.value">{{ option.label }}</option></select></div>
</template><script>
export default {name: 'CustomSelect',props: {value: [String, Number],options: {type: Array,default: () => []},placeholder: {type: String,default: '请选择'}},methods: {handleChange(event) {this.$emit('input', event.target.value)}}
}
</script>

使用示例:

<template><div><h3>v-model 双向绑定示例</h3><!-- 自定义输入框 --><CustomInputv-model="formData.name"label="姓名"placeholder="请输入姓名":error="errors.name"@focus="clearError('name')"/><!-- 自定义复选框 --><CustomCheckboxv-model="formData.agree"label="我同意用户协议"/><!-- 自定义选择器 --><CustomSelectv-model="formData.city":options="cityOptions"placeholder="请选择城市"/><!-- 显示数据 --><div class="form-data"><h4>表单数据:</h4><pre>{{ formData }}</pre></div><button @click="validateForm">验证表单</button></div>
</template><script>
import CustomInput from './CustomInput.vue'
import CustomCheckbox from './CustomCheckbox.vue'
import CustomSelect from './CustomSelect.vue'export default {name: 'FormExample',components: {CustomInput,CustomCheckbox,CustomSelect},data() {return {formData: {name: '',agree: false,city: ''},errors: {},cityOptions: [{ value: 'beijing', label: '北京' },{ value: 'shanghai', label: '上海' },{ value: 'guangzhou', label: '广州' },{ value: 'shenzhen', label: '深圳' }]}},methods: {validateForm() {this.errors = {}if (!this.formData.name.trim()) {this.errors.name = '姓名不能为空'}if (!this.formData.agree) {alert('请同意用户协议')}if (!this.formData.city) {alert('请选择城市')}if (Object.keys(this.errors).length === 0 && this.formData.agree && this.formData.city) {alert('表单验证通过!')}},clearError(field) {if (this.errors[field]) {this.$delete(this.errors, field)}}}
}
</script>

面试要点

  • v-model 默认使用 value prop 和 input 事件

  • 可以通过 model 选项自定义 prop 和 event

  • 适用于表单组件开发


10. .sync 修饰符

基本概念

.sync 修饰符是 v-model 的扩展,允许对多个 prop 进行双向绑定

代码示例

子组件:

<template><div class="user-editor"><h4>用户编辑器</h4><div class="form-group"><label>姓名:</label><input :value="name"@input="updateName"placeholder="请输入姓名"></div><div class="form-group"><label>年龄:</label><input type="number":value="age"@input="updateAge"placeholder="请输入年龄"></div><div class="form-group"><label>邮箱:</label><input type="email":value="email"@input="updateEmail"placeholder="请输入邮箱"></div><div class="form-group"><label>状态:</label><select :value="status" @change="updateStatus"><option value="active">激活</option><option value="inactive">未激活</option><option value="banned">禁用</option></select></div><div class="actions"><button @click="resetForm">重置</button><button @click="randomizeData">随机数据</button></div></div>
</template><script>
export default {name: 'UserEditor',props: {name: String,age: Number,email: String,status: String},methods: {updateName(event) {this.$emit('update:name', event.target.value)},updateAge(event) {this.$emit('update:age', parseInt(event.target.value) || 0)},updateEmail(event) {this.$emit('update:email', event.target.value)},updateStatus(event) {this.$emit('update:status', event.target.value)},resetForm() {this.$emit('update:name', '')this.$emit('update:age', 0)this.$emit('update:email', '')this.$emit('update:status', 'active')},randomizeData() {const names = ['张三', '李四', '王五', '赵六']const domains = ['qq.com', '163.com', 'gmail.com']const statuses = ['active', 'inactive', 'banned']const randomName = names[Math.floor(Math.random() * names.length)]const randomAge = Math.floor(Math.random() * 50) + 18const randomEmail = `${randomName.toLowerCase()}@${domains[Math.floor(Math.random() * domains.length)]}`const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]this.$emit('update:name', randomName)this.$emit('update:age', randomAge)this.$emit('update:email', randomEmail)this.$emit('update:status', randomStatus)}}
}
</script><style scoped>
.user-editor {border: 1px solid #ddd;padding: 20px;border-radius: 8px;
}.form-group {margin: 15px 0;display: flex;align-items: center;
}.form-group label {width: 80px;margin-right: 10px;
}.form-group input,
.form-group select {flex: 1;padding: 8px;border: 1px solid #ccc;border-radius: 4px;
}.actions {margin-top: 20px;text-align: center;
}.actions button {margin: 0 10px;padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;
}.actions button:first-child {background-color: #f0f0f0;
}.actions button:last-child {background-color: #007bff;color: white;
}
</style>

父组件:

<template><div><h3>.sync 修饰符示例</h3><!-- 使用 .sync 修饰符 --><UserEditor:name.sync="userData.name":age.sync="userData.age":email.sync="userData.email":status.sync="userData.status"/><!-- 等价于以下写法: --><!--<UserEditor:name="userData.name":age="userData.age":email="userData.email":status="userData.status"@update:name="val => userData.name = val"@update:age="val => userData.age = val"@update:email="val => userData.email = val"@update:status="val => userData.status = val"/>--><div class="user-display"><h4>用户数据:</h4><pre>{{ userData }}</pre></div><div class="controls"><button @click="saveUser">保存用户</button><button @click="loadUser">加载用户</button></div><!-- 多个用户编辑器 --><div class="multiple-editors"><h4>多用户编辑:</h4><div v-for="(user, index) in users" :key="index" class="user-item"><h5>用户 {{ index + 1 }}</h5><UserEditor:name.sync="user.name":age.sync="user.age":email.sync="user.email":status.sync="user.status"/><button @click="removeUser(index)">删除用户</button></div><button @click="addUser">添加用户</button></div></div>
</template><script>
import UserEditor from './UserEditor.vue'export default {name: 'SyncExample',components: {UserEditor},data() {return {userData: {name: '张三',age: 25,email: 'zhangsan@example.com',status: 'active'},users: [{name: '李四',age: 30,email: 'lisi@example.com',status: 'active'},{name: '王五',age: 28,email: 'wangwu@example.com',status: 'inactive'}]}},methods: {saveUser() {console.log('保存用户数据:', this.userData)// 模拟保存到服务器alert('用户数据已保存!')},loadUser() {// 模拟从服务器加载数据this.userData = {name: '赵六',age: 35,email: 'zhaoliu@example.com',status: 'banned'}},addUser() {this.users.push({name: '',age: 0,email: '',status: 'active'})},removeUser(index) {this.users.splice(index, 1)}},watch: {userData: {handler(newVal) {console.log('用户数据变化:', newVal)},deep: true},users: {handler(newVal) {console.log('用户列表变化:', newVal)},deep: true}}
}
</script><style scoped>
.user-display {margin: 20px 0;padding: 15px;background-color: #f8f9fa;border-radius: 8px;
}.controls {text-align: center;margin: 20px 0;
}.controls button {margin: 0 10px;padding: 10px 20px;border: none;border-radius: 4px;cursor: pointer;background-color: #28a745;color: white;
}.multiple-editors {margin-top: 30px;
}.user-item {margin: 20px 0;padding: 15px;border: 1px solid #eee;border-radius: 8px;position: relative;
}.user-item button {position: absolute;top: 10px;right: 10px;background-color: #dc3545;color: white;border: none;padding: 5px 10px;border-radius: 4px;cursor: pointer;
}pre {background-color: #f8f9fa;padding: 10px;border-radius: 4px;font-size: 14px;
}
</style>

面试要点

  • .sync 是语法糖,等价于 v-bind + v-on

  • 事件名必须是 update:propName 格式

  • 适用于需要双向绑定多个属性的场景


11. 总结对比

通信方式对比表

通信方式适用场景优点缺点推荐度
Props/Events父子组件简单直接,单向数据流层级深时传递繁琐⭐⭐⭐⭐⭐
$parent/$children父子组件直接访问强耦合,不推荐⭐⭐
ref父子组件直接操作子组件破坏组件封装性⭐⭐⭐
provide/inject跨层级组件避免逐层传递不是响应式⭐⭐⭐⭐
插槽内容分发高度灵活学习成本高⭐⭐⭐⭐⭐
Event Bus任意组件简单易用难以维护⭐⭐⭐
Vuex复杂状态管理统一状态管理小项目过度设计⭐⭐⭐⭐⭐
$attrs/$listeners属性透传高阶组件开发使用场景有限⭐⭐⭐⭐
v-model双向绑定表单组件只能绑定一个值⭐⭐⭐⭐⭐
.sync多属性双向绑定多个属性同步Vue 3 中已移除⭐⭐⭐⭐

选择建议

  1. 简单父子通信:优先使用 Props/Events

  2. 跨层级通信:使用 provide/inject 或 Vuex

  3. 复杂状态管理:使用 Vuex

  4. 组件库开发:使用 $attrs/$listeners

  5. 表单组件:使用 v-model 或 .sync

  6. 内容分发:使用插槽

面试常考点

  1. Vue 组件通信有哪些方式?

    • 至少能说出 5-6 种方式

    • 能说明各自的适用场景

  2. Props 的特点是什么?

    • 单向数据流

    • 类型验证

    • 默认值设置

  3. Vuex 的核心概念?

    • State、Getters、Mutations、Actions

    • 单一状态树

    • 严格模式

  4. provide/inject 是否是响应式的?

    • 默认不是响应式

    • 如何实现响应式

  5. v-model 的原理?

    • 语法糖本质

    • 自定义组件如何支持 v-model

这份教程涵盖了 Vue 组件通信的主要方式,每种方式都有详细的代码示例和使用场景说明,非常适合面试准备和日常开发参考。

http://www.dtcms.com/a/457573.html

相关文章:

  • 代码随想录 637.二叉树的层平均值
  • Spring前置准备(七)——DefaultListableBeanFactory
  • Linux 进程间通信——System V
  • 【Spring Boot】自定义starter
  • 微网站建设网络温州大军建设有限公司网站
  • 残差特征蒸馏网络(RFDN)探索札记:轻量化图像超分的突破
  • 一般做网站什么价格可以做公众号的网站
  • 优选算法---字符串
  • 任丘网站建设资料查询网站怎么做
  • 华为OD机试C卷 - 流量波峰 - 暴力搜索 - (Java C++ JavaScript Python)
  • 使用CSS3动画属性实现斜线动画 -- 弧线动画 -- 波纹动画 -- 点绕圆旋转动画 -- 浮动动画
  • 打工人日报#20251008
  • 手机网站触摸版萧山中兴建设有限公司网站
  • Python游戏开发入门:从零开始制作贪吃蛇小游戏
  • kanass入门到实战(11) - Kanass如何有效集成sward文档
  • 尚硅谷SpringBoot3零基础教程,课程介绍,笔记01
  • 51网站统计德州网站建设的公司
  • C++23 高级编程 Professional C++, Sixth Edition(一)
  • Verilog和FPGA的自学笔记3——仿真文件Testbench的编写
  • 记录gitee的使用
  • 动态业务流程的案例管理标准(CMMN)
  • 广东门户网站建设哪个网站有适合小学生做的题
  • .NET周刊【9月第4期 2025-09-28】
  • 一级a做爰片365网站天门建设局官方网站
  • 电子商城网站制作广东网站营销seo费用
  • HarmonyOS应用开发 - 无受限权限保存资源到媒体库
  • 网上书店电子商务网站建设企业网站模板下载psd格式
  • 京东手机项目:手机受欢迎的影响因素分析
  • linux zgrep命令介绍
  • 成都著名网站建设公司php 抓取 wordpress 文字内容