吃透 Vue 項目開發實踐|16個方面深刻前端工程化開發技巧《上》

前言

以前使用過 Vue 開發後臺、中臺項目,也作過移動端 H5,弄過一點小的前端架構。每作一個項目都會收穫了不同的經驗和理解。下面我把這些點點滴滴的經驗總結下來,作一個系列的文章分享和階段性的總結。javascript

常規操做,先點贊後觀看哦!你的點贊是我創做的動力之一!css

概覽

問題

我將從 16 個方面來論述 vue 開發過程當中的一些技巧和原理。固然因爲篇幅有限,先論述前 8 個問題,下節將完成全系列內容。html

本篇文章將圍繞下列問題進行論述:前端

  • 如何規範你的 git 提交,並自動生成並提交日誌?
  • 如何配置和使用 Sass 和 PUG 提高你的編碼效率?
  • 如何處理你的代碼風格問題,以及如何使用 perttier 與 eslint 解決效率風格兩難問題?
  • 如何管理頁面的路由,如何編寫異步路由?
  • 如何編寫組件,引入組件庫?
  • 如何管理你的資源,如何引入圖標,樣式?
  • 如何封裝你的 axios,管理你的api?
  • 如何使用 mock 模擬你的數據,實現真正意義的先後端分離?

實踐

實踐以前:我但願你有以下準備,或者知識儲備。vue

  • 瞭解 npm/yarn/git/sass/pug/vue/vuex/vue-router/axios/mock/ssr/jest 的使用和原理。
  • 固然上面知識不瞭解也不要緊哈哈哈,文章中會提到大體用法和做用。

如何規範 git 提交

代碼提交記錄是一個很好的代碼修改日誌。規範的代碼提交記錄,無論在平時代碼開發維護過程當中,仍是在定位 bug 或者回退版原本說都是極爲重要。java

原理

兩種作法:node

  • 本身手動規範 git 的提交原則或者團隊統一制定。這個靠自覺,好習慣養成以後就沒問題來
  • 使用插件規範,好比下面這種

爲了規範提交,我使用了以下插件:webpack

  • commitizen
  • conventional-changelog
  • cz-conventional-changelog
  • conventional-changelog-cli

解決方案

安裝系列插件依賴

yarn add -D commitizen conventional-changelog cz-conventional-changelog
複製代碼

安裝依賴時,要注意是不是生產環境須要的。顯然 commitizen 只在開發環境中使用。-D 只在 dev 環境使用ios

配置依賴路徑

package.json 中添加配置git

{
    //...
    "config": {
        "commitizen": {
          "path": "./node_modules/cz-conventional-changelog"
        }
    }
}
複製代碼

在命令行中輸入

git add -A
git-cz
複製代碼

出現了交互輸入方式,規範你的 commit 輸入格式

生成 CHANGELOG

npm i -g conventional-changelog-cli
複製代碼

增長一個npm 命令,快速生成日誌

"genlog": "conventional-changelog -p angular -i .github/CHANGELOG.md -s"
複製代碼

使用yarn命令生成日誌

yarn genlog
複製代碼

自動生成的log

# 0.1.0 (2019-12-27)

### Features

