Vue Vuex 实际项目中的典型应用场景
2024-03-053.6k 阅读
Vuex 基础概念
在深入探讨 Vuex 在实际项目中的应用场景之前,我们先来回顾一下 Vuex 的基础概念。Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
核心概念
- State:Vuex 使用单一状态树,即每个应用将仅仅包含一个 store 实例。所有的状态都集中存放在这个单一状态树中。例如,在一个电商应用中,商品列表、用户信息、购物车等所有状态都可以放在 state 中。
// 定义 state
const state = {
count: 0,
userInfo: null,
productList: []
}
- Getters:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,只有当它的依赖值发生了改变才会被重新计算。例如,在一个包含商品列表的电商应用中,如果我们想要获取价格大于某个值的商品列表,可以这样定义一个 getter。
const getters = {
expensiveProducts: state => {
return state.productList.filter(product => product.price > 100)
}
}
- Mutations:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。例如,我们要增加 count 的值:
const mutations = {
increment (state) {
state.count++
}
}
- Actions:Actions 类似于 mutations,不同在于:Actions 提交的是 mutation,而不是直接变更状态。Actions 可以包含任意异步操作。比如在一个用户登录的场景中,我们可能需要在登录成功后从服务器获取用户信息并更新到 state 中。
const actions = {
async login ({ commit }, userData) {
try {
const response = await axios.post('/api/login', userData)
commit('SET_USER_INFO', response.data.userInfo)
return response.data.token
} catch (error) {
throw new Error('Login failed')
}
}
}
- Modules:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter 甚至是嵌套子模块。
// 定义模块
const moduleA = {
state: {
someValue: 0
},
mutations: {
increment (state) {
state.someValue++
}
},
getters: {
doubleValue: state => state.someValue * 2
},
actions: {
async asyncIncrement ({ commit }) {
await new Promise(resolve => setTimeout(resolve, 1000))
commit('increment')
}
}
}
Vuex 在实际项目中的典型应用场景
用户认证与状态管理
- 场景描述 在几乎所有的 Web 应用中,用户认证都是至关重要的一部分。我们需要记录用户是否已登录,用户的基本信息,如用户名、用户 ID、用户角色等。当用户进行登录、注销等操作时,应用的各个部分都需要能够感知到这些状态的变化,并做出相应的调整,比如显示或隐藏某些菜单选项,根据用户角色加载不同的页面内容等。
- 实现方式
- State 定义:在 Vuex 的 state 中定义用户相关的状态。
const state = {
isLoggedIn: false,
userInfo: null,
userRole: null
}
- Mutations:用于更新用户状态。例如,当用户登录成功时,更新
isLoggedIn
为true
,并设置userInfo
和userRole
。
const mutations = {
SET_LOGIN_STATUS (state, isLoggedIn) {
state.isLoggedIn = isLoggedIn
},
SET_USER_INFO (state, userInfo) {
state.userInfo = userInfo
state.userRole = userInfo.role
}
}
- Actions:处理用户登录、注销的异步操作。以登录为例,与后端 API 进行交互。
import axios from 'axios'
const actions = {
async login ({ commit }, userData) {
try {
const response = await axios.post('/api/login', userData)
commit('SET_LOGIN_STATUS', true)
commit('SET_USER_INFO', response.data.userInfo)
return response.data.token
} catch (error) {
throw new Error('Login failed')
}
},
logout ({ commit }) {
commit('SET_LOGIN_STATUS', false)
commit('SET_USER_INFO', null)
}
}
- 组件中使用
在组件中,可以通过
mapState
、mapMutations
和mapActions
辅助函数来方便地使用 Vuex 中的状态和方法。例如,在导航栏组件中显示用户信息或登录/注销按钮。
<template>
<div>
<div v-if="isLoggedIn">
Welcome, {{ userInfo.username }}! <button @click="logout">Logout</button>
</div>
<div v-else>
<button @click="openLoginModal">Login</button>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState(['isLoggedIn', 'userInfo'])
},
methods: {
...mapActions(['logout'])
}
}
</script>
购物车功能实现
- 场景描述 电商应用中的购物车是一个典型的需要状态管理的场景。购物车需要记录添加的商品列表,每个商品的数量、价格计算,以及购物车的总价等。同时,用户在购物车中添加、删除商品,修改商品数量等操作都需要实时更新购物车的状态,并反映在页面上。
- 实现方式
- State 定义:定义购物车相关的状态。
const state = {
cartItems: [],
cartTotal: 0
}
- Mutations:处理购物车状态的变更。例如,添加商品到购物车,更新商品数量,计算总价等。
const mutations = {
ADD_TO_CART (state, product) {
const existingItem = state.cartItems.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
product.quantity = 1
state.cartItems.push(product)
}
state.cartTotal += product.price
},
UPDATE_ITEM_QUANTITY (state, { productId, quantity }) {
const item = state.cartItems.find(item => item.id === productId)
if (item) {
const priceDiff = (quantity - item.quantity) * item.price
item.quantity = quantity
state.cartTotal += priceDiff
}
},
REMOVE_FROM_CART (state, productId) {
const itemIndex = state.cartItems.findIndex(item => item.id === productId)
if (itemIndex!== -1) {
const removedItem = state.cartItems[itemIndex]
state.cartTotal -= removedItem.price * removedItem.quantity
state.cartItems.splice(itemIndex, 1)
}
}
}
- Actions:虽然购物车功能大多是同步操作,但如果涉及到与后端同步购物车数据等异步操作,也可以在 actions 中处理。例如,保存购物车到服务器。
const actions = {
async saveCartToServer ({ state }) {
try {
await axios.post('/api/saveCart', state.cartItems)
} catch (error) {
console.error('Failed to save cart to server', error)
}
}
}
- 组件中使用 在购物车组件中,通过计算属性获取购物车状态,并使用 methods 调用 Vuex 的 actions 和 mutations。
<template>
<div>
<ul>
<li v-for="item in cartItems" :key="item.id">
{{ item.name }} - Quantity: {{ item.quantity }} - Price: {{ item.price * item.quantity }}
<button @click="removeFromCart(item.id)">Remove</button>
<input type="number" v-model="item.quantity" @input="updateItemQuantity(item.id, item.quantity)">
</li>
</ul>
<div>Total: {{ cartTotal }}</div>
<button @click="saveCartToServer">Save Cart</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState(['cartItems', 'cartTotal'])
},
methods: {
...mapMutations(['ADD_TO_CART', 'UPDATE_ITEM_QUANTITY', 'REMOVE_FROM_CART']),
...mapActions(['saveCartToServer'])
}
}
</script>
多语言切换
- 场景描述 随着应用的国际化,多语言支持变得越来越重要。用户可以在应用中选择不同的语言,应用的界面文本需要实时切换。而且,不同组件可能会用到不同的语言文本,需要统一管理语言状态,确保所有组件的语言一致性。
- 实现方式
- State 定义:定义当前语言状态。
const state = {
currentLanguage: 'en'
}
- Mutations:用于切换语言。
const mutations = {
SET_LANGUAGE (state, language) {
state.currentLanguage = language
}
}
- Getters:可以根据当前语言从语言包中获取对应的文本。假设我们有一个语言包对象
languagePack
。
const languagePack = {
en: {
greeting: 'Hello',
welcome: 'Welcome to our app'
},
zh: {
greeting: '你好',
welcome: '欢迎来到我们的应用'
}
}
const getters = {
getText: state => key => {
return languagePack[state.currentLanguage][key]
}
}
- 组件中使用 在组件中,通过计算属性获取当前语言对应的文本。
<template>
<div>
<p>{{ greetingText }}</p>
<select @change="changeLanguage">
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['currentLanguage']),
...mapGetters(['getText']),
greetingText () {
return this.getText('greeting')
}
},
methods: {
...mapMutations(['SET_LANGUAGE']),
changeLanguage (e) {
this.SET_LANGUAGE(e.target.value)
}
}
}
</script>
页面加载状态管理
- 场景描述 在很多应用中,页面在加载数据时需要显示加载状态,比如一个加载动画。而且,可能存在多个组件同时需要加载数据的情况,需要统一管理这些加载状态,避免出现加载状态混乱的情况。例如,在一个新闻列表页面,当用户点击加载更多时,需要显示加载状态,同时,在页面初次加载时也需要显示加载状态。
- 实现方式
- State 定义:定义页面加载相关的状态。
const state = {
isLoading: false,
loadingCount: 0
}
- Mutations:处理加载状态的变更。当开始加载时,增加
loadingCount
并设置isLoading
为true
;当加载完成时,减少loadingCount
并判断是否所有加载都完成,如果是则设置isLoading
为false
。
const mutations = {
START_LOADING (state) {
state.loadingCount++
state.isLoading = true
},
FINISH_LOADING (state) {
state.loadingCount--
if (state.loadingCount === 0) {
state.isLoading = false
}
}
}
- Actions:在 actions 中调用 mutation 来控制加载状态。例如,在获取新闻列表数据的 action 中。
const actions = {
async fetchNewsList ({ commit }) {
commit('START_LOADING')
try {
const response = await axios.get('/api/newsList')
// 处理新闻列表数据
commit('FINISH_LOADING')
} catch (error) {
console.error('Failed to fetch news list', error)
commit('FINISH_LOADING')
}
}
}
- 组件中使用 在页面组件中,根据加载状态显示或隐藏加载动画。
<template>
<div>
<div v-if="isLoading">Loading...</div>
<ul>
<li v-for="news in newsList" :key="news.id">{{ news.title }}</li>
</ul>
<button @click="fetchNewsList">Load More</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState(['isLoading'])
},
methods: {
...mapActions(['fetchNewsList'])
}
}
</script>
多步骤表单状态管理
- 场景描述 在一些复杂的业务流程中,我们会遇到多步骤表单,比如用户注册流程可能分为填写基本信息、设置密码、验证邮箱等多个步骤。每个步骤可能有不同的验证规则,而且用户在填写过程中可能会在不同步骤之间切换,需要保存每个步骤的填写状态,确保数据不会丢失。
- 实现方式
- State 定义:定义表单相关的状态,包括当前步骤、每个步骤的数据等。
const state = {
currentStep: 1,
step1Data: {},
step2Data: {},
step3Data: {}
}
- Mutations:用于更新表单状态,如切换步骤,保存每个步骤的数据。
const mutations = {
SET_CURRENT_STEP (state, step) {
state.currentStep = step
},
SAVE_STEP1_DATA (state, data) {
state.step1Data = data
},
SAVE_STEP2_DATA (state, data) {
state.step2Data = data
},
SAVE_STEP3_DATA (state, data) {
state.step3Data = data
}
}
- Actions:可以在 actions 中处理表单提交等逻辑,例如在最后一步提交整个表单数据到后端。
const actions = {
async submitForm ({ state }) {
try {
const formData = {
...state.step1Data,
...state.step2Data,
...state.step3Data
}
const response = await axios.post('/api/submitForm', formData)
return response.data
} catch (error) {
console.error('Failed to submit form', error)
throw new Error('Form submission failed')
}
}
}
- 组件中使用 在每个步骤的表单组件中,通过计算属性获取当前步骤的数据,并使用 methods 调用 Vuex 的 mutations 保存数据。
<template>
<div v-if="currentStep === 1">
<form @submit.prevent="saveStep1Data">
<input v-model="step1Data.username" placeholder="Username">
<input v-model="step1Data.email" placeholder="Email">
<button type="submit">Next</button>
</form>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['currentStep','step1Data'])
},
methods: {
...mapMutations(['SAVE_STEP1_DATA'])
}
}
</script>
缓存数据管理
- 场景描述 在一些应用中,我们可能会频繁请求相同的数据,为了提高性能和减少服务器压力,需要对数据进行缓存。例如,在一个博客应用中,文章列表可能会被多次请求,我们可以将获取到的文章列表数据缓存起来,下次请求时直接从缓存中获取,而不是再次向服务器请求。
- 实现方式
- State 定义:定义缓存相关的状态,如缓存数据对象和缓存时间。
const state = {
articleListCache: null,
cacheTime: null
}
- Mutations:用于更新缓存数据和缓存时间。
const mutations = {
SET_ARTICLE_LIST_CACHE (state, { articleList, time }) {
state.articleListCache = articleList
state.cacheTime = time
}
}
- Actions:在获取文章列表的 action 中,先判断缓存是否有效,如果有效则直接返回缓存数据,否则从服务器获取数据并更新缓存。
const actions = {
async getArticleList ({ state, commit }) {
const now = new Date().getTime()
if (state.articleListCache && now - state.cacheTime < 60 * 1000) { // 缓存 1 分钟
return state.articleListCache
}
try {
const response = await axios.get('/api/articleList')
const articleList = response.data
commit('SET_ARTICLE_LIST_CACHE', { articleList, time: now })
return articleList
} catch (error) {
console.error('Failed to get article list', error)
throw new Error('Failed to fetch article list')
}
}
}
- 组件中使用 在文章列表组件中,通过调用 action 获取文章列表数据。
<template>
<div>
<ul>
<li v-for="article in articleList" :key="article.id">{{ article.title }}</li>
</ul>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data () {
return {
articleList: []
}
},
created () {
this.fetchArticleList()
},
methods: {
...mapActions(['getArticleList']),
async fetchArticleList () {
try {
this.articleList = await this.getArticleList()
} catch (error) {
console.error('Error fetching article list', error)
}
}
}
}
</script>
通过以上这些典型应用场景的介绍,我们可以看到 Vuex 在实际项目中能够有效地管理复杂的状态,使得应用的状态变化更加可预测,代码结构更加清晰,各个组件之间的状态共享和交互更加顺畅。在实际开发中,我们可以根据项目的具体需求灵活运用 Vuex 的各种功能,提升应用的开发效率和质量。