从零基础到最佳实践:Vue.js 系列(6/10):《Composition API(组合式 API)》

引言

Vue.js 作为前端开发领域的热门框架之一,在 Vue 3 中引入了 Composition API(组合式 API),为开发者带来了全新的代码组织方式。相比传统的 Options API,Composition API 更加灵活,能够更好地应对复杂逻辑的开发需求。它通过将相关逻辑集中在一起,提升了代码的可读性和可维护性,成为现代 Vue 开发的标配。

本文将带你从零开始学习 Composition API,涵盖基础用法、进阶技巧以及大量实际开发场景。无论你是刚接触 Vue 的新手,还是希望深入掌握 Vue 3 的老手,这篇文章都能为你提供实用的知识和灵感。


一、Composition API 基础

1.1 什么是 setup 函数?

setup 函数是 Composition API 的核心,它是组件的入口点,在组件创建时被调用,用于定义响应式数据、计算属性、方法等。它接收两个参数:

  • props:组件接收的外部属性。
  • context:包含 attrs(非 prop 属性)、slots(插槽)和 emit(事件触发器)等。

简单示例

<template>
  <div>{{ count }} * 2 = {{ double }}</div>
  <button @click="increment">加 1</button>
</template>

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

export default {
  setup() {
    const count = ref(0); // 响应式数据
    const double = computed(() => count.value * 2); // 计算属性
    const increment = () => { count.value++; }; // 方法

    return { count, double, increment }; // 返回给模板使用
  },
};
</script>

在这个例子中,setup 函数定义了 count(计数器)、double(计算属性)和 increment(加 1 方法),并通过 return 将它们暴露给模板。

1.2 响应式数据:refreactive

Vue 的响应式系统是其核心特性之一,Composition API 提供了 refreactive 来创建响应式数据。

  • ref:用于基本数据类型(如数字、字符串)的响应式引用。访问和修改时需要使用 .value
  • reactive:用于复杂数据类型(如对象、数组)的响应式引用,不需要 .value

示例

<template>
  <div>{{ count }} - {{ user.name }}</div>
  <button @click="count++">加 1</button>
  <button @click="user.name = '李四'">改名</button>
</template>

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

