文章

Vue状态管理方案对比深度解析

深入对比 Vuex 和 Pinia 两大 Vue 状态管理方案,从设计理念、API 差异到源码实现,帮助你在项目中做出正确选择。

Vue状态管理方案对比深度解析

一句话概括

Vuex 是 Vue 2 生态的标志性状态管理方案,Pinia 作为 Vue 3 官方推荐的继任者,在设计上更简洁、类型更友好、且完美支持 Composition API,二者代表了 Vue 生态系统从 Options API 到 Composition API 的设计哲学演进。

背景与意义

组件之间的状态共享一直是前端框架需要解决的核心问题。在小规模应用中,props 逐级传递足以应对,但随着应用规模的增长,跨组件、跨页面的状态共享变得越来越复杂。

Vue 1.x 时代,开发者使用 event bus 和简单的全局对象来管理状态。Vue 2 时代,Vuex 1.0 在 2016 年发布,借鉴了 Flux 架构和 Redux 的设计理念,成为 Vue 生态系统中状态管理的标准方案。到了 Vue 3 时代,Pinia 以更现代的 API、更好的 TypeScript 支持和更轻量的体积成为官方推荐方案。

理解 Vuex 到 Pinia 的演变,不仅是学习两个库 API 的差异,更是理解前端状态管理设计思想演进的过程。

概念与定义

Flux 架构:由 Facebook 提出的一种前端应用架构模式,核心特征为「单向数据流」——View → Action → Dispatcher → Store → View。

Store(仓库):集中管理应用状态的容器。所有组件都可以读取 Store 中的状态,但只能通过约定好的方式修改状态。

Mutation(变更):Vuex 中唯一允许修改状态的方式,必须是同步函数。这为调试提供了可追踪的时间旅行能力。

Action(动作):Vuex 中处理异步逻辑的方式。Action 提交 Mutation,而不是直接修改状态。

Getter(获取器):类似于 Vue 的 computed,从 Store 中派生出计算后的状态。

最小示例

Vuex 4(配合 Vue 3)的典型用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// store/index.js - Vuex 4
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: null,
    items: [],
  },
  getters: {
    doubleCount: (state) => state.count * 2,
    isLoggedIn: (state) => state.user !== null,
  },
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    SET_USER(state, user) {
      state.user = user
    },
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => commit('INCREMENT'), 1000)
    },
    async login({ commit }, credentials) {
      const user = await api.login(credentials)
      commit('SET_USER', user)
    },
  },
})

Pinia 的典型用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// stores/counter.js - Pinia
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      // 直接修改 state!不需要 mutation
      this.count++
    },
    async incrementAsync() {
      await delay(1000)
      this.count++
    },
  },
})

核心知识点拆解

1. Vuex:成熟稳重的 Flux 实践者

Vuex 的设计严格遵循 Flux 架构的四个核心概念:State、Getter、Mutation、Action。

Strict 模式与状态溯源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Vuex strict 模式:防止直接修改 state
const store = createStore({
  strict: process.env.NODE_ENV !== 'production',
  state: { count: 0 },
  mutations: {
    INCREMENT(state) { state.count++ },
  },
})

// ❌ 直接修改(strict 模式下会报错)
store.state.count = 5 // Error: [vuex] Do not mutate vuex store state outside mutation handlers.

// ✅ 通过 mutation 修改
store.commit('INCREMENT')

Module 模块化方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Vuex 模块化
const userModule = {
  namespaced: true, // 开启命名空间
  state: () => ({
    profile: null,
    permissions: [],
  }),
  getters: {
    hasPermission: (state) => (perm) => state.permissions.includes(perm),
  },
  mutations: {
    SET_PROFILE(state, profile) { state.profile = profile },
  },
  actions: {
    async fetchProfile({ commit }) {
      const profile = await api.getProfile()
      commit('SET_PROFILE', profile)
    },
  },
}

const store = createStore({
  modules: {
    user: userModule,
    cart: cartModule,
    product: productModule,
  },
})

// 访问模块化状态
store.state.user.profile
// 带命名空间的 getter
store.getters['user/hasPermission']('admin')
// 提交模块的 mutation
store.commit('user/SET_PROFILE', profile)

Vuex 的辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 mapState、mapGetters、mapActions、mapMutations
import { mapState, mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    // 映射 state
    ...mapState({
      count: (state) => state.count,
      userName: (state) => state.user.name,
    }),
    // 映射 getter(带命名空间)
    ...mapGetters('user', ['isLoggedIn', 'hasPermission']),
  },
  methods: {
    ...mapActions(['incrementAsync']),
    ...mapActions('user', ['fetchProfile']),
  },
}

