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
,里面包含commit
、state
等方法/属性):
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里的方法,不用重复写useStore
和computed
:
<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跟踪数据变化。
解决:所有读state
或getters
的地方,都用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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。