MST

星途 面试题库

面试题:复杂场景下Vue计算属性、侦听器及跨组件通信替代方案的优化

假设你正在开发一个大型单页应用,有多层嵌套组件,数据关系复杂。某些组件数据频繁变化,计算属性依赖的数据源也在不断更新,同时跨组件通信存在性能瓶颈。请阐述如何优化计算属性、侦听器以及跨组件通信的替代方案,以提高应用性能和可维护性,并给出具体的优化思路和示例代码。
20.4万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

优化计算属性

  1. 优化思路
    • 防抖与节流:对于计算属性依赖的频繁变化数据源,若计算属性计算成本较高,可对数据源变化进行防抖或节流处理。防抖是指在一定时间内,若数据源多次变化,只在最后一次变化后执行计算属性的重新计算;节流是指在一定时间间隔内,无论数据源变化多少次,只允许执行一次计算属性的重新计算。
    • 缓存计算结果:对于一些不依赖响应式数据变化的计算属性,可以缓存其计算结果,避免重复计算。例如使用 WeakMap 来缓存计算结果,在计算属性依赖的数据没有变化时,直接返回缓存结果。
  2. 示例代码
    • 防抖处理计算属性
    <template>
      <div>
        <input v-model="inputValue">
        <p>{{ debouncedComputed }}</p>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          inputValue: '',
          timer: null
        };
      },
      computed: {
        debouncedComputed() {
          clearTimeout(this.timer);
          return new Promise((resolve) => {
            this.timer = setTimeout(() => {
              resolve(this.inputValue.toUpperCase());
            }, 300);
          });
        }
      }
    };
    </script>
    
    • 缓存计算结果
    <template>
      <div>
        <p>{{ cachedComputed }}</p>
      </div>
    </template>
    
    <script>
    const cache = new WeakMap();
    export default {
      data() {
        return {
          fixedData: 'constant value'
        };
      },
      computed: {
        cachedComputed() {
          if (!cache.has(this)) {
            const result = this.fixedData.split('').reverse().join('');
            cache.set(this, result);
          }
          return cache.get(this);
        }
      }
    };
    </script>
    

优化侦听器

  1. 优化思路
    • 减少不必要的侦听:确保只对真正需要响应变化的属性进行侦听。避免过度侦听一些不会影响业务逻辑的属性变化,以减少不必要的性能开销。
    • 使用深度侦听策略:对于对象或数组类型的数据变化,若要精确捕捉内部深层次的变化,应合理使用深度侦听,但注意深度侦听会带来性能损耗,所以只在必要时开启。
    • 合并侦听器:如果多个侦听器逻辑类似,可以考虑合并成一个,减少重复代码和性能开销。
  2. 示例代码
    • 避免过度侦听
    <template>
      <div>
        <input v-model="user.name">
        <input v-model="user.age">
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          user: {
            name: '',
            age: 0
          }
        };
      },
      watch: {
        // 只侦听对业务逻辑重要的属性
        'user.name': {
          handler(newVal, oldVal) {
            // 处理名字变化的业务逻辑
          },
          immediate: true
        }
      }
    };
    </script>
    
    • 深度侦听
    <template>
      <div>
        <input v-model="user.address.city">
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          user: {
            address: {
              city: ''
            }
          }
        };
      },
      watch: {
        user: {
          handler(newVal, oldVal) {
            // 处理地址变化的业务逻辑
          },
          deep: true,
          immediate: true
        }
      }
    };
    </script>
    

跨组件通信替代方案

  1. 优化思路
    • 使用 Vuex 或 Pinia:对于大型应用,状态管理库如 Vuex(Vue 2)或 Pinia(Vue 3)可以集中管理共享状态,避免多层嵌套组件之间繁琐的传值。通过将共享数据存放在状态管理库中,各个组件可以方便地获取和修改数据,并且状态的变化具有可追踪性。
    • 事件总线:对于兄弟组件或跨层级不太深的组件通信,可以使用事件总线。创建一个空的 Vue 实例作为事件总线,在需要通信的组件中通过事件总线触发和监听事件来传递数据。但事件总线在大型应用中可能导致代码难以维护,所以适用于较小规模的组件通信场景。
    • Provide / Inject:对于祖先 - 后代组件通信,provideinject 可以实现数据的向下传递,而不需要通过中间多层组件层层传递。provide 用于在祖先组件中提供数据,inject 用于后代组件中注入数据。不过要注意,provideinject 传递的数据不是响应式的,若要实现响应式,需使用 refreactive 包装数据。
  2. 示例代码
    • 使用 Vuex(Vue 2 示例,Vue 3 中类似概念是 Pinia)
      • store.js
      import Vue from 'vue';
      import Vuex from 'vuex';
      
      Vue.use(Vuex);
      
      const store = new Vuex.Store({
        state: {
          sharedData: 'initial value'
        },
        mutations: {
          updateSharedData(state, newData) {
            state.sharedData = newData;
          }
        }
      });
      
      export default store;
      
      • 组件中使用
      <template>
        <div>
          <p>{{ $store.state.sharedData }}</p>
          <button @click="updateData">Update Data</button>
        </div>
      </template>
      
      <script>
      export default {
        methods: {
          updateData() {
            this.$store.commit('updateSharedData', 'new value');
          }
        }
      };
      </script>
      
    • 事件总线
      • main.js
      import Vue from 'vue';
      import App from './App.vue';
      
      Vue.prototype.$eventBus = new Vue();
      
      new Vue({
        render: h => h(App)
      }).$mount('#app');
      
      • 发送数据组件
      <template>
        <div>
          <button @click="sendData">Send Data</button>
        </div>
      </template>
      
      <script>
      export default {
        methods: {
          sendData() {
            this.$eventBus.$emit('data - sent', 'Hello from component 1');
          }
        }
      };
      </script>
      
      • 接收数据组件
      <template>
        <div>
          <p>{{ receivedData }}</p>
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {
            receivedData: ''
          };
        },
        mounted() {
          this.$eventBus.$on('data - sent', (data) => {
            this.receivedData = data;
          });
        }
      };
      </script>
      
    • Provide / Inject
      • 祖先组件
      <template>
        <div>
          <child - component></child - component>
        </div>
      </template>
      
      <script>
      import ChildComponent from './ChildComponent.vue';
      import { ref } from 'vue';
      
      export default {
        components: {
          ChildComponent
        },
        setup() {
          const sharedData = ref('data from ancestor');
          return {
            sharedData
          };
        },
        provide() {
          return {
            sharedData: this.sharedData
          };
        }
      };
      </script>
      
      • 后代组件
      <template>
        <div>
          <p>{{ injectedData }}</p>
        </div>
      </template>
      
      <script>
      export default {
        inject: ['sharedData'],
        setup(props, { inject }) {
          const injectedData = inject('sharedData');
          return {
            injectedData
          };
        }
      };
      </script>