Vue项目代码组织和风格约定
代码组织和分层
代码组织是一个仁者见仁,智者见智的话题,没有银弹。不过不管怎么变化,指导思想还是不变的高内聚,低耦合。
强烈推荐两篇文章,能够拓宽你的视野,带你走向新高度。
代码组织的优雅,模块化才能够做好。
分层
按照职能的不同进行不同维度进行层级划分,层级划分之后,进行进一步的模块划分(原则上,每一个文件夹都是一个模块)
文件夹和文件命名
选择适合自己的风格。
文件夹和文件都使用kebab-case
kebab-case重度使用者可以选择这种。
文件夹使用kebab-case, 文件使用Pascal Case
建议使用这种。
文件夹:event-bus文件:EventBus.ts复制代码
例外
index文件不受上述约束
工具自动生成的文件(自己考虑是否受约束)
模块化原则
模块化代码首先要做到代码的分层、隔离、抽象。
不同模块完成不同的职能,不同职能之间相互协作。
每个模块保持一个入口和出口
对于外部模块来说,尽量保证一个入口
对于内部子模块来说,尽量保证一个出口
如果按照文件夹作为模块界限,每个文件夹下都有一个出口(可以默认为index文件)
模块的入口名称默认index或者文件夹名字的文件
// 例如: group文件夹group/|---index.ts // A. 默认作为入口|---group.ts // B. 也可以默认作为入口二者任选其一就好,A方案应该是大家默认的方案;B方案,检索代码的时候更方便复制代码
模块内部分层
模块内部还可以有base,common,components,helper,utils,filter,config等层级(词穷了.....)
import和export原则
import导入,指定到文件
指定到文件能够提高编译打包的速度
// 指定到index文件import{ Logger }from'./common/index';复制代码
common模块
独立文章说明。
router模块
router模块的风格约定。
模块结构
router├── helper│ ├── ImportRoute.ts│ └── RouteGenerator.ts├── modules│ ├── AboutRoutes.ts│ └── HomeRoutes.ts├── router.ts└── Routes.ts复制代码
helper: 帮助工具方法
modules: 不同的业务模块
router: vue-router初始化的地方,也是模块入口
Routes:RouteConfig的出口,其它模块都从这里获取route配置,从而达到解耦的目的,尤其是不同的views里面的路由跳转,使用Routes配置达到解耦的目的。
例子:
import{ HomeRoute }from'Routes';// 跳转,这样没有硬编码任何的route信息,全部都是从Routes配置来,达到解耦的目的。this.$router.push({name: HomeRoute.name})复制代码
modules子模块
文件名:views下文件夹名(模块名) + Routes结尾
modules下的文件,最好和views下文件夹一一对应,方便维护(对模块切分有较高的理解)
例如:views├── group│ ├── xxx1.vue│ └── xxx2.vue├── report│ ├── xxx3.vue│ └── xxx4.vue├── Home.vue复制代码
modules对应的就是
例如:router├── modules│ ├── GroupRoutes.ts| ├── ReportRoutes.ts│ └── HomeRoutes.ts复制代码
导出模块配置:
// HomeRoutes.tsexportconstHomeRoute = { path:'/', name:'HomeRoute', component:'Home',};// 必须导出一个数组,因为是一个模块的配置信息,可能有多个配置,还可以进行配置层级关系exportdefault[HomeRoute];复制代码
注意:这只是个思路,具体的操作还要灵活运用。
RouteConfig风格约定
RouteConfig变量名
vue文件名+Route结尾
// 文件Home.vue// 变量名HomeRouteexportconstHomeRoute = { path:'/', name:'HomeRoute', component:'Home',};复制代码
name属性
和RouteConfig变量名保持一致
component属性
如果异步加载,component需要使用相对于views的path格式,因为在ImportRoute中统一处理。
// ImportRoute.ts统一处理exportfunctionimportRoute(file:string){// @see https://github.com/webpack/webpack/issues/1949return()=>import(/* webpackChunkName: "chunk-[request][index]" */'@/views/'+ file +'.vue');}复制代码
path属性
没有最佳实践,最好使用restful风格约束
如果使用route.query等之类的参数传递,面包屑导航很难处理。
meta属性
没有最佳实践,多数情况下有这么几个属性
exportconstHomeRoute = {path:'/',name:'HomeRoute',component:'Home',// component: () => import('@/views/Home.vue')meta: {// title的值可以为`i18n`的语言文件key,方便做国际化title:'首页',// 作为menu.title和breadcrumb.title备选icon:'',// icon的class,作为menu.icon和breadcrumb.icon备选menu: {title:'首页',visible:true,icon:'',// icon的class},breadcrumb: {title:'路径名',visible:false,// 有的时候不需要在面包屑上渲染icon:'',// icon的class},auth: {roles: [1,2,3] } }};复制代码
props属性
使用props方式把components和$route解耦,这样components既可以单独使用,也可以当作子组件使用,而且方便测试。
特殊场景可以不使用props,例如本来就不是通用的组件,是需要组合在一起使用的父子组件,是可以和route耦合的。
如果props被设置为true,route.params将会被设置为组件属性。
// 函数式(动态)constrouter =newVueRouter({routes: [{path:'/search',component: SearchUser,// route是SearchUser内部的this.$routeprops:(route) =>({query: route.query.q }) }]})复制代码
// 静态constrouter =newVueRouter({routes: [{path:'/promotion/from-newsletter',component: Promotion,props: {newsletterPopup:false} }]})复制代码
store模块
vue应用的状态模块
模块结构
store├── StoreTypes.ts# actions mutations getters类型├── Actions.ts# 根级别的 action├── Mutations.ts# 根级别的 Mutations├── Getters.ts# 根级别的 getters├── modules# 模块│ ├── xxxStore.ts# 子模块│ ├── SystemStore.ts# SystemStore子模块├── index.ts# 入口复制代码
index: 作为入口
StoreTypes作为类型的常量文件
所以mutation和action的类型全部使用常量,方便其它模块和store模块的解耦。
modules子模块
文件名:模块名 + Store结尾
modules下的业务store,最好和views下文件夹对应,方便维护
# 例子: LocaleStore.ts# i18n模块LoginStore.ts# 登录模块UserStore.ts# 用户模块复制代码
Store module约定
主要约定module内部代码结构大致如下:
SystemStore为例:
需要使用前缀的地方使用模块名作为前缀
例子中模块名为System
约定声明大致顺序如下:除state部分外其他都是可选
state声明(例如:systemState)
getter types声明(可选,灵活运用)
getters声明(可选, 灵活运用)
mutation types 声明(可选)
mutations声明(可选)
action types声明(可选)
actions声明(可选)
store options导出
注意:其中的action types, mutation types和 getter types 不强制要求,在es6环境中使用官方的mapState,mapGetters,mapActions,mapMutations工具函数更方便。
api模块
api模块是接口服务层,主要做一些对参数的转换处理,同时解耦其它业务层。
模块结构
模块的大致参考结构
api├── api.ts# 入口└── modules# 子模块├── DictionaryService.ts# 具体的业务模块├── GroupService.ts ├── HistoryService.ts复制代码
service module约定
文件名:模块名 + Service结尾
modules下的业务service,和views下文件夹对应,方便维护
如果单个service文件对应的业务模块接口太多,可以使用文件夹来进一步分割。
//例子:LoginService// 声明url,导出url方便mock模块使用exportconstGET_SIGN_IN ='/api/login';// 声明service函数exportfunctionsignIn(data: {account:string; pass:string}){returnajax({ url: GET_SIGN_IN, data, method:'post'});}复制代码
URL前缀约定:
查询:使用GET作为前缀(特殊情况例外)
更新:使用UPDATE
新增:使用ADD
删除:使用DELETE
其它:EXPORT,IMPORT,UPLOAD,DOWNLOAD等
service 函数名前缀约定:
对于单个实体可以考虑使用get, add, update, delete作为前缀
i18n模块
主要是建议下代码的分割的约定。
模块结构
i18n├── i18n.ts├── index.d.ts└── locales ├── en_US.js ├── modules │ └── actions │ ├── en_US.js │ └── zh_CN.js └── zh_CN.js复制代码
语言文件组织
语言模块的组织,主要按照模块(文件夹层次)来组织,能够最小的减少冲突可能性。
语言文件约定:
方言文件放置在locals文件夹下
文件以方言编码命名
层级组织的例子:
views├── account│ ├── Account.vue│ ├── locales│ │ ├── en_US.js│ │ └── zh_CN.js│ └── XXX.vue├── feedback│ ├── locales│ │ ├── en_US.js│ │ └── zh_CN.js│ └── Suggestion.vue├── locales│ │ ├── en_US.js│ │ └── zh_CN.js复制代码
// views/feedback/locales/zh_CN.jsexportconstfeedbackModule = {// 语言模块属性:模块名+module结尾// 可选的方式就是:严格按照文件夹层次来构造属性层级(缺点取属性值时太长,后期可以使用__dirname自动生成)label:'您的意见:',textarea: {placeholder:'请写下您的意见与建议, 500字符以内'} };复制代码
// views/locales/zh_CN.jsimportfeedbackModulefrom'../feedback/locales/zh_CN.js';exportdefault{// 如果模块众多建议加上views // views: {// ...feedbackModule,// }// 如果模块少,直接feedbackModule,};复制代码
// i18n/locales/zh_CN.jsimportviewsLocalesfrom'../../views/locales/zh_CN';exportdefault{ ...viewsLocales}复制代码
mock模块
mock模块主要做些数据模拟的工作
模块结构
子模块的大致参考结构
mock├── mock.js# 入口└── modules# 子模块├── LoginMock.js# 具体的业务模块复制代码
mock module约定
文件名:模块名 + mock结尾
modules下的mock文件,和api模块下文件对应,方便维护
方法约定:
mock模块方法和service请求方法对应
例如:LoginService的signInmock模块对应也叫signIn
mock module需要提供setup方法
setup方法供外部统一调用,作为mock module的开关
例子:
// LoginMock.jsimportMockfrom'mockjs';import{ genSuccessResult, genFailResult }from'./mock-utils';import{ GET_SIGN_IN }from'../api/api';importCookiesfrom'js-cookie';constsignIn =(data) =>{ Cookies.set('SESSIONID','mock SESSIONID');returngenSuccessResult({msg:'登陆成功', });}exportdefault{ setup() { Mock.mock(GET_SIGN_IN,'post', signIn); }}复制代码
// mock.jsimportloginMockerfrom'./modules/login';exportconststart =function(){ loginMocker.setup();};复制代码
// main.jsimportmockerfrom'./mock/mock'if(process.env.NODE_ENV ==='development') { mocker.start();}复制代码
其它模块
其它模块如filters,directives,test目前没有总结,以后补充。