最近,在和朋友们协做的时候,暴露了许多问题,这其中最让人头疼的应该是,代码组织问题。javascript
代码组织能力会随着知识面的拓展逐渐养成,可是在这个过程当中,你是否看过优秀的、让人眼前一亮的代码,并从中得到感悟,很大程度上影响着最终代码组织能力的强弱。这并不仅仅看你的阅历,和作过项目的多少,而是有意识地,主动地经过阅读优秀的源码,从别人那里学来的一种软实力。html
但是我在网上并无找到不少相似的文章,因而决定本身写一篇,基于最近在作的竞赛管理系统项目,梳理一下Vuex在项目中运用,总结出一些通用的东西。我不敢说本身的方式是最棒的,但相信能对你们有所帮助。前端
转载请附上原文连接,支持原创vue
名称: 竞赛管理系统java
功能简介:用户信息增删改查、赛事信息增删改查、参赛记录增删改查、附件上传、成绩审核等。ios
技术栈(前端):Vue全家桶 + ant-design-vuegit
项目地址:GitHubgithub
Vuex 并不限制你的代码结构。可是,它规定了一些须要遵照的规则:vuex
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的惟一方法,而且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵照以上规则,如何组织代码随你便。若是你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。axios
—— 摘自官方文档
所谓应用层级的状态,在本项目中即为:用户信息,赛事信息,参赛记录。
将状态做为应用级的状态的标准,能够考虑该状态是否为全部组件或多个组件共用,或者你已经为了让这个状态在组件间通讯耗费了巨大的精力。若是是这样,就大胆地应用吧。
下面从Store提及。
做为管理系统,少不了增删改查。以管理员为例,管理员的任务是对用户信息(users)、赛事信息(races)、参赛记录(records)进行增删改查。使用Vuex咱们首先须要建立一个store目录。
── store
├── index.js # 咱们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
├── races # races 模块
│ ├── index.js # 导出module
│ ├── mutations.js # module 级别的mutations
│ └── actions.js # module 级别的actions
├── users
│ ├── index.js
│ ├── mutations.js
│ └── actions.js
└── records
├── index.js
├── mutations.js
└── actions.js
复制代码
因为每一种数据都涉及增删改查操做,我选择使用Vuex中的Module来组织他们。每一个文件夹导出一个module,最终汇总到外部的index中,进而导出store对象,注册Vuex。
每个模块都有一个action和一个mutation,action用来发送请求,mutation根据请求结果修改state(因为state结构简单,我直接写在了模块内部),最终将变化体现到视图上。
下面以赛事信息数据(races)示例:
如下三段代码对应races目录下:races/index.js,races/actions.js,races/mutations.js
/** * index.js */
import actions from './actions'
import mutations from './mutations'
// 这里处处races模块,并在store/index.js中注册为module
export default {
namespaced: true, // 设置命名空间只是为了使模块更加独立,具体能够参考官方文档
state: {
races: []
},
mutations,
actions
}
复制代码
/** * actions.js */
import { SET_RACE_LIST, ADD_RACE, UPDATE_RACE, DELETE_RACE } from '../mutation-types'
import { getRaceList, addRace, updateRace, deleteRace } from '../../api'
import { message } from 'ant-design-vue'
export default {
[SET_RACE_LIST] ({ commit }, params) {
return new Promise((resolve, reject) => {
getRaceList(params).then(({ data: races }) => {
resolve(races)
commit(SET_RACE_LIST, races)
}).catch(e => {
reject(e)
message.error('系统错误,请重试')
})
})
},
[ADD_RACE] ({ commit }, race) {
const stopLoading = message.loading('请稍后')
return new Promise((resolve, reject) => {
addRace(race).then(({ data }) => {
resolve(data)
commit(ADD_RACE, data)
message.success('添加成功')
}).catch(e => {
reject(e)
message.error('系统错误,请重试')
}).finally(() => {
stopLoading()
})
})
},
[UPDATE_RACE] ({ commit }, race) {
...
},
[DELETE_RACE] ({ commit }, _id) {
...
}
}
复制代码
/** * mutations.js */
import { SET_RACE_LIST, ADD_RACE, UPDATE_RACE, DELETE_RACE } from '../mutation-types'
export default {
[SET_RACE_LIST] (state, races) {
state.races = races
},
[ADD_RACE] (state, race) {
state.races.push(race)
},
[UPDATE_RACE] (state, race) {
state.races = state.races.map(item => {
return item._id === race._id ? race : item
})
},
[DELETE_RACE] (state, _id) {
state.races = state.races.filter(item => item._id !== _id)
}
}
复制代码
看了上面这些代码,可能你会有如下疑问
/** * mutation-types.js —— 主要是定义一些常量 */
export const LOGIN = 'LOGIN'
export const LOGOUT = 'LOGOUT'
export const REFRESH_TOKEN = 'REFRESH_TOKEN'
// races 模块
export const SET_RACE_LIST = 'SET_RACE_LIST'
export const ADD_RACE = 'ADD_RACE'
export const UPDATE_RACE = 'UPDATE_RACE'
export const DELETE_RACE = 'DELETE_RACE'
...
...
复制代码
mutation-types定义的其实就是action和mutation的方法名,在这里定义,再引过去,使用【中括号语法】定义方法,能够保证同一类操做的名字不会写错,减小所以产生的bug,同时还能享受编译器的语法提示,很香的。
实际上这些常量的值具体是什么并不重要,只要不重复就行,通常会和变量名相同。还可使用ES6中的Symbol来实现。
他们的目的主要有两点:
可能你要问了,变量名和变量值是同样的,为何不直接使用字符串呢?举例以下:
import { ADD_RECORD } from 'mutation-types'
store.dispatch(ADD_RECORD) // 若是写错,编译器会报错,直接发现错误,当场改正
store.dispatch('ADD_RECORD') // 编译器不会报错,若是写错,运行时才能发现
复制代码
其实若是你直接使用字符串,即使发现了错误,你也不必定知道是在哪里写错的,由于这样会使得代码中处处都是字符串,谁能保证都写对了呢?并且官方也是推荐使用这种方式来维护日趋庞大的项目得,有图有真相:
什么?你只是个小demo?好的没问题,下一个👌。
大型项目中,为了便于后期的项目维护,屡次出现的常量必定要提取出来,单独定义。小demo请自便。
Action 相似于 mutation,不一样在于:
这是官方给出的定义。在官方的示例文档中,咱们能够看到不少种使用方式。其中组合使用Action的方式值得你们留意,Action返回一个Promise对象就能够自由地和业务逻辑进行组合。如此一来,咱们在组件中就能够自定义不少逻辑,如自定义loading状态:
import { GET_RACE_LIST } from 'mutation-types'
export default {
mounted () {
this.loading = true
this.$store.dispatch(GET_RACE_LIST).finally(() => {
this.loading = false
})
}
}
复制代码
在上面races/action.js
的代码中,引入了ant-design-vue
中的message api
。根据请求结果不一样作出不一样的提示,成功反馈,错误处理等等。这样一来,组件中只须要调用store.dispatch()
发出请求,并基于请求结果作一些和组件有关的事情便可,跟数据有关的事情由action配合mutation自动处理好。组件中只要关注组件自己的逻辑就能够了。
在races/actions.js
中有这样一行代码
import { getRaceList, addRace, updateRace, deleteRace } from '../../api'
复制代码
API是单独封装的,理由很简单,一是为了复用,二是为了解决相似mutation-types
的常量引用问题。试想若是每一次发请求都要将请求地址完整的写上,那么请求地址若是变化了,你必定会疯掉的,甚至逼到你使用全局搜索来进行替换,这无疑是低效的。
这是个人API目录结构
api
├── index.js # 导出api
├── handle404.js # 配合axios.js处理响应拦截
└── axios.js # 导出axios对象,作一些全局配置,如请求拦截,响应拦截,baseUrl等
复制代码
大体是这样,具体因业务不一样而异,下面看看index.js
的内容:
/** * index.js 定义接口 */
...
...
export const getRaceList = params => axios.get('/race/list', { params })
export const addRace = data => axios.post('/race/add', data)
export const updateRace = data => axios.put('/race/update', data)
export const deleteRace = _id => {
return axios.delete('/race/delete', { data: { _id } })
}
...
...
复制代码
关于axios.js
和handle404.js
的内容,涉及全局拦截和token无状态刷新,感兴趣的朋友能够看个人另外一篇文章 —— 登录验证明践 —— Token & Refresh Token。
曾几什么时候,被问起项目经验时,心底里都有一个疑问:什么是项目?
我想起了大一时的本身,那时候我以为项目就是特别高大上的东西,由不少文件组成,彼此之间存在着复杂的关系,必定是不少人一块儿协做才能搞得出来的。可如今我明白,所谓项目,无非是一个功能集合。分离出的文件最终都会合起来,实现一个完整的功能,而之因此分开,是为了维护方便。从这点上说,项目不管大小,只要是一个完整的功能,均可以称做项目,哪怕只有一个简单的index.html
文件。
固然啦,本文针对的主要仍是大型项目中的代码组织方式,小demo的话怎么舒服怎么来。毕竟代码风格这种东西因人而异,咱们不能说谁的代码写的漂亮就必定好。总的来讲,良好的代码风格,有助与排查错误,有利于团队协做,有利于修身养性,有助于...(跑偏了)总之好处不会少。
最后,但愿能对你们有所帮助,转载请附上原文连接