export default {
  setup() {
    const count = ref(0);
    const user = reactive({ name: '张三', age: 18 });

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

注意:在模板中,ref 的值会自动解包,无需写 .value

1.3 计算属性:computed

computed 用于创建依赖于其他响应式数据的计算属性,只有当依赖发生变化时才会重新计算。

示例

<template>
  <div>总价:{{ totalPrice }}</div>
</template>

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

export default {
  setup() {
    const price = ref(100);
    const quantity = ref(2);
    const totalPrice = computed(() => price.value * quantity.value);

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

1.4 侦听器:watchwatchEffect

  • watch:显式监听某个数据源的变化,并执行回调函数。
  • watchEffect:自动追踪依赖的变化并执行副作用。

示例

<template>
  <div>{{ count }}</div>
  <button @click="count++">加 1</button>
</template>

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

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

    // watch:显式监听 count
    watch(count, (newVal, oldVal) => {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`);
    });

    // watchEffect:自动追踪依赖
    watchEffect(() => {
      console.log(`count 当前值:${count.value}`);
    });

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

二、进阶用法

2.1 生命周期钩子

Composition API 提供了与 Options API 类似的生命周期钩子,如 onMountedonUpdated 等,在 setup 中使用。

示例

<template>
  <div>{{ message }}</div>
</template>

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

export default {
  setup() {
    const message = ref('Hello');

    onMounted(() => {
      console.log('组件已挂载');
    });

    onUnmounted(() => {
      console.log('组件已卸载');
    });

    return { message };
  },
};
</script>

2.2 依赖注入:provideinject

provideinject 用于跨组件传递数据,特别适合祖孙组件通信。

示例

<!-- 父组件 -->
<template>
  <Child />
</template>

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

export default {
  setup() {
    provide('config', { theme: 'dark' });
  },
  components: { Child },
};
</script>

<!-- 子组件 -->
<template>
  <div>主题:{{ theme }}</div>
</template>

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

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

2.3 自定义 Hooks

自定义 Hooks 是 Composition API 的亮点之一,可以封装可复用的逻辑。

示例:鼠标位置追踪 Hook

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

export function useMouse() {
  const x = ref(0);
  const y = ref(0);

  const update = (event) => {
    x.value = event.pageX;
    y.value = event.pageY;
  };

  onMounted(() => {
    window.addEventListener('mousemove', update);
  });

  onUnmounted(() => {
    window.removeEventListener('mousemove', update);
  });

  return { x, y };
}

使用

<template>
  <div>鼠标位置:{{ x }}, {{ y }}</div>
</template>

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

export default {
  setup() {
    const { x, y } = useMouse();
    return { x, y };
  },
};
</script>

三、实际开发应用场景

3.1 表单处理与验证

案例:用户注册表单

<template>
  <form @submit.prevent="submit">
    <input v-model="form.username" placeholder="用户名" />
    <input v-model="form.password" type="password" placeholder="密码" />
    <div v-if="errors.username">{{ errors.username }}</div>
    <div v-if="errors.password">{{ errors.password }}</div>
    <button type="submit" :disabled="hasErrors">提交</button>
  </form>
</template>

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

export default {
  setup() {
    const form = reactive({
      username: '',
      password: '',
    });

    const errors = computed(() => {
      const errs = {};
      if (!form.username) errs.username = '用户名不能为空';
      if (form.password.length < 6) errs.password = '密码至少 6 位';
      return errs;
    });

    const hasErrors = computed(() => Object.keys(errors.value).length > 0);

    const submit = () => {
      if (!hasErrors.value) {
        console.log('提交成功:', form);
      }
    };

    return { form, errors, hasErrors, submit };
  },
};
</script>

3.2 数据请求与状态管理

案例:获取用户列表

<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="error">{{ error }}</div>
  <ul v-else>
    <li v-for="user in users" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

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

export default {
  setup() {
    const users = ref([]);
    const loading = ref(true);
    const error = ref(null);

    const fetchUsers = async () => {
      try {
        const res = await fetch('https://jsonplaceholder.typicode.com/users');
        users.value = await res.json();
      } catch (err) {
        error.value = '加载失败:' + err.message;
      } finally {
        loading.value = false;
      }
    };

    onMounted(fetchUsers);

    return { users, loading, error };
  },
};
</script>

3.3 组件通信与状态共享

案例:主题切换

<!-- App.vue -->
<template>
  <div :class="theme">
    <ThemeToggle />
    <Child />
  </div>
</template>

<script>
import { ref, provide } from 'vue';
import ThemeToggle from './ThemeToggle.vue';
import Child from './Child.vue';

export default {
  setup() {
    const theme = ref('light');
    provide('theme', theme);

    return { theme };
  },
  components: { ThemeToggle, Child },
};
</script>

<!-- ThemeToggle.vue -->
<template>
  <button @click="toggleTheme">切换主题</button>
</template>

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

export default {
  setup() {
    const theme = inject('theme');
    const toggleTheme = () => {
      theme.value = theme.value === 'light' ? 'dark' : 'light';
    };
    return { toggleTheme };
  },
};
</script>

<!-- Child.vue -->
<template>
  <div>当前主题:{{ theme }}</div>
</template>

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

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

3.4 动态表格与分页

案例:带分页的用户表格

<template>
  <div>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>姓名</th>
          <th>邮箱</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in paginatedUsers" :key="user.id">
          <td>{{ user.id }}</td>
          <td>{{ user.name }}</td>
          <td>{{ user.email }}</td>
        </tr>
      </tbody>
    </table>
    <button @click="prevPage" :disabled="currentPage === 1">上一页</button>
    <span>第 {{ currentPage }} 页</span>
    <button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
  </div>
</template>

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

export default {
  setup() {
    const users = ref([]);
    const currentPage = ref(1);
    const pageSize = 5;

    const totalPages = computed(() => Math.ceil(users.value.length / pageSize));
    const paginatedUsers = computed(() => {
      const start = (currentPage.value - 1) * pageSize;
      const end = start + pageSize;
      return users.value.slice(start, end);
    });

    const fetchUsers = async () => {
      const res = await fetch('https://jsonplaceholder.typicode.com/users');
      users.value = await res.json();
    };

    const prevPage = () => { if (currentPage.value > 1) currentPage.value--; };
    const nextPage = () => { if (currentPage.value < totalPages.value) currentPage.value++; };

    onMounted(fetchUsers);

    return { paginatedUsers, currentPage, totalPages, prevPage, nextPage };
  },
};
</script>

四、优化技巧

4.1 性能优化

  • 减少不必要计算:使用 computed 缓存计算结果,避免重复计算。
  • 控制 watchEffect:尽量减少依赖项,防止频繁触发。
  • 解构响应式对象:使用 toRefs 保持响应性。

示例

import { reactive, toRefs } from 'vue';

const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state); // 解构后仍保持响应性

4.2 代码组织与重用

  • 抽离逻辑:将复杂逻辑封装为自定义 Hooks。
  • 模块化:将功能按模块拆分到单独文件中。

示例:计数器 Hook

// useCounter.js
import { ref } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const increment = () => { count.value++; };
  const decrement = () => { count.value--; };
  return { count, increment, decrement };
}

使用

<template>
  <div>{{ count }}</div>
  <button @click="increment">加 1</button>
  <button @click="decrement">减 1</button>
</template>

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

export default {
  setup() {
    const { count, increment, decrement } = useCounter(10);
    return { count, increment, decrement };
  },
};
</script>

4.3 错误处理

在异步操作中添加完善的错误处理逻辑。

示例

<template>
  <div v-if="error">{{ error }}</div>
  <div v-else>{{ data }}</div>
</template>

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

export default {
  setup() {
    const data = ref(null);
    const error = ref(null);

    const fetchData = async () => {
      try {
        const res = await fetch('https://api.example.com/data');
        if (!res.ok) throw new Error('网络错误');
        data.value = await res.json();
      } catch (err) {
        error.value = err.message;
      }
    };

    onMounted(fetchData);

    return { data, error };
  },
};
</script>

五、未来趋势展望

随着 Vue 3 的普及,Composition API 将成为 Vue 开发的主流方式。它与 TypeScript 的深度结合将为大型项目提供更好的类型支持。此外,社区可能会推出更多基于 Composition API 的插件和工具,如状态管理库、表单处理库等,进一步丰富 Vue 生态。

未来,Composition API 还可能与 Vite 等现代构建工具深度整合,提升开发体验和性能。


六、总结

Composition API 是 Vue 3 的核心特性之一,它不仅提升了代码的灵活性,还为开发者提供了强大的工具来应对复杂场景。通过本文的学习,你应该能够掌握从基础到进阶的用法,并在实际开发中灵活运用。

希望你能在实践中不断探索 Composition API 的潜力,构建出高效、可维护的前端应用!

这篇文章详细介绍了 Composition API 的方方面面,包含多个实用案例和优化建议,适合不同层次的开发者学习和参考。希望对你有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值