* **git:** 增長commitizen工具規範提交 ([58e3937](https://github.com/suoyuesmile/suo-design-pro/commit/58e39370aa838fd99312f73b37d092ffadc85990))
複製代碼

如何管理代碼風格

較統一的代碼風格利於閱讀,也利於協做。

原理與解決方案

使用 eslint 約束基本風格和語法,使用 prettier 自動格式化你的代碼。

實踐

安裝 eslint 依賴

{
    "eslint": "^5.16.0",
    "eslint-config-standard": "^6.2.1",
    "eslint-friendly-formatter": "^2.0.7",
    "eslint-loader": "^2.1.2",
    "eslint-plugin-html": "^2.0.1",
    "eslint-plugin-promise": "^3.5.0",
    "eslint-plugin-standard": "^2.3.1",
    "eslint-plugin-vue": "^5.0.0"
}
複製代碼

使用兩個插件,一個 plugin:vue/essential,一個是 standardvue/essential 爲了在 vue 裏面也能夠生效。另外一個是 standardstandard 標準文檔

使用 recommend 也能夠,採用推薦 lint,更加輕量化

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: ['plugin:vue/essential', 'standard'],
  rules: {
    quotes: ['error', 'single'],
    indent: ['error', 2, { MemberExpression: 'off' }],
    'arrow-parens': 0,
    'no-loop-func': 2,
    'space-before-function-paren': ['error', 'never'],
    indent: ['error', 2, { SwitchCase: 1 }]
  },
  parserOptions: {
    parser: require.resolve('babel-eslint'),
    ecmaVersion: 2018,
    sourceType: 'module'
  }
}
複製代碼

能夠自定義 rules 的規則

rules 的規則 { 規則名:[是否關閉/規則等級,配置的值,只對部分配置] } indent: ['error', 2, { SwitchCase: 1 }] 兼容 prettier,prettier 會將代碼格式化成 eslint 報錯的狀況。 規則等級:0 關閉 1 警告 2 報錯

使用 prettier

配置 prettier 文件

{
  "printWidth": 150,
  "singleQuote": true,
  "trailingComma": "none",
  "semi": false,
  "tabWidth": 2,
  "useTabs": false,
  "bracketSpacing": true,
  "jsxBracketSameLine": false,
  "arrowParens": "always",
  "proseWrap": "preserve",
  "overrides": [
    {
        "files": ["*.json", ".eslintrc", ".tslintrc", ".prettierrc", ".tern-project"],
        "options": {
            "parser": "json",
            "tabWidth": 2
        }
    },
    {
        "files": "*.{css,sass,scss,less}",
        "options": {
            "parser": "css",
            "tabWidth": 2
        }
    },
    {
        "files": "*.ts",
        "options": {
            "parser": "typescript"
        }
    },
    {
        "files": "*.vue",
        "options": {
            "parser": "vue"
        }
    },
    {
        "files": "*.md",
        "options": {
            "parser": "markdown"
        }
    }
  ]
}
複製代碼

開啓 vscode 自動格式化

{
  // prettier
  "prettier.singleQuote": true,
  "prettier.semi": false,
  "prettier.tabWidth": 2,
  "[javascript]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}
複製代碼

如何提高編碼效率

原理與解決方案

我主要從 3 個方面來作一些編碼效率上的改進

  • 升級你的 vue-cli 減小 webpack 配置的成本
  • 使用 sass,利用裏面函數、mixins、變量提高 css 文件的複用
  • 使用 pug,減小 html 的代碼編寫量

實踐

vue-cli3+、vue-cli4+ 相比於 vue-cli2+ 最大的改變就是將約定俗稱的配置,所有公共化了,也就是作了一次二次封裝。這樣的好處在於,咱們沒必要要在繁多的配置代碼中尋找須要的配置。

簡單新建一個配置入口就能操做咱們大多數想要的功能。在 root 目錄下新建一個 vue.config.js 文件,做爲咱們 webpack 的配置文件。

初始化 vue 配置

const autoprefixer = require('autoprefixer')

module.exports = {
  publicPath: process.env === 'production' ? '' : '/',
  outputDir: 'dist',
  assetsDir: 'static',
  filenameHashing: true,
  lintOnSave: true,
  runtimeCompiler: false,
  transpileDependencies: [/\/node_modules\/vue-echarts\//, /\/node_modules\/resize-detector\//],
  productionSourceMap: false
}
複製代碼

簡單的配置完成後,咱們引入一個 sass 工具用於編寫 sass文件

用法見 sass 參考資料!

使用 Sass

安裝與使用

yarn add sass sass-loader
複製代碼

如何處理樣式

assets 目錄中創建一個 styles 文件專門來存放樣式文件,新增入口index.scss文件,便於 JavaScript 引入,增長 utils.scss、reset.scss、varibles 文件。

這些樣式工具都是爲了提高咱們 scss 開發效率,具備暢快的開發體驗!

使用 varibles 變量文件

爲了提高咱們代碼的可讀性,複用性。使用 sass 變量必不可少。

還有一點就是利於全局修改樣式,若是須要更換皮膚這個功能,咱們只須要更改全局的主題色,便可更換主題,那樣更加方便。

// 主題色
$color-red: #ff3333;
$color-purple: #ff33a9;
$color-orange: #ff8833;
$color-blue: #3377ff;

// 文字色
$color-black: #000;
$color-dark: #333;
$color-deep: #555;
$color-pl: #999999;
$color-weak: #B3B3B3;
$color-white: #fff;

// 背景色
$bg-bar: #F9F9F9;
$bg-page: #F3F3F3;
$bg-page-light: #F9F9F9;
複製代碼

使用變量以後,sass 文件不會直接生效,至少在 vue 文件 裏面是訪問不到的。 須要在 vue.config.js 裏面增長以下配置。

module.exports = {
    // ...
    css: {
        sourceMap: true,
        loaderOptions: {
            sass: {
                prependData: ` @import "@/assets/styles/variable.scss"; `
            }
        }
    }
}
複製代碼

覆蓋默認樣式

常規操做, 引入 reset.scss 將默認樣式覆蓋掉

/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: auto;
  margin: 0;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
}
複製代碼

使用樣式工具集

有時候咱們發現,光是引入變量還不夠,變量工具只能容許咱們在 css 類文件中使用。假如我想着模版中直接使用樣式,有沒有更快的方案呢?

固然有的,咱們能夠自定義一個經常使用的樣式工具集。設置一些背景顏色、字體顏色、盒子模型中的常規操做。

要是有設計規範更好哦,我也是經常向設計師提出要求,必定要制定出一套產品的設計規範。

// utils 工具
// 顏色
.bg-red {background-color: $color-red!important;}
.bg-purple {background-color: $color-purple!important;}
.bg-orange {background-color: $color-orange!important;}
.bg-blue {background-color: $color-blue!important;}
.color-red {color: $color-red!important;}
.color-purple {color: $color-purple!important;}
.color-orange {color: $color-orange!important;}
.color-blue {color: $color-blue!important;}
.text-black {color: #000;}
.text-dark {color: #333;}
.text-deep {color: #555;}
.text-weak {color: #B3B3B3;}
.text-white {color: #fff;}

// 字體
.f10 {font-size: 10px;}
.f12 {font-size: 12px;}
.f14 {font-size: 14px;}
.f15 {font-size: 15px;}
.f17 {font-size: 17px;}
.f20 {font-size: 20px;}
.f24 {font-size: 24px;}

// 文字對齊
.tl {text-align: left;}
.tc {text-align: center;}
.tr {text-align: right;}

// 浮動與清除浮動
.fl {float: left;}
.fr {float: right;}
.fix {*zoom: 1;}
.fix:after{display:table; content:''; clear:both;}

// 顯示
.dn{display:none;}
.di{display:inline;}
.db{display:block;}
.dib{display:inline-block;}
.dt{display:table;}
div.dib{*display:inline; *zoom:1;}
.vm {vertical-align: middle;}
.vib {display:inline-block; vertical-align: middle;}

// 定位
.pr {position: relative;}
.pa {position: absolute;}
.pf {position: fixed;}

// 盒子模型
.ml4 {margin-left: 4px;}
.mr4 {margin-right: 4px;}
.mt4 {margin-top: 4px;}
.mb4 {margin-bottom: 4px;}
.ml8 {margin-left: 8px;}
.mr8 {margin-right: 8px;}
.mt8 {margin-top: 8px;}
.mb8 {margin-bottom: 8px;}
.ml12 {margin-left: 12px;}
.mr12 {margin-right: 12px;}
.mt12 {margin-top: 12px;}
.mb12 {margin-bottom: 12px;}
.ml16 {margin-left: 16px;}
.mr16 {margin-right: 16px;}
.mt16 {margin-top: 16px;}
.mb16 {margin-bottom: 16px;}
.ml20 {margin-left: 20px;}
.mr20 {margin-right: 20px;}
.mt20 {margin-top: 20px;}
.mb20 {margin-bottom: 20px;}
.ml24 {margin-left: 24px;}
.mr24 {margin-right: 24px;}
.mt24 {margin-top: 24px;}
.mb24 {margin-bottom: 24px;}

.ml10 {margin-left: 10px;}
.mr10 {margin-right: 10px;}
.mt10 {margin-top: 10px;}
.mb10 {margin-bottom: 10px;}
.ml15 {margin-left: 15px;}
.mr15 {margin-right: 15px;}
.mt15 {margin-top: 15px;}
.mb15 {margin-bottom: 15px;}

// 按鈕禁用
.disabled{outline:0 none; cursor:default!important; opacity:.4; filter:alpha(opacity=40); -ms-pointer-events:none; pointer-events:none;}
複製代碼

增長樣式入口文件

最後一步,新建一個入口文件,將樣式工具類所有導入進來,供主程序引入。

// index.scss 文件
import './reset.scss';
import './varibles.scss';
improt './utils/scss';
複製代碼

main.js 中直接引入 index.scss

import '@/assets/styles/index.scss'
複製代碼

vue 中寫樣式要注意哪些方面,有哪些技巧呢?

避免全局污染

在頁面中寫 css/scss 加上 scopedscoped 的功能就是使頁面的樣式是局部的,不讓影響其餘頁面的樣式。

bem 規範

咱們大多數人時候會遇到問題,樣式嵌套太多了怎麼命名

BEM是塊(block)、元素(element)、修飾符(modifier)的簡寫,由 Yandex 團隊提出的一種前端 CSS 命名方法論。

名字太長易讀性太差

.cardbox {
    .cardbox-card {
        .cardbox-card-wrapper
          .cardbox-card-wrapper-body{
              .cardbox-card-item {
                .cardbox-card-item-title {
                    // ...
                }  
              }
          }
        }
    }
}
複製代碼

bem 使用方式

block-name__element-name--color

  • 區分塊,子元素,修飾元素
  • 塊,頁面中獨立的單元
  • 子元素,塊裏面的兒子 card__item 使用 __ 鏈接
  • 子元素長命名使用 - 鏈接
  • 修飾(易變的)card__item--warning 使用 --

咱們使用 bem 改造樣式

.cardbox {
    .card {
        .&__item {
            &__title {
                //...
            }
        }
    }
}
複製代碼

如今編寫樣式效率提升也更加規範了,那麼編寫 HTML 也是有不少累贅的代碼。

好比大多數標籤都是前開後閉的。經過 pug 咱們能夠省略不少字符的敲打,下面咱們談談如何使用 pug 編寫模版。

固然喜歡哪一種 HTML 編寫風格見人見智啦,我本身更加傾向 pug,那種縮進和簡潔的表達,有種在寫 scss 的感受。

如何使用 pug

相似 sass,首先安裝 pug 和 pug 的 loader

yarn add -D pug pug-html-loader pug-plain-loader
複製代碼

完成配置

module.exports = {
  // ...
  chainWebpack: (config) => {
    config.module
      .rule('pug')
      .test(/\.pug$/)
      .use('pug-html-loader')
      .loader('pug-html-loader')
      .end()
  }
}
複製代碼

編寫 pug 代碼

使用 scss 工具與 pug 完美搭配,少寫不少代碼

// 登錄
<template lang="pug">
  .login
    h1.login__title.ml15 註冊/登錄
    .login__form.mt15.ml15
      van-field.login__form__input(placeholder="輸入手機號" v-model="phone")
      .login__form__protocol.mt15
        .login__form__protocol__tips.dib.text-weak 註冊或登陸即表示贊成
        .login__form__protocol__name.dib.color-orange 《用戶協議》
      app-button.mt15(size="large"
        theme="orange"
        :disabled="phone.length !== 11"
        @click="handleSubmit"
      ) 下一步
</template>
複製代碼

咱們已經引入了樣式,接下來我將談談其餘資源的引入

如何管理你的資源

原理與解決方案

我暫時把資源分爲下面幾種

  • 字體
  • ICON
  • 圖片
  • 樣式
    把他們各自新建一個目錄,都放在 assets 目錄下面分門別類,供其餘地方調用。 使用 alias 更好重命名,使之更便捷的訪問到。

增長 vue.config.js 配置 ,設置assets別名

const path = require('path')
function resolve(dir) {
  return path.join(__dirname, dir)
}
module.exports = {
  //...
  chainWebpack: (config) => {
    config.resolve.alias.set('@', resolve('src')).set('@assets', resolve('src/assets'))
  }
}
複製代碼

ICON

引入 iconfont

  1. iconfont 阿里圖標項目中下載,將整個項目圖標包一塊兒下載下來

  1. 引入 iconfont 樣式 須要下面四個文件
  • iconfont.eot
  • iconfont.ttf
  • iconfont.woff
  • iconfont.woff2
  1. 項目中引入 iconfont 讓 icon 組件化
<template>
  <i class="iconfont" v-on="$listeners" :class="name"></i>
</template>

<script> export default { props: { name: String } } </script>

<style lang="scss" scoped> .iconfont { font-size: 16px; color: $color-icon; } </style>
複製代碼

引入圖片做爲 ICON

讓圖片組件化,咱們再來寫一個 img 組件

<template lang="pug">
  img(
    :src="require(`@/assets/images/${name}.png`)"
    v-bind="$attrs"
    v-on="$listeners"
    :style="{'width': width ? width + 'px' : size + 'px', 'height': height ? height + 'px' : size + 'px' }")
</template>
<script>
export default {
  name: 'app-img',
  props: {
    name: {
      type: String,
      default: ''
    },
    size: {
      type: Number,
      default: 16
    },
    width: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    }
  }
}
</script>
複製代碼

如何管理你的路由

原理與解決方案

使用 vue-router,使用import() 生成異步路由,只有在訪問時候纔會加載模塊。

爲何使用 import() 會異步加載模塊?

MDN:在您但願按照必定的條件或者按需加載模塊的時候,動態import() 是很是有用的。而靜態型的 import 是初始化加載依賴項的最優選擇,使用靜態 import 更容易從代碼靜態分析工具和 tree shaking 中受益。

說白了就是起到一個按需加載的目的。如今大多數實現的按需加載,基本都是依賴 import() 這個方法。

安裝 vue-router

yarn add vue-router
複製代碼

安裝完 router,在編寫 router 先建立頁面

新建一個空頁面

src 目錄下新增 views 目錄存放頁面文件。建立 index 目錄和 home 頁面

<template lang="pug">
  .home 首頁
</template>

<script> export default { } </script>
<style lang="scss" scoped> </style>
複製代碼

編寫路由

const routes = [
    {
        // 首頁
        path: '/',
        name: 'index',
        redirect: '/home',
        component: App,
        children: [
            {
                // 首頁
                path: 'home',
                name: 'home',
                // 路由懶加載
                component: () =>
                    import(
                        /* webpackChunkName: "index" */ '../views/index/home.vue'
                    )
            }
        ]
    }
]
Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    routes: routes,
    base: process.env.BASE_URL,
    props: true
})

export default router
複製代碼

爲了消除 # 顯得路徑更加好看簡潔,咱們採用 history 模式,可是 history 模式有個問題就是,異步路由沒有緩存在頁面中,第一次進入頁面會找不到

在 vue.config.js 中增長配置,開發環境能夠訪問到,恢復正常

module.exports = {
  // ...
  devServer: {
    historyApiFallback: true
  }
}
複製代碼

關於路由還有不少能夠研究到地方,能夠自行研究哦!

組件化開發

原理與解決方案

通常來講,咱們根據組件的複用度,分給基礎(公共)組件和業務組件。

爲了節省時間,快速開發,這裏基礎組件大部分引用開源組件。固然不能直接就用哦。

通常要進行二次封裝,也就是高階組件開發。

  1. 經過修改和覆蓋當前組件的樣式來達到修改樣式的做用。
  2. 經過攔截事件來更改js的邏輯。

下面咱們先引入 vant 組件

實踐

引入 vant

yarn add vant
複製代碼

對基礎組件進行二次封裝和改造

下面 7 步來寫好一個公共組件

  1. 新建一個 components 目錄來存放基礎組件
  2. 基礎組件命名爲 app-xxxappXxx,新建一個 app-button 目錄,新建 index.vue
  3. 根據設計稿設計和編寫組件

編寫組件以前首先要設計組件,根據組件的不變性和可變性原則編寫。不變性是組件的核心,可變性根據參數對組件對相關部分進行調節,實現可選擇的功能。

  1. 實現組件
<template lang="pug">
  div.dib
    van-button.btn(
      @click="$emit('click')"
      :class="getClass" v-bind="$attrs"
      :style="{'width': size === 'large' ? '345px': '', 'backgroundColor': getBgColor, borderColor: getBgColor, color: getBgColor}")
      slot
</template>

<script> import { Button } from 'vant' import Vue from 'vue' import { getColor } from '@/utils' Vue.use(Button) export default { name: 'app-button', props: { type: { type: String, default: 'primary' }, theme: { type: String, default: 'blue' }, size: { type: String, default: '' } }, computed: { getClass() { if (!this.type) { return '' } return `app-btn--${this.type}` }, getBgColor() { if (!this.theme) { return '' } return getColor(this.theme) } } } </script>

<style lang="scss" scoped> .app-btn { ::v-deep &--primary { padding: 8px 30px; height: 40px; border-radius: 4px; font-size: 15px; font-weight: 400; line-height: 19px; color: white!important; } ::v-deep &--minor { padding: 5px 10px; height: 26px; border-radius: 14px; font-size: 12px; font-weight: 400; line-height: 16px; background-color: #fff!important; } ::v-deep &--rect { padding: 5px px; height: 49px; font-size: 14px; color: white!important; } } </style>
複製代碼

::v-deep 樣式深覆蓋,scoped 狀況下覆蓋組件樣式,不改變其樣式

  1. 寫好基礎組件 README,爲何要寫文檔呢?若是多人開發同一個項目,基礎組件會被其餘人引用。方便其餘人使用,因此要寫文檔。

一句話言:只要可能被其餘人用的公共方法和組件,註釋或文檔是很重要的,對本身的代碼負責哈。

其餘用法參照 vant

  1. 全局引用,基礎組件許多頁面都會用到,將其設置成全局組件,其餘地方就沒必要再引用了哦。

新建一個文件 global 存放全局組件註冊,在 main.js 引入

import Vue from 'vue'
import appButton from '@/components/app-button'
Vue.component('app-button', appButton)
複製代碼
  1. 寫好 demo,即便暫時不寫單元測試,也要寫好一個 demo,使之能正常的運行

添加 demo 頁面和路由

<template lang="pug">
  div(class="base")
    // 按鈕組件
    app-button.mt4(theme="blue") 確認支付
    app-button(theme="orange") 確認支付
    app-button(theme="purple") 確認支付
    app-button.mt4(theme="red") 確認支付
    app-button(theme="grey") 確認支付
    app-button.mt4(theme="blue" size="large") 修改地址
    app-button.mt4(theme="orange" size="large") 修改地址
    app-button.mt4(theme="purple" size="large") 修改地址
    app-button.mt4(theme="red" size="large") 修改地址
    app-button.mt4(theme="grey" size="large") 修改地址
    app-button.mt4(theme="blue" type="minor") 確認收貨
    app-button(theme="orange" type="minor") 確認收貨
    app-button(theme="purple" type="minor") 確認收貨
    app-button(theme="red" type="minor") 修改地址
    app-button(theme="grey" type="minor") 修改地址
    app-button.mt4(theme="blue" type="rect") 確認收貨
    app-button(theme="orange" type="rect") 確認收貨
    app-button(theme="purple" type="rect") 確認收貨
    app-button(theme="red" type="rect") 修改地址
    app-button(theme="grey" type="rect") 修改地址
</template>
<script> export default { } </script>
<style lang="scss"> </style>
複製代碼

實現效果

如何封裝請求

原理與解決方案

基本上就是對 axios 的封裝,封裝主要有兩個目的。

  • 修改一些基礎的配置:請求地址,超時,其餘的雜七雜八的
  • 統一操做:統一處理錯誤,統一處理請求參數和格式,響應參數和格式。統一處理 message,統一攔截掛載等等。

網上已經有不少相似的文章了, 我這裏給出我經常使用的封裝方案。

實踐

根據不一樣環境設置請求地址

// .env-default.js 文件
// 不一樣環境訪問不一樣的路徑
const api = {
  dev: 'http://xxxx:8080',
  mock: 'http://xxxx',
  feature: 'http://xxxx',
  test: 'http://xxxx',
  prod: 'http://xxxx'
}

export const baseURL = () => {
  if (process.env.NODE_ENV === 'production') {
    return api.prod
  } else if (process.env.NODE_ENV === 'testing') {
    return api.test
  } else if (process.env.NODE_ENV === 'mock') {
    return api.mock
  } else if (process.env.NODE_ENV === 'feature') {
    return api.feature
  } else {
    return api.dev
  }
}

複製代碼

新建一個 utils 工具

咱們如今將 axios 封裝成咱們本身須要的配置,而後定義四個經常使用的請求方法供調用

// utils/http.js 文件
import axios from 'axios'
import { baseURL } from '../../.env-defalut.js'

// axios 配置
const defaultBaseUrl = 'http://localhost:8080/'
// 默認超時時間
axios.defaults.timeout = 15000
// 數據接口域名統一配置.env
axios.defaults.baseURL = baseURL() || defaultBaseUrl 

// http request 攔截器
axios.interceptors.request.use(
  (config) => {
    config.headers = {
    }
    return config
  },
  (err) => {
    return Promise.reject(err)
  }
)

// http response 攔截器
axios.interceptors.response.use(
  (response) => {
    return response
  },
  (error) => {
    const data = error.response.data
    return Promise.reject(data || error)
  }
)

export default axios

/** * fetch 請求方法 * @param {*} url * @param {*} params */
export function fetch(url, params = {}) {
  return new Promise((resolve, reject) => {
    axios
      .get(url, {
        params: params
      })
      .then((response) => {
        resolve(response.data)
      })
      .catch((err) => {
        reject(err)
      })
  })
}

/** * post 請求方法,發送數據格式 json * @param {*} url * @param {*} data */
export function post( url, data = {}, config = { transformRequest: [ function(fData, headers) {
        headers['Content-Type'] = 'application/json'
        return JSON.stringify(fData)
      }
    ]
  }
) {
  return new Promise((resolve, reject) => {
    axios.post(url, data, config).then(
      (response) => {
        resolve(response.data)
      },
      (err) => {
        reject(err)
      }
    )
  })
}

/** * patch 請求方法,發送數據格式 json * @param {*} url * @param {*} data */
export function patch(url, data = {}) {
  return new Promise((resolve, reject) => {
    axios
      .patch(url, data, {
        transformRequest: [
          function(fData, headers) {
            headers['Content-Type'] = 'application/json'
            return JSON.stringify(fData)
          }
        ]
      })
      .then(
        (response) => {
          resolve(response.data)
        },
        (err) => {
          reject(err)
        }
      )
  })
}

export function del(url, data) {
  return new Promise((resolve, reject) => {
    axios.delete(url, { data }).then(
      (response) => {
        resolve(response.data)
      },
      (err) => {
        reject(err)
      }
    )
  })
}
複製代碼

如何管理 api

原理

首先要制定一個 api 的原則 個人原則通常是這些:

  • 乾淨純粹
  • 儘可能不要處理數據
  • 獨立單一不要互相依賴

好處在於:不在 api 裏面處理數據,api裏面的接口和接口文檔上同樣。避免別人引用個人api,還要去看代碼,只須要看文檔就行了。

例子:想象這樣一種狀況,別人引用了個人 api,忽然發現響應數據不對。首先它排查到頁面數據沒更改。看了api文檔,數據也沒問題,最後發現我在寫 api 的時候進行了處理,這個 api 呢又不能改動,改了影響我本身的模塊。只能它在從新寫一個api,這樣顯得很繁雜了,不夠乾淨優雅。

import { fetch, post } from '@/utils/http'

// 用戶登錄
export const login = data => post('/user/login', data)

// 獲取用戶信息
export const getUserInfo = (data) => fetch('/api/user/info', data)
複製代碼

若是須要處理數據,要麼使用一箇中間工具處理,要麼在頁面裏面處理。固然仍是實際問題實際分析。

如何使用mock模擬數據

原理與解決方案

通常就是兩種方案,一是模擬後端,使用遠程在線 JSON 服務器。另一種搭建本地 JSON 或者 使用現成的 Node 服務器攔截請求。

這兩種方式各有千秋,沒有優劣之分,適合就是最好的。

遠程在線mock

我用過的遠程在線mock

  • apizza:好用,功能齊全,喜歡他的文件展開目錄api,免費版只支持 2 我的共同編輯
  • swagger:開源免費,api管理太凌亂,
  • rap/rap2:開源免費,能夠搭建本地api,須要本身搭建

使用遠程mock的優勢:

  • 不須要在項目內部增長mock
  • 功能更加全面完善
  • 能夠在接口文檔基礎上mock,與接口文檔放在一塊兒查看更加方便。

缺點:須要本身另外搭建服務器,只支持靜態的mock,不能與單元測試結合使用

本地JSON mock

  • 使用 webpack 內部 mock 配置
devServer: {
    // 接口未實現狀況下,使用mock
    before: require('./mock')
}
複製代碼

基本原理:主要是使用 node 讀取文件,轉換成 JSON 格式,使用mock.js 模擬數據,最後 webpack 攔截請求生成json響應數據

const Mock = require('mockjs')

module.exports = (app) => {
    function getJsonFile (filePath) {
        var json = fs.readFileSync(path.resolve(__dirname, filePath), 'utf-8')
        return JSON.parse(json)
    },
    const returnMock = (datafile, res, req) => {
        setTimeout(() => {
            var json
            if (/\.json$/.test(datafile)) {
                // json文件暴露的是mock的模板
                json = getJsonFile(datafile)
            } else if (/\.js$/.test(datafile)) {
                json = require(datafile)(req.query)
            }
            res.json(Mock.mock(json))
        }, 500)
    }
}
複製代碼
  • 使用 json-server 搭建 主要分爲下面幾步
  1. npm 安裝 json-server
  2. 編寫 npm 腳本命令,引入 mock 配置文件
  3. 編寫 mock 路由匹配規則

比較簡單這裏不詳細描述了!

本地的缺點在於須要

  • 前端須要根據api文檔寫mock數據格式
  • 功能沒有遠程mock那麼完善,支持restful須要去研究下
  • 也是須要配置相關mock工具

優勢在於

  • 不用查看編輯api文檔
  • 在代碼中就能夠更改和查看mock數據
  • 支持使用JavaScipt動態處mock,能夠與單元測試結合使用

總結

本篇文章耗費做者一個多星期的業餘時間,存手工敲打 6000+字,同時收集,整理以前不少技巧和邊寫做邊思考總結。若是能對你有幫助,即是它最大的價值。都看到這裏還不點贊,太過不去啦!😄

因爲技術水平有限,文章中若有錯誤地方,請在評論區指出,感謝!

文中大多數代碼將在suo-design-pro 中更新

項目有時間會盡可能完善

寫實踐總結性文章真的很耗費時間。如何文章中有幫到你的地方分享下唄,讓更多人看到!

下節內容預告

  • 如何編寫原生組件,以及組件編寫的思考與原則?
  • 如何使用vuex 以及它的應用場景和原理
  • 如何使用過濾器,編寫本身的過濾器
  • 如何使用 Jest 測試你的代碼?TDD 與 BDD 的比較
  • 回顧 vue 的生命週期,他們都使用哪些場景?
  • 使用 mixins 與 組件的區別,如何充分的複用代碼?
  • vue 技術棧架構設計上的思考,如何編寫本身的組件庫?
  • vue ssr 原理及其應用

參考資料

開源項目

上述功能配置,均suo-design-pro 中實現,但願能持續關注!

相關文章
相關標籤/搜索