Vue Vuex state、getters、mutations与actions的功能解析
Vuex 基础概念
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
在 Vuex 中,核心概念包括 state、getters、mutations 和 actions。理解这些概念对于有效地使用 Vuex 管理应用状态至关重要。
State
什么是 State
State 是 Vuex 中的状态存储,它就像一个全局的对象,用于存储应用中多个组件共享的状态数据。在 Vue 组件中,我们可以通过 $store.state
来访问 Vuex 中的 state。
State 的使用场景
想象一个电商应用,购物车的商品列表就是一个适合存储在 state 中的数据。多个组件,比如商品详情页、购物车页面等都可能需要访问和修改购物车的商品列表,这时将购物车数据存储在 state 中就非常合适。
代码示例
首先,我们创建一个简单的 Vuex 实例并定义 state。
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
}
});
export default store;
在 Vue 组件中访问 state:
<template>
<div>
<p>The count is: {{ count }}</p>
</div>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count;
}
}
};
</script>
在这个例子中,count
是 state 中的一个属性,通过 this.$store.state.count
可以在组件中访问到它。我们使用计算属性 count
来简化访问。
State 的响应式原理
Vuex 的 state 利用了 Vue 的响应式系统。当 state 中的数据发生变化时,依赖它的组件会自动更新。这是因为 Vue 在初始化 state 时,会使用 Object.defineProperty
将 state 中的属性转换为响应式数据。
模块中的 State
在大型应用中,我们可能需要将 state 按照功能模块进行划分。例如,一个博客应用可能有用户模块、文章模块等。我们可以这样定义模块中的 state:
// modules/user.js
const state = {
userInfo: {
name: '',
age: 0
}
};
export default state;
然后在主 store 中引入模块:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
user
}
});
export default store;
在组件中访问模块中的 state:
<template>
<div>
<p>User name: {{ user.userInfo.name }}</p>
</div>
</template>
<script>
export default {
computed: {
user() {
return this.$store.state.user;
}
}
};
</script>
Getters
什么是 Getters
Getters 相当于 Vue 组件中的计算属性。它用于从 state 中派生出一些状态,比如对 state 中的数据进行过滤、计算等操作。Getters 的值会根据它的依赖被缓存起来,只有当它的依赖值发生了改变才会重新计算。
Getters 的使用场景
继续以电商应用为例,假设 state 中存储了所有商品的列表,而在某些场景下,我们只需要显示有库存的商品。这时就可以使用 getters 来过滤出有库存的商品列表。
代码示例
在 store 中定义 getters:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
products: [
{ id: 1, name: 'Product 1', stock: 10 },
{ id: 2, name: 'Product 2', stock: 0 },
{ id: 3, name: 'Product 3', stock: 5 }
]
},
getters: {
availableProducts(state) {
return state.products.filter(product => product.stock > 0);
}
}
});
export default store;
在组件中使用 getters:
<template>
<div>
<ul>
<li v-for="product in availableProducts" :key="product.id">{{ product.name }}</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
availableProducts() {
return this.$store.getters.availableProducts;
}
}
};
</script>
在这个例子中,availableProducts
是一个 getters,它从 state.products
中过滤出了库存大于 0 的商品。在组件中通过 this.$store.getters.availableProducts
来访问这个 getters。
Getters 接受其他 Getters 作为参数
Getters 不仅可以依赖 state,还可以依赖其他 getters。例如,我们有一个计算商品总价的 getters,并且只想计算有库存商品的总价:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
products: [
{ id: 1, name: 'Product 1', stock: 10, price: 100 },
{ id: 2, name: 'Product 2', stock: 0, price: 200 },
{ id: 3, name: 'Product 3', stock: 5, price: 150 }
]
},
getters: {
availableProducts(state) {
return state.products.filter(product => product.stock > 0);
},
totalPriceOfAvailableProducts(state, getters) {
return getters.availableProducts.reduce((total, product) => total + product.price * product.stock, 0);
}
}
});
export default store;
在组件中使用:
<template>
<div>
<p>Total price of available products: {{ totalPrice }}</p>
</div>
</template>
<script>
export default {
computed: {
totalPrice() {
return this.$store.getters.totalPriceOfAvailableProducts;
}
}
};
</script>
这里 totalPriceOfAvailableProducts
依赖于 availableProducts
getters。
Getters 在模块中的使用
在模块中定义 getters 时,需要注意命名空间。如果模块开启了命名空间,访问模块内的 getters 需要带上模块名。
// modules/product.js
const state = {
products: [
{ id: 1, name: 'Product 1', stock: 10, price: 100 },
{ id: 2, name: 'Product 2', stock: 0, price: 200 },
{ id: 3, name: 'Product 3', stock: 5, price: 150 }
]
};
const getters = {
availableProducts(state) {
return state.products.filter(product => product.stock > 0);
}
};
export default {
state,
getters
};
在主 store 中引入模块:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import product from './modules/product';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
product: {
namespaced: true,
...product
}
}
});
export default store;
在组件中访问模块内的 getters:
<template>
<div>
<ul>
<li v-for="product in availableProducts" :key="product.id">{{ product.name }}</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
availableProducts() {
return this.$store.getters['product/availableProducts'];
}
}
};
</script>
Mutations
什么是 Mutations
Mutations 是 Vuex 中唯一允许修改 state 的地方。它是一个同步函数,通过提交 mutation 来修改 state,这样可以保证 state 的变化是可追踪和可预测的。
Mutations 的使用场景
在电商应用中,当用户添加商品到购物车时,就需要通过 mutation 来修改购物车的商品列表(即 state 中的购物车数据)。
代码示例
在 store 中定义 mutation:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
}
});
export default store;
在组件中提交 mutation:
<template>
<div>
<p>The count is: {{ count }}</p>
<button @click="incrementCount">Increment</button>
</div>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count;
}
},
methods: {
incrementCount() {
this.$store.commit('increment');
}
}
};
</script>
在这个例子中,increment
是一个 mutation,通过 this.$store.commit('increment')
在组件中提交这个 mutation 来修改 state.count
。
Mutation 传递参数
Mutation 可以接受额外的参数,称为 payload(负载)。例如,在购物车应用中,添加商品到购物车时,需要传递商品的信息作为参数。
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
cart: []
},
mutations: {
addToCart(state, product) {
state.cart.push(product);
}
}
});
export default store;
在组件中提交 mutation 并传递参数:
<template>
<div>
<button @click="addToCart(product)">Add to Cart</button>
</div>
</template>
<script>
export default {
data() {
return {
product: { id: 1, name: 'Sample Product' }
};
},
methods: {
addToCart(product) {
this.$store.commit('addToCart', product);
}
}
};
</script>
这里 product
就是 payload,传递给 addToCart
mutation 来更新购物车。
多个 Mutation 调用
在实际应用中,可能需要在一个组件中调用多个 mutation。例如,在一个用户登录的场景中,可能需要更新用户信息并设置登录状态。
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
user: null,
isLoggedIn: false
},
mutations: {
setUser(state, userData) {
state.user = userData;
},
setLoggedIn(state, status) {
state.isLoggedIn = status;
}
}
});
export default store;
在组件中调用多个 mutation:
<template>
<div>
<button @click="login">Login</button>
</div>
</template>
<script>
export default {
methods: {
login() {
const userData = { name: 'John Doe', age: 30 };
this.$store.commit('setUser', userData);
this.$store.commit('setLoggedIn', true);
}
}
};
</script>
Mutation 在模块中的使用
在模块中定义 mutation 时,如果模块开启了命名空间,提交 mutation 需要带上模块名。
// modules/user.js
const state = {
user: null
};
const mutations = {
setUser(state, userData) {
state.user = userData;
}
};
export default {
state,
mutations
};
在主 store 中引入模块:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
user: {
namespaced: true,
...user
}
}
});
export default store;
在组件中提交模块内的 mutation:
<template>
<div>
<button @click="setUser">Set User</button>
</div>
</template>
<script>
export default {
methods: {
setUser() {
const userData = { name: 'Jane Smith', age: 25 };
this.$store.commit('user/setUser', userData);
}
}
};
</script>
Actions
什么是 Actions
Actions 类似于 Mutations,不同的是 Actions 可以包含异步操作,比如调用 API。Actions 提交的是 mutation,而不是直接修改 state。
Actions 的使用场景
在电商应用中,当需要从服务器获取商品列表时,就需要使用 actions 来处理异步操作,获取数据后再通过 mutation 更新 state。
代码示例
假设我们使用 axios 来进行 API 调用,首先安装 axios:
npm install axios
在 store 中定义 action:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
products: []
},
mutations: {
setProducts(state, products) {
state.products = products;
}
},
actions: {
async fetchProducts({ commit }) {
try {
const response = await axios.get('/api/products');
commit('setProducts', response.data);
} catch (error) {
console.error('Error fetching products:', error);
}
}
}
});
export default store;
在组件中调用 action:
<template>
<div>
<button @click="fetchProducts">Fetch Products</button>
<ul>
<li v-for="product in products" :key="product.id">{{ product.name }}</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
products() {
return this.$store.state.products;
}
},
methods: {
fetchProducts() {
this.$store.dispatch('fetchProducts');
}
}
};
</script>
在这个例子中,fetchProducts
是一个 action,它通过 axios.get
异步获取商品数据,然后通过 commit
提交 setProducts
mutation 来更新 state.products
。在组件中通过 this.$store.dispatch('fetchProducts')
来调用这个 action。
Action 传递参数
Action 也可以接受参数,与 mutation 类似。例如,我们可能需要根据分类来获取商品列表,这时可以传递分类参数给 action。
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
products: []
},
mutations: {
setProducts(state, products) {
state.products = products;
}
},
actions: {
async fetchProductsByCategory({ commit }, category) {
try {
const response = await axios.get(`/api/products?category=${category}`);
commit('setProducts', response.data);
} catch (error) {
console.error('Error fetching products:', error);
}
}
}
});
export default store;
在组件中调用 action 并传递参数:
<template>
<div>
<button @click="fetchProducts('electronics')">Fetch Electronics Products</button>
<ul>
<li v-for="product in products" :key="product.id">{{ product.name }}</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
products() {
return this.$store.state.products;
}
},
methods: {
fetchProducts(category) {
this.$store.dispatch('fetchProductsByCategory', category);
}
}
};
</script>
这里 category
就是传递给 fetchProductsByCategory
action 的参数。
组合 Action
在复杂应用中,可能需要多个 action 协同工作。例如,在用户注册场景中,可能需要先验证用户名是否可用,然后再进行注册。
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
user: null
},
mutations: {
setUser(state, userData) {
state.user = userData;
}
},
actions: {
async checkUsernameAvailability({ commit }, username) {
try {
const response = await axios.get(`/api/check-username?username=${username}`);
return response.data.available;
} catch (error) {
console.error('Error checking username availability:', error);
return false;
}
},
async registerUser({ commit, dispatch }, userData) {
const isAvailable = await dispatch('checkUsernameAvailability', userData.username);
if (isAvailable) {
try {
const response = await axios.post('/api/register', userData);
commit('setUser', response.data);
} catch (error) {
console.error('Error registering user:', error);
}
} else {
console.error('Username is not available');
}
}
}
});
export default store;
在组件中调用组合 action:
<template>
<div>
<button @click="register">Register</button>
</div>
</template>
<script>
export default {
data() {
return {
userData: { username: 'testuser', password: 'testpass' }
};
},
methods: {
register() {
this.$store.dispatch('registerUser', this.userData);
}
}
};
</script>
这里 registerUser
action 依赖于 checkUsernameAvailability
action,先检查用户名可用性,再进行注册操作。
Action 在模块中的使用
在模块中定义 action 时,如果模块开启了命名空间,调用 action 需要带上模块名。
// modules/user.js
import axios from 'axios';
const state = {
user: null
};
const mutations = {
setUser(state, userData) {
state.user = userData;
}
};
const actions = {
async login({ commit }, credentials) {
try {
const response = await axios.post('/api/login', credentials);
commit('setUser', response.data);
} catch (error) {
console.error('Error logging in:', error);
}
}
};
export default {
state,
mutations,
actions
};
在主 store 中引入模块:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
user: {
namespaced: true,
...user
}
}
});
export default store;
在组件中调用模块内的 action:
<template>
<div>
<button @click="login">Login</button>
</div>
</template>
<script>
export default {
data() {
return {
credentials: { username: 'testuser', password: 'testpass' }
};
},
methods: {
login() {
this.$store.dispatch('user/login', this.credentials);
}
}
};
</script>
通过上述对 Vuex 中 state、getters、mutations 和 actions 的详细解析以及丰富的代码示例,希望读者能够深入理解并熟练运用这些概念来管理 Vue 应用的状态,构建出更加健壮和可维护的前端应用。