2. Pinia:面向 Vue 3 的全新设计

Pinia 的诞生是为了解决 Vuex 的几个核心痛点:

  1. TypeScript 支持薄弱:Vuex 4 的 TS 类型推断不够完善,this.$store 的类型推导困难
  2. Mutations 显得多余:Vue 3 的响应式系统可以直接修改 state,不需要 Mutation 这一层
  3. 模块系统复杂:嵌套模块的命名空间配置容易出错
  4. Composition API 支持不佳:Vuex 4 是为 Options API 设计的

Pinia 的状态定义方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// stores/user.js - Pinia 完整的 store 定义
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', () => {
  // setup store 语法(类似 Composition API)
  const profile = ref(null)
  const permissions = ref([])
  const loginHistory = ref([])

  const isLoggedIn = computed(() => profile.value !== null)
  const displayName = computed(() => profile.value?.name || '访客')

  async function login(credentials) {
    const user = await api.login(credentials)
    profile.value = user
    permissions.value = user.permissions
  }

  function hasPermission(perm) {
    return permissions.value.includes(perm)
  }

  function logout() {
    profile.value = null
    permissions.value = []
  }

  return {
    profile,
    permissions,
    loginHistory,
    isLoggedIn,
    displayName,
    login,
    hasPermission,
    logout,
  }
})

Pinia Options API vs Setup Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Options API 风格(类似 Vuex)
const useProductStore = defineStore('product', {
  state: () => ({
    items: [],
    currentProduct: null,
    loading: false,
  }),
  getters: {
    availableItems: (state) => state.items.filter((i) => i.stock > 0),
    totalValue: (state) => state.items.reduce((sum, i) => sum + i.price * i.stock, 0),
  },
  actions: {
    async fetchProducts(category) {
      this.loading = true
      try {
        this.items = await api.getProducts(category)
      } finally {
        this.loading = false
      }
      // 直接修改 state,不需要 mutations
    },
  },
})

// Setup Store 风格(推荐,更灵活)
const useProductStoreSetup = defineStore('product', () => {
  const items = ref([])
  const loading = ref(false)

  const availableItems = computed(() => items.value.filter((i) => i.stock > 0))
  const totalItems = computed(() => items.value.length)

  async function fetchProducts(category) {
    loading.value = true
    items.value = await api.getProducts(category)
    loading.value = false
  }

  // 可以返回 watch 等副作用
  watch(items, (newItems) => {
    console.log(`Products updated: ${newItems.length} items`)
  })

  return { items, loading, availableItems, totalItems, fetchProducts }
})

Pinia 的类型安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// typescript 中的类型自动推导
import { defineStore } from 'pinia'
import type { User, LoginCredentials } from '@/types'

export const useAuthStore = defineStore('auth', () => {
  const user = ref<User | null>(null)
  const token = ref<string | null>(null)

  const isAuthenticated = computed(() => token.value !== null)

  async function login(credentials: LoginCredentials): Promise<void> {
    const response = await api.post<{ user: User; token: string }>('/login', credentials)
    user.value = response.user
    token.value = response.token
  }

  // 类型完全自动推导,无需额外声明
  return { user, token, isAuthenticated, login }
})

// 使用时的类型安全
const store = useAuthStore()
await store.login({ username: 'admin', password: '...' })
// store.user 类型为 User | null
// store.isAuthenticated 类型为 boolean

3. Vuex vs Pinia 核心差异对照

维度Vuex 4Pinia
发布年份20212021 (v2)
大小~10KB gzip~1KB gzip
TypeScript支持有限原生支持,类型安全
Mutations必需❌ 已移除
Modules内置嵌套模块平铺式(每个 store 独立)
Devtools支持更好(时间旅行+热更新)
SSR需额外配置原生支持
插件系统官方支持更简单的插件 API

4. Vue 3 Composition API 中的状态管理

除了 Vuex 和 Pinia,Vue 3 的 Composition API 本身就能实现简单的状态共享:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// shared.js - 使用 Composition API 实现轻量级状态管理
import { ref, computed, readonly } from 'vue'

// 全局单例状态
const globalCount = ref(0)
const globalUser = ref(null)

