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

Vue Composition API 实际项目中的典型应用场景

2023-10-016.7k 阅读

Vue Composition API 实际项目中的典型应用场景

状态管理与逻辑复用

在传统的 Vue 开发中,当组件逻辑变得复杂,特别是涉及到多个组件间共享逻辑时,代码维护和复用会变得棘手。Vue Composition API 通过 setup 函数和 reactiveref 等方法,为状态管理和逻辑复用提供了更高效的方式。

组件内状态管理

在一个简单的计数器组件中,使用 Vue Composition API 可以这样管理状态:

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

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

export default {
  setup() {
    const count = ref(0);

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

    return {
      count,
      increment
    };
  }
};
</script>

在上述代码中,通过 ref 创建了一个响应式的 count 变量。ref 会自动将传入的值包装成一个响应式对象,访问和修改时需要通过 .valuesetup 函数返回一个对象,对象中的属性和方法可以在模板中直接使用。

逻辑复用 - 自定义 Hook

假设在多个组件中都需要处理页面滚动时的某些逻辑,比如判断页面是否滚动到了底部加载更多数据。可以通过自定义 Hook 来复用这一逻辑。

// useScroll.js
import { ref, onMounted, onUnmounted } from 'vue';

export const useScroll = () => {
  const isBottom = ref(false);

  const handleScroll = () => {
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    const clientHeight = document.documentElement.clientHeight;
    const scrollHeight = document.documentElement.scrollHeight;
    isBottom.value = scrollTop + clientHeight >= scrollHeight - 100;
  };

  onMounted(() => {
    window.addEventListener('scroll', handleScroll);
  });

  onUnmounted(() => {
    window.removeEventListener('scroll', handleScroll);
  });

  return {
    isBottom
  };
};

在组件中使用这个 Hook:

<template>
  <div>
    <p v-if="isBottom">You are near the bottom of the page.</p>
  </div>
</template>

<script>
import { useScroll } from './useScroll';

export default {
  setup() {
    const { isBottom } = useScroll();
    return {
      isBottom
    };
  }
};
</script>

通过自定义 Hook,将页面滚动相关的逻辑封装起来,多个组件可以轻松复用,提高了代码的可维护性和复用性。

生命周期管理

Vue Composition API 为生命周期管理带来了更加直观和灵活的方式。在 setup 函数中,可以通过引入不同的生命周期钩子函数来处理组件不同阶段的逻辑。

常用生命周期钩子的使用

  • onMounted:组件挂载后执行,常用于初始化一些需要 DOM 元素的操作。
<template>
  <div id="myDiv">This is a div</div>
</template>

<script>
import { onMounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      const div = document.getElementById('myDiv');
      if (div) {
        div.style.color = 'red';
      }
    });
  }
};
</script>
  • onUpdated:组件更新后执行,当组件的数据发生变化重新渲染后,会触发这个钩子。比如在数据更新后可能需要重新计算一些 DOM 元素的位置或样式。
<template>
  <div>
    <input v-model="text" />
    <p ref="textParagraph">{{ text }}</p>
  </div>
</template>

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

export default {
  setup() {
    const text = ref('');
    const textParagraph = ref(null);

    onUpdated(() => {
      if (textParagraph.value) {
        const width = textParagraph.value.offsetWidth;
        console.log(`The width of the paragraph is ${width}`);
      }
    });

    return {
      text,
      textParagraph
    };
  }
};
</script>
  • onUnmounted:组件卸载时执行,用于清理一些在组件挂载期间创建的副作用,比如移除事件监听器。
<template>
  <div></div>
</template>

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

export default {
  setup() {
    const handleClick = () => {
      console.log('Clicked outside the component');
    };

    onMounted(() => {
      document.addEventListener('click', handleClick);
    });

    onUnmounted(() => {
      document.removeEventListener('click', handleClick);
    });
  }
};
</script>

响应式数据处理与计算属性

Vue Composition API 提供了强大的响应式数据处理能力,结合计算属性可以实现复杂的数据处理和展示逻辑。

响应式数据的深度监听

在处理复杂对象或数组时,reactive 可以创建深度响应式的数据。

<template>
  <div>
    <input v-model="user.name" />
    <input v-model="user.age" />
    <p>{{ user.name }} is {{ user.age }} years old.</p>
  </div>
</template>

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

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

    return {
      user
    };
  }
};
</script>

上述代码中,user 对象是深度响应式的,当 user.nameuser.age 发生变化时,模板会自动更新。

计算属性

计算属性在 Vue Composition API 中通过 computed 方法实现。比如在一个购物车组件中,计算商品总价。

<template>
  <div>
    <input type="number" v-model="quantity" />
    <input type="number" v-model="price" />
    <p>Total: {{ totalPrice.value }}</p>
  </div>
</template>

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

export default {
  setup() {
    const quantity = ref(1);
    const price = ref(10);

    const totalPrice = computed(() => {
      return quantity.value * price.value;
    });

    return {
      quantity,
      price,
      totalPrice
    };
  }
};
</script>

