面试题答案
一键面试父子组件通信
- 通信方式
- 父传子:父组件通过在子组件标签上定义属性,子组件通过
props
接收。由于Vue的响应式系统,父组件数据变化会自动更新子组件接收的props
数据,触发子组件重新渲染。例如:
<!-- 父组件 --> <template> <div> <child-component :parent - data="parentData"></child-component> <button @click="updateParentData">更新父数据</button> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { parentData: '初始数据' }; }, methods: { updateParentData() { this.parentData = '更新后的数据'; } } }; </script>
<!-- 子组件 --> <template> <div>{{parentData}}</div> </template> <script> export default { props: ['parentData'] }; </script>
- 子传父:子组件通过
$emit
触发自定义事件,父组件在子组件标签上监听该事件。当子组件数据变化并触发事件时,父组件响应式更新相关数据。例如:
<!-- 子组件 --> <template> <div> <button @click="sendDataToParent">发送数据给父组件</button> </div> </template> <script> export default { methods: { sendDataToParent() { this.$emit('child - event', '子组件数据'); } } }; </script>
<!-- 父组件 --> <template> <div> <child-component @child - event="handleChildEvent"></child-component> <p>{{childDataFromChildComponent}}</p> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { childDataFromChildComponent: '' }; }, methods: { handleChildEvent(data) { this.childDataFromChildComponent = data; } } }; </script>
- 父传子:父组件通过在子组件标签上定义属性,子组件通过
- 性能影响:父组件数据变化频繁会导致子组件频繁重新渲染,若子组件渲染开销大,会影响性能。特别是当传递复杂对象时,对象内部属性变化也会触发子组件重新渲染,即便实际使用到的属性未变。
兄弟组件通信
- 通信方式
- 事件总线:创建一个空的Vue实例作为事件总线,兄弟组件通过该实例的
$on
和$emit
进行通信。例如:
// eventBus.js import Vue from 'vue'; export const eventBus = new Vue();
<!-- 组件A --> <template> <div> <button @click="sendDataToB">发送数据给组件B</button> </div> </template> <script> import { eventBus } from './eventBus.js'; export default { methods: { sendDataToB() { eventBus.$emit('from - A - to - B', '组件A的数据'); } } }; </script>
<!-- 组件B --> <template> <div>{{dataFromA}}</div> </template> <script> import { eventBus } from './eventBus.js'; export default { data() { return { dataFromA: '' }; }, mounted() { eventBus.$on('from - A - to - B', (data) => { this.dataFromA = data; }); } }; </script>
- Vuex:状态管理工具,兄弟组件共享Vuex中的状态。组件通过
mapState
辅助函数获取状态,通过mapMutations
或mapActions
修改状态。例如:
// store.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { sharedData: '初始共享数据' }, mutations: { updateSharedData(state, newData) { state.sharedData = newData; } }, actions: { updateSharedDataAction({ commit }, newData) { commit('updateSharedData', newData); } } });
<!-- 组件A --> <template> <div> <button @click="updateSharedDataInVuex">更新共享数据</button> </div> </template> <script> import { mapActions } from 'vuex'; export default { methods: { ...mapActions(['updateSharedDataAction']), updateSharedDataInVuex() { this.updateSharedDataAction('新的共享数据'); } } }; </script>
<!-- 组件B --> <template> <div>{{sharedData}}</div> </template> <script> import { mapState } from 'vuex'; export default { computed: { ...mapState(['sharedData']) } }; </script>
- 事件总线:创建一个空的Vue实例作为事件总线,兄弟组件通过该实例的
- 性能影响:事件总线方式若事件过多且处理逻辑复杂,会使代码难以维护,且每次事件触发可能导致相关组件不必要的重新渲染。Vuex方式如果状态管理不当,例如频繁无意义地修改状态,也会导致依赖该状态的组件频繁重新渲染。
跨层级组件通信
- 通信方式
- provide / inject:祖先组件通过
provide
提供数据,后代组件通过inject
注入使用。例如:
<!-- 祖先组件 --> <template> <div> <child - component></child - component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, provide() { return { ancestorData: '祖先组件数据' }; } }; </script>
<!-- 后代组件 --> <template> <div>{{injectedData}}</div> </template> <script> export default { inject: ['ancestorData'], data() { return { injectedData: this.ancestorData }; } }; </script>
- Vuex:同兄弟组件通信方式,跨层级组件都可以从Vuex获取和修改状态。
- provide / inject:祖先组件通过
- 性能影响:
provide / inject
方式如果祖先组件数据频繁变化,可能导致所有依赖该注入数据的后代组件频繁重新渲染。Vuex方式若状态管理不善,同样会引起跨层级组件不必要的重新渲染。
优化因响应式带来的潜在性能问题
- 使用计算属性:对于依赖其他数据但不改变原数据的情况,使用计算属性。计算属性会基于其依赖进行缓存,只有依赖数据变化时才会重新计算。例如:
<template> <div> <p>{{computedData}}</p> <button @click="updateBaseData">更新基础数据</button> </div> </template> <script> export default { data() { return { baseData: 10 }; }, computed: { computedData() { return this.baseData * 2; } }, methods: { updateBaseData() { this.baseData++; } } }; </script>
- 使用
shouldComponentUpdate
:在组件中定义shouldComponentUpdate
方法,手动控制组件是否重新渲染。例如:<template> <div>{{data}}</div> </template> <script> export default { data() { return { data: '初始数据' }; }, shouldComponentUpdate(nextProps, nextState) { // 仅当data发生变化时重新渲染 return this.data!== nextState.data; } }; </script>
- 拆分组件:将大组件拆分成多个小组件,减少单个组件的渲染开销。例如,将一个复杂的表单组件拆分成输入框组件、按钮组件等,当某个小组件数据变化时,只影响该小组件的渲染。
- 使用Vue.mixin:在混入对象中定义公共逻辑,减少重复代码。例如,对于多个组件都需要的防抖逻辑,可以通过混入实现,避免在每个组件中重复编写。
// debounceMixin.js export default { methods: { debounce(func, delay) { let timer; return function() { const context = this; const args = arguments; clearTimeout(timer); timer = setTimeout(() => { func.apply(context, args); }, delay); }; } } };
<!-- 使用混入的组件 --> <template> <div> <button @click="debouncedFunction">防抖按钮</button> </div> </template> <script> import debounceMixin from './debounceMixin.js'; export default { mixins: [debounceMixin], methods: { originalFunction() { console.log('执行原始函数'); }, debouncedFunction: function() { this.debounce(this.originalFunction, 500)(); } } }; </script>