export function useGlobalState() {
  const doubleCount = computed(() => globalCount.value * 2)
  const isLoggedIn = computed(() => globalUser.value !== null)

  function increment() {
    globalCount.value++
  }

  function setUser(user) {
    globalUser.value = user
  }

  return {
    count: readonly(globalCount), // 外部只能读取不能修改
    user: readonly(globalUser),
    doubleCount,
    isLoggedIn,
    increment,
    setUser,
  }
}

// 任何组件调用 useGlobalState() 得到的是同一份状态
// 适合小规模应用的简单状态共享

实战案例:从 Vuex 迁移到 Pinia

假设我们有一个使用 Vuex 的电商购物车模块,现在要迁移到 Pinia:

Vuex 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// vuex/cart.js - Vuex 购物车模块
const cartModule = {
  namespaced: true,
  state: () => ({
    items: [],
    couponCode: null,
    discount: 0,
  }),
  getters: {
    totalCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
    subtotal: (state) => state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    total: (state, getters) => getters.subtotal - state.discount,
  },
  mutations: {
    ADD_ITEM(state, product) {
      const existing = state.items.find((i) => i.id === product.id)
      if (existing) existing.quantity++
      else state.items.push({ ...product, quantity: 1 })
    },
    REMOVE_ITEM(state, productId) {
      state.items = state.items.filter((i) => i.id !== productId)
    },
    UPDATE_QUANTITY(state, { id, quantity }) {
      const item = state.items.find((i) => i.id === id)
      if (item) item.quantity = quantity
    },
    APPLY_COUPON(state, code) {
      state.couponCode = code
      state.discount = code === 'SAVE20' ? state.items.reduce((s, i) => s + i.price * i.quantity, 0) * 0.2 : 0
    },
  },
  actions: {
    async checkout({ state, commit }) {
      const order = {
        items: state.items,
        coupon: state.couponCode,
        total: state.items.reduce((sum, item) => sum + item.price * item.quantity, 0) - state.discount,
      }
      const response = await api.createOrder(order)
      commit('CLEAR_CART')
      return response
    },
    CLEAR_CART(state) {
      state.items = []
      state.couponCode = null
      state.discount = 0
    },
  },
}

Pinia 版本(迁移后)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// pinia/cart.js - Pinia 购物车 store
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/services/api'

export const useCartStore = defineStore('cart', () => {
  // state
  const items = ref([])
  const couponCode = ref(null)
  const discount = ref(0)

  // getters
  const totalCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )

  const subtotal = computed(() =>
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )

  const total = computed(() => subtotal.value - discount.value)

  // actions
  function addItem(product) {
    const existing = items.value.find((i) => i.id === product.id)
    if (existing) {
      existing.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }

  function removeItem(productId) {
    items.value = items.value.filter((i) => i.id !== productId)
  }

  function updateQuantity(id, quantity) {
    const item = items.value.find((i) => i.id === id)
    if (item) item.quantity = quantity
  }

  function applyCoupon(code) {
    couponCode.value = code
    discount.value = code === 'SAVE20' ? subtotal.value * 0.2 : 0
  }

  async function checkout() {
    const order = {
      items: items.value,
      coupon: couponCode.value,
      total: total.value,
    }
    const response = await api.createOrder(order)
    // 直接清空
    items.value = []
    couponCode.value = null
    discount.value = 0
    return response
  }

  return {
    items, couponCode, discount,
    totalCount, subtotal, total,
    addItem, removeItem, updateQuantity, applyCoupon, checkout,
  }
})

迁移收益

  1. 代码量减少约 30%(不用写 mutations)
  2. TypeScript 类型完全自动推断
  3. 不再需要考虑 namespaced 配置
  4. Devtools 的 time-travel 调试体验更好

底层原理(含源码分析)

1. Vuex 的响应式核心

Vuex 的核心是使用 Vue 的响应式系统来管理状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Vuex 源码简化:Store 构造函数核心逻辑
class Store {
  constructor(options) {
    const { state, mutations, actions, getters } = options

    // 核心:使用 Vue 的响应式系统包装 state
    // Vuex 2 使用 new Vue(),Vuex 4 使用 reactive()
    this._state = reactive({
      data: typeof state === 'function' ? state() : state,
    })

    // 注册 mutations
    this._mutations = Object.create(null)
    Object.keys(mutations).forEach((type) => {
      this._mutations[type] = (payload) => {
        mutations[type](this.state, payload)
      }
    })

    // 注册 actions
    this._actions = Object.create(null)
    Object.keys(actions).forEach((type) => {
      this._actions[type] = (payload) => {
        return actions[type]({
          state: this.state,
          commit: this.commit.bind(this),
          dispatch: this.dispatch.bind(this),
        }, payload)
      }
    })

    // 注册 getters(使用 computed)
    this.getters = Object.create(null)
    Object.keys(getters).forEach((name) => {
      Object.defineProperty(this.getters, name, {
        get: () => computed(() => getters[name](this.state, this.getters)).value,
        enumerable: true,
      })
    })

    // 用于 strict 模式的深层 watch
    if (options.strict) {
      this._committing = false
      watch(this._state.data, (newVal, oldVal) => {
        if (!this._committing) {
          console.warn('[vuex] Do not mutate state outside mutation handlers')
        }
      }, { deep: true, flush: 'sync' })
    }
  }