computed 会缓存计算结果,只有当依赖的响应式数据(这里是 quantityprice)发生变化时,才会重新计算。

依赖注入

依赖注入在 Vue 中是一种在组件树中共享数据的方式。Vue Composition API 对依赖注入的支持使得这一过程更加简洁和高效。

简单的依赖注入示例

假设在一个应用中有一个全局的配置对象,多个组件可能需要使用这个配置。

// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);
const config = {
  theme: 'light',
  apiUrl: 'https://example.com/api'
};
app.provide('config', config);
app.mount('#app');

在子组件中使用注入的数据:

<template>
  <div>
    <p>The current theme is {{ config.theme }}</p>
    <p>The API URL is {{ config.apiUrl }}</p>
  </div>
</template>

<script>
import { inject } from 'vue';

export default {
  setup() {
    const config = inject('config');
    return {
      config
    };
  }
};
</script>

通过 provide 在父级组件中提供数据,通过 inject 在子组件中获取数据,实现了数据在组件树中的共享。

复杂依赖注入场景 - 多层组件传递

当组件嵌套层次较深时,依赖注入的优势更加明显。例如,在一个多层菜单组件中,顶层组件提供一些菜单配置,底层菜单项组件可以直接注入使用。

<!-- ParentComponent.vue -->
<template>
  <div>
    <ChildComponent />
  </div>
</template>

<script>
import { provide } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  setup() {
    const menuConfig = {
      menuItems: ['Home', 'About', 'Contact'],
      menuStyle: 'horizontal'
    };
    provide('menuConfig', menuConfig);
  }
};
</script>
<!-- ChildComponent.vue -->
<template>
  <div>
    <GrandChildComponent />
  </div>
</template>

<script>
import GrandChildComponent from './GrandChildComponent.vue';

export default {
  components: {
    GrandChildComponent
  }
};
</script>
<!-- GrandChildComponent.vue -->
<template>
  <div>
    <ul :style="{ display: menuConfig.menuStyle === 'horizontal'? 'flex' : 'block' }">
      <li v-for="item in menuConfig.menuItems" :key="item">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
import { inject } from 'vue';

export default {
  setup() {
    const menuConfig = inject('menuConfig');
    return {
      menuConfig
    };
  }
};
</script>

这样即使经过多层组件传递,底层组件依然可以方便地获取到顶层提供的数据,避免了通过层层 props 传递数据的繁琐。

表单处理

在实际项目中,表单处理是常见的需求。Vue Composition API 可以让表单处理的逻辑更加清晰和易于维护。

基本表单绑定

对于一个简单的文本输入表单,可以这样处理:

<template>
  <div>
    <input v-model="inputText" />
    <p>You entered: {{ inputText }}</p>
  </div>
</template>

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

export default {
  setup() {
    const inputText = ref('');
    return {
      inputText
    };
  }
};
</script>

这里通过 ref 创建了一个响应式的 inputText,实现了表单输入和数据的双向绑定。

复杂表单验证

在一个包含多个字段且需要验证的表单中,例如注册表单,需要验证用户名、邮箱和密码等。

<template>
  <div>
    <form @submit.prevent="submitForm">
      <label>Username:</label>
      <input v-model="formData.username" />
      <p v-if="errors.username" class="error">{{ errors.username }}</p>
      <label>Email:</label>
      <input v-model="formData.email" />
      <p v-if="errors.email" class="error">{{ errors.email }}</p>
      <label>Password:</label>
      <input type="password" v-model="formData.password" />
      <p v-if="errors.password" class="error">{{ errors.password }}</p>
      <button type="submit">Submit</button>
    </form>
  </div>
</template>

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

export default {
  setup() {
    const formData = ref({
      username: '',
      email: '',
      password: ''
    });
    const errors = ref({
      username: '',
      email: '',
      password: ''
    });

    const validateUsername = () => {
      if (formData.value.username.length < 3) {
        errors.value.username = 'Username must be at least 3 characters long';
      } else {
        errors.value.username = '';
      }
    };

    const validateEmail = () => {
      const re = /\S+@\S+\.\S+/;
      if (!re.test(formData.value.email)) {
        errors.value.email = 'Please enter a valid email address';
      } else {
        errors.value.email = '';
      }
    };

    const validatePassword = () => {
      if (formData.value.password.length < 6) {
        errors.value.password = 'Password must be at least 6 characters long';
      } else {
        errors.value.password = '';
      }
    };

    const submitForm = () => {
      validateUsername();
      validateEmail();
      validatePassword();
      if (!errors.value.username &&!errors.value.email &&!errors.value.password) {
        console.log('Form submitted successfully:', formData.value);
      }
    };

    return {
      formData,
      errors,
      submitForm
    };
  }
};
</script>

在上述代码中,通过 ref 创建了 formDataerrors 对象。分别编写了各个字段的验证函数,并在 submitForm 函数中调用这些验证函数。根据验证结果在模板中显示错误信息,实现了复杂表单的验证功能。

路由相关处理

