Prior to the start
With the continuous accumulation of business, at present, the code amount of the main project on the ToC side, excluding node_modules, build configuration file and dist static resource file is 137,521 lines, and the total number of the sub-application code under the background management system, excluding dependencies and other files has reached a little more than 1 million lines.
Code means nothing, can only prove that module is very much, but the same two events, and in the case of the same runtime performance, 100000 lines of code you can accommodate 150 modules, and maintain and develop smoothly and my project in 100000 lines of code can only hold 100 module, add functionality, to maintain is also tedious, It’s worth thinking about.
This article will be described in the main to the Vue technology stack as the main body, the ToC end project business main body, in the process of building, encounter or summarize point (also referred to some ToB project scenarios), may not be suitable for your business scenarios (for reference only), I will describe the problem as much as possible of its thinking, the greatest possible help to the need of students, I also appreciate the timely feedback from developers when they find any problems or unreasonable/incorrect places. I will revise them as soon as possible. We welcome a better way to implement PR.
Git address
- vue-develop-template webpack + vue 2.x;
- Vue3-develop-template vite 2.0 + Vue 3.x; vue3-develop-template vite 2.0 + Vue 3.x;
The React project
You can refer to the article written by ant Financial’s data experience technology team:
- How to manage a 100,000-line front-end single-page application
This article is not based on the above article, but I found some similarities after reading their article at that time. Compared with this article, this article may be boring and there will be a lot of codes, so students can directly use the warehouse to read it.
① Single page, multiple pages
First of all, we should consider whether the final construction subject of our project is single page, multiple pages, or single page + multiple pages, and analyze their advantages and disadvantages:
- Single page (SPA)
- Advantages: good experience, jump between the route process, can be customized transition animation, used
Lazy loading
Can effectively reduce the home screen time, compared toMultiple page
Reduced user access to static resource servers, etc. - Disadvantages: Large static resources are loaded initially and grow larger as the business grows,
Lazy loading
There are also his disadvantages, do not do special treatment is not conducive to SEO.
- Advantages: good experience, jump between the route process, can be customized transition animation, used
- Multi-page (MPA):
- Advantages: Search engine friendly, low development difficulty.
- Disadvantages: more resource requests, poor whole page refresh experience, data transfer between pages can only rely on
URL
.cookie
.storage
And so on, more limited.
- SPA + MPA
- This approach is common in comparison
Relocation of old MPA projects to SPA
, shortcomings of the combination of the two, the two main communication can only be compatibleMPA shall prevail
- But this way also has his benefits, if your SPA, there is a similar article to share such (no back end straight out, back end back
HTML string
In the case of), want to ensure user experience in SPA to develop a page, in MPA also develop a page, remove useless dependence, or directly use native JS to develop, share out is MPA article page, so canSpeed up the opening of share out and reduce the stress on static resource servers, because if the article page of SPA is shared out, the static resources required by SPAAt the very least, you need to negotiate the request
Of course, ignore this if the service is configured with strong caching.
- This approach is common in comparison
We first determine the construction subject according to the business needs, and we choose the experiential SPA and Vue technology stack.
② Directory structure
In fact, most open source projects have similar directory structures, so we can put together a common SRC directory:
SRC ├─ assets // Resource Directory Images, Styles, Iconfont ├─ Components // Global Generic Component Directory ├─ config // Item Configuration, Block, Switch ├─ Generate instances of routes, requests, stores, etc. Instance and mount the Vue ├ ─ ─ directives / / expand instruction set ├ ─ ─ routes / / routing configuration ├ ─ ─ service / / service layer ├ ─ ─ utils / / tools └ ─ ─ views / / the view layerCopy the code
③ Common components
Components will store common common components from the UI component library, which will be used directly in the project through the alias, and sent to NPM if other projects need to use them.
structure
/ / components simple structure components ├ ─ ─ dist ├ ─ ─ build ├ ─ ─ the SRC ├ ─ ─ modal ├ ─ ─ toast └ ─ ─... ├ ─ ─ index. Js └ ─ ─ package. The jsonCopy the code
Used in the project
If you want to compile it into ES5, you can directly use it in HTML or deploy it on CDN, configure simple packaging logic in build, build automatic packaging and release of UI components with package.json, and finally deploy the content under Dist and publish it to NPM.
We can also use the es6 code directly:
import 'Components/src/modal'
Copy the code
Other items
Let’s say we publish the NPM package called BM-UI and download it to the local NPM I BM-uI-s:
Add include, node_modules/bm-ui to babel-loader or happypack in rules:
// webpack.base.conf.rules: [{
test: /\.vue$/,
loader: 'vue-loader'.options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader'.// add here
include: [resolve('src'), resolve('test'), resolve('node_modules/bm-ui')]}, {... }]...Copy the code
Then use babel-plugin-import directly in your project:
import { modal } from 'bm-ui'
Copy the code
Multiple component libraries
If you have multiple component libraries at the same time, or if you have students who specialize in component development, you can subdivide components internally, adding a file layer.
Components ├ ─ ─ bm - UI - 1 ├ ─ ─ bm - UI - 2 └ ─ ─...Copy the code
Your package configuration file can be placed under components, for unified packaging, of course, if you want to open source or put in the corresponding library.
(4) Global configuration, plug-ins and interceptors
This is actually one of the things that is often overlooked, or rarely aggregated, in the project, but I think it’s one of the most important things in the project, as we’ll see in a few examples.
Global configuration, interceptor directory structure
// If you are intercepting something else.js, you need to get an update on it └ -... └ ─ ─...Copy the code
Global configuration
We might have the following configuration in config/index.js:
// config/index.js
// The current host platform compatibility for multiple platforms should be achieved through some specific functions
export const HOST_PLATFORM = 'WEB'
// I won't go into that
export const NODE_ENV = process.env.NODE_ENV || 'prod'
// Whether to force all requests to access the local MOCK. As you can guess, each request can also control whether to request the MOCK individually
export const AJAX_LOCALLY_ENABLE = false
// Whether to enable monitoring
export const MONITOR_ENABLE = true
// The route is configured by default, and the routing table is not injected from there
export const ROUTER_DEFAULT_CONFIG = {
waitForData: true.transitionOnLoad: true
}
// Axios is configured by default
export const AXIOS_DEFAULT_CONFIG = {
timeout: 20000.maxContentLength: 2000.headers: {}}Vuex is configured by default
export const VUEX_DEFAULT_CONFIG = {
strict: process.env.NODE_ENV ! = ='production'
}
// API default configuration
export const API_DEFAULT_CONFIG = {
mockBaseURL: ' '.mock: true.debug: false.sep: '/'
}
// CONST default configuration
export const CONST_DEFAULT_CONFIG = {
sep: '/'
}
// There are also some business-related configurations
// ...
// There are also some configurations for ease of development
export const CONSOLE_REQUEST_ENABLE = true // Enable request parameter printing
export const CONSOLE_RESPONSE_ENABLE = true // Enable response parameter printing
export const CONSOLE_MONITOR_ENABLE = true // Monitor record printing
Copy the code
As you can see, this is a collection of all the configurations used in the project. Now we instantiate the plugins and inject the corresponding configurations as follows:
Plug-in directory structure
├─ API.js // Service layer API ├─ API.js // Service layer API ├─ API.js // Service layer API.js // Service layer API.js // Service layer API.js // Service layer API.js // Service layer API.js // Service layer API.js // Service layer API.js // Service layer API.js // Service layer API.js // Injection.js // inject Vue prototype plug-in ├ ─ router. Js // route instance plug-inCopy the code
Instantiate the plug-in and inject configuration
Here are two examples: how do we inject configuration interceptors and instantiate them
Instantiate the 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)
// Inject the default configuration and routing table
let routerInstance = newRouter({ ... ROUTER_DEFAULT_CONFIG,routes: ROUTES
})
// Inject interceptor
routerInstance.beforeEach(routerBeforeEachFunc)
export default routerInstance
Copy the code
Instantiate 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)
// Inject request interception
axiosInstance
.interceptors.request.use(requestSuccessFunc, requestFailFunc)
// Inject response interception
axiosInstance
.interceptors.response.use(responseSuccessFunc, responseFailFunc)
export default axiosInstance
Copy the code
We inject the plugin in main.js:
// main.js
import Vue from 'vue'
GLOBAL.vbus = new Vue()
// import 'Components'// global component registration
import 'Directives' / / instructions
// Import plug-ins
import router from 'Plugins/router'
import inject from 'Plugins/inject'
import store from 'Plugins/store'
// Introduce the component library and its component library style
// Libraries that do not require configuration are introduced here
// If necessary, put it in plugin
import VueOnsen from 'vue-onsenui'
import 'onsenui/css/onsenui.css'
import 'onsenui/css/onsen-css-components.css'
// Import the root component
import App from './App'
Vue.use(inject)
Vue.use(VueOnsen)
// render
new Vue({
el: '#app',
router,
store,
template: '<App/>'.components: { App }
})
Copy the code
We don’t refer to the axios instance directly, but as you might have guessed, it refers to the Inject plugin.
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
// All the things that need to be mounted are placed here}}Copy the code
This is where you can mount the API that you want to easily access in your business (vUE instance). Besides $Ajax, the API and const plugins are the main functions in our service layer, which will be described later. This allows us to get our plugin flow roughly up and running.
Request, route interceptor
In the ajax blocker (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)
// Custom request interception logic, can handle permissions, request send monitoring, etc
// ...
return requestObj
}
export function requestFailFunc (requestError) {
// Customize request sending failure logic, disconnection, request sending monitoring, etc
// ...
return Promise.reject(requestError);
}
export function responseSuccessFunc (responseObj) {
// Customize the response success logic, global interception interface, do different processing according to different services, response success monitoring, etc
// ...
// Suppose we request body is
/ / {
// code: 1010,
// msg: 'this is a msg',
// data: null
// }
let resData = responseObj.data
let {code} = resData
switch(code) {
case 0: // If the service succeeds, the callback succeeds
return resData.data;
case 1111:
// If the service fails, perform different operations according to different codes
// For example, the most common authorization expiration jump login
// Specific popovers
// Jump to specific pages, etc
location.href = xxx // The path here can also be placed in the global configuration
return;
default:
// There will also be some special code logic in the business, which we can unify here or lower down to the business layer! responseObj.config.noShowDefaultError && GLOBAL.vbus.$emit('global.$dialog.show', resData.msg);
return Promise.reject(resData); }}export function responseFailFunc (responseError) {
/ / response fails, but according to responseError. Message and responseError response. The status for monitoring process
// ...
return Promise.reject(responseError);
}
Copy the code
Define routing interceptor (config/interceptors/router. Js) :
// config/interceptors/router.js
export function routerBeforeFunc (to, from, next) {
// Here can do page blocking, many background systems are also very like to do permission processing in this
// next(...)
}
Copy the code
Finally in the entrance to the file (config/interceptors/index. Js) introduced and exposed to:
import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from './ajax'
import {routerBeforeEachFunc} from './router'
let interceptors = {
requestSuccessFunc,
requestFailFunc,
responseSuccessFunc,
responseFailFunc,
routerBeforeEachFunc
}
export default interceptors
Copy the code
ResponseSuccessFunc switch default responseSuccessFunc switch default
responseObj.config.noShowDefaultError
This might not be easy to understand
When we request it, we can pass in a noShowDefaultError argument that doesn’t make sense in AXIos for our business. When false or not, we fire the global event global.dialog.show. Global.dialog. show we will register in app.vue:
// app.vue
export default{...created() {
this.bindEvents
},
methods: {
bindEvents() {
GLOBAL.vbus.$on('global.dialog.show'.(msg) = > {
if(msg) return
// We will register events in the global need to manipulate the attempt layer here, which is easy to call in non-business code via publish-subscribe
this.$dialog.popup({
content: msg }); })}... }}Copy the code
It is also possible to put popover states in the Store, where we like to register public states involving view logic, separate from business.
GLOBAL
We mount itwindow
On theGlobal object
We put everything we need to mountwindow.GLOBAL
To reduce the possibility of namespace collisions.vbus
That’s where we startnew Vue()
Mounted on it
GLOBAL.vbus = new Vue()
Copy the code
- Here we are
Promise.reject
Get out, and we can be inerror
The callback handles only our business logic, and the restBroken network
,timeout
,Server error
All of them are processed uniformly by interceptors.
Before and after interceptor processing
Compare the code that sends the request in the business before and after processing:
Before interceptor processing:
this.$axios.get('test_url').then(({code, data}) = > {
if( code === 0 ) {
// Business is successful
} else if () {}
// em... A variety of services are not successfully processed, and if common processing is encountered, it needs to be removed
}, error= > {
// You need to do all kinds of good processing logic according to error, disconnection, timeout, etc.
})
Copy the code
After interceptor processing:
// The service fails and goes to the default popup logic
this.$axios.get('test_url').then(({data}) = > {
// If the service is successful, you can directly operate data
})
// Service failure user-defined
this.$axios.get('test_url', {
noShowDefaultError: true / / is optional
}).then(({data}) = > {
// If the service is successful, you can directly operate data
}, (code, msg) = > {
// When there is a particular code that needs special handling, pass noShowDefaultError:true, handled in this callback
})
Copy the code
Why configure interceptors this way?
Allow us to deal with the unpredictability of requirements during project development faster and better
Many students will feel that such a simple introduction of judgment is not necessary. For example, for a requirement we have recently made, our ToC side project has been opened in the wechat public account before, and we need to open most of the process in the small program through webview, and we do not have time. There was no room to rewrite nearly 100 + page flows in a small program, something we didn’t think of when we started development. At this time, the project must be compatible with the small program side, in the process of compatibility may need to solve the following problems:
- The request path is completely different.
- You need to be compatible with two different permissions systems.
- Some processes need to be changed on the applet side to jump to a specific page.
- Some public accounts
api
, useless in small program, need to call small program logic, need to do compatibility. - Many of the elements on the page, the small program side does not show.
It can be seen that a little carelessness will affect the existing logic of the public number.
- Adding request Interception
interceptors/minaAjax.js
.interceptors/minaRouter.js
Change the original for moreinterceptors/officalAjax.js
.interceptors/officalRouter.js
, in the entry fileinterceptors/index.js
.Based on the currentThe host platform
, that is, global configurationHOST_PLATFORM
Through theThe proxy pattern
andThe strategy pattern
To inject the interceptor for the corresponding platform.inminaAjax.js
Override request path and permission processing, inminaRouter.js
Add page blocking configuration to jump to a specific page“And that solved the problemQuestion 1,2,3
. Question 4
In fact, it is easier to deal with, copy needs to be compatibleapi
Page, rewrite the logic inside, throughThe route interceptor also does the jump processing
.Question 5
It’s also easy to expand by twoCustom instructions V-mina -show and V-mina -hideYou can use instructions directly where the display is out of sync.
Finally with the least code, the fastest time perfect online, has no impact on the existing toC side of the business, and so all compatible logic is mostly gathered together, convenient secondary expansion and modification.
While this may not be convincing in terms of its own business combination, it’s not hard to see that the global configuration/interceptor, while not much code, is one of the core parts of the whole project and could be used to do more awesome things.
⑤ Route configuration and lazy loading
There is nothing reported in directives, but many of the problems are resolved through him, always remember that we can instruct the virtual DOM to operate from there.
The routing configuration
We split the configuration according to the nature of our business and ultimately according to the business process:
├─ ├─ register.js // ├─ register.js // ├─ register.js // ├─ register.js // ├─ register.js //Copy the code
Js is finally exposed to plugins/ Router instances. There are two things to note about this split configuration:
- Depending on the nature of your business, some projects may be suitable
Lines of business
Division, some projects are more suitable forfunction
Partitioning. - Conflict should be avoided or minimized in multi-person collaboration.
Lazy loading
We can use lazy loading to split the static resources and reduce the white screen time. However, the lazy loading also needs to be discussed at the beginning of the article:
- If more components are loaded asynchronously, greater access pressure will be brought to the static resource server/CDN. Meanwhile, if multiple asynchronous components are modified, the version number will change, and the risk of CDN breakdown will be greatly increased during release.
- Lazy loading A blank screen is displayed when an asynchronous component that is not cached is loaded for the first time, resulting in poor user experience.
- Load common components asynchronously, and the page may be displayed unevenly in case of network latency.
This requires some trade-offs between space and time depending on the project.
The following points can serve as a simple reference:
- For projects with controllable access, such as
Company background management system
, you can operate the view unit for asynchronous loading, general components all synchronous loading way. - For applications with high complexity and real-time performance, you can press
Function module splitting
Perform asynchronous component loading. - If the project wants to ensure high integrity and experience, the iteration frequency is controllable, and the first load time is not a concern, asynchronous loading can be used as needed or not used at all.
Most of the size of the packaged main.js is a view component that is imported and registered in the route.
⑥ Service Service layer
The service layer, as one of the other cores in the project, has been the focus “since time immemorial”.
I don’t know if you’ve seen the following way of organizing code:
views/
pay/
index.vue
service.js
components/
a.vue
b.vue
Copy the code
Write the source of write data in service.js
export const CONFIAG = {
apple: 'apple'.banana: 'banana'
}
// ...
//
export function getBInfo ({name = ' ', id = ' '}) {
return this.$ajax.get('/api/info', {
name,
id
}).then({age} => {
this.$modal.show({
content: age
})
})
}
// write the request method
export function getAInfo ({name = ' ', id = ' '}) {
return this.$ajax.get('/api/info', {
name,
id
})
}
...
Copy the code
Simple analysis:
- ① There is no more to say, the separation is not simple enough, as a secondary development, you have to find this popover in the end where out.
- (2) looks very good, without business logic, but I don’t know you and haven’t met such situation, often have other business need to use the same enumeration, request the same interface, and to develop other business students don’t know you’re here, there are a data source, the final result is data source code redundancy.
I believe that ② can be seen in most projects.
So our purpose is obvious, to solve the redundancy, easy to use, we put enumeration and request interface method, through the plug-in, mount to a large object, inject Vue prototype, aspect business use can be.
Directory level (for reference only)
API ├ service ├ ─ ─ ─ ─ index. The js / / entry documents ├ ─ ─ the order. The js / / order related interface configuration └ ─ ─... ├ ─ ─ const ├ ─ ─ index. Js / / entry documents ├ ─ ─ the order. The js / / order constant interface configuration └ ─ ─... ├ ─ ─ store / / vuex state management ├ ─ ─ expands / / expand ├ ─ ─ the monitor. The js / / monitor ├ ─ ─ beacon. Js/dot / ├ ─ ─ localstorage. Js / / local storage └ ─ ─... // press └...Copy the code
Abstract model
First, the request interface model can be extracted according to the domain model (service/ API /index.js):
{
user: [{
name: 'info'.method: 'GET'.desc: 'Test interface 1'.path: '/api/info'.mockPath: '/api/info'.params: {
a: 1.b: 2}}, {name: 'info2'.method: 'GET'.desc: 'Test Interface 2'.path: '/api/info2'.mockPath: '/api/info2'.params: {
a: 1.b: 2.b: 3}}].order: [{
name: 'change'.method: 'POST'.desc: 'Order Change'.path: '/api/order/change'.mockPath: '/api/order/change'.params: {
type: 'SUCCESS'}}]... }Copy the code
Several functions required under customization:
- Automatic interception of request parameters.
- If the request parameters are not sent, the default configuration parameters are sent.
- You need a namespace.
- Enable debugging mode through global configuration.
- Global configuration to control whether to use local mocks or online interfaces.
Plug-in to write
With the functionality customized, start writing simple plugins/api.js plugins:
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}`.// Namespace
url = mock ? mockPath : path,// Control whether to mock or line
baseURL = mock && mockBaseURL
// Enable debugging mode through global configuration.
debug && console.info('invokes the service layer interface${apiname}, the interface is described as${desc}`)
debug && assert(name, `${apiUrl}: The interface name attribute cannot be empty)
debug && assert(apiUrl.indexOf('/') = = =0.`${apiUrl}: Indicates the path of the interface. The value must start with a slash (/))
Object.defineProperty(this.api, `${namespace}${sep}${name}`, {
value(outerParams, outerOptions) {
// Request parameters are automatically intercepted.
// If the request parameters are not worn, send the default configuration parameters.
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) {
// Here you can do case conversion and other types of RESTFUl compatibility
if (options.method === 'POST') {
options.data = data
} else if (options.method === 'GET') {
options.params = data
}
return options
}
// Inject the model and global configuration and expose it
export default new MakeApi({
config: API_CONFIG, ... API_DEFAULT_CONFIG })['api']
Copy the code
Mount to the Vue prototype, as mentioned above, via plugins/inject.js
import api from './api'
export default {
install: (Vue, options) = > {
Vue.prototype.$api = api
// All the things that need to be mounted are placed here}}Copy the code
use
This way we can happily use business layer code in business:
/ /. Vue
export default {
methods: {
test() {
this.$api['order/info'] ({a: 1.b: 2})}}}Copy the code
Can be used even outside of business:
import api from 'Plugins/api'
api['order/info'] ({a: 1.b: 2
})
Copy the code
Of course, for projects with high operational efficiency, to avoid excessive memory usage, we need to transform the API, introduce use in a deconstructive way, and finally reduce packaging volume by using The tree-shaking of Webpack. A couple of simple ideas
In general, many people collaboration when everyone can look at the API if there is a corresponding interface, when the volume up, someone is sure to find, or find it more difficult, in this time we can request the interceptor, the url of the current request and under the request of the API for judgment, if there is a repeated interface request path, The developer is reminded that the relevant request has been configured, and can configure it again depending on the situation.
Finally, we can extend the Service layer’s various functions:
basis
- api:
Asynchronous and back-end interactions
- const:
Constant enumeration
- store:
Vuex
State management
expand
- LocalStorage: local data, slightly encapsulated, supports access to objects
- monitor:
monitoring
Function, custom collection strategy, callapi
Interface send in - beacon:
dot
Function, custom collection strategy, callapi
Interface send in - .
Const, localStorage, Monitor and beacon can be expanded and exposed to the business according to the business. The idea is the same. Here, store(Vuex) is emphasized.
By the way, if this doesn’t make any sense, think about the singleton pattern in plugins/api.js. Should I use it?
⑦ State management and view splitting
Vuex source code analysis can be seen in my previous article.
Do we really need state management?
The answer is no, even if your project reaches 100,000 lines of code, that doesn’t mean you have to use Vuex, it depends on the business scenario.
The business scenario
- Category I items:It is not recommended to use Vuex because the complexity of services and views is not high, which may cause development and maintenance costs, use simple
vbus
doThe namespace, to decouple.
let vbus = new Vue()
vbus.$on('print.hello'.() = > {
console.log('hello')
})
vbus.$emit('print.hello')
Copy the code
- Category 2: Similar
Multi-person collaborative project management
.Youdao Cloud Note
.Netease Cloud Music
.Wechat web/desktop version
Etc.applicationFeature concentration, high space utilization, real-time interactive projects, no doubtVuex is a better choice
. This is the kind of application where we can directlyDetach from the business domain model
:
Store ├ ─ ─ index. Js ├ ─ ─ actions. The js/action/root level ├ ─ ─ mutations. Js / / root level mutation └ ─ ─ modules ├ ─ ─ the user. The js / / user modules ├ ─ ─ ├─ ├─ class.txt // class.txt // class.txt // Class.txt // Class.txt // Class.txtCopy the code
Of course, vuEX may not be the best choice for this kind of project, but interested students can learn RXJS.
- Third category:
The background system
orProjects with low business coupling between pages
, such projects should account for a large proportion. Let’s think about such projects:
There are not many global shared states, but it is inevitable that there will be functions with high complexity in a module (customer service system, real-time chat, multi-person collaboration, etc.). At this time, for the manageability of the project, we also manage in store. With the iteration of the project, it is not difficult to encounter such situations:
store/
...
modules/
b.js
...
views/
...
a/
b.js
...
Copy the code
- Imagine that there are dozens of modules, corresponding to hundreds of business modules here, the cost of debugging and development between two horizontal directories is huge.
- These modules can be accessed anywhere in the project, but often they are redundant and rarely referred to by any other module than the function module referenced.
- The maintainability of a project increases as the project grows.
How to solve the problem of store use for category 3 projects?
Let’s start with our goals:
- Modules in a project can decide whether to use Vuex or not. (Progressive enhancement)
- To jump from a stateful module to a module that doesn’t have one, we don’t want to mount the previous state to the store, we want to run more efficiently. (redundant)
- Make state management for such projects more maintainable. (Development cost/communication cost)
implementation
Vuex provides both registerModule and unregisterModule to solve these problems. We put globally shared state in service/store:
service/
store/
index.js
actions.js
mutations.js
getters.js
state.js
Copy the code
In general, the global status of this kind of project is not much, if more split module can be.
Write plug-ins to generate store instances:
import Vue from 'vue'
import Vuex from 'vuex'
import {VUEX_DEFAULT_CONFIG} from 'Config'
import commonStore from 'Service/store'
Vue.use(Vuex)
export default newVuex.Store({ ... commonStore, ... VUEX_DEFAULT_CONFIG })Copy the code
Layering a page or module that requires state management:
views/
pageA/
index.vue
components/
a.vue
b.vue
...
children/
childrenA.vue
childrenB.vue
...
store/
index.js
actions.js
moduleA.js
moduleB.js
Copy the code
Module directly includes getters, mutations, state, and we make an article in 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'])}}Copy the code
Finally introduced in index.vue, register these state and management state rules before the page jump, uninstall these state and management state rules before the route leaves:
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()
}
}
Copy the code
Of course, if your status is shared globally, do not perform uninstall.
This solves the first three problems, different developers in the development of the page, according to the characteristics of the page, progressive enhancement of the choice of a form of development.
other
Here is a brief list of other aspects, need to be in-depth and use according to the project.
Package, build
There are already many optimization methods on the web: DLL, happypack, multithreading packaging, etc., but as the project code level, every time dev preservation of compilation is more and more slow, and the slow time of the year we are going to have to split, that’s for sure, and before the break up as far as possible to accommodate more maintainable code, there are a few to try and avoid points:
- Optimize project flow: This point may seem useless, but the change is the most intuitive, the page/business simplification will be directly reflected in the code, but also increase the project maintainability, extensibility, etc.
- Reduce the vertical depth of project file hierarchy.
- Reduce unnecessary business code and avoid unnecessary or excessive dependencies (similar
moment.js
Such a library), etc.
style
- Separate modules as much as possible to make the style more flexible underneath, while minimizing redundancy.
- If you use
sass
If so, make good use of%placeholder
Reduce garbage code packaging.
MPA applications with too much style redundancy, %placeholder will also help you.
Mock
Many large companies have their own mock platform. The current backend formats the interface and generates the corresponding Mock API. If you don’t have a mock platform, look for a relatively easy tool such as JSON-server.
Code specification
Force esLint to hang on git hooks. Regular diff code, regular training etc.
TypeScript
It is highly recommended to write projects with TS, which can be awkward to write.vue, so that most of the front-end errors are resolved at compile time, while also improving the browser runtime efficiency, possibly reducing the re-optimize phase time, etc.
test
This is also a very important part of the project, and if your project doesn’t already use some testing tools, please get them in as soon as possible. I won’t go into details here.
Split system
When the project reaches a certain level of business, due to too many modules in the project, the maintenance cost of new students and development cost will rise sharply, so we have to split the project. The simple practice of our ToB project in the split system will be shared later.
The last
There are a variety of mature solutions, here is just a simple build share, which depends on the version of our stable version, need to upgrade according to their own actual situation.
The bottom construction of a project is often neglected by the front end. We should not only look at a project or the whole business line from an overall perspective, but also strive for excellence in each line of code and constantly optimize the development experience, so as to better deal with the unknown changes after slowly accumulating.
- As for me, you can call me Zero and attach Git address
- Article title picture address
Allow me to conclude with a little advertising
EROS
If the front end students want to try using Vue to develop apps, or those who are familiar with WEEx development, they can try using eros, our open source solution. Although there is no advertising, but incomplete statistics, there are 50 online apps, looking forward to your joining.
- Discussion on the evolution of hybrid application
- Learn more about WEEX
- [Article] A guide to Weex-EROS
- The project address
- The document address
Finally, some product screenshots are attached
(escape ~)