  // commit:执行 mutation
  commit(type, payload) {
    this._committing = true
    this._mutations[type](payload)
    this._committing = false
  }

  // dispatch:执行 action
  dispatch(type, payload) {
    return this._actions[type](payload)
  }

  get state() {
    return this._state.data
  }
}

2. Pinia 的简化设计

Pinia 的源码比 Vuex 简洁得多,因为它直接利用了 Vue 3 的 Composition API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Pinia 源码核心逻辑简化
function defineStore(id, storeSetup) {
  // 返回一个 useStore 函数
  return function useStore() {
    // 获取当前 Vue 应用实例
    const app = getCurrentInstance().appContext.app
    const pinia = app.config.globalProperties.$pinia

    // 检查 store 是否已注册
    if (!pinia._s.has(id)) {
      // 创建新的 store
      createStore(id, storeSetup, pinia)
    }

    // 返回已注册的 store 实例
    const store = pinia._s.get(id)
    return store
  }
}

function createStore(id, storeSetup, pinia) {
  let scope, isSetup = false

  // 核心:在 effectScope 中执行 setup 函数
  // 这确保了 store 卸载时,所有响应式副作用自动清理
  scope = effectScope()

  const setupStore = scope.run(() => {
    // 支持 Options API 和 Composition API 两种定义方式
    if (typeof storeSetup === 'function') {
      return storeSetup() // setup store
    } else {
      return setupFromOptions(storeSetup) // options store
    }
  })

  // 包装返回的对象,使其拥有 $patch、$reset 等方法
  const store = wrapStore(id, setupStore, pinia, scope)
  pinia._s.set(id, store)
  return store
}

// Vue 3 的 effectScope 是关键:
// 它允许 Pinia 在一个作用域内管理所有响应式效果
// 当 store 不再需要时,调用 scope.stop() 可以一次性清理所有副作用

3. $patch 批量更新的实现

Pinia 的 $patch 方法支持两种批量更新方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 用法 1:传入一个对象
store.$patch({
  count: store.count + 1,
  name: 'new name',
})

// 用法 2:传入一个函数(可以访问当前 state)
store.$patch((state) => {
  state.items.push({ id: 1, name: 'new item' })
  state.count++
})

// 源码实现:
function $patch(partialStateOrMutator) {
  if (typeof partialStateOrMutator === 'function') {
    // 函数模式:在批量更新的上下文中执行
    withBatch(() => {
      partialStateOrMutator(this.$state)
    })
  } else {
    // 对象模式:使用 reactive 的批量赋值
    Object.assign(this.$state, partialStateOrMutator)
  }
}

4. Devtools 集成原理

Vuex 和 Pinia 都通过插件与 Vue Devtools 集成。核心是发送「时间旅行快照」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Devtools 通信逻辑
function setupDevtoolsPlugin(app, store, type) {
  const devtoolsApi = app.config.globalProperties.__VUE_DEVTOOLS_GLOBAL_HOOK__

  // 每次 mutation/action 后发送快照
  store.$subscribe((mutation, state) => {
    devtoolsApi.emit('vuex:mutation', {
      type: mutation.type,
      payload: mutation.payload,
      state: cloneDeep(state), // 深拷贝快照
    })
  })

  // 时间旅行:恢复到某个历史状态
  devtoolsApi.on('vuex:travel-to-state', (state) => {
    store.$state = state
  })
}

高频面试题解析

Q1: Pinia 为什么去掉了 Mutations?

考察点:理解 Vue 3 响应式系统的变化和 Pinia 的设计哲学。

答案核心

Mutations 在 Vuex 中存在的核心原因是 Vue 2 的响应式系统限制和 Devtools 的追踪需求。Vue 2 使用 Object.defineProperty 实现响应式,需要通过 Vue.set 来确保新增属性也是响应式的。Mutations 提供了一个统一的门户,确保所有状态修改都能被正确追踪。

