Vue3组合式API实战:从响应式原理到大型项目状态管理的工程方案

Vue3组合式API解决了什么问题

Vue2的选项式API在大型组件中暴露出明显的组织缺陷——一个功能的数据、计算属性、方法和生命周期钩子分散在不同选项中,一个需求变更需要在多个选项之间跳转修改。组合式API用setup函数(或<script setup>语法糖)将同一功能的逻辑聚合在一起,可读性和可维护性大幅提升。

这篇文章从响应式原理讲起,覆盖组合式API核心用法、自定义Hook封装模式和大型项目的Pinia状态管理方案。

响应式系统:ref与reactive的选择

Vue3响应式基于Proxy实现。ref和reactive两种声明方式各有适用场景:

import { ref, reactive } from 'vue'

// ref:基础类型和需要重新赋值的对象
const count = ref(0)
const user = ref({ name: '张三', age: 28 })

// reactive:不需要重新赋值的复杂对象
const form = reactive({
  username: '',
  password: '',
  remember: false
})

选择原则:

– 基础类型(string/number/boolean)必须用ref
– 对象如果整体替换(=新对象),用ref
– 对象只修改属性不整体替换,用reactive,减少模板中的.value书写

reactive的解构陷阱是常见Bug来源:

// 错误:解构后失去响应式
const { username, password } = form  // 响应式丢失!

// 正确:用toRefs保持响应式
import { toRefs } from 'vue'
const { username, password } = toRefs(form)  // 保持响应式

computed与watch的工程化用法

computed用于派生状态,watch用于副作用响应。两者的核心区别是computed有缓存,watch没有返回值。

import { computed, watch, ref } from 'vue'

const productList = ref<Product[]>([])
const keyword = ref('')

// computed:派生过滤后的列表
const filteredList = computed(() => {
  return productList.value.filter(item =>
    item.name.includes(keyword.value)
  )
})

// watch:搜索关键词变化时请求接口
watch(keyword, async (newVal, oldVal) => {
  if (newVal.length < 2) return  // 防止短关键词触发
  const { data } = await searchProducts(newVal)
  productList.value = data
}, {
  immediate: false,    // 是否立即执行一次
  flush: 'post',       // DOM更新后执行
})

watch监听多个源时,避免写多个watch,用一个watch监听数组:

watch([keyword, category, sortBy], async () => {
  const params = {
    keyword: keyword.value,
    category: category.value,
    sort: sortBy.value
  }
  const { data } = await fetchProducts(params)
  productList.value = data
})

watchEffect是watch的简化版,自动追踪依赖:

// 自动追踪内部使用的所有响应式变量
watchEffect(async () => {
  // keyword或category变化都会触发
  const { data } = await fetchProducts({
    keyword: keyword.value,
    category: category.value
  })
  productList.value = data
})

注意watchEffect无法获取旧值,也无法条件跳过执行,适用于不需要旧值的场景。

自定义Hook:逻辑复用的核心模式

自定义Hook是Vue3中逻辑复用的推荐方式,替代Vue2的Mixin。Hook的命名以use开头:

// usePagination.ts
import { ref, computed, reactive, readonly } from 'vue'

interface PaginationState {
  page: number
  pageSize: number
  total: number
}

export function usePagination(defaultPageSize = 10) {
  const state = reactive<PaginationState>({
    page: 1,
    pageSize: defaultPageSize,
    total: 0
  })

  const totalPages = computed(() =>
    Math.ceil(state.total / state.pageSize)
  )

  const hasNext = computed(() => state.page < totalPages.value)
  const hasPrev = computed(() => state.page > 1)

  function goPage(page: number) {
    if (page < 1 || page > totalPages.value) return
    state.page = page
  }

  function setTotal(total: number) {
    state.total = total
    // 数据减少时自动修正当前页
    if (state.page > totalPages.value && totalPages.value > 0) {
      state.page = totalPages.value
    }
  }

  return {
    state: readonly(state),  // 只读暴露,防止外部直接修改
    totalPages,
    hasNext,
    hasPrev,
    goPage,
    setTotal
  }
}

组件中使用:

import { usePagination } from '@/hooks/usePagination'

const { state, totalPages, goPage, setTotal } = usePagination(20)

// 请求列表数据
async function fetchList() {
  const { data, total } = await api.getList({
    page: state.page,
    pageSize: state.pageSize
  })
  setTotal(total)
  list.value = data
}

Pinia状态管理:大型项目的数据流方案

大型项目中跨组件状态共享用Pinia替代Vuex。Pinia的Store分两种:

Option Store(类似Vuex写法):

// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    userInfo: null as UserInfo | null,
  }),
  getters: {
    isLoggedIn: (state) => !!state.token,
  },
  actions: {
    async login(credentials: LoginForm) {
      const { token, user } = await api.login(credentials)
      this.token = token
      this.userInfo = user
      localStorage.setItem('token', token)
    },
    logout() {
      this.token = ''
      this.userInfo = null
      localStorage.removeItem('token')
    }
  }
})

Setup Store(组合式写法,更灵活):

// stores/app.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useAppStore = defineStore('app', () => {
  const sidebarCollapsed = ref(false)
  const theme = ref<'light' | 'dark'>('light')

  const themeClass = computed(() => `theme-${theme.value}`)

  function toggleSidebar() {
    sidebarCollapsed.value = !sidebarCollapsed.value
  }

  function switchTheme(newTheme: 'light' | 'dark') {
    theme.value = newTheme
    document.documentElement.setAttribute('data-theme', newTheme)
  }

  return {
    sidebarCollapsed,
    theme,
    themeClass,
    toggleSidebar,
    switchTheme
  }
})

Store之间组合调用:

export const useCartStore = defineStore('cart', () => {
  const userStore = useUserStore()
  
  async function checkout() {
    if (!userStore.isLoggedIn) {
      throw new Error('请先登录')
    }
    // 结算逻辑...
  }
})

项目工程化配置清单

Vue3大型项目的工程化需要配置以下工具链:

TypeScript:启用strict模式,类型覆盖所有props和emit
ESLint + Prettier:统一代码风格,配合husky在提交前自动格式化
UnoCSS:原子化CSS方案,比Tailwind更轻量,按需生成样式
Vite:开发服务器启动速度快,HMR响应在100ms以内
VueRouter:路由守卫做权限控制,懒加载减少首屏体积

组合式API不是银弹,但它解决了Vue2选项式API在大型项目中逻辑分散的核心痛点。配合TypeScript和Pinia,Vue3项目可以支撑百万行级的代码库规模。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-shi-zhan-cong-xiang-ying-shi-yuan-li-dao/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