在单页应用(SPA)开发中,路由是重要的组成部分。Vue Composition API 与 Vue Router 结合,可以更灵活地处理路由相关的逻辑。

获取当前路由信息

在组件中获取当前路由的参数、路径等信息。

<template>
  <div>
    <p>Current route path: {{ currentRoute.path }}</p>
    <p>Current route parameter: {{ currentRoute.params.id }}</p>
  </div>
</template>

<script>
import { useRoute } from 'vue-router';

export default {
  setup() {
    const currentRoute = useRoute();
    return {
      currentRoute
    };
  }
};
</script>

通过 useRoute 可以方便地获取到当前路由的详细信息,例如,当路由为 /user/:id 时,可以获取到 id 参数。

路由导航

在组件内实现路由导航,比如点击按钮跳转到另一个页面。

<template>
  <div>
    <button @click="goToAbout">Go to About Page</button>
  </div>
</template>

<script>
import { useRouter } from 'vue-router';

export default {
  setup() {
    const router = useRouter();

    const goToAbout = () => {
      router.push('/about');
    };

    return {
      goToAbout
    };
  }
};
</script>

通过 useRouter 获取到路由实例,然后调用 push 方法实现页面跳转。

路由守卫

在路由切换前进行一些验证或操作,例如检查用户是否登录。

// router.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from './views/Home.vue';
import About from './views/About.vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: About,
      beforeEnter: (to, from, next) => {
        const isLoggedIn = true; // 这里可以根据实际情况判断用户是否登录
        if (isLoggedIn) {
          next();
        } else {
          next('/login');
        }
      }
    }
  ]
});

export default router;

在上述代码中,通过 beforeEnter 守卫,在进入 /about 路由前检查用户是否登录,如果未登录则跳转到 /login 页面。结合 Vue Composition API,在组件内也可以通过 router.beforeEach 等方法实现更复杂的全局路由守卫逻辑。

与第三方库的集成

在实际项目中,常常需要与各种第三方库集成。Vue Composition API 提供了一种清晰的方式来管理与第三方库相关的逻辑。

集成 Chart.js 绘制图表

以集成 Chart.js 绘制柱状图为例:

<template>
  <div>
    <canvas ref="chartCanvas"></canvas>
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';
import Chart from 'chart.js/auto';

export default {
  setup() {
    const chartCanvas = ref(null);
    let chartInstance = null;

    onMounted(() => {
      if (chartCanvas.value) {
        const ctx = chartCanvas.value.getContext('2d');
        chartInstance = new Chart(ctx, {
          type: 'bar',
          data: {
            labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
            datasets: [
              {
                label: '# of Votes',
                data: [12, 19, 3, 5, 2, 3],
                backgroundColor: [
                  'rgba(255, 99, 132, 0.2)',
                  'rgba(54, 162, 235, 0.2)',
                  'rgba(255, 206, 86, 0.2)',
                  'rgba(75, 192, 192, 0.2)',
                  'rgba(153, 102, 255, 0.2)',
                  'rgba(255, 159, 64, 0.2)'
                ],
                borderColor: [
                  'rgba(255, 99, 132, 1)',
                  'rgba(54, 162, 235, 1)',
                  'rgba(255, 206, 86, 1)',
                  'rgba(75, 192, 192, 1)',
                  'rgba(153, 102, 255, 1)',
                  'rgba(255, 159, 64, 1)'
                ],
                borderWidth: 1
              }
            ]
          },
          options: {
            scales: {
              y: {
                beginAtZero: true
              }
            }
          }
        });
      }
    });

    onUnmounted(() => {
      if (chartInstance) {
        chartInstance.destroy();
      }
    });

    return {
      chartCanvas
    };
  }
};
</script>

在上述代码中,通过 ref 创建了 chartCanvas 引用,在 onMounted 钩子中初始化 Chart.js 图表,在 onUnmounted 钩子中销毁图表实例,实现了 Vue 组件与 Chart.js 的集成。

集成第三方 UI 库

以 Element Plus 为例,在 Vue 项目中使用其组件:

<template>
  <div>
    <el-button @click="showMessage">Click me</el-button>
  </div>
</template>

<script>
import { defineComponent } from 'vue';
import { ElButton, ElMessage } from 'element-plus';

export default defineComponent({
  components: {
    ElButton
  },
  setup() {
    const showMessage = () => {
      ElMessage({
        message: 'This is a message from Element Plus',
        type:'success'
      });
    };

    return {
      showMessage
    };
  }
});
</script>

首先在组件中注册 ElButton 组件,然后在 setup 函数中使用 ElMessage 方法实现点击按钮弹出消息提示,展示了 Vue Composition API 与第三方 UI 库的集成方式。

通过以上各个典型应用场景的介绍,我们可以看到 Vue Composition API 在实际项目中为前端开发带来了更高的灵活性、可维护性和复用性,使得代码结构更加清晰,逻辑更加易于管理。无论是小型项目还是大型复杂项目,合理运用 Vue Composition API 都能提升开发效率和代码质量。