MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue Composition API 最佳实践与代码优化策略

2023-08-247.0k 阅读

Vue Composition API 基础回顾

在深入探讨最佳实践与优化策略之前,先简要回顾一下 Vue Composition API 的基础概念。Vue Composition API 是 Vue 3.0 引入的一套新的 API,它允许我们以函数的形式组织组件逻辑,相比于 Vue 2.x 的 Options API,提供了更好的逻辑复用、代码组织和可维护性。

响应式数据

Vue Composition API 中,使用 refreactive 来创建响应式数据。ref 用于创建一个包含基本类型或对象类型的响应式引用,而 reactive 则用于创建一个深度响应式的对象。

<template>
  <div>
    <p>Count: {{ count.value }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);

const increment = () => {
  count.value++;
};
</script>

在上述代码中,通过 ref 创建了一个 count 响应式变量,在模板中使用 count.value 访问其值,在点击按钮时通过修改 count.value 触发视图更新。

对于复杂对象,可以使用 reactive

<template>
  <div>
    <p>Name: {{ user.name }}</p>
    <p>Age: {{ user.age }}</p>
    <button @click="updateUser">Update User</button>
  </div>
</template>

<script setup>
import { reactive } from 'vue';

const user = reactive({
  name: 'John',
  age: 30
});

const updateUser = () => {
  user.name = 'Jane';
  user.age = 31;
};
</script>

这里使用 reactive 创建了一个 user 对象,直接在模板中访问对象属性,修改对象属性也能触发视图更新。

计算属性与监听

计算属性和监听在 Vue Composition API 中有了新的实现方式。计算属性通过 computed 函数创建,而监听则使用 watchwatchEffect

<template>
  <div>
    <p>First Name: {{ user.firstName }}</p>
    <p>Last Name: {{ user.lastName }}</p>
    <p>Full Name: {{ fullName.value }}</p>
  </div>
</template>

<script setup>
import { reactive, computed } from 'vue';

const user = reactive({
  firstName: 'John',
  lastName: 'Doe'
});

const fullName = computed(() => {
  return user.firstName + ' ' + user.lastName;
});
</script>

上述代码中,computed 创建了 fullName 计算属性,依赖于 user.firstNameuser.lastName,当这两个属性变化时,fullName 会自动更新。

监听方面,watch 用于监听特定数据的变化:

<template>
  <div>
    <p>Count: {{ count.value }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue';

const count = ref(0);

watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`);
});

const increment = () => {
  count.value++;
};
</script>

watchEffect 则会自动追踪其依赖,只要依赖的数据变化就会重新执行:

<template>
  <div>
    <p>Count: {{ count.value }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue';

const count = ref(0);

watchEffect(() => {
  console.log(`Count is: ${count.value}`);
});

const increment = () => {
  count.value++;
};
</script>

最佳实践

逻辑复用与组合函数

组合函数(Composition Functions)是 Vue Composition API 的核心优势之一,它允许我们将相关的逻辑抽取到独立的函数中,实现逻辑复用。

例如,假设我们有多个组件都需要一个计数器逻辑:

import { ref, increment } from 'vue';

const useCounter = () => {
  const count = ref(0);

  const increment = () => {
    count.value++;
  };

  return {
    count,
    increment
  };
};

在组件中使用这个组合函数:

<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <button @click="counter.increment">Increment</button>
  </div>
</template>

<script setup>
import { useCounter } from './useCounter';

const counter = useCounter();
</script>

这样,多个组件都可以引入 useCounter 组合函数来复用计数器逻辑,使得代码更加模块化和可维护。

组件间通信

在 Vue Composition API 中,组件间通信可以通过多种方式实现。对于父子组件通信,依然可以使用 props 和 emits。

父组件传递数据给子组件:

<!-- Parent.vue -->
<template>
  <div>
    <Child :message="parentMessage" @child-event="handleChildEvent"/>
  </div>
</template>

<script setup>
import Child from './Child.vue';
import { ref } from 'vue';

const parentMessage = ref('Hello from parent');

const handleChildEvent = (data) => {
  console.log('Received from child:', data);
};
</script>

子组件接收 props 并触发事件:

<!-- Child.vue -->
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendEvent">Send Event</button>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

const props = defineProps(['message']);
const emits = defineEmits(['child-event']);

const sendEvent = () => {
  emits('child-event', 'Data from child');
};
</script>

对于兄弟组件或跨层级组件通信,可以使用 Vuex 或通过创建一个全局的事件总线。使用 Vuex 时,在 store 中定义状态和 mutations、actions,组件通过 useStore 来获取和修改状态。

// store.js
import { createStore } from 'vuex';

const store = createStore({
  state: {
    sharedData: ''
  },
  mutations: {
    updateSharedData(state, data) {
      state.sharedData = data;
    }
  },
  actions: {
    updateSharedDataAction({ commit }, data) {
      commit('updateSharedData', data);
    }
  }
});

export default store;

在组件中使用 Vuex:

<template>
  <div>
    <p>Shared Data: {{ sharedData }}</p>
    <button @click="updateData">Update Data</button>
  </div>
</template>

<script setup>
import { useStore } from 'vuex';

const store = useStore();
const sharedData = computed(() => store.state.sharedData);

const updateData = () => {
  store.dispatch('updateSharedDataAction', 'New data');
};
</script>

生命周期钩子

Vue Composition API 提供了与 Options API 相对应的生命周期钩子函数。例如,onMounted 对应 mounted 钩子,onUpdated 对应 updated 钩子等。

<template>
  <div>
    <p>Component is mounted and updated</p>
  </div>
</template>

<script setup>
import { onMounted, onUpdated } from 'vue';

onMounted(() => {
  console.log('Component mounted');
});

onUpdated(() => {
  console.log('Component updated');
});
</script>

这些钩子函数可以方便地在组件的不同生命周期阶段执行特定的逻辑,如初始化数据、获取数据、清理资源等。

代码优化策略

性能优化

  1. 减少不必要的重新渲染:Vue 的响应式系统会在数据变化时重新渲染组件,但有时候可能会发生不必要的重新渲染。例如,在计算属性中,如果依赖的数据没有变化,计算属性不应该重新计算。可以通过合理使用 computed 的缓存机制来避免不必要的计算。
<template>
  <div>
    <p>Full Name: {{ fullName.value }}</p>
    <p>Other Data: {{ otherData }}</p>
    <button @click="updateOtherData">Update Other Data</button>
  </div>
</template>

<script setup>
import { reactive, computed, ref } from 'vue';

const user = reactive({
  firstName: 'John',
  lastName: 'Doe'
});

const fullName = computed(() => {
  return user.firstName + ' ' + user.lastName;
});

const otherData = ref('Some other data');

const updateOtherData = () => {
  otherData.value = 'Updated other data';
};
</script>

在上述代码中,updateOtherData 操作不会触发 fullName 的重新计算,因为 fullName 只依赖于 user.firstNameuser.lastName

  1. 异步数据加载优化:在组件加载异步数据时,可以使用 Suspense 组件来处理加载状态,避免在数据未加载完成时组件闪烁或出现错误。
<template>
  <Suspense>
    <template #default>
      <div>
        <p>User Name: {{ user.name }}</p>
      </div>
    </template>
    <template #fallback>
      <p>Loading...</p>
    </template>
  </Suspense>
</template>

<script setup>
import { ref } from 'vue';

const loadUser = async () => {
  // 模拟异步操作
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return { name: 'John' };
};

const user = ref(null);

// 使用立即执行函数加载数据
(async () => {
  user.value = await loadUser();
})();
</script>

在数据加载过程中,Suspensefallback 模板会显示加载提示,直到数据加载完成。

代码结构优化

  1. 合理拆分组件:将复杂的组件拆分成多个小的、功能单一的组件,每个组件只负责自己的逻辑,这样可以提高代码的可读性和可维护性。例如,一个大型的表单组件可以拆分成多个字段组件和表单提交组件。
<!-- Form.vue -->
<template>
  <div>
    <FormField label="Name" v-model="name"/>
    <FormField label="Email" v-model="email"/>
    <FormSubmit @submit="handleSubmit"/>
  </div>
</template>

<script setup>
import FormField from './FormField.vue';
import FormSubmit from './FormSubmit.vue';
import { ref } from 'vue';

const name = ref('');
const email = ref('');

const handleSubmit = () => {
  console.log('Form submitted:', { name: name.value, email: email.value });
};
</script>
<!-- FormField.vue -->
<template>
  <div>
    <label>{{ label }}</label>
    <input v-model="value"/>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

const props = defineProps(['label']);
const emits = defineEmits(['input']);

const value = ref('');

const updateValue = (e) => {
  value.value = e.target.value;
  emits('input', value.value);
};
</script>
<!-- FormSubmit.vue -->
<template>
  <button @click="submitForm">Submit</button>
</template>

<script setup>
import { defineEmits } from 'vue';

const emits = defineEmits(['submit']);

const submitForm = () => {
  emits('submit');
};
</script>
  1. 使用 TypeScript 增强类型检查:在 Vue 项目中使用 TypeScript 可以提高代码的健壮性和可维护性。对于 Vue Composition API,TypeScript 可以很好地推断类型。
<template>
  <div>
    <p>Count: {{ count.value }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';

const count: Ref<number> = ref(0);

const increment = () => {
  count.value++;
};
</script>

通过 TypeScript 的类型标注,可以在开发过程中及时发现类型错误,减少运行时错误。

资源管理优化

  1. 组件卸载时清理资源:在组件使用一些需要清理的资源,如定时器、事件监听器等时,要在组件卸载时进行清理。可以使用 onUnmounted 生命周期钩子。
<template>
  <div>
    <p>Component with timer</p>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';

let timer: number;

onMounted(() => {
  timer = setInterval(() => {
    console.log('Timer is running');
  }, 1000);
});

onUnmounted(() => {
  clearInterval(timer);
});
</script>
  1. 优化图片加载:对于图片加载,可以使用 loading="lazy" 属性实现图片的懒加载,减少初始页面加载时的资源请求。
<template>
  <div>
    <img src="image.jpg" alt="Example" loading="lazy"/>
  </div>
</template>

这样,只有当图片进入视口时才会加载,提高了页面的性能和用户体验。

通过以上最佳实践和代码优化策略,可以让基于 Vue Composition API 的项目更加高效、可维护和健壮。在实际开发中,需要根据项目的具体需求和场景灵活运用这些方法,不断优化代码质量。