Vue 系列:从入门到组件化开发

2024-07-13 前端

Vue.js 是目前最流行的前端框架之一,以其渐进式、易上手和高性能著称。本篇文章以 Vue 3 组合式 API 为主线,带你从入门到组件化开发实战。


一、Vue 3 组合式 API(Composition API)

为什么选择组合式 API?

Vue 2 的选项式 API(Options API)按选项组织代码(data、methods、computed),当组件复杂度上升时,同一逻辑被分散在不同选项中,难以维护。组合式 API 允许按逻辑关注点组织代码。

维度 选项式 API(Vue 2) 组合式 API(Vue 3)
代码组织 按选项类型分离 按逻辑关注点聚合
逻辑复用 Mixins(命名冲突、来源不明) Composables(清晰、可组合)
TypeScript 支持 较差 优秀
Tree Shaking 不支持 支持
学习曲线 低 中等

setup / ref / reactive

ref vs reactive 选择建议:

  • ref 优先:可以包装任意类型,解构不丢失响应性,.value 明确标识响应式数据。
  • reactive:适合表单对象等不需要解构的场景。但解构会丢失响应性(需要 toRefs),不能被重新赋值。

二、模板语法与指令

指令 用途 简写
v-bind 动态绑定属性/类/样式 :
v-on 事件监听 @
v-model 双向数据绑定 —
v-if / v-else-if / v-else 条件渲染(销毁/重建) —
v-show 条件显示(display 切换) —
v-for 列表渲染 —
v-once 一次性渲染 —
v-html 渲染 HTML 字符串 —

v-if vs v-show:频繁切换用 v-show(CSS 开销),条件很少改变用 v-if(避免初始渲染开销)。


三、组件通信

Props 与 Emits

v-model 组件通信

<!-- ===== 父组件 ===== -->
<template>
  <CustomInput v-model="searchText" />
  <!-- 等价于 -->
  <CustomInput
    :model-value="searchText"
    @update:model-value="searchText = $event"
  />

  <!-- 多个 v-model -->
  <UserForm
    v-model:name="userName"
    v-model:email="userEmail"
  />
</template>

<!-- ===== CustomInput.vue ===== -->
<script setup lang="ts">
const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{ 'update:modelValue': [value: string] }>();

function onInput(e: Event) {
  emit('update:modelValue', (e.target as HTMLInputElement).value);
}
</script>

<template>
  <input :value="modelValue" @input="onInput" />
</template>

Provide / Inject(跨层级通信)

<!-- ===== 祖先组件 ===== -->
<script setup>
import { provide, ref, readonly } from 'vue';

const theme = ref('light');
const updateTheme = (t) => { theme.value = t; };

provide('theme', readonly(theme));  // 只读,防止子组件直接修改
provide('updateTheme', updateTheme);
</script>

<!-- ===== 深层子组件 ===== -->
<script setup>
import { inject } from 'vue';

const theme = inject('theme', 'light');        // 带默认值
const updateTheme = inject('updateTheme');
</script>

<template>
  <div :class="theme">
    <button @click="updateTheme('dark')">切换暗色</button>
  </div>
</template>
通信方式 适用场景 优点 缺点
Props ↓ / Emits ↑ 父子直连 清晰、类型安全 多层级繁琐
v-model 表单组件 简洁优雅 适合单一值
Provide/Inject 跨层级 无需逐层传递 数据来源不直观
Pinia / Vuex 全局状态 任意组件访问 增加复杂度
Event Bus (mitt) 任意组件 灵活 不推荐,难以追踪

四、生命周期钩子

选项式 API 组合式 API 说明
beforeCreate setup() setup 本身
created setup() setup 本身
beforeMount onBeforeMount 模板编译完成,DOM 未挂载
mounted onMounted DOM 已挂载,可访问 $el
beforeUpdate onBeforeUpdate 响应式数据变化,DOM 更新前
updated onUpdated DOM 更新后
beforeUnmount onBeforeUnmount 卸载前,适合清理
unmounted onUnmounted 卸载完成

五、路由(Vue Router)


六、状态管理(Pinia)

Pinia 是 Vue 官方推荐的状态管理库,比 Vuex 更简洁、更符合组合式 API 风格。

<!-- 组件中使用 Pinia -->
<script setup>
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';

const userStore = useUserStore();

// storeToRefs:保持响应性
const { user, isLoggedIn, userName } = storeToRefs(userStore);
// actions 直接解构即可(不需要响应性)
const { login, logout } = userStore;
</script>

<template>
  <div v-if="isLoggedIn">
    <p>欢迎,{{ userName }}</p>
    <button @click="logout">退出</button>
  </div>
  <div v-else>
    <button @click="login({ email: 'a@b.com', password: '123' })">
      登录
    </button>
  </div>
</template>

Pinia vs Vuex 对比

特性 Pinia Vuex 4
体积 ~1.5 KB ~10 KB
类型支持 完美 TypeScript 较弱
API 设计 组合式 + 选项式 仅选项式
模块化 天然支持(多个 Store) 需要 modules
去掉了 Mutations ✅ 直接修改 state 需要 commit
DevTools ✅ ✅
官方推荐 ✅ Vue 3 首选 遗留项目

七、与 React 的简单对比

维度 Vue 3 React 18
核心范式 响应式(自动追踪) 不可变(手动 setState)
模板 SFC 模板(HTML-like) JSX(JavaScript + HTML)
响应式系统 Proxy 自动追踪 手动声明依赖(useMemo/useCallback)
组件更新 自动精准更新 自上而下重新渲染(需优化)
状态管理 Pinia(内置响应式) Redux / Zustand / Jotai
路由 Vue Router(官方) React Router
学习曲线 平缓(模板接近 HTML) 较陡(JSX + 更多概念)
生态系统 官方集中维护 社区分散
适用场景 中小型到中大型 任意规模,生态更丰富
<!-- Vue:模板语法 -->
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>
// React:JSX
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

八、实用 Composables 示例

// composables/useDebounce.ts
import { ref, watch, type Ref } from 'vue';

export function useDebounce<T>(source: Ref<T>, delay = 300) {
  const debounced = ref(source.value) as Ref<T>;
  let timer: ReturnType<typeof setTimeout>;

  watch(source, (val) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      debounced.value = val;
    }, delay);
  });

  return debounced;
}

九、最佳实践速查

实践 说明
✅ <script setup> + TypeScript 官方推荐,更简洁
✅ Composables 提取复用逻辑 替代 Mixins
✅ ref 优先于 reactive 更灵活,解构安全
✅ 路由懒加载 () => import(...)
✅ defineProps + defineEmits 类型声明 编译时类型检查
✅ shallowRef / shallowReactive 优化大列表 减少深层响应式开销
❌ 不要直接修改 props 单向数据流
❌ 不要在 computed 中做副作用 用 watch 代替
❌ 避免 v-if 和 v-for 同时使用 v-if 优先级更高,性能差

结语

Vue 3 的组合式 API 不仅带来了更好的逻辑组织方式,也在 TypeScript 支持上迈出了一大步。从入门到组件化开发,关键在于:

  1. 理解响应式:ref / reactive / computed / watch
  2. 掌握组件通信:props ↓ / emits ↑ / provide-inject
  3. 善用生态:Vue Router + Pinia + Composables

配合官方文档和实际项目练习,你会很快感受到 Vue 开发的乐趣。前端三件套(HTML / CSS / JS)+ 一个主流框架(Vue / React),就是现代 Web 开发的完整技能树!