随着业务的不断累积,目前咱们 ToC 端
主要项目,除去 node_modules
, build 配置文件
,dist 静态资源文件
的代码量为 137521
行,后台管理系统下各个子应用代码,除去依赖等文件的总行数也达到 100万
多一点。css
代码量意味不了什么,只能证实模块不少,但相同两个项目,在
运行时性能相同
状况下,你的10 万
行代码能容纳并维护150
个模块,而且开发顺畅,个人项目中10 万
行代码却只能容纳100
个模块,添加功能也好,维护起来也较为繁琐,这就很值得思考。html
本文会在主要描述以 Vue 技术栈
为技术主体
,ToC 端
项目业务主体
,在构建过程当中,遇到或者总结的点(也会说起一些 ToB 项目的场景),可能并不适合你的业务场景(仅供参考),我会尽量多的描述问题与其中的思考,最大可能的帮助到须要的同窗,也辛苦开发者发现问题或者不合理/不正确的地方及时向我反馈,会尽快修改,欢迎有更好的实现方式来 pr
。前端
能够参考蚂蚁金服数据体验技术团队
编写的文章:vue
本文并非基于上面文章写的,不过当时在看到他们文章以后以为有类似的地方,相较于这篇文章,本文可能会枯燥些,会有大量代码,同窗能够直接用上仓库看。node
首先要思考咱们的项目最终的构建主体
是单页面
,仍是多页面
,仍是单页 + 多页
,经过他们的优缺点来分析:webpack
懒加载
可有效减小首页白屏时间,相较于多页面
减小了用户访问静态资源服务器的次数等。懒加载
也有他的弊端,不作特殊处理不利于 SEO 等。URL
,cookie
,storage
等方式,较为局限。老 MPA 项目迁移至 SPA 的状况
,缺点结合二者,两种主体通讯方式也只能以兼容MPA 为准
HTML 串
的状况下),想保证用户体验在 SPA 中开发一个页面,在 MPA 中也开发一个页面,去掉没用的依赖,或者直接用原生 JS 来开发,分享出去是 MPA 的文章页面,这样能够加快分享出去的打开速度,同时也能减小静态资源服务器的压力,由于若是分享出去的是 SPA 的文章页面,那 SPA 所需的静态资源至少都须要去进行协商请求
,固然若是服务配置了强缓存就忽略以上所说。咱们首先根据业务所需,来最终肯定构建主体
,而咱们选择了体验至上的 SPA
,并选用 Vue
技术栈。ios
其实咱们看开源的绝大部分项目中,目录结构都会差不太多,咱们能够综合一下来个通用的 src
目录:git
src
├── assets // 资源目录 图片,样式,iconfont
├── components // 全局通用组件目录
├── config // 项目配置,拦截器,开关
├── plugins // 插件相关,生成路由、请求、store 等实例,并挂载 Vue 实例
├── directives // 拓展指令集合
├── routes // 路由配置
├── service // 服务层
├── utils // 工具类
└── views // 视图层
复制代码
components
中咱们会存放 UI 组件库中的那些常见通用组件了,在项目中直接经过设置别名
来使用,若是其余项目须要使用,就发到 npm
上。es6
// components 简易结构
components
├── dist
├── build
├── src
├── modal
├── toast
└── ...
├── index.js
└── package.json
复制代码
若是想最终编译成 es5
,直接在 html 中使用或者部署 CDN 上,在 build
配置简单的打包逻辑,搭配着 package.json
构建 UI组件 的自动化打包发布,最终部署 dist
下的内容,并发布到 npm
上便可。github
而咱们也可直接使用 es6
的代码:
import 'Components/src/modal'
复制代码
假设咱们发布的 npm 包
叫 bm-ui
,而且下载到了本地 npm i bm-ui -S
:
修改项目的最外层打包配置,在 rules 里 babel-loader
或 happypack
中添加 include
,node_modules/bm-ui
:
// webpack.base.conf
...
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
// 这里添加
include: [resolve('src'), resolve('test'), resolve('node_modules/bm-ui')]
},{
...
}]
...
复制代码
而后搭配着 babel-plugin-import
直接在项目中使用便可:
import { modal } from 'bm-ui'
复制代码
同时有多个组件库的话,又或者有同窗专门进行组件开发的话,把 components 内部细分
一下,多一个文件分层。
components
├── bm-ui-1
├── bm-ui-2
└── ...
复制代码
你的打包配置文件能够放在 components
下,进行统一打包,固然若是要开源出去仍是放在对应库下。
这个点其实会是项目中常常被忽略的,或者说不多聚合到一块儿,但同时我认为是整个项目中的重要之一,后续会有例子说道。
config
├── index.js // 全局配置/开关
├── interceptors // 拦截器
├── index.js // 入口文件
├── axios.js // 请求/响应拦截
├── router.js // 路由拦截
└── ...
└── ...
复制代码
咱们在 config/index.js
可能会有以下配置:
// config/index.js
// 当前宿主平台 兼容多平台应该经过一些特定函数来取得
export const HOST_PLATFORM = 'WEB'
// 这个就很少说了
export const NODE_ENV = process.env.NODE_ENV || 'prod'
// 是否强制全部请求访问本地 MOCK,看到这里同窗不难猜到,每一个请求也能够单独控制是否请求 MOCK
export const AJAX_LOCALLY_ENABLE = false
// 是否开启监控
export const MONITOR_ENABLE = true
// 路由默认配置,路由表并不今后注入
export const ROUTER_DEFAULT_CONFIG = {
waitForData: true,
transitionOnLoad: true
}
// axios 默认配置
export const AXIOS_DEFAULT_CONFIG = {
timeout: 20000,
maxContentLength: 2000,
headers: {}
}
// vuex 默认配置
export const VUEX_DEFAULT_CONFIG = {
strict: process.env.NODE_ENV !== 'production'
}
// API 默认配置
export const API_DEFAULT_CONFIG = {
mockBaseURL: '',
mock: true,
debug: false,
sep: '/'
}
// CONST 默认配置
export const CONST_DEFAULT_CONFIG = {
sep: '/'
}
// 还有一些业务相关的配置
// ...
// 还有一些方便开发的配置
export const CONSOLE_REQUEST_ENABLE = true // 开启请求参数打印
export const CONSOLE_RESPONSE_ENABLE = true // 开启响应参数打印
export const CONSOLE_MONITOR_ENABLE = true // 监控记录打印
复制代码
能够看出这里聚集了项目中全部用到的配置,下面咱们在 plugins
中实例化插件,注入对应配置,目录以下:
plugins
├── api.js // 服务层 api 插件
├── axios.js // 请求实例插件
├── const.js // 服务层 const 插件
├── store.js // vuex 实例插件
├── inject.js // 注入 Vue 原型插件
└── router.js // 路由实例插件
复制代码
这里先举出两个例子,看咱们是如何注入配置,拦截器并实例化的
实例化 router
:
import Vue from 'vue'
import Router from 'vue-router'
import ROUTES from 'Routes'
import {ROUTER_DEFAULT_CONFIG} from 'Config/index'
import {routerBeforeEachFunc} from 'Config/interceptors/router'
Vue.use(Router)
// 注入默认配置和路由表
let routerInstance = new Router({
...ROUTER_DEFAULT_CONFIG,
routes: ROUTES
})
// 注入拦截器
routerInstance.beforeEach(routerBeforeEachFunc)
export default routerInstance
复制代码
实例化 axios
:
import axios from 'axios'
import {AXIOS_DEFAULT_CONFIG} from 'Config/index'
import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from 'Config/interceptors/axios'
let axiosInstance = {}
axiosInstance = axios.create(AXIOS_DEFAULT_CONFIG)
// 注入请求拦截
axiosInstance
.interceptors.request.use(requestSuccessFunc, requestFailFunc)
// 注入响应拦截
axiosInstance
.interceptors.response.use(responseSuccessFunc, responseFailFunc)
export default axiosInstance
复制代码
咱们在 main.js
注入插件:
// main.js
import Vue from 'vue'
GLOBAL.vbus = new Vue()
// import 'Components'// 全局组件注册
import 'Directives' // 指令
// 引入插件
import router from 'Plugins/router'
import inject from 'Plugins/inject'
import store from 'Plugins/store'
// 引入组件库及其组件库样式
// 不须要配置的库就在这里引入
// 若是须要配置都放入 plugin 便可
import VueOnsen from 'vue-onsenui'
import 'onsenui/css/onsenui.css'
import 'onsenui/css/onsen-css-components.css'
// 引入根组件
import App from './App'
Vue.use(inject)
Vue.use(VueOnsen)
// render
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
复制代码
axios
实例咱们并无直接引用,相信你也猜到他是经过 inject
插件引用的,咱们看下 inject
:
import axios from './axios'
import api from './api'
import consts from './const'
GLOBAL.ajax = axios
export default {
install: (Vue, options) => {
Vue.prototype.$api = api
Vue.prototype.$ajax = axios
Vue.prototype.$const = consts
// 须要挂载的都放在这里
}
}
复制代码
这里能够挂载你想在业务中( vue
实例中)便捷访问的 api
,除了 $ajax
以外,api
和 const
两个插件是咱们服务层中主要的功能,后续会介绍,这样咱们插件流程大体运转起来,下面写对应拦截器的方法。
在ajax 拦截器
中(config/interceptors/axios.js
):
// config/interceptors/axios.js
import {CONSOLE_REQUEST_ENABLE, CONSOLE_RESPONSE_ENABLE} from '../index.js'
export function requestSuccessFunc (requestObj) {
CONSOLE_REQUEST_ENABLE && console.info('requestInterceptorFunc', `url: ${requestObj.url}`, requestObj)
// 自定义请求拦截逻辑,能够处理权限,请求发送监控等
// ...
return requestObj
}
export function requestFailFunc (requestError) {
// 自定义发送请求失败逻辑,断网,请求发送监控等
// ...
return Promise.reject(requestError);
}
export function responseSuccessFunc (responseObj) {
// 自定义响应成功逻辑,全局拦截接口,根据不一样业务作不一样处理,响应成功监控等
// ...
// 假设咱们请求体为
// {
// code: 1010,
// msg: 'this is a msg',
// data: null
// }
let resData = responseObj.data
let {code} = resData
switch(code) {
case 0: // 若是业务成功,直接进成功回调
return resData.data;
case 1111:
// 若是业务失败,根据不一样 code 作不一样处理
// 好比最多见的受权过时跳登陆
// 特定弹窗
// 跳转特定页面等
location.href = xxx // 这里的路径也能够放到全局配置里
return;
default:
// 业务中还会有一些特殊 code 逻辑,咱们能够在这里作统一处理,也能够下方它们到业务层
!responseObj.config.noShowDefaultError && GLOBAL.vbus.$emit('global.$dialog.show', resData.msg);
return Promise.reject(resData);
}
}
export function responseFailFunc (responseError) {
// 响应失败,可根据 responseError.message 和 responseError.response.status 来作监控处理
// ...
return Promise.reject(responseError);
}
复制代码
定义路由拦截器
(config/interceptors/router.js
):
// config/interceptors/router.js
export function routerBeforeFunc (to, from, next) {
// 这里能够作页面拦截,不少后台系统中也很是喜欢在这里面作权限处理
// next(...)
}
复制代码
最后在入口文件(config/interceptors/index.js)
中引入并暴露出来便可:
import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from './ajax'
import {routerBeforeEachFunc} from './router'
let interceptors = {
requestSuccessFunc,
requestFailFunc,
responseSuccessFunc,
responseFailFunc,
routerBeforeEachFunc
}
export default interceptors
复制代码
请求拦截这里代码都很简单,对于 responseSuccessFunc
中 switch default
逻辑作下简单说明:
responseObj.config.noShowDefaultError
这里可能不太好理解 咱们在请求的时候,能够传入一个 axios 中并无意义的 noShowDefaultError
参数为咱们业务所用,当值为 false 或者不存在时,咱们会触发全局事件 global.dialog.show
,global.dialog.show
咱们会注册在 app.vue
中:// app.vue
export default {
...
created() {
this.bindEvents
},
methods: {
bindEvents() {
GLOBAL.vbus.$on('global.dialog.show', (msg) => {
if(msg) return
// 咱们会在这里注册全局须要操控试图层的事件,方便在非业务代码中经过发布订阅调用
this.$dialog.popup({
content: msg
});
})
}
...
}
}
复制代码
这里也能够把弹窗状态放入
Store
中,按团队喜爱,咱们习惯把公共的涉及视图逻辑的公共状态在这里注册,和业务区分开来。
GLOBAL
是咱们挂载 window
上的全局对象
,咱们把须要挂载的东西都放在 window.GLOBAL
里,减小命名空间冲突的可能。vbus
其实就是咱们开始 new Vue()
挂载上去的GLOBAL.vbus = new Vue()
复制代码
Promise.reject
出去,咱们就能够在 error
回调里面只处理咱们的业务逻辑,而其余如断网
、超时
、服务器出错
等均经过拦截器进行统一处理。对比下处理先后在业务中的发送请求的代码
:
拦截器处理前:
this.$axios.get('test_url').then(({code, data}) => {
if( code === 0 ) {
// 业务成功
} else if () {}
// em... 各类业务不成功处理,若是遇到通用的处理,还须要抽离出来
}, error => {
// 须要根据 error 作各类抽离好的处理逻辑,断网,超时等等...
})
复制代码
拦截器处理后:
// 业务失败走默认弹窗逻辑的状况
this.$axios.get('test_url').then(({data}) => {
// 业务成功,直接操做 data 便可
})
// 业务失败自定义
this.$axios.get('test_url', {
noShowDefaultError: true // 可选
}).then(({data}) => {
// 业务成功,直接操做 data 便可
}, (code, msg) => {
// 当有特定 code 须要特殊处理,传入 noShowDefaultError:true,在这个回调处理就行
})
复制代码
在应对项目开发过程当中需求的不可预见性时,让咱们能处理的更快更好
到这里不少同窗会以为,就这么简单的引入判断,无关紧要, 就如咱们最近作的一个需求来讲,咱们 ToC 端项目以前一直是在微信公众号中打开的,而咱们须要在小程序中经过 webview 打开大部分流程,而咱们也没有时间,没有空间
在小程序中重写近 100 + 的页面流程,这是咱们开发之初并无想到的。这时候必须把项目兼容到小程序端,在兼容过程当中可能须要解决如下问题:
api
,在小程序中无用,须要调用小程序的逻辑,须要作兼容。能够看出,稍微不慎,会影响公众号现有逻辑。
interceptors/minaAjax.js
, interceptors/minaRouter.js
,原有的换更为 interceptors/officalAjax.js
,interceptors/officalRouter.js
,在入口文件interceptors/index.js
,根据当前宿主平台
,也就是全局配置 HOST_PLATFORM
,经过代理模式
和策略模式
,注入对应平台的拦截器,在minaAjax.js
中重写请求路径和权限处理,在 minaRouter.js
中添加页面拦截配置,跳转到特定页面,这样一并解决了上面的问题 1,2,3
。问题 4
其实也比较好处理了,拷贝须要兼容 api
的页面,重写里面的逻辑,经过路由拦截器一并作跳转处理
。问题 5
也很简单,拓展两个自定义指令 v-mina-show 和 v-mina-hide ,在展现不一样步的地方能够直接使用指令。最终用最少的代码,最快的时间完美上线,丝毫没影响到现有 toC 端业务,并且这样把全部兼容逻辑绝大部分聚合到了一块儿,方便二次拓展和修改。
虽然这只是根据自身业务结合来讲明,可能没什么说服力,不过不难看出全局配置/拦截器 虽然代码很少,但倒是整个项目的核心之一,咱们能够在里面作更多 awesome
的事情。
directives
里面没什么可说的,不过不少难题均可以经过他来解决,要时刻记住,咱们能够再指令里面操做虚拟 DOM。
而咱们根据本身的业务性质,最终根据业务流程来拆分配置:
routes
├── index.js // 入口文件
├── common.js // 公共路由,登陆,提示页等
├── account.js // 帐户流程
├── register.js // 挂号流程
└── ...
复制代码
最终经过 index.js 暴露出去给 plugins/router
实例使用,这里的拆分配置有两个注意的地方:
业务线
划分,有的项目更适合以 功能
划分。文章开头说到单页面静态资源过大,首次打开/每次版本升级
后都会较慢,能够用懒加载
来拆分静态资源,减小白屏时间,但开头也说到懒加载
也有待商榷的地方:
这就须要咱们根据项目状况在空间和时间
上作一些权衡。
如下几点能够做为简单的参考:
公司后台管理系统
中,能够以操做 view 为单位进行异步加载,通用组件所有同步加载的方式。功能模块拆分
进行异步组件加载。打包出来的 main.js 的大小,绝大部分都是在路由中引入的并注册的视图组件。
服务层做为项目中的另外一个核心之一,“自古以来”都是你们比较关心的地方。
不知道你是否看到过以下组织代码方式:
views/
pay/
index.vue
service.js
components/
a.vue
b.vue
复制代码
在 service.js
中写入编写数据来源
export const CONFIAG = {
apple: '苹果',
banana: '香蕉'
}
// ...
// ① 处理业务逻辑,还弹窗
export function getBInfo ({name = '', id = ''}) {
return this.$ajax.get('/api/info', {
name,
id
}).then({age} => {
this.$modal.show({
content: age
})
})
}
// ② 不处理业务,仅仅写请求方法
export function getAInfo ({name = '', id = ''}) {
return this.$ajax.get('/api/info', {
name,
id
})
}
...
复制代码
简单分析:
我相信②在绝大多数项目中都能看到。
那么咱们的目的就很明显了,解决冗余,方便使用,咱们把枚举和请求接口的方法,经过插件,挂载到一个大对象上,注入 Vue 原型,方面业务使用便可。
service
├── api
├── index.js // 入口文件
├── order.js // 订单相关接口配置
└── ...
├── const
├── index.js // 入口文件
├── order.js // 订单常量接口配置
└── ...
├── store // vuex 状态管理
├── expands // 拓展
├── monitor.js // 监控
├── beacon.js // 打点
├── localstorage.js // 本地存储
└── ... // 按需拓展
└── ...
复制代码
首先抽离请求接口模型,可按照领域模型抽离
(service/api/index.js
):
{
user: [{
name: 'info',
method: 'GET',
desc: '测试接口1',
path: '/api/info',
mockPath: '/api/info',
params: {
a: 1,
b: 2
}
}, {
name: 'info2',
method: 'GET',
desc: '测试接口2',
path: '/api/info2',
mockPath: '/api/info2',
params: {
a: 1,
b: 2,
b: 3
}
}],
order: [{
name: 'change',
method: 'POST',
desc: '订单变动',
path: '/api/order/change',
mockPath: '/api/order/change',
params: {
type: 'SUCCESS'
}
}]
...
}
复制代码
定制下须要的几个功能:
定制好功能,开始编写简单的 plugins/api.js
插件:
import axios from './axios'
import _pick from 'lodash/pick'
import _assign from 'lodash/assign'
import _isEmpty from 'lodash/isEmpty'
import { assert } from 'Utils/tools'
import { API_DEFAULT_CONFIG } from 'Config'
import API_CONFIG from 'Service/api'
class MakeApi {
constructor(options) {
this.api = {}
this.apiBuilder(options)
}
apiBuilder({
sep = '|',
config = {},
mock = false,
debug = false,
mockBaseURL = ''
}) {
Object.keys(config).map(namespace => {
this._apiSingleBuilder({
namespace,
mock,
mockBaseURL,
sep,
debug,
config: config[namespace]
})
})
}
_apiSingleBuilder({
namespace,
sep = '|',
config = {},
mock = false,
debug = false,
mockBaseURL = ''
}) {
config.forEach( api => {
const {name, desc, params, method, path, mockPath } = api
let apiname = `${namespace}${sep}${name}`,// 命名空间
url = mock ? mockPath : path,//控制走 mock 仍是线上
baseURL = mock && mockBaseURL
// 经过全局配置开启调试模式。
debug && console.info(`调用服务层接口${apiname},接口描述为${desc}`)
debug && assert(name, `${apiUrl} :接口name属性不能为空`)
debug && assert(apiUrl.indexOf('/') === 0, `${apiUrl} :接口路径path,首字符应为/`)
Object.defineProperty(this.api, `${namespace}${sep}${name}`, {
value(outerParams, outerOptions) {
// 请求参数自动截取。
// 请求参数不穿则发送默认配置参数。
let _data = _isEmpty(outerParams) ? params : _pick(_assign({}, params, outerParams), Object.keys(params))
return axios(_normoalize(_assign({
url,
desc,
baseURL,
method
}, outerOptions), _data))
}
})
})
}
}
function _normoalize(options, data) {
// 这里能够作大小写转换,也能够作其余类型 RESTFUl 的兼容
if (options.method === 'POST') {
options.data = data
} else if (options.method === 'GET') {
options.params = data
}
return options
}
// 注入模型和全局配置,并暴露出去
export default new MakeApi({
config: API_CONFIG,
...API_DEFAULT_CONFIG
})['api']
复制代码
挂载到 Vue 原型
上,上文有说到,经过 plugins/inject.js
import api from './api'
export default {
install: (Vue, options) => {
Vue.prototype.$api = api
// 须要挂载的都放在这里
}
}
复制代码
这样咱们能够在业务中
愉快的使用业务层代码:
// .vue 中
export default {
methods: {
test() {
this.$api['order/info']({
a: 1,
b: 2
})
}
}
}
复制代码
即便在业务以外
也可使用:
import api from 'Plugins/api'
api['order/info']({
a: 1,
b: 2
})
复制代码
固然对于运行效率要求高
的项目中,避免内存使用率过大
,咱们须要改造 API,用解构的方式引入使用,最终利用 webpack
的 tree-shaking
减小打包体积。几个简单的思路
通常来讲,多人协做时候你们均可以先看
api
是否有对应接口,当业务量上来的时候,也确定会有人出现找不到,或者找起来比较费劲,这时候咱们彻底能够在 请求拦截器中,把当前请求的url
和api
中的请求作下判断,若是有重复接口请求路径,则提醒开发者已经配置相关请求,根据状况是否进行二次配置便可。
最终咱们能够拓展 Service 层的各个功能:
基础
异步与后端交互
常量枚举
Vuex
状态管理拓展
监控
功能,自定义搜集策略,调用 api
中的接口发送打点
功能,自定义搜集策略,调用 api
中的接口发送const
,localStorage
,monitor
和 beacon
根据业务自行拓展暴露给业务使用便可,思想也是同样的,下面着重说下 store(Vuex)
。
插一句:若是看到这里没感受不妥的话,想一想上面
plugins/api.js
有没有用单例模式
?该不应用?
答案是否认的,就算你的项目达到 10 万行代码,那也并不意味着你必须使用 Vuex,应该由业务场景决定。
vbus
作好命名空间,来解耦便可。let vbus = new Vue()
vbus.$on('print.hello', () => {
console.log('hello')
})
vbus.$emit('print.hello')
复制代码
多人协做项目管理
,有道云笔记
,网易云音乐
,微信网页版/桌面版
等应用,功能集中,空间利用率高,实时交互的项目,无疑 Vuex 是较好的选择
。这类应用中咱们能够直接抽离业务领域模型
:store
├── index.js
├── actions.js // 根级别 action
├── mutations.js // 根级别 mutation
└── modules
├── user.js // 用户模块
├── products.js // 产品模块
├── order.js // 订单模块
└── ...
复制代码
固然对于这类项目,vuex
或许不是最好的选择,有兴趣的同窗能够学习下 rxjs
。
后台系统
或者页面之间业务耦合不高的项目
,这类项目是占比应该是很大的,咱们思考下这类项目:全局共享状态很少,可是不免在某个模块中会有复杂度较高的功能(客服系统,实时聊天,多人协做功能等),这时候若是为了项目的可管理性,咱们也在 store
中进行管理,随着项目的迭代咱们不难遇到这样的状况:
store/
...
modules/
b.js
...
views/
...
a/
b.js
...
复制代码
先梳理咱们的目标:
store
上,想提升运行效率。(冗余)咱们借助 Vuex 提供的 registerModule
和 unregisterModule
一并解决这些问题,咱们在 service/store
中放入全局共享的状态:
service/
store/
index.js
actions.js
mutations.js
getters.js
state.js
复制代码
通常这类项目全局状态很少,若是多了拆分 module 便可。
编写插件生成 store 实例
:
import Vue from 'vue'
import Vuex from 'vuex'
import {VUEX_DEFAULT_CONFIG} from 'Config'
import commonStore from 'Service/store'
Vue.use(Vuex)
export default new Vuex.Store({
...commonStore,
...VUEX_DEFAULT_CONFIG
})
复制代码
对一个须要状态管理页面或者模块进行分层:
views/
pageA/
index.vue
components/
a.vue
b.vue
...
children/
childrenA.vue
childrenB.vue
...
store/
index.js
actions.js
moduleA.js
moduleB.js
复制代码
module 中直接包含了 getters
,mutations
,state
,咱们在 store/index.js
中作文章:
import Store from 'Plugins/store'
import actions from './actions.js'
import moduleA from './moduleA.js'
import moduleB from './moduleB.js'
export default {
install() {
Store.registerModule(['pageA'], {
actions,
modules: {
moduleA,
moduleB
},
namespaced: true
})
},
uninstall() {
Store.unregisterModule(['pageA'])
}
}
复制代码
最终在 index.vue
中引入使用, 在页面跳转以前注册这些状态和管理状态的规则,在路由离开以前,先卸载这些状态和管理状态的规则:
import store from './store'
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters('pageA', ['aaa', 'bbb', 'ccc'])
},
beforeRouterEnter(to, from, next) {
store.install()
next()
},
beforeRouterLeave(to, from, next) {
store.uninstall()
next()
}
}
复制代码
固然若是你的状态要共享到全局,就不执行 uninstall
。
这样就解决了开头的三个问题,不一样开发者在开发页面的时候,能够根据页面特性,渐进加强的选择某种开发形式。
这里简单列举下其余方面,须要自行根据项目深刻和使用。
这里网上已经有不少优化方法:dll
,happypack
,多线程打包
等,但随着项目的代码量级,每次 dev 保存的时候编译的速度也是会越来越慢的,而一过慢的时候咱们就不得不进行拆分,这是确定的,而在拆分以前尽量容纳更多的可维护的代码,有几个能够尝试和规避的点:
moment.js
这样的库)等。sass
的话,善用 %placeholder
减小无用代码打包进来。
MPA 应用
中样式冗余过大,%placeholder
也会给你带来帮助。
不少大公司都有本身的 mock 平台
,当先后端定好接口格式,放入生成对应 mock api
,若是没有 mock 平台,那就找相对好用的工具如 json-server
等。
请强制使用 eslint
,挂在 git 的钩子上。按期 diff 代码,按期培训等。
很是建议用 TS 编写项目,可能写 .vue 有些别扭,这样前端的大部分错误在编译时解决,同时也能提升浏览器运行时效率,可能减小 re-optimize
阶段时间等。
这也是项目很是重要的一点,若是你的项目还未使用一些测试工具,请尽快接入,这里不过多赘述。
当项目到达到必定业务量级时,因为项目中的模块过多,新同窗维护成本,开发成本都会直线上升,不得不拆分项目,后续会分享出来咱们 ToB
项目在拆分系统中的简单实践。
时下有各类成熟的方案,这里只是一个简单的构建分享,里面依赖的版本都是咱们稳定下来的版本,须要根据本身实际状况进行升级。
项目底层构建每每会成为前端忽略的地方,咱们既要从一个大局观来看待一个项目或者整条业务线,又要对每一行代码精益求精,对开发体验不断优化,慢慢累积后才能更好的应对未知的变化。
若是前端同窗想尝试使用 Vue
开发 App
,或者熟悉 weex
开发的同窗,能够来尝试使用咱们的开源解决方案 eros
,虽然没作过什么广告,但不彻底统计,50 个在线 APP 仍是有的
,期待你的加入。
最后附上部分产品截图~
(逃~)