本文由图雀社区成员 Holy 使用 Tuture 实战教程写做工具 写做而成,欢迎加入图雀社区,一块儿创做精彩的免费技术实战教程,予力编程行业发展。javascript
前面五篇教程咱们已经基本实现了迷你全栈电商应用的界面展现以及功能逻辑,相信你们在这个过程当中都收获颇丰,而且迈向了全栈工程师的第一步。可是咱们却并不知足于此,咱们还须要对咱们的项目代码进行优化,使得咱们的代码可读性更高,也更好维护。相信细心的大家已经感受到了项目中的store实例实在是过于臃肿,所以,本篇教程就是带你们一块儿学习如何抽出 Getters 、 Mutations 和Actions 逻辑实现store的“减重”以及如何干掉 mutation-types 硬编码。css
欢迎阅读《从零到部署:用 Vue 和 Express 实现迷你全栈电商应用》系列:html
若是你但愿直接从这一步开始,请运行如下命令:vue
git clone -b section-six https://github.com/tuture-dev/vue-online-shop-frontend.git
cd vue-online-shop-frontend
复制代码
本文所涉及的源代码都放在了 Github 上,若是您以为咱们写得还不错,但愿您能给❤️这篇文章点赞+Github仓库加星❤️哦~java
这一节咱们来学习如何抽出在store
实例中定义的复杂getters
和mutation
逻辑。ios
咱们发现以前咱们直接把全部的getter
属性和方法都定义在了store
实例中的getters
属性中,全部的mutation
属性也都定义在了store
实例中的mutations
属性中,这样显得store
实例特别的累赘,所以咱们能够经过对象展开运算符将这些复杂的逻辑抽取到对应的 Getters
和 Mutations
文件中。git
首先咱们作一点本土化,把以前的 src/pages/admin/Index.vue
中的英文导航改为中文版,方便查看;而且咱们增长了查看生产商导航。github
<template>
<div>
<div class="admin-new">
<div class="container">
<div class="col-lg-3 col-md-3 col-sm-12 col-xs-12">
<ul class="admin-menu">
<li>
<router-link to="/admin">查看商品</router-link>
</li>
<li>
<router-link to="/admin/new">添加商品</router-link>
</li>
<li>
<router-link to="/admin/manufacturers">查看生产商</router-link>
</li>
</ul>
</div>
<router-view></router-view>
</div>
</div>
</div>
</template>
复制代码
这里咱们将有关商品的导航栏修改成中文版,让用户可以秒懂;除此以外咱们又添加了有关制造商的导航,这里增长的是查看生产商导航,并添加了对应的导航跳转路径,该路径须要与对应路由参数一致。vuex
咱们建立的src/pages/admin/Manufacturers.vue
文件是本地制造商组件,用于展现制造商的信息。编程
<template>
<div>
<table class="table">
<thead>
<tr>
<th>制造商</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="manufacturer in manufacturers" :key="manufacturer._id">
<td>{{manufacturer.name}}</td>
<td class="modify"><router-link :to="'/admin/manufacturers/edit/' + manufacturer._id">修改</router-link></td>
<td class="remove"><a @click="removeManufacturer(manufacturer._id)" href="#">删除</a></td>
</tr>
</tbody>
</table>
</div>
</template>
<style> table { margin: 0 auto; } .modify { color: blue; } .remove a { color: red; } </style>
<script> export default { created() { if (this.manufacturers.length === 0) { this.$store.dispatch('allManufacturers'); } }, computed: { manufacturers() { return this.$store.getters.allManufacturers } }, methods: { removeManufacturer(manufacturerId) { // 使用 JavaScript BOM 的 confirm 方法来询问用户是否删除此制造商 const res = confirm('是否删除此制造商?'); // 若是用户赞成,那么就删除此制造商 if (res) { this.$store.dispatch('removeManufacturer', { manufacturerId, }) } } } } </script>
复制代码
这里首先定义了一个计算属性manufacturers
,经过this.$store.getters.allManufacturers
属性访问的形式调用对应的getter
属性allManufacturers
从本地获取manufacturers
,并返回给计算属性manufacturers
。
而后在该组件刚被建立时判断本地中是否存在manufacturers
,若是没有则经过this.$store.dispatch
分发到类型为allManufacturers
的action
中进行异步操做获取全部制造商,并将获取的制造商提交到对应的mutation
中,在mutation
中修改本地状态,将获取的全部制造商保存到本地。
最后利用v-for
在表格中遍历manufacturers
,每一个制造商的信息在一行展现,除了信息以外还有两个功能(修改和删除制造商),点击修改则会根据'/admin/manufacturers/edit/' + manufacturer._id
路由到指定页面;点击删除则会触发removeManufacturer
事件,首先询问用户是否赞成删除,若用户赞成则将选中制造商的id做为载荷分发到类型为removeManufacturer
的action
中,在action
中进行异步操做删除后端对应商品,并将对应商品id提交到对应的mutation
中,在mutation
中进行本地状态修改,删除本地对应的商品。
根据Manufacturers
组件的设计原则,咱们须要再次进入src/pages/admin/Products.vue
文件。按照Manufacturers
组件的UI展现以及数据处理,将Products
组件进行一下重构。
<template>
<div>
<table class="table">
<thead>
<tr>
<th>名称</th>
<th>价格</th>
<th>制造商</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product._id">
<td>{{product.name}}</td>
<td>{{product.price}}</td>
<td>{{product.manufacturer.name}}</td>
<td class="modify"><router-link :to="'/admin/edit/' + product._id">修改</router-link></td>
<td class="remove"><a @click="removeProduct(product._id)" href="#">删除</a></td>
</tr>
</tbody>
</table>
</div>
</template>
<style> table { margin: 0 auto; } .modify { color: blue; } .remove a { color: red; } </style>
<script> export default { created() { if (this.products.length === 0) { this.$store.dispatch('allProducts'); } }, computed: { products() { return this.$store.getters.allProducts } }, methods: { removeProduct(productId) { // 使用 JavaScript BOM 的 confirm 方法来询问用户是否删除此商品 const res = confirm('是否删除此商品?'); // 若是用户赞成,那么就删除此商品 if (res) { this.$store.dispatch('removeProduct', { productId, }) } } } } // ... 复制代码
这部分代码逻辑与src/pages/admin/Manufacturers.vue
文件中的代码逻辑类似,若是您理解了上面的代码逻辑,那么咱们相信您对这里的代码也能融会贯通,因此这里就再也不赘述了。
咱们已经建立了有关制造商的导航以及查看制造商组件,还需对其配置相应的路由参数才能实现跳转。所以再次进入src/router/index.js
文件,这里咱们导入了制造商组件并增长了制造商相关路由参数。
// ...
import New from '@/pages/admin/New';
import Products from '@/pages/admin/Products';
import Edit from '@/pages/admin/Edit';
import Manufacturers from '@/pages/admin/Manufacturers';
Vue.use(Router);
// ...
name: 'Edit',
component: Edit,
},
{
path: 'manufacturers',
name: 'Manufacturers',
component: Manufacturers,
},
]
},
{
// ...
复制代码
把项目跑起来,点击Admin
而后再点击查看生产商,咱们能够看到从后端获取的全部生产商:
咱们首先建立了src/store/getters.js
文件,用于存放各类不一样类型的getter
属性和方法。这里咱们导出了两个对象分别为productGetters
和manufacturerGetters
,前者包含了有关商品的getter
属性与方法,后者包含了有关制造商的getter
属性与方法。
export const productGetters = {
allProducts(state) {
return state.products
},
productById: (state, getters) => id => {
if (getters.allProducts.length > 0) {
return getters.allProducts.filter(product => product._id === id)[0]
} else {
return state.product;
}
}
}
export const manufacturerGetters = {
allManufacturers(state) {
return state.manufacturers;
}
}
复制代码
在productGetters
对象中定义的就是有关商品的getter
属性和方法,如allProducts
,productById
等等;在manufacturerGetters
对象中定义的就是有关制造商的getter
属性和方法,如allManufacturers
等等。
咱们能够采用属性调用和方法调用的方式调用这里的getter(根据咱们在getter对象中定义的是属性仍是方法)
一样的咱们建立了src/store/mutations.js
文件,用于存放从store
实例的mutations
属性中抽取出来的各类mutation
属性,这里咱们定义了三个对象分别为productMutations
,cartMutations
以及manufacturerMutations
。
export const productMutations = {
ALL_PRODUCTS(state) {
state.showLoader = true;
},
ALL_PRODUCTS_SUCCESS(state, payload) {
const { products } = payload;
state.showLoader = false;
state.products = products;
},
PRODUCT_BY_ID(state) {
state.showLoader = true;
},
PRODUCT_BY_ID_SUCCESS(state, payload) {
state.showLoader = false;
const { product } = payload;
state.product = product;
},
REMOVE_PRODUCT(state) {
state.showLoader = true;
},
REMOVE_PRODUCT_SUCCESS(state, payload) {
state.showLoader = false;
const { productId } = payload;
state.products = state.products.filter(product => product._id !== productId);
}
};
export const cartMutations = {
ADD_TO_CART(state, payload) {
const { product } = payload;
state.cart.push(product)
},
REMOVE_FROM_CART(state, payload) {
const { productId } = payload
state.cart = state.cart.filter(product => product._id !== productId)
},
}
export const manufacturerMutations = {
ALL_MANUFACTURERS(state) {
state.showLoader = true;
},
ALL_MANUFACTURERS_SUCCESS(state, payload) {
const { manufacturers } = payload;
state.showLoader = false;
state.manufacturers = manufacturers;
},
REMOVE_MANUFACTURER(state) {
state.showLoader = true;
},
REMOVE_MANUFACTURER_SUCCESS(state, payload) {
state.showLoader = false;
const { manufacturerId } = payload;
state.manufacturers = state.manufacturers.filter(manufacturer => manufacturer._id !== manufacturerId);
}
}
复制代码
在productMutations
对象中定义了有关商品响应Vue视图层以及avtion
中提交的事件,好比ALL_PRODUCTS
,ALL_PRODUCTS_SUCCESS
,PRODUCT_BY_ID
以及PRODUCT_BY_ID_SUCCESS
等等。
在cartMutations
对象中定义了有关购物车响应Vue视图层提交的事件,好比ADD_TO_CART
,REMOVE_FROM_CART
等等。
在manufacturerMutations
对象中定义了有关制造商响应Vue视图层以及avtion
中提交的事件,好比ALL_MANUFACTURERS
,ALL_MANUFACTURERS_SUCCESS
,REMOVE_MANUFACTURER
以及REMOVE_MANUFACTURER_SUCCESS
等等。
咱们将store
实例中的Getter
属性和Mutation
属性抽出以后要再进行导入。再回到src/store/index.js
文件,这里就是抽离getters
和mutations
逻辑以后的store
实例,看起来是否是轻盈了不少,也加强了代码的可读性。
// ...
import Vuex from 'vuex';
import axios from 'axios';
import { productGetters, manufacturerGetters } from './getters';
import { productMutations, cartMutations, manufacturerMutations } from './mutations';
const API_BASE = 'http://localhost:3000/api/v1';
Vue.use(Vuex);
// ...
manufacturers: [],
},
mutations: {
...productMutations,
...cartMutations,
...manufacturerMutations,
},
getters: {
...productGetters,
...manufacturerGetters,
},
actions: {
allProducts({ commit }) {
// ...
product: response.data,
});
})
},
removeProduct({ commit }, payload) {
commit('REMOVE_PRODUCT');
const { productId } = payload;
axios.delete(`${API_BASE}/products/${productId}`).then(() => {
// 返回 productId,用于删除本地对应的商品
commit('REMOVE_PRODUCT_SUCCESS', {
productId,
});
})
},
allManufacturers({ commit }) {
commit('ALL_MANUFACTURERS');
axios.get(`${API_BASE}/manufacturers`).then(response => {
commit('ALL_MANUFACTURERS_SUCCESS', {
manufacturers: response.data,
});
})
},
removeManufacturer({ commit }, payload) {
commit('REMOVE_MANUFACTURER');
const { manufacturerId } = payload;
axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`).then(() => {
// 返回 manufacturerId,用于删除本地对应的制造商
commit('REMOVE_MANUFACTURER_SUCCESS', {
manufacturerId,
});
})
},
}
});
复制代码
这里首先导入了getters
和mutations
文件中导出的全部对象,而后在store
实例的getters
和mutations
属性中经过对象展开运算符的方式将对应的属性和方法导入到store
实例中。
对象展开运算符是ES7草案中的新特性,将一个对象当中的对象的一部分取出来成为一个新对象赋值给展开运算符的参数,而后插入到另一个对象当中。例如:
let shortcuts = {
attr1: 3,
attr2: 4
}
let shortcuts2 = {}
shortcuts2 = {...shortcuts}
//上面的这种用法实际上至关因而:
shortcuts2 = {attr1: 3, attr2: 4}
复制代码
除此以外咱们又在actions
中添加了一些其余的action
属性,由于此时actions
还未被抽离,因此可能依然显得有些臃肿,不过在后面咱们立刻也会将它抽离出来。
这一节咱们学习了如何抽出Getters
和Mutations
逻辑,减轻store
实例中的负载:
getters
和mutations
JS文件,在两个JS文件中分别定义不一样类型的getters
和mutations
对象并导出,而后在getters
和mutations
对象中定义相应的一些属性和方法。store
的index
文件中导入这些getters
和mutations
对象,并在store
实例的getters
和mutations
属性中经过对象展开运算符混入这些对象。this.$store.getters.属性
和this.$store.mutations.属性
的方式调用.上一节咱们学习了如何抽出Getters
和Mutations
逻辑,这一节咱们以一样的方式抽出Actions
逻辑。
src/pages/admin/Edit.vue
是商品编辑组件,当触发'/admin/edit/' + product._id
就会路由到指定商品信息编辑页面,而后对商品信息进行修改。
以前咱们直接将展现商品信息的代码放在该组件中,可是咱们发现展现商品信息这部分功能在新建商品和编辑商品组件中都须要使用,所以咱们打算把这部分代码封装为一个展现商品信息的表单组件ProductForm
,这样的话咱们在新建商品和编辑商品组件中都能复用该组件。
除此以外咱们还在该组件中添加了数据处理功能。
<template>
<div>
<div class="title">
<h1>This is Admin/Edit</h1>
</div>
<product-form @save-product="updateProduct" :model="model" :manufacturers="manufacturers" :isEditing="true" ></product-form>
</div>
</template>
<script> import ProductForm from '@/components/products/ProductForm.vue'; export default { created() { const { name } = this.model; if (!name) { this.$store.dispatch('productById', { productId: this.$route.params['id'] }); } if (this.manufacturers.length === 0) { this.$store.dispatch('allManufacturers'); } }, computed: { manufacturers() { return this.$store.getters.allManufacturers; }, model() { const product = this.$store.getters.productById(this.$route.params['id']); // 这里返回 product 的拷贝,是为了在修改 product 的拷贝以后,在保存以前不修改本地 Vuex stire 的 product 属性 return { ...product, manufacturer: { ...product.manufacturer } }; } }, methods: { updateProduct(product) { this.$store.dispatch('updateProduct', { product, }) } }, components: { 'product-form': ProductForm } } </script>
复制代码
咱们先来看该组件的script
部分,首先定义了两个计算属性model
和manufacturers
返回本地商品和制造商。经过方法访问的方式调用指定的getter
属性productById
,参数为当前处于激活状态的路由对象的id,这里返回product
的拷贝,是为了在修改 product
的拷贝以后,在保存以前不修改本地 Vuex store 的product
属性。计算属性manufacturers
经过相同的方式获取本地数据。
当该组件刚被建立时判断计算属性model
中是否有值,若是没有则表示本地中没有该商品,将包含该商品id的对象做为载荷分发到类型为productById
的action
中,在action
中进行异步操做从后端获取对应商品,并提交到对应类型的mutation
中,在mutation
中将获取到的商品保存到本地。除此以外判断计算属性manufacturers
中是否有值,若是没有则经过相同的方式从后端获取并保存到本地。
在template
中使用了子组件ProductForm
用表单的形式来展现商品信息,当用户提交表单则会向父组件发射save-product
事件,父组件监听到以后触发updateProduct
事件,并将传入的商品参数做为载荷分发到类型为updateProduct
的action
中,通知后端进行同步更新数据并提交到对应的mutation
中进行本地数据更新。
src/pages/admin/New.vue
是添加商品组件,与Edit
组件的代码逻辑类似,只是一个是修改商品信息,一个是添加商品信息。
咱们将该组件中原先写死的数据改为了从后端动态获取, 并将获取的数据传递给子组件ProductForm。
<template>
<product-form @save-product="addProduct" :model="model" :manufacturers="manufacturers" >
</product-form>
</template>
<script> import ProductForm from '@/components/products/ProductForm.vue'; export default { created() { if (this.manufacturers.length === 0) { this.$store.dispatch('allManufacturers'); } }, computed: { manufacturers() { return this.$store.getters.allManufacturers; }, model() { return {}; } }, methods: { addProduct(model) { this.$store.dispatch('addProduct', { product: model, }) }, }, components: { 'product-form': ProductForm } } </script>
复制代码
该组件代码逻辑和Edit.vue
组件类似,只是在这里咱们定义的计算属性model
返回一个空对象做为默认值,由于咱们是添加商品,本地中还不存在该商品。
像以前同样咱们建立了src/store/actions.js
文件,用于存储从store
实例的actions
属性中抽取出来的不一样类型的action
属性。这里咱们定义了两个Actions
对象:productActions
和manufacturerActions
,分别表示有关商品和制造商对视图层分发的事件做出的响应,并导出了这两个对象。
import axios from 'axios';
const API_BASE = 'http://localhost:3000/api/v1';
export const productActions = {
allProducts({ commit }) {
commit('ALL_PRODUCTS')
axios.get(`${API_BASE}/products`).then(response => {
commit('ALL_PRODUCTS_SUCCESS', {
products: response.data,
});
})
},
productById({ commit }, payload) {
commit('PRODUCT_BY_ID');
const { productId } = payload;
axios.get(`${API_BASE}/products/${productId}`).then(response => {
commit('PRODUCT_BY_ID_SUCCESS', {
product: response.data,
});
})
},
removeProduct({ commit }, payload) {
commit('REMOVE_PRODUCT');
const { productId } = payload;
axios.delete(`${API_BASE}/products/${productId}`).then(() => {
// 返回 productId,用于删除本地对应的商品
commit('REMOVE_PRODUCT_SUCCESS', {
productId,
});
})
},
updateProduct({ commit }, payload) {
commit('UPDATE_PRODUCT');
const { product } = payload;
axios.put(`${API_BASE}/products/${product._id}`, product).then(() => {
commit('UPDATE_PRODUCT_SUCCESS', {
product,
});
})
},
addProduct({ commit }, payload) {
commit('ADD_PRODUCT');
const { product } = payload;
axios.post(`${API_BASE}/products`, product).then(response => {
commit('ADD_PRODUCT_SUCCESS', {
product: response.data,
})
})
}
};
export const manufacturerActions = {
allManufacturers({ commit }) {
commit('ALL_MANUFACTURERS');
axios.get(`${API_BASE}/manufacturers`).then(response => {
commit('ALL_MANUFACTURERS_SUCCESS', {
manufacturers: response.data,
});
})
},
removeManufacturer({ commit }, payload) {
commit('REMOVE_MANUFACTURER');
const { manufacturerId } = payload;
axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`).then(() => {
// 返回 manufacturerId,用于删除本地对应的制造商
commit('REMOVE_MANUFACTURER_SUCCESS', {
manufacturerId,
});
})
},
}
复制代码
在该文件中咱们首先导入axios
依赖,以及定义了 API_BASE
后端接口根路由;
而后咱们定义并导出了两个对象:
productActions
对象中定义了一些有关商品在视图层分发对应的事件时,action
做出的响应,好比allProducts
,productById
,removeProduct
以及updateProduct
等等。manufacturerActions
对象中定义了一些有关制造商在视图层分发对应的事件时,action
做出的响应,好比allManufacturers
,removeManufacturer
等等。咱们再次来到src/store/index.js
文件中,添加有关抽取Actions
逻辑以后的信息。
import Vue from 'vue';
import Vuex from 'vuex';
import { productGetters, manufacturerGetters } from './getters';
import { productMutations, cartMutations, manufacturerMutations } from './mutations';
import { productActions, manufacturerActions } from './actions';
Vue.use(Vuex);
// ...
actions: {
...productActions,
...manufacturerActions,
}
});
复制代码
这里咱们首先导入了actions.js
文件中导出的一些Action对象,并经过对象展开运算符在store
实例的actions
属性中混入了不一样类型的action
属性,实现了Actions逻辑的抽取。
咱们在src/store/mutations.js
文件中又添加了一些mutation
属性,用于用户进行不一样的操做进行本地数据的同步。
// ...
const { productId } = payload;
state.products = state.products.filter(product => product._id !== productId);
},
UPDATE_PRODUCT(state) {
state.showLoader = true;
},
UPDATE_PRODUCT_SUCCESS(state, payload) {
state.showLoader = false;
const { product: newProduct } = payload;
state.product = newProduct;
state.products = state.products.map(product => {
if (product._id === newProduct._id) {
return newProduct;
}
return product;
})
},
ADD_PRODUCT(state) {
state.showLoader = true;
},
ADD_PRODUCT_SUCCESS(state, payload) {
state.showLoader = false;
const { product } = payload;
state.products = state.products.concat(product);
}
};
// ...
复制代码
上述添加的都是有关商品的mutation
属性:UPDATE_PRODUCT
,UPDATE_PRODUCT_SUCCESS
,ADD_PRODUCT
以及ADD_PRODUCT_SUCCESS
分别表示更新商品信息,更新商品信息成功,添加商品以及添加商品成功。
这一节咱们学习了如何抽出Actions
逻辑,减轻store
实例中的负载:
actions
JS文件,在文件中定义不一样类型的Actions
对象并导出,而后在Actions
对象中定义相应的一些属性。store
的index
文件中导入这些Actions
对象,并在store
实例的actions
属性中经过对象展开运算符混入这些对象。this.$store.actions.属性
的方式调用。这一节咱们主要是进一步完善咱们的项目功能以及去掉一些硬编码。
和商品信息展现功能同样,咱们也须要将制造商信息展现部分封装到一个单独的组件ManufacturerForm
中,以便咱们在新建制造商和编辑制造商组件中都能复用该组件。
所以咱们建立了src/components/ManufacturerForm.vue
文件,用于展现制造商信息的表单组件。
<template>
<form @submit.prevent="saveManufacturer">
<div class="form-group">
<label>Name</label>
<input type="text" placeholder="Name" v-model="model.name" name="name" class="form-control" />
</div>
<div class="form-group new-button">
<button class="button">
<i class="fa fa-pencil"></i>
<!-- Conditional rendering for input text -->
<span v-if="isEditing">Update Manufacturer</span>
<span v-else>Add Manufacturer</span>
</button>
</div>
</form>
</template>
<script> export default { props: ['model', 'isEditing'], methods: { saveManufacturer() { this.$emit('save-manufacturer', this.model) } } } </script>
复制代码
该组件经过父子组件传值从父组件获取到了model
和isEditing
的值,并将model
对象的信息展现在表单中。
表单信息中还经过v-if
来判断isEditing
的值是true
仍是false
,若是是true
则建立Update Manufacturer
,反之建立Add Manufacturer
。
当用户提交表单时触发saveManufacturer
事件,此时会向父组件发送类型为save-manufacturer
的事件通知其保存这次的修改操做。
在建立编辑制造商组件以前,咱们须要在getters文件中添加对应的getter属性。
咱们在src/store/getters.js
文件的manufacturerGetters
对象中又添加了一个manufacturerById
方法,用于获取本地中指定的制造商。
// ...
export const manufacturerGetters = {
allManufacturers(state) {
return state.manufacturers;
},
manufacturerById: (state, getters) => id => {
if (getters.allManufacturers.length > 0) {
return getters.allManufacturers.filter(manufacturer => manufacturer._id === id)[0]
} else {
return state.manufacturer;
}
}
}
复制代码
manufacturerById
方法中的id参数是Vue视图层经过方法调用时传入的id,经过这个id判断本地中是否存在该制造商,若是存在则返回该制造商,若是不存在则返回一个空对象。
在建立了展现制造商信息的表单组件ManufacturerForm
以及添加了用于获取本地指定制造商数据的getter属性以后,紧接着咱们又建立了src/pages/admin/EditManufacturers.vue
文件,用于修改制造商信息。
<template>
<manufacturer-form @save-manufacturer="addManufacturer" :model="model" :isEditing="true" >
</manufacturer-form>
</template>
<script> import ManufacturerForm from '@/components/ManufacturerForm.vue'; export default { created() { this.$store.dispatch('manufacturerById', { manufacturerId: this.$route.params['id'] }); }, computed: { model() { const manufacturer = this.$store.getters.manufacturerById(this.$route.params['id']); // 这里返回 product 的拷贝,是为了在修改 product 的拷贝以后,在保存以前不修改本地 Vuex stire 的 product 属性 return { ...manufacturer }; } }, methods: { addManufacturer(model) { this.$store.dispatch('updateManufacturer', { manufacturer: model, }) }, }, components: { 'manufacturer-form': ManufacturerForm } } </script>
复制代码
该组件刚被建立时将当前处于激活状态的路由对象的id参数做为载荷分发到类型为manufacturerById
的action
中,在action
中进行异步操做从服务器获取对应制造商,而后将该制造商提交到对应mutation
中进行本地状态修改,将获取到的制造商保存到本地。
咱们定义了计算属性model
返回manufacturer
的拷贝,是为了在修改manufacturer
的拷贝以后,在保存以前不修改本地 store
中的manufacturer
属性。这里以方法访问的形式从getters
中经过当前激活的路由对象中的id参数获取本地状态中的对应制造商做为manufacturer
的拷贝,并返回给计算属性model
,而后传给子组件ManufacturerForm
。
该组件在addManufacturer
事件中将子组件传入的新制造商对象做为载荷分发到类型为updateManufacturer
的action
中,在action
中进行异步操做修改后端对应的商品信息,而后将新对象提交到对应的mutation
中进行本地状态修改,修改本地状态中的manufacturer
对象。
一样的咱们继续建立了src/pages/admin/NewManufacturers.vue
文件,用于添加制造商信息。该组件和添加商品信息组件代码逻辑相似。
<template>
<manufacturer-form @save-manufacturer="addManufacturer" :model="model" >
</manufacturer-form>
</template>
<script> import ManufacturerForm from '@/components/ManufacturerForm.vue'; export default { computed: { model() { return {}; } }, methods: { addManufacturer(model) { this.$store.dispatch('addManufacturer', { manufacturer: model, }) }, }, components: { 'manufacturer-form': ManufacturerForm } } </script>
复制代码
该组件逻辑代码与New.vue
组件相似,一个是添加商品组件,一个是添加制造商组件,您能够对比着来看。
以前咱们在该入口文件中增长了查看生产商导航,这里咱们又增长了添加生产商导航。
// ...
<li>
<router-link to="/admin/manufacturers">查看生产商</router-link>
</li>
<li>
<router-link to="/admin/manufacturers/new">添加生产商</router-link>
</li>
</ul>
</div>
<router-view></router-view>
// ...
复制代码
咱们已经建立了添加和修改制造商组件以及添加了对应的入口导航,接着咱们须要在该文件中对其进行路由参数配置。
再次进入src/router/index.js
文件,咱们导入了添加制造商和修改制造商的组件并配置了相关路由参数。
// ...
import Products from '@/pages/admin/Products';
import Edit from '@/pages/admin/Edit';
import Manufacturers from '@/pages/admin/Manufacturers';
import NewManufacturers from '@/pages/admin/NewManufacturers';
import EditManufacturers from '@/pages/admin/EditManufacturers';
Vue.use(Router);
// ...
name: 'Manufacturers',
component: Manufacturers,
},
{
path: 'manufacturers/new',
name: 'NewManufacturers',
component: NewManufacturers,
},
{
path: 'manufacturers/edit/:id',
name: 'EditManufacturers',
component: EditManufacturers,
},
]
},
{
// ...
复制代码
这里添加制造商的路由配置就是静态路由的配置方式;修改制造商的路由配置采用了动态传参的方式,这里使用的是$route.params
对象的Post
方式传参。
配置好添加制造商和修改制造商的路由参数以后,咱们又能够进行验收啦,运行项目,点击Admin而后再点击添加制造商,咱们能够看到添加制造商的表单:
这一节咱们将对咱们的项目代码进行优化,干掉一些硬编码。
咱们都知道在actions
文件和mutations
文件中有一部分的事件类型是须要保持一致的,好比在咱们在视图层分发一个添加商品事件ADD_PRODUCT
,在actions
文件中就须要有对应事件类型的action
接收,而后向后端发起请求并将请求结果提交到对应类型的mutation
中,这就要求了这几个文件中的对应事件类型都要保持一致。但是咱们在开发过程当中不免会出错,好比漏掉一个字母就会致使两个文件中的对应事件没法接收,尴尬的是控制台也没有报错,这就形成了咱们很难查错。
所以,咱们采用了字符串常量的形式定义actions
文件和mutations
文件中的事件类型,只要咱们写错一个单词都会致使字符串常量不一致,关键的是这个时候会报错,利于咱们查错。
进而咱们建立了src/store/mutation-types.js
文件,用于定义一些字符串常量来表示各类事件类型,并导出这些字符串常量。
export const ALL_PRODUCTS = 'ALL_PRODUCTS';
export const ALL_PRODUCTS_SUCCESS = 'ALL_PRODUCTS_SUCCESS';
export const PRODUCT_BY_ID = 'PRODUCT_BY_ID';
export const PRODUCT_BY_ID_SUCCESS = 'PRODUCT_BY_ID_SUCCESS';
export const ADD_PRODUCT = 'ADD_PRODUCT';
export const ADD_PRODUCT_SUCCESS = 'ADD_PRODUCT_SUCCESS';
export const UPDATE_PRODUCT = 'UPDATE_PRODUCT';
export const UPDATE_PRODUCT_SUCCESS = 'UPDATE_PRODUCT_SUCCESS';
export const REMOVE_PRODUCT = 'REMOVE_PRODUCT';
export const REMOVE_PRODUCT_SUCCESS = 'REMOVE_PRODUCT_SUCCESS';
export const ADD_TO_CART = 'ADD_TO_CART';
export const REMOVE_FROM_CART = 'REMOVE_FROM_CART';
export const ALL_MANUFACTURERS = 'ALL_MANUFACTURER';
export const ALL_MANUFACTURERS_SUCCESS = 'ALL_MANUFACTURER_S';
export const MANUFACTURER_BY_ID = 'MANUFACTURER_BY_ID';
export const MANUFACTURER_BY_ID_SUCCESS = 'MANUFACTURER_BY_ID_SUCCESS';
export const ADD_MANUFACTURER = 'ADD_MANUFACTURER';
export const ADD_MANUFACTURER_SUCCESS = 'ADD_MANUFACTURER_SUCCESS';
export const UPDATE_MANUFACTURER = 'UPDATE_MANUFACTURER';
export const UPDATE_MANUFACTURER_SUCCESS = 'UPDATE_MANUFACTURER_SUCCESS';
export const REMOVE_MANUFACTURER = 'REMOVE_MANUFACTURER';
export const REMOVE_MANUFACTURER_SUCCESS = 'REMOVE_MANUFACTURER_SUCCESS';
复制代码
咱们再次来到src/store/actions.js
文件中,将全部的事件类型用字符串常量表示。
import axios from 'axios';
import {
ADD_PRODUCT,
ADD_PRODUCT_SUCCESS,
PRODUCT_BY_ID,
PRODUCT_BY_ID_SUCCESS,
UPDATE_PRODUCT,
UPDATE_PRODUCT_SUCCESS,
REMOVE_PRODUCT,
REMOVE_PRODUCT_SUCCESS,
ALL_PRODUCTS,
ALL_PRODUCTS_SUCCESS,
ALL_MANUFACTURERS,
ALL_MANUFACTURERS_SUCCESS,
MANUFACTURER_BY_ID,
MANUFACTURER_BY_ID_SUCCESS,
ADD_MANUFACTURER,
ADD_MANUFACTURER_SUCCESS,
UPDATE_MANUFACTURER,
UPDATE_MANUFACTURER_SUCCESS,
REMOVE_MANUFACTURER,
REMOVE_MANUFACTURER_SUCCESS,
} from './mutation-types';
const API_BASE = 'http://localhost:3000/api/v1';
export const productActions = {
allProducts({ commit }) {
commit(ALL_PRODUCTS)
axios.get(`${API_BASE}/products`).then(response => {
commit(ALL_PRODUCTS_SUCCESS, {
products: response.data,
});
})
},
productById({ commit }, payload) {
commit(PRODUCT_BY_ID);
const { productId } = payload;
axios.get(`${API_BASE}/products/${productId}`).then(response => {
commit(PRODUCT_BY_ID_SUCCESS, {
product: response.data,
});
})
},
removeProduct({ commit }, payload) {
commit(REMOVE_PRODUCT);
const { productId } = payload;
axios.delete(`${API_BASE}/products/${productId}`).then(() => {
// 返回 productId,用于删除本地对应的商品
commit(REMOVE_PRODUCT_SUCCESS, {
productId,
});
})
},
updateProduct({ commit }, payload) {
commit(UPDATE_PRODUCT);
const { product } = payload;
axios.put(`${API_BASE}/products/${product._id}`, product).then(() => {
commit(UPDATE_PRODUCT_SUCCESS, {
product,
});
})
},
addProduct({ commit }, payload) {
commit(ADD_PRODUCT);
const { product } = payload;
axios.post(`${API_BASE}/products`, product).then(response => {
commit(ADD_PRODUCT_SUCCESS, {
product: response.data,
})
})
// ...
export const manufacturerActions = {
allManufacturers({ commit }) {
commit(ALL_MANUFACTURERS);
axios.get(`${API_BASE}/manufacturers`).then(response => {
commit(ALL_MANUFACTURERS_SUCCESS, {
manufacturers: response.data,
});
})
},
manufacturerById({ commit }, payload) {
commit(MANUFACTURER_BY_ID);
const { manufacturerId } = payload;
axios.get(`${API_BASE}/manufacturers/${manufacturerId}`).then(response => {
commit(MANUFACTURER_BY_ID_SUCCESS, {
manufacturer: response.data,
});
})
},
removeManufacturer({ commit }, payload) {
commit(REMOVE_MANUFACTURER);
const { manufacturerId } = payload;
axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`).then(() => {
// 返回 manufacturerId,用于删除本地对应的制造商
commit(REMOVE_MANUFACTURER_SUCCESS, {
manufacturerId,
});
})
},
updateManufacturer({ commit }, payload) {
commit(UPDATE_MANUFACTURER);
const { manufacturer } = payload;
axios.put(`${API_BASE}/manufacturers/${manufacturer._id}`, manufacturer).then(() => {
commit(UPDATE_MANUFACTURER_SUCCESS, {
manufacturer,
});
})
},
addManufacturer({ commit }, payload) {
commit(ADD_MANUFACTURER);
const { manufacturer } = payload;
axios.post(`${API_BASE}/manufacturers`, manufacturer).then(response => {
commit(ADD_MANUFACTURER_SUCCESS, {
manufacturer: response.data,
})
})
}
}
复制代码
这里咱们首先导入了mutation-types
文件中定义的一些字符串常量,替换掉了对应的事件类型。
同actions文件同样,咱们再次进入src/store/mutations.js
文件,将文件中的各类事件类型用字符串常量替代。
import {
ADD_PRODUCT,
ADD_PRODUCT_SUCCESS,
PRODUCT_BY_ID,
PRODUCT_BY_ID_SUCCESS,
UPDATE_PRODUCT,
UPDATE_PRODUCT_SUCCESS,
REMOVE_PRODUCT,
REMOVE_PRODUCT_SUCCESS,
ADD_TO_CART,
REMOVE_FROM_CART,
ALL_PRODUCTS,
ALL_PRODUCTS_SUCCESS,
ALL_MANUFACTURERS,
ALL_MANUFACTURERS_SUCCESS,
MANUFACTURER_BY_ID,
MANUFACTURER_BY_ID_SUCCESS,
ADD_MANUFACTURER,
ADD_MANUFACTURER_SUCCESS,
UPDATE_MANUFACTURER,
UPDATE_MANUFACTURER_SUCCESS,
REMOVE_MANUFACTURER,
REMOVE_MANUFACTURER_SUCCESS,
} from './mutation-types';
export const productMutations = {
[ALL_PRODUCTS](state) {
state.showLoader = true;
},
[ALL_PRODUCTS_SUCCESS](state, payload) {
const { products } = payload;
state.showLoader = false;
state.products = products;
},
[PRODUCT_BY_ID](state) {
state.showLoader = true;
},
[PRODUCT_BY_ID_SUCCESS](state, payload) {
state.showLoader = false;
const { product } = payload;
state.product = product;
},
[REMOVE_PRODUCT](state) {
state.showLoader = true;
},
[REMOVE_PRODUCT_SUCCESS](state, payload) {
state.showLoader = false;
const { productId } = payload;
state.products = state.products.filter(product => product._id !== productId);
},
[UPDATE_PRODUCT](state) {
state.showLoader = true;
},
[UPDATE_PRODUCT_SUCCESS](state, payload) {
state.showLoader = false;
const { product: newProduct } = payload;
// ...
return product;
})
},
[ADD_PRODUCT](state) {
state.showLoader = true;
},
[ADD_PRODUCT_SUCCESS](state, payload) {
state.showLoader = false;
const { product } = payload;
// ...
};
export const cartMutations = {
[ADD_TO_CART](state, payload) {
const { product } = payload;
state.cart.push(product)
},
[REMOVE_FROM_CART](state, payload) {
const { productId } = payload
state.cart = state.cart.filter(product => product._id !== productId)
},
};
export const manufacturerMutations = {
[ALL_MANUFACTURERS](state) {
state.showLoader = true;
},
[ALL_MANUFACTURERS_SUCCESS](state, payload) {
const { manufacturers } = payload;
state.showLoader = false;
state.manufacturers = manufacturers;
},
[MANUFACTURER_BY_ID](state) {
state.showLoader = true;
},
[MANUFACTURER_BY_ID_SUCCESS](state, payload) {
state.showLoader = false;
const { manufacturer } = payload;
state.manufacturer = manufacturer;
},
[REMOVE_MANUFACTURER](state) {
state.showLoader = true;
},
[REMOVE_MANUFACTURER_SUCCESS](state, payload) {
state.showLoader = false;
const { manufacturerId } = payload;
state.manufacturers = state.manufacturers.filter(manufacturer => manufacturer._id !== manufacturerId);
},
[UPDATE_MANUFACTURER](state) {
state.showLoader = true;
},
[UPDATE_MANUFACTURER_SUCCESS](state, payload) {
state.showLoader = false;
const { manufacturer: newManufacturer } = payload;
state.manufacturers = state.manufacturers.map(manufacturer => {
if (manufacturer._id === newManufacturer._id) {
return newManufacturer;
}
return manufacturer;
})
},
[ADD_MANUFACTURER](state) {
state.showLoader = true;
},
[ADD_MANUFACTURER_SUCCESS](state, payload) {
state.showLoader = false;
const { manufacturer } = payload;
state.manufacturers = state.manufacturers.concat(manufacturer);
}
}
复制代码
这里咱们首先导入了mutation-types
文件中定义的一些字符串常量,替换掉了对应的事件类型。
这一节咱们主要作了如下工做:
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。
本文所涉及的源代码都放在了 Github 上,若是您以为咱们写得还不错,但愿您能给❤️这篇文章点赞+Github仓库加星❤️哦