Code前端首页关于Code前端联系我们

Vue3项目里咋装Vuex?

terry 2小时前 阅读数 3 #Vue
文章标签 Vue3 Vuex

不少刚开始用Vue3做项目的同学,一碰到组件间共享数据、复杂状态管理就头大,总疑惑“Vue3里到底咋用Vuex?”别慌!这篇从最基础的安装配置,到核心概念咋用,再到实战案例和避坑技巧,把Vue3 + Vuex的关键逻辑拆得明明白白,哪怕是新手也能跟着一步步上手~

想在Vue3里用Vuex,得先搞对版本!Vue3对应的是**Vuex 4**(要是装错成Vuex 3,Vue3项目准报错),安装方式分两种:
  • 要是用Vue CLI或Vite创建项目时,选“手动配置”,可以直接勾上Vuex,脚手架会自动帮你装好并配置基础结构;

  • 要是项目已经建好,手动装的话,打开终端输命令:

    npm install vuex@4 --save

    装完后,得在项目里创建store文件夹(一般放src目录下),新建index.js文件来配置Vuex核心逻辑:

    // src/store/index.js
    import { createStore } from 'vuex'
    const store = createStore({
      state() { // 数据源,返回要管理的状态
        return {
          count: 0
        }
      },
      mutations: { // 同步修改state的方法
        increment(state) {
          state.count++
        }
      }
    })
    export default store

    得把store“挂”到Vue3的App实例上!打开src/main.js,加入这两行:

    import { createApp } from 'vue'
    import App from './App.vue'
    import store from './store' // 引入刚才写的store
    const app = createApp(App)
    app.use(store) // 注册Vuex,让整个应用能访问store
    app.mount('#app')

    要是没在main.js里写app.use(store),后面用useStore()的时候准报错,这步千万不能漏~

Vuex里的state在Vue3咋用?

state是Vuex存数据的“大仓库”,组件要读state里的数据,得用useStore()先拿到store实例,但Vue3是组合式API,直接写store.state.xxx可能不响应式,得结合computed保证数据变化时页面自动更新!

举个例子:组件里要显示state里的count,代码得这么写:

<template>
  <div>当前计数:{{ count }}</div>
  <button @click="handleIncrement">+1</button>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
// 用computed包裹,让count变成响应式数据
const count = computed(() => store.state.count) 
const handleIncrement = () => {
  // 后面讲mutations时会说咋改state,这里先看“读”的逻辑
}
</script>

要是你不想每次都写computed,也能自己封装工具函数,或者用mapState(不过组合式API里mapState得结合computed用,相对麻烦,新手先掌握useStore + computed更直观)。

getters在Vue3里咋玩?

getters有点像Vue组件里的computed,专门处理“派生状态”——比如从state里的列表数据,过滤出已完成的任务。

先在store/index.js里定义getters:

const store = createStore({
  state() {
    return {
      todos: [
        { id: 1, text: '学Vue3', done: false },
        { id: 2, text: '学Vuex', done: true }
      ]
    }
  },
  getters: {
    doneTodos(state) {
      return state.todos.filter(todo => todo.done)
    }
  }
})

组件里用的时候,同样得用useStore + computed(因为getters本身是响应式的,但直接读store.getters.doneTodos在组合式API里得包一下才能让页面响应):

<template>
  <ul>
    <li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li>
  </ul>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const doneTodos = computed(() => store.getters.doneTodos)
</script>

这样不管state里的todos咋变,doneTodos都会自动更新,省得在组件里重复写过滤逻辑~

mutations和actions有啥区别?Vue3里咋调用?

很多新手最容易搞混这俩!简单说:

  • mutations:只能写同步代码,专门用来“修改state”(Vuex规定,所有state的修改必须走mutation,不然响应式会出问题);
  • actions:可以写异步代码(比如调后端接口),然后通过触发mutation来改state。

先看mutations咋用

store/index.js里定义mutation(函数接收state和传参payload):

mutations: {
  increment(state, payload) { 
    state.count += payload.num || 1 // payload是传过来的参数
  }
}

组件里触发mutation,得用store.commit('mutation名字', 传参)

<script setup>
import { useStore } from 'vuex'
const store = useStore()
const handleClick = () => {
  store.commit('increment', { num: 2 }) // 传参让count一次加2
}
</script>

再看actions咋用

要是有异步操作(比如登录时调接口),就得用action,先在store/index.js里定义action(函数接收context,里面包含commitstate等方法/属性):