到了 Vue 3,Proxy 代理可以自动检测到新属性的添加和删除,这个限制不复存在。Pinia 直接修改 state 就能获得完整的响应式能力,Mutations 变成了多余的包装层。

Pinia 的设计理念是「少一层抽象,多一分简洁」。直接修改 state 更加直观,同时通过 Devtools 插件仍然可以完整追踪每次修改。事实上,Pinia Devtools 比 Vuex Devtools 的追踪能力更强。

Q2: Pinia 的 $subscribe 和 Vuex 的 watch 有什么区别?

考察点:对状态变化监听机制的深度理解。

答案核心

1
2
3
4
5
6
7
8
9
10
11
12
// Vuex 的 watch:直接使用 Vue 的 $watch
store.watch(
  (state) => state.cart.items.length,
  (newVal) => console.log('Cart size changed:', newVal)
)

// Pinia 的 $subscribe:专用的订阅 api
cartStore.$subscribe((mutation, state) => {
  console.log('Mutation type:', mutation.type) // 'direct' 或 'patch object'
  console.log('Store id:', mutation.storeId)
  console.log('New state:', state)
})

主要区别:

  1. 触发时机$subscribe 在每次修改 state 后触发,包括 $patch;Vuex 的 watch 在值变化时触发并带有防抖
  2. 批量更新$subscribe 在一个 $patch 调用中只触发一次;Vuex 的 watch 对每个变化都触发
  3. SSR 兼容:Pinia 的 $subscribe 在 SSR 中不会触发;Vuex 需要手动处理
  4. 性能开销$subscribe 更轻量,因为它在 Pinia 内部是直接挂在 reactive 上的 callback;Vuex 的 watch 依赖 Vue 完整的 watch API

Q3: 在大型项目中,Vuex 的模块嵌套和 Pinia 的平铺模式各有什么优劣?

考察点:架构设计能力和实际工程经验。

答案核心

Vuex 嵌套模块

  • 优势:结构化清晰,与后端 API 架构对应,适合严格的 DDD(领域驱动设计)
  • 劣势:命名空间配置繁琐,模块间通信需要 rootState/rootGetters,调试时路径嵌套深

Pinia 平铺模式

  • 优势:每个 store 独立,互相引用时只需 useXxxStore(),类型安全更好
  • 劣势:可能出现循环依赖(storeA 引用 storeB,storeB 又引用 storeA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vuex 嵌套方案                        Pinia 平铺方案
─────────────────────               ─────────────────────
store/
  user/                              stores/
    index.js                           user.js
    profile.js                         cart.js
    settings.js                        product.js
  cart/                              orders.js
    index.js                         notifications.js
    items.js
    coupons.js
  product/
    catalog.js
    detail.js

实践建议

  • 中小型项目:Pinia 平铺模式,每个功能模块一个 store,清晰简单
  • 大型项目(20+ 模块):Pinia 平铺 + 按目录分组,避免循环引用
  • 遗产项目:Vuex 无需急于迁移,除非遇到类型安全或维护成本问题

总结与扩展

从 Vuex 到 Pinia 的演进,反映了前端状态管理从「严格约束」到「简洁高效」的设计趋势。Vuex 的 Mutation + Action 双重架构虽然保证了严格的单向数据流,但也增加了样板代码。Pinia 去掉了这些冗余层,让状态管理回归本质——数据 + 方法。

选型建议

  • 新项目(Vue 3):无脑选择 Pinia,它是官方推荐方案
  • 老项目(Vue 2):继续使用 Vuex 3,迁移成本可能超过收益
  • 需要严格审计和调试:Vuex 的严格模式仍然有价值
  • 追求代码简洁和类型安全:Pinia 是更好的选择

扩展思考

状态管理的未来趋势可能不再是「集中式 store」,而是朝向「原子化」和「组合式」发展。类似 Recoil 和 Jotai 在 React 生态中的做法——将状态拆解为最小的「原子」,按需组合。Vue 生态中,VueUse 函数库正在朝这个方向探索。

此外,Server State(服务端状态管理)逐渐与 Client State(客户端状态管理)分离。TanStack Query(Vue Query)、SWR 等库专注于管理异步服务端状态,而 Pinia 和 Vuex 则聚焦于客户端状态,这种职责分明的趋势越来越明显。

本文由作者按照 CC BY 4.0 进行授权