基于Vue的前端架構(gòu),我總結(jié)15點(diǎn)經(jīng)驗(yàn)
(給前端大學(xué)加星標(biāo),提升前端技能.)
作者:codexu
https://juejin.cn/post/6901466994478940168
1.分解需求
技術(shù)棧
考慮到后續(xù)招人和現(xiàn)有人員的技術(shù)棧,選擇 Vue 作為框架。
公司主要業(yè)務(wù)是 GIS 和 BIM,通常開發(fā)一些中大型的系統(tǒng),所以 vue-router 和 vuex 都是必不可少的。
放棄了 Element UI 選擇了 Ant Design Vue(最近 Element 好像復(fù)活了,麻蛋)。
工具庫選擇 lodash。
建立腳手架
搭建 NPM 私服。
使用 Node 環(huán)境開發(fā) CLI 工具,參考我自己寫過的一篇 -【 搭建自己的腳手架—“優(yōu)雅”生成前端工程】。
基于 @vue/cli 搭建基礎(chǔ)的模板(大家都比較了解,節(jié)省開發(fā)時(shí)間,遠(yuǎn)勝于從零開始搭建)。
根據(jù)業(yè)務(wù)需求定義各種開發(fā)中可能用到的功能(組件庫、狀態(tài)管理、過濾器、指令、CSS內(nèi)置變量、CSS Mixins、表單驗(yàn)證、工具函數(shù)等)。
性能優(yōu)化,例如對(duì) Ant Design Vue 組件庫的優(yōu)化。
開發(fā)規(guī)范
對(duì)代碼風(fēng)格、命名規(guī)則、目錄結(jié)構(gòu)進(jìn)行統(tǒng)一規(guī)范。
靜態(tài)資源的使用規(guī)范。
單元測(cè)試、提交線上測(cè)試規(guī)范。
Git 提交記錄和多人協(xié)作規(guī)范。
2.樣式
CSS 預(yù)處理器的選擇
Sass/Scss ?
Less ?
Stylus ?
為什么選擇了兩個(gè)?因?yàn)楣緢F(tuán)隊(duì)跟傾向于使用 scss 開發(fā),less 是為了覆蓋 ant design vue 的樣式,stylus 只有我自己喜歡這種風(fēng)格。
局部樣式與全局樣式
局部樣式
一般都是使用 scoped 方案:
...
全局樣式
全局樣式 目錄:@/styles
variable.scss: 全局變量管理 mixins.scss: 全局 Mixins 管理 global.scss: 全局樣式
其中 variable.scss 和 mixins.scss 會(huì)優(yōu)先于 global.css 加載,并且可以不通過 import 的方式在項(xiàng)目中任何位置使用這些變量和 mixins。
// vue.config.jsmodule.exports = {css: {loaderOptions: {sass: {prependData: `@import '@/styles/variable.scss';@import '@/styles/mixins.scss';`,},},},}
體驗(yàn)優(yōu)化
頁面載入進(jìn)度條
使用 nprogress 對(duì)路由跳轉(zhuǎn)時(shí)做一個(gè)偽進(jìn)度條,這樣做在網(wǎng)絡(luò)不好的情況下可以讓用戶知道頁面已經(jīng)在加載了:
import NProgress from 'nprogress';router.beforeEach(() => {NProgress.start();});router.afterEach(() => {NProgress.done();});
美化滾動(dòng)條
一直用 Mac 做前端,突然發(fā)現(xiàn)同事的 Windows 上出現(xiàn)了十分丑陋的滾動(dòng)條,為了保持一致:
::-webkit-scrollbar {width: 6px;height: 6px;}::-webkit-scrollbar-track {width: 6px;background: rgba(#101F1C, 0.1);-webkit-border-radius: 2em;-moz-border-radius: 2em;border-radius: 2em;}::-webkit-scrollbar-thumb {background-color: rgba(#101F1C, 0.5);background-clip: padding-box;min-height: 28px;-webkit-border-radius: 2em;-moz-border-radius: 2em;border-radius: 2em;}::-webkit-scrollbar-thumb:hover {background-color: rgba(#101F1C, 1);}
靜態(tài)資源加載頁面
首次加載頁面時(shí),會(huì)產(chǎn)生大量的白屏?xí)r間,這時(shí)做一個(gè) loading 效果看起來會(huì)很友好,其實(shí)很簡單,直接在 public/index.html 里寫一些靜態(tài)的樣式即可。
移動(dòng)端 100vh 問題
在移動(dòng)端使用 100vh 時(shí),發(fā)現(xiàn)在 Chrome、Safari 瀏覽器中,因?yàn)闉g覽器欄和一些導(dǎo)航欄、鏈接欄導(dǎo)致不一樣的呈現(xiàn):
你以為的 100vh === 視口高度
實(shí)際上 100vh === 視口高度 + 瀏覽器工具欄(地址欄等等)的高度
解決方案
安裝 vh-check npm install vh-check --save
import vhCheck from 'vh-check';vhCheck('browser-address-bar');
定義一個(gè) CSS Mixin
@mixin vh($height: 100vh) {height: $height;height: calc(#{$height} - var(--browser-address-bar, 0px));}
之后就是哪里不會(huì)點(diǎn)哪里。
3.組件庫
因?yàn)?Element UI 長期沒更新,并且之前使用過 React 的 Ant Design(重點(diǎn)),所以組件庫選擇了Ant Design Vue。
覆蓋 Ant ?Design Vue 樣式
設(shè)計(jì)師眼中的 Ant Design === '丑'(心酸)。
1.使用 .less 文件
Ant ?Design Vue 的樣式使用了 Less 作為開發(fā)語言,并定義了一系列全局/組件的樣式變量,所以需要安裝了 less、less-loader,在 @/styles/antd-theme.less 可以覆蓋默認(rèn)樣式。
優(yōu)點(diǎn)是:
方便快捷,可以修改 class,覆蓋默認(rèn)變量。
缺點(diǎn)是:
必須引入 @import '~ant-design-vue/dist/antd.less'; ,引入后會(huì)將所有的組件樣式全部引入,導(dǎo)致打包后的 css 體積達(dá)到 500kb 左右。
2.使用 JavaScript 對(duì)象
通過 JavaScript 對(duì)象的方式可以修改內(nèi)置變量,需要對(duì) Less 進(jìn)行配置:
// vue.config.jsconst modifyVars = require('./src/styles/antdTheme.js');module.exports = {css: {loaderOptions: {less: {lessOptions: {javascriptEnabled: true,modifyVars,},},},},}
這一步還可以繼續(xù)優(yōu)化,通過 babel-plugin-import 使 Ant Design Vue 的組件樣式可以按需加載:
// babel.config.jsmodule.exports = {presets: ['@vue/cli-plugin-babel/preset',],plugins: [['import',{ libraryName: 'ant-design-vue', libraryDirectory: 'es', style: true },],],};
優(yōu)點(diǎn)是:
可以按需引入,打包后的 CSS 體積取決于你引用了多少個(gè)組件。
缺點(diǎn)是:
不能使用 class 進(jìn)行樣式覆蓋。
干掉無用的圖標(biāo)
Ant Design Vue 把所有的 Icon 一次性引入(不管你因用了多少個(gè)組件),這使得體積打包后圖標(biāo)所占的體積竟然有幾百 kb 之多。這些圖標(biāo)大多數(shù)不會(huì)被設(shè)計(jì)師所采納,所以部分圖標(biāo)都應(yīng)該被干掉:
創(chuàng)建一個(gè) icons.js 來管理 Ant ?Design Vue 圖標(biāo),這里以一個(gè) Loading 圖標(biāo)為例:
// @/src/assets/icons.jsexport?{?default?as?LoadingOutline?}?from?'@ant-design/icons/lib/outline/LoadingOutline';
如何知道你要加載的圖標(biāo)在什么路徑下?
在 @ant-design/icons/lib 目錄下有三種風(fēng)格的圖標(biāo),分別是 fill、outline、twotone,這里面內(nèi)部的文件并不是 svg 格式,而是 js 和 ts 格式,這就是為什么我們可以這么引入圖標(biāo)的關(guān)鍵所在了。
下一步是通過配置 vue.config.js 將這個(gè)文件引入進(jìn)來:
// vue.config.jsmodule.exports = {configureWebpack: {resolve: {alias: {'@ant-design/icons/lib/dist$': path.resolve(__dirname, './src/assets/icons.js'),},},},}
解決 Moment 多國語
解決到這之后,Ant Design Vue 居然還很大,這是因?yàn)?moment 是 Ant Design Vue 中有強(qiáng)依賴該插件,所以使用 webpack 插件減小打包體積,這里我們只保留 zh-cn 語言包:
// vue.config.jsmodule.exports = {chainWebpack: (config) => {config.plugin('ContextReplacementPlugin').use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/, /zh-cn/]);},}
部分組件需要在頁面內(nèi)引用
Ant Design Vue 中部分體積較大的組件,例如 DatePicker,根據(jù)業(yè)務(wù)需求,應(yīng)考慮在頁面中進(jìn)行加載,盡量保證首屏加載的速度:
4.靜態(tài)資源與圖標(biāo)
靜態(tài)資源
所有的靜態(tài)資源文件都會(huì)上傳到 阿里云 OSS 上,所以在環(huán)境變量上加以區(qū)分。
.env.development 與 .env.production 的 VUE_APP_STATIC_URL 屬性分別配置了本地的靜態(tài)資源服務(wù)器地址和線上 OSS 的地址。
本地的靜態(tài)資源服務(wù)器是通過 pm2 + http-server 創(chuàng)建的,設(shè)計(jì)師切完直接扔進(jìn)來就好了。
自動(dòng)注冊(cè) Svg 圖標(biāo)
在日常的開發(fā)中,總是會(huì)有著大量的圖標(biāo)需要使用,這里我們直接選擇使用 SVG 圖標(biāo)。但是如果每次使用圖標(biāo)還需要通過路徑找到這張圖標(biāo)豈不是很麻煩?
下面這種才是我想要的方案(直接 name 等于 文件名即可):
而且最后打包后需要合并成一張雪碧圖。
首先需要對(duì) ?@/assets/icons 文件夾下的 svg 圖標(biāo)進(jìn)行自動(dòng)注冊(cè),需要對(duì) webpack 和 svg-sprite-loader 進(jìn)行了相關(guān)設(shè)置,文件全部打包成 svg-sprite。
module.exports = {chainWebpack: (config) => {config.module.rule('svg').exclude.add(resolve('src/assets/icons')).end();config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/assets/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader');},}
寫一個(gè)全局用的 Vue 組件 :
// @/components/m-svg/index.jsconst requireAll = (requireContext) => requireContext.keys().map(requireContext);const req = require.context('@/assets/icons', false, /\.svg$/);requireAll(req);
@/components/m-svg/index.vue
.mw-svg {width: 1.4em;height: 1.4em;fill: currentColor;overflow: hidden;line-height: 1em;display: inline-block;}
參數(shù) name
類型:String
默認(rèn)值:null
說明:放置在
@/assets/icons文件夾下的文件名
樣式
圖標(biāo)的大小可以通過 width + height 屬性改變。
通過改變 font-size 屬性改變,寬高 = font-zise * 1.4
5.異步請(qǐng)求
封裝 Axios
在 @/libs/request.js 路徑下對(duì) Axios 進(jìn)行封裝,封裝了請(qǐng)求參數(shù),請(qǐng)求頭,以及錯(cuò)誤提示信息、 request 攔截器、response 攔截器、統(tǒng)一的錯(cuò)誤處理、baseURL 設(shè)置等。
廢話不說直接貼代碼:
import axios from 'axios';import get from 'lodash/get';import storage from 'store';// 創(chuàng)建 axios 實(shí)例const request = axios.create({// API 請(qǐng)求的默認(rèn)前綴baseURL: process.env.VUE_APP_BASE_URL,timeout: 10000, // 請(qǐng)求超時(shí)時(shí)間});// 異常攔截處理器const errorHandler = (error) => {const status = get(error, 'response.status');switch (status) {/* eslint-disable no-param-reassign */case 400: error.message = '請(qǐng)求錯(cuò)誤'; break;case 401: error.message = '未授權(quán),請(qǐng)登錄'; break;case 403: error.message = '拒絕訪問'; break;case 404: error.message = `請(qǐng)求地址出錯(cuò): ${error.response.config.url}`; break;case 408: error.message = '請(qǐng)求超時(shí)'; break;case 500: error.message = '服務(wù)器內(nèi)部錯(cuò)誤'; break;case 501: error.message = '服務(wù)未實(shí)現(xiàn)'; break;case 502: error.message = '網(wǎng)關(guān)錯(cuò)誤'; break;case 503: error.message = '服務(wù)不可用'; break;case 504: error.message = '網(wǎng)關(guān)超時(shí)'; break;case 505: error.message = 'HTTP版本不受支持'; break;default: break;/* eslint-disabled */}return Promise.reject(error);};// request interceptorrequest.interceptors.request.use((config) => {// 如果 token 存在// 讓每個(gè)請(qǐng)求攜帶自定義 token 請(qǐng)根據(jù)實(shí)際情況自行修改// eslint-disable-next-line no-param-reassignconfig.headers.Authorization = `bearer ${storage.get('ACCESS_TOKEN')}`;return config;}, errorHandler);// response interceptorrequest.interceptors.response.use((response) => {const dataAxios = response.data;// 這個(gè)狀態(tài)碼是和后端約定的const { code } = dataAxios;// 根據(jù) code 進(jìn)行判斷if (code === undefined) {// 如果沒有 code 代表這不是項(xiàng)目后端開發(fā)的接口return dataAxios;// eslint-disable-next-line no-else-return} else {// 有 code 代表這是一個(gè)后端接口 可以進(jìn)行進(jìn)一步的判斷switch (code) {case 200:// [ 示例 ] code === 200 代表沒有錯(cuò)誤return dataAxios.data;case 'xxx':// [ 示例 ] 其它和后臺(tái)約定的 codereturn 'xxx';default:// 不是正確的 codereturn '不是正確的code';}}}, errorHandler);export default request;
通過 VUE_APP_BASE_URL 區(qū)分線上與開發(fā)環(huán)境的 API 地址。
code 起到一個(gè)比較關(guān)鍵的作用,例如 token 過期時(shí)的驗(yàn)證。
使用了一個(gè)叫 sotre 的包作為本地儲(chǔ)存的工具用來存儲(chǔ) token。
跨域問題
跨域問題一般情況直接找后端解決了,你要是不好意思打擾他們的話,可以用 devServer 提供的 proxy 代理:
// vue.config.jsdevServer: {proxy: {'/api': {target: 'http://47.100.186.132/your-path/api',ws: true,changeOrigin: true,pathRewrite: {'^/api': ''}}}}
Mock 數(shù)據(jù)
一個(gè)很常見的情況,后端接口沒出來,前端在這干瞪眼。
Mock 數(shù)據(jù)功能是基于 mock.js (opens new window)開發(fā),通過 webpack 進(jìn)行自動(dòng)加載 mock 配置文件。
規(guī)則
所有的 mock 配置文件均應(yīng)放置在
@/mock/services路徑內(nèi)。在
@/mock/services內(nèi)部可以建立業(yè)務(wù)相關(guān)的文件夾分類存放配置文件。所有的配置文件應(yīng)按照
***.mock.js的命名規(guī)范創(chuàng)建。配置文件使用 ES6 Module 導(dǎo)出
export default或export一個(gè)數(shù)組。
入口文件
import Mock from 'mockjs';Mock.setup({timeout: '500-800',});const context = require.context('./services', true, /\.mock.js$/);context.keys().forEach((key) => {Object.keys(context(key)).forEach((paramKey) => {Mock.mock(...context(key)[paramKey]);});});
示例模板
import Mock from 'mockjs';const { Random } = Mock;export default [RegExp('/example.*'),'get',{'range|50-100': 50,'data|10': [{// 唯一 IDid: '@guid()',// 生成一個(gè)中文名字cname: '@cname()',// 生成一個(gè) urlurl: '@url()',// 生成一個(gè)地址county: Mock.mock('@county(true)'),// 從數(shù)組中隨機(jī)選擇一個(gè)值'array|1': ['A', 'B', 'C', 'D', 'E'],// 隨機(jī)生成一個(gè)時(shí)間time: '@datetime()',// 生成一張圖片image: Random.dataImage('200x100', 'Mock Image'),},],},];
6.路由
Layout
布局暫時(shí)分為三大類:
frameIn:基于
BasicLayout,通常需要登錄或權(quán)限認(rèn)證的路由。frameOut:不需要?jiǎng)討B(tài)判斷權(quán)限的路由,如登錄頁或通用頁面。
errorPage:例如404。
權(quán)限驗(yàn)證
通過獲取當(dāng)前用戶的權(quán)限去比對(duì)路由表,生成當(dāng)前用戶具的權(quán)限可訪問的路由表,通過 router.addRoutes 動(dòng)態(tài)掛載到 router 上。
判斷頁面是否需要登陸狀態(tài),需要?jiǎng)t跳轉(zhuǎn)到 /user/login
本地存儲(chǔ)中不存在 token 則跳轉(zhuǎn)到 /user/login
如果存在 token,用戶信息不存在,自動(dòng)調(diào)用 vuex '/system/user/getInfo'
在路由中,集成了權(quán)限驗(yàn)證的功能,需要為頁面增加權(quán)限時(shí),在 meta 下添加相應(yīng)的 key:
auth
類型:Boolean
說明:當(dāng) auth 為 true 時(shí),此頁面需要進(jìn)行登陸權(quán)限驗(yàn)證,只針對(duì) frameIn 路由有效。
permissions
類型:Object
說明:permissions 每一個(gè) key 對(duì)應(yīng)權(quán)限功能的驗(yàn)證,當(dāng) key 的值為 true 時(shí),代表具有權(quán)限,若 key 為 false,配合
v-permission指令,可以隱藏相應(yīng)的 DOM。
在這里貼一下路由跳轉(zhuǎn)時(shí)權(quán)限驗(yàn)證的代碼:
import router from '@/router';import store from '@/store';import storage from 'store';import util from '@/libs/utils';// 進(jìn)度條import NProgress from 'nprogress';import 'nprogress/nprogress.css';const loginRoutePath = '/user/login';const defaultRoutePath = '/home';/*** 路由攔截* 權(quán)限驗(yàn)證*/router.beforeEach(async (to, from, next) => {// 進(jìn)度條NProgress.start();// 驗(yàn)證當(dāng)前路由所有的匹配中是否需要有登錄驗(yàn)證的if (to.matched.some((r) => r.meta.auth)) {// 是否存有token作為驗(yàn)證是否登錄的條件const token = storage.get('ACCESS_TOKEN');if (token && token !== 'undefined') {// 是否處于登錄頁面if (to.path === loginRoutePath) {next({ path: defaultRoutePath });// 查詢是否儲(chǔ)存用戶信息} else if (Object.keys(store.state.system.user.info).length === 0) {store.dispatch('system/user/getInfo').then(() => {next();});} else {next();}} else {// 沒有登錄的時(shí)候跳轉(zhuǎn)到登錄界面// 攜帶上登陸成功之后需要跳轉(zhuǎn)的頁面完整路徑next({name: 'Login',query: {redirect: to.fullPath,},});NProgress.done();}} else {// 不需要身份校驗(yàn) 直接通過next();}});router.afterEach((to) => {// 進(jìn)度條NProgress.done();util.title(to.meta.title);});
頁面開發(fā)
根據(jù)業(yè)務(wù)需要?jiǎng)澐郑凑章酚蓪蛹?jí)在 views 中創(chuàng)建相對(duì)應(yīng)的頁面組件,以文件夾的形式創(chuàng)建,并在文件夾內(nèi)創(chuàng)建 index.vue 文件作為頁面的入口文件。
頁面內(nèi)的組件:在頁面文件夾下創(chuàng)建 components 文件夾,在其內(nèi)部對(duì)應(yīng)創(chuàng)建相應(yīng)的組件文件,如果是復(fù)雜組件,應(yīng)以文件夾的形式創(chuàng)建組件。
工具模塊:能夠高度抽象的工具模塊,應(yīng)創(chuàng)建在 @/src/libs 內(nèi)創(chuàng)建 js 文件。
7、構(gòu)建優(yōu)化
包分析工具
構(gòu)建代碼之后,到底是什么占用了這么多空間?靠直覺猜測(cè)或者使用 webpack-bundle-analyzer。
const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');module.exports = {chainWebpack: (config) => {if (process.env.use_analyzer) {config.plugin('webpack-bundle-analyzer').use(WebpackBundleAnalyzer.BundleAnalyzerPlugin);}},};
開啟 Gzip
對(duì),這這么一句話,后端就得支持你的 .gz 文件了,而你只需要坐著等老板夸:
chainWebpack: (config) => {config.plugin('CompressionPlugin').use(CompressionPlugin, []);},
路由懶加載
這塊 @vue/cli 已經(jīng)幫忙處理好了,但也需要了解一下他的原理和如何配置。
{path: 'home',name: 'Home',component: () => import(/* webpackChunkName: "home" */ '@/views/home/index.vue'),},
webpackChunkName 這條注釋還是很有必要加的,至少你打包后知道又是哪個(gè)頁面變得又臭又大。
Preload & Prefetch
不清楚這兩個(gè)功能的去 @vue/cli 補(bǔ)課,這兩個(gè)功能非常有助于你處理加載的性能。
8.測(cè)試框架
直接使用了官方提供的 Vue Test Utils,這東西可以對(duì)組件進(jìn)行測(cè)試,很不錯(cuò)。
寫單元測(cè)試在團(tuán)隊(duì)里其實(shí)很難推進(jìn),不知道大家怎么看。
9.組件庫
對(duì)于很多第三方的工具,我堅(jiān)持認(rèn)為二次封裝成 vue 插件并沒有多少開發(fā)成本,反而讓你在后續(xù)的開發(fā)中變得很靈活。
我對(duì)以下庫進(jìn)行了 vue 插件的封裝,并提交到 npm 私服:
數(shù)字動(dòng)畫
代碼高亮
大文件上傳(切片、斷點(diǎn)續(xù)傳、秒傳)需要與后端配合
圖片預(yù)覽
Excel 導(dǎo)入導(dǎo)出
富文本編輯器
Markdown 編輯器
代碼編輯器
大文件上傳有興趣的可以留言,我后續(xù)單獨(dú)拎出來詳細(xì)的寫一下這塊。
10.Vuex
內(nèi)置一些功能,主要是對(duì)以下這些功能做了一些封裝:
用戶信息管理(儲(chǔ)存信息、對(duì) token 進(jìn)行操作等)
登陸(調(diào)接口)
菜單管理(儲(chǔ)存路由信息,生成菜單,模糊查詢等功能)
UA信息
全屏操作
Loading
日志管理(消息提醒、日志留存、日志上報(bào))
11.過濾器
過濾器是 Vue 提供的一個(gè)很好用的功能,聽說 vue3 沒了?
{{ message | capitalize }}我寫了幾個(gè)常用的過濾器:
日期時(shí)間
剩余時(shí)間
區(qū)分環(huán)境的鏈接(主要針對(duì)本地靜態(tài)資源服務(wù)器和 OSS )
文件大小
數(shù)字金額
浮點(diǎn)型精度
12.指令
自定義指令可以提供很好的幫助:
組件權(quán)限驗(yàn)證
文本復(fù)制
快捷鍵綁定
滾動(dòng)至指定位置
圖片懶加載
焦點(diǎn)
13.開發(fā)規(guī)范
ESLint
不管是多人合作還是個(gè)人項(xiàng)目,代碼規(guī)范都是很重要的。這樣做不僅可以很大程度地避免基本語法錯(cuò)誤,也保證了代碼的可讀性。
這里我們采用了 Airbnb JavaScript Style Guide。
這套規(guī)范給我的感覺就是 很嚴(yán)謹(jǐn)!
CSS 規(guī)范
降低選擇器復(fù)雜性
瀏覽器讀取選擇器,遵循的原則是從選擇器的右邊到左邊讀取。
#block .text p {color: red;}
查找所有 P 元素。
查找結(jié)果 1 中的元素是否有類名為 text 的父元素
查找結(jié)果 2 中的元素是否有 id 為 block 的父元素
選擇器優(yōu)先級(jí)
內(nèi)聯(lián) > ID選擇器 > 類選擇器 > 標(biāo)簽選擇器
選擇器越短越好。
盡量使用高優(yōu)先級(jí)的選擇器,例如 ID 和類選擇器。
避免使用通配符 *。
使用 flexbox
在早期的 CSS 布局方式中我們能對(duì)元素實(shí)行絕對(duì)定位、相對(duì)定位或浮動(dòng)定位。而現(xiàn)在,我們有了新的布局方式 flexbox,它比起早期的布局方式來說有個(gè)優(yōu)勢(shì),那就是性能比較好。不過 flexbox 兼容性還是有點(diǎn)問題,不是所有瀏覽器都支持它,所以要謹(jǐn)慎使用。各瀏覽器兼容性:
Chrome 29+
Firefox 28+
Internet Explorer 11
Opera 17+
Safari 6.1+ (prefixed with -webkit-)
Android 4.4+
iOS 7.1+ (prefixed with -webkit-)
動(dòng)畫性能優(yōu)化
在 CSS 中,transforms 和 opacity 這兩個(gè)屬性更改不會(huì)觸發(fā)重排與重繪,它們是可以由合成器(composite)單獨(dú)處理的屬性。
屬性值
當(dāng)數(shù)值為 0 - 1 之間的小數(shù)時(shí),建議省略整數(shù)部分的 0。
當(dāng)長度為 0 時(shí)建議省略單位。
建議不使用命名色值。
建議當(dāng)元素需要撐起高度以包含內(nèi)部的浮動(dòng)元素時(shí),通過對(duì)偽類設(shè)置 clear 或觸發(fā) BFC 的方式進(jìn)行 clearfix。盡量不使用增加空標(biāo)簽的方式。
除公共樣式之外,在業(yè)務(wù)代碼中盡量不能使用 !important。
建議將 z-index 進(jìn)行分層,對(duì)文檔流外絕對(duì)定位元素的視覺層級(jí)關(guān)系進(jìn)行管理。
字體排版
字號(hào)應(yīng)不小于 12px(PC端)。
font-weight 屬性建議使用數(shù)值方式描述。
line-height 在定義文本段落時(shí),應(yīng)使用數(shù)值。
Vue 代碼規(guī)范
常規(guī)
當(dāng)在組件中使用 data 屬性的時(shí)候 (除了 new Vue 外的任何地方),它的值必須是返回一個(gè)對(duì)象的函數(shù)
data() { return {...} }。prop 的定義應(yīng)該盡量詳細(xì),至少需要指定其類型。
布爾類型的 attribute, 為 true 時(shí)直接寫屬性值。
不要在computed中對(duì)vue變量進(jìn)行操作。
應(yīng)該優(yōu)先通過 prop 和事件進(jìn)行父子組件之間的通信,而不是 this.$parent 或改變 prop。
在組件上總是必須用 key 配合 v-for,以便維護(hù)內(nèi)部組件及其子樹的狀態(tài)。
v-if 和 v-for 不能同時(shí)使用
公共方法盡量不要掛到原型上, 可以寫在 utils 文件,也可以使用 mixin 文件。不要將業(yè)務(wù)公共組件注冊(cè)到全局。
不要將任何第三方插件掛載到 vue 原型上。
具有高度通用性的方法,要封裝到 libs、全局組件或指令集里。
為組件樣式設(shè)置作用域。
盡量使用指令縮寫。
vuex
State (opens new window)為單一狀態(tài)樹,在 state 中需要定義我們所需要管理的數(shù)組、對(duì)象、字符串等等,只有在這里定義了,在 vue 的組件中才能獲取你定義的這個(gè)對(duì)象的狀態(tài)。
修改
state中數(shù)據(jù)必須通過mutations。每一個(gè)可能發(fā)生改變的
state必須同步創(chuàng)建一條或多條用來改變它的mutations。服務(wù)端獲取的數(shù)據(jù)存放在
state中,作為原始數(shù)據(jù)保留,不可變動(dòng)。
Getters (opens new window)有點(diǎn)類似 vue.js 的計(jì)算屬性,當(dāng)我們需要從 store 的 state 中派生出一些狀態(tài),那么我們就需要使用 getters,getters 會(huì)接收 state 作為第一個(gè)參數(shù),而且 getters 的返回值會(huì)根據(jù)它的依賴被緩存起來,只有 getters 中的依賴值(state 中的某個(gè)需要派生狀態(tài)的值)發(fā)生改變的時(shí)候才會(huì)被重新計(jì)算。
通過
getters處理你需要得到的數(shù)據(jù)格式,而不是通過修改state原始數(shù)據(jù)。組件內(nèi)不強(qiáng)制使用
mapGetters,因?yàn)槟憧赡苄枰褂?getter和setter。改變
state的唯一方法就是提交 mutations (opens new window)。組件內(nèi)使用
mapMutations輔助函數(shù)將組件中的methods映射為store.commit調(diào)用。命名采用
大寫字母+下劃線的規(guī)則。定義
CLEAR,以確保路由切換時(shí)可以初始化數(shù)據(jù)。
Actions
頁面重的數(shù)據(jù)接口盡量在 actions (opens new window)中調(diào)用。
服務(wù)端返回的數(shù)據(jù)盡量不作處理,保留原始數(shù)據(jù)。
獲取到的數(shù)據(jù)必須通過調(diào)用
mutations改變state。
Modules
通常情況下按照頁面劃分 modules (opens new window)。
默認(rèn)內(nèi)置了
system保證了腳手架的基礎(chǔ)功能。每個(gè)頁面模塊或頁面的子模塊添加屬性
namespaced: true。
14.完成詳細(xì)的使用文檔
不論是功能還是組件庫等等的工具,都需要完善的文檔提供查閱,即使是輪子的構(gòu)建者,也抵不住時(shí)間長了會(huì)忘記許多細(xì)節(jié)。
這里我使用 vuepress 構(gòu)建文檔,方便快捷。
參考【拯救懶癌文檔君 - VuePress + Travis CI + Github Pages 自動(dòng)線上生成文檔】
15.Git 多人協(xié)作流程
公司使用內(nèi)部搭建的 GitLab 托管代碼
Root 倉庫
項(xiàng)目啟動(dòng)時(shí),由項(xiàng)目管理者搭建起最原始的倉庫,稱為 Root 倉庫(源倉庫)。
源倉庫的有個(gè)作用 :
匯總參與該項(xiàng)目的各個(gè)開發(fā)者的代碼。
存放趨于穩(wěn)定和可發(fā)布的代碼。
向 Master 分支提交 Merge Requests 可以觸發(fā)測(cè)試環(huán)境構(gòu)建(CI/CD)。
源倉庫是受保護(hù)的,開發(fā)者不可直接對(duì)其進(jìn)行開發(fā)工作。
開發(fā)者倉庫
任何開發(fā)者都沒有權(quán)限對(duì) Root 倉庫進(jìn)行直接的操作,源倉庫建立以后,每個(gè)開發(fā)者需要做的事情就是把源倉庫的 Fork 一份,作為自己日常開發(fā)的倉庫。
每個(gè)開發(fā)者所Fork的倉庫是完全獨(dú)立的,互不干擾。
每個(gè)開發(fā)者提交到代碼到自己的倉庫中,開發(fā)工作完成以后,開發(fā)者可以向源倉庫發(fā)送 Pull Request ,本地倉庫先合并源倉庫,解決沖突。
發(fā)起 Merge Request 請(qǐng)求管理員把自己的代碼合并到源倉庫中的 master 或 其他分支。
Git 流程

前端項(xiàng)目會(huì)在
Root倉庫下創(chuàng)建dev分支,用于代碼的拉取和合并,如果有多個(gè)不同的測(cè)試環(huán)境,按照測(cè)試環(huán)境創(chuàng)建分支。在本地的倉庫中創(chuàng)建你的
dev分支和其他功能性的分支。開發(fā)過程中不允許直接在
master分支上開發(fā),創(chuàng)建一個(gè)新的分支進(jìn)行開發(fā),git checkout –b {branch_name}。規(guī)范且詳細(xì)的書寫
commit,推薦使用git-cz工具進(jìn)行提交。完成開發(fā)后將相應(yīng)的分支合并到自己倉庫的
master分支。將
master分支push到自己的遠(yuǎn)程倉庫(Fork倉庫)。向
Root倉庫dev分支提交Merge Requests。提醒前端負(fù)責(zé)人審查代碼、解決沖突或測(cè)試環(huán)境上線。
解決沖突后
git pull upstream dev拉取解決后的最新代碼。