actions: {
  login(context, userInfo) { 
    return new Promise((resolve, reject) => {
      // 模拟调接口
      setTimeout(() => {
        if (userInfo.username === 'admin' && userInfo.password === '123') {
          // 接口成功后,通过commit触发mutation改state
          context.commit('setUser', { username: 'admin', token: 'xxx' })
          resolve()
        } else {
          reject('账号或密码错误')
        }
      }, 1000)
    })
  }
},
mutations: {
  setUser(state, user) {
    state.user = user // 修改state里的user
  }
},
state() {
  return {
    user: null
  }
}

组件里触发action,用store.dispatch('action名字', 传参)

<script setup>
import { useStore } from 'vuex'
import { ref } from 'vue'
const store = useStore()
const username = ref('')
const password = ref('')
const handleLogin = async () => {
  try {
    await store.dispatch('login', { username: username.value, password: password.value })
    // 登录成功后,可做跳转等逻辑
  } catch (err) {
    alert(err)
  }
}
</script>

修改state必须走mutation,哪怕action里是异步操作,最后改state也得通过context.commit调mutation~

Vue3组合式API和Vuex结合有啥技巧?

组合式API强调“逻辑复用”,和Vuex结合时,推荐用自定义Hook封装store的操作,让组件代码更简洁。

比如封装一个useCounterStore.js,把和“计数”相关的state、mutation、action都包进去:

// src/hooks/useCounterStore.js
import { useStore } from 'vuex'
import { computed } from 'vue'
export function useCounterStore() {
  const store = useStore()
  // 读state里的count
  const count = computed(() => store.state.count)
  // 触发mutation:increment
  const increment = (num) => {
    store.commit('increment', { num })
  }
  // 触发action:asyncIncrement(假设有的话)
  const asyncIncrement = () => {
    store.dispatch('asyncIncrement')
  }
  return {
    count,
    increment,
    asyncIncrement
  }
}

组件里用的时候,直接调hook里的方法,不用重复写useStorecomputed

<template>
  <div>{{ count }}</div>
  <button @click="increment(2)">+2</button>
  <button @click="asyncIncrement">异步+1</button>
</template>
<script setup>
import { useCounterStore } from '../hooks/useCounterStore'
const { count, increment, asyncIncrement } = useCounterStore()
</script>

这样不管多少组件要用计数逻辑,直接引入这个hook就行,代码复用性拉满~

实战:用Vuex做个待办事项(Todo)应用咋设计?

光讲理论太虚,咱直接落地个小项目,看看Vuex咋串联需求。

需求分析

做个能“添加todo、标记完成、删除todo、按状态筛选(全部/已完成/未完成)”的应用。

设计store结构

store/index.js里,把state(存数据)、getters(派生状态)、mutations(同步改状态)、actions(异步逻辑)都安排上:

import { createStore } from 'vuex'
const store = createStore({
  state() {
    return {
      todos: [] // 存所有todo项
    }
  },
  getters: {
    allTodos: (state) => state.todos, // 全部todo
    doneTodos: (state) => state.todos.filter(t => t.done), // 已完成
    undoneTodos: (state) => state.todos.filter(t => !t.done) // 未完成
  },
  mutations: {
    addTodo(state, newTodo) { // 添加新todo
      state.todos.push({ id: Date.now(), ...newTodo, done: false })
    },
    toggleTodo(state, todoId) { // 切换完成状态
      const todo = state.todos.find(t => t.id === todoId)
      if (todo) todo.done = !todo.done
    },
    deleteTodo(state, todoId) { // 删除todo
      state.todos = state.todos.filter(t => t.id !== todoId)
    }
  },
  actions: {
    // 模拟异步添加(比如调后端接口)
    addTodoAsync(context, newTodo) {
      return new Promise((resolve) => {
        setTimeout(() => {
          context.commit('addTodo', newTodo)
          resolve()
        }, 500)
      })
    }
  }
})
export default store

组件里咋用?

写个TodoList.vue组件,负责渲染列表、处理交互:

<template>
  <div class="todo-container">
    <!-- 添加todo区域 -->
    <div class="add-todo">
      <input v-model="newTodoText" placeholder="输入要做的事" />
      <button @click="handleAdd">添加</button>
    </div>
    <!-- 筛选tab -->
    <div class="tabs">
      <button @click="activeTab = 'all'">全部</button>
      <button @click="activeTab = 'done'">已完成</button>
      <button @click="activeTab = 'undone'">未完成</button>
    </div>
    <!-- todo列表 -->
    <ul class="todo-list">
      <li 
        v-for="todo in filteredTodos" 
        :key="todo.id" 
        :class="{ done: todo.done }"
      >
        <input type="checkbox" :checked="todo.done" @change="handleToggle(todo.id)" />
        <span>{{ todo.text }}</span>
        <button @click="handleDelete(todo.id)">删除</button>
      </li>
    </ul>
  </div>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed, ref } from 'vue'
const store = useStore()
const newTodoText = ref('') // 绑定输入框内容
const activeTab = ref('all') // 当前选中的筛选tab
// 根据tab筛选数据
const filteredTodos = computed(() => {
  if (activeTab.value === 'all') return store.getters.allTodos
  if (activeTab.value === 'done') return store.getters.doneTodos
  if (activeTab.value === 'undone') return store.getters.undoneTodos
  return []
})
// 点击“添加”时,调action模拟异步添加
const handleAdd = () => {
  if (!newTodoText.value.trim()) return // 空内容不处理
  store.dispatch('addTodoAsync', { text: newTodoText.value }).then(() => {
    newTodoText.value = '' // 清空输入框
  })
}
// 切换完成状态,调mutation
const handleToggle = (todoId) => {
  store.commit('toggleTodo', todoId)
}
// 删除todo,调mutation
const handleDelete = (todoId) => {
  store.commit('deleteTodo', todoId)
}
</script>
<style scoped>
.done { text-decoration: line-through; opacity: 0.5; }
/* 其他样式可根据需求补充 */
</style>

这样一套下来,从“添加”到“筛选”再到“修改/删除”,所有状态都由Vuex统一管理,哪怕以后扩展功能(比如加个统计已完成数量的组件),直接读Vuex的getters就行,不用操心组件间传值的麻烦~

Vuex在Vue3项目里常见坑有哪些?咋避?

用Vuex时稍不注意就踩坑,这几个高频问题得警惕:

忘记在main.js注册store,导致useStore()报错

表现:控制台提示“[Vuex] must call Vue.use(Vuex) before creating a store instance.”
原因:Vue3应用没注册Vuex插件,所以拿不到store实例。
解决:打开main.js,确保写了app.use(store),并且store是从./store正确引入的。

直接修改state,没走mutation,导致数据不响应

表现:手动写store.state.count = 10,页面没更新。
原理:Vuex的state是通过Vue响应式系统实现的,但必须通过mutation修改才能触发更新(Vuex内部做了依赖收集和更新触发)。
解决:把所有state修改逻辑放到mutation里,用store.commit触发,别直接改store.state.xxx

actions里写同步代码,职责混乱

表现:明明是同步改state,却放到action里,绕了一圈才commit mutation。
原理:actions设计初衷是处理异步或多步骤复杂逻辑,同步修改属于mutation的职责。
解决:同步操作(比如直接改数值)写mutation;异步/多步骤操作(比如调接口+改状态)写action,再在action里commit mutation。

组合式API里没给state包computed,数据不更新

表现:组件里写const count = store.state.count,后来state.count变了,页面没反应。
原理:组合式API中,直接读store.state.xxx是非响应式的,得用computed包裹才能让Vue跟踪数据变化。
解决:所有读stategetters的地方,都用computed(() => store.state.xxx)computed(() => store.getters.xxx)

多人协作时mutation/action命名冲突

表现:不同模块里的mutation重名(比如都叫update),触发时逻辑混乱。
解决:用Vuex的modules拆分模块(比如用户模块、商品模块),每个模块开启namespaced: true,让mutation、action命名只在模块内唯一,示例:

// src/store/modules/user.js(用户模块)
export default {
  namespaced: true, // 开启命名空间
  state() { /* 模块内的state */ },
  mutations: { /* 模块内的mutation */ },
  actions: { /* 模块内的action */ }
}
// src/store/index.js(总配置)
import user from './modules/user'
const store = createStore({
  modules: {
    user // 注册用户模块
  }
})

组件里调用时,得加模块名,比如store.commit('user/update', payload),避免全局命名冲突~

看完这些,再回头看“Vue3咋用Vuex”,是不是清晰多了?从安装到核心概念,再到实战和避坑,其实核心逻辑就那几条:用`createStore`建仓库、通过`mutation`改state、`action`处理异步、组合式API结合`

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门