前端工程化常用工具總結(jié)

一渠缕、代碼規(guī)范

1.1 vscode集成editorconfig

安裝editorconfig插件后慢蜓,在項(xiàng)目根目錄下生成如下配置

.editorconfig

# http://editorconfig.org

root = true

[*] # 表示所有文件適用
charset = utf-8 # 設(shè)置文件字符集為 utf-8
indent_style = space # 縮進(jìn)風(fēng)格(tab | space)
indent_size = 2 # 縮進(jìn)大小
end_of_line = lf # 控制換行類型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始終在文件末尾插入一個(gè)新行

[*.md] # 表示僅 md 文件適用以下規(guī)則
max_line_length = off
trim_trailing_whitespace = false

1.2 使用prettier格式化工具

Prettier 是一款強(qiáng)大的代碼格式化工具谍椅,支持 JavaScript笨觅、TypeScript赠橙、CSS髓介、SCSS凯砍、Less箱硕、JSX、Angular悟衩、Vue剧罩、GraphQL、JSON座泳、Markdown 等語言惠昔,基本上前端能用到的文件格式它都可以搞定,是當(dāng)下最流行的代碼格式化工具挑势。

  1. 安裝prettier

    npm install prettier -D
    

    -D 就是npm install --save-dev 表示改依賴只在開發(fā)環(huán)境中

  2. prettier配置文件.prettierrc

    • useTabs:使用tab縮進(jìn)還是空格縮進(jìn)镇防,選擇false;
    • tabWidth:tab是空格的情況下薛耻,是幾個(gè)空格营罢,選擇2個(gè);
    • printWidth:當(dāng)行字符的長(zhǎng)度饼齿,推薦80饲漾,也有人喜歡100或者120;
    • singleQuote:使用單引號(hào)還是雙引號(hào)缕溉,選擇true考传,使用單引號(hào);
    • trailingComma:在多行輸入的尾逗號(hào)是否添加证鸥,設(shè)置為 none僚楞;
    • semi:語句末尾是否要加分號(hào),默認(rèn)值true枉层,選擇false表示不加泉褐;

    .prettierrc

    {
      "useTabs": false,
      "tabWidth": 2,
      "printWidth": 80,
      "singleQuote": true,
      "trailingComma": "none",
      "semi": false
    }
    
  3. prettier忽略文件.prettierignore

    /dist/*
    .local
    .output.js
    /node_modules/**
    
    **/*.svg
    **/*.sh
    
    /public/*
    
  4. package.json中添加格式化所有文件的腳本

    "prettier": "prettier --write ."
    

1.3 使用ESLint檢測(cè)

  1. 如果使用vue cli創(chuàng)建項(xiàng)目的時(shí)候選擇了ESLint,則vue會(huì)默認(rèn)配置好ESLint所需的環(huán)境

  2. vscode安裝ESLint插件

  3. 解決ESLintprettier之間的沖突

    vue cli創(chuàng)建的項(xiàng)目中鸟蜡,ESLint的規(guī)范是vue團(tuán)隊(duì)的膜赃,如果我們想要用自己的ESLint配置,則會(huì)和他們的規(guī)范沖突揉忘,這樣一來prettier格式化后就會(huì)和ESLint的不一致跳座,為了解決這個(gè)問題端铛,需要安裝如下兩個(gè)插件

    npm i eslint-plugin-prettier eslint-config-prettier -D
    

    這兩個(gè)插件如果在vue cli創(chuàng)建項(xiàng)目時(shí)選擇了ESLint + prettier,則會(huì)默認(rèn)幫我們裝上的

    將插件添加到.eslintrc.js

    extends: [
        'plugin:vue/vue3-essential',
        'eslint:recommended',
        '@vue/typescript/recommended',
        '@vue/prettier',
        '@vue/prettier/@typescript-eslint',
        'plugin:prettier/recommended'
    ],
    

    即在最后一行加上'plugin:prettier/recommended'即可

1.4 git Husky保證提交代碼的規(guī)范

雖然我們已經(jīng)要求項(xiàng)目使用eslint了疲眷,但是不能保證組員提交代碼之前都將eslint中的問題解決掉了:

  • 也就是我們希望保證代碼倉庫中的代碼都是符合eslint規(guī)范的禾蚕;
  • 那么我們需要在組員執(zhí)行 git commit 命令的時(shí)候?qū)ζ溥M(jìn)行校驗(yàn),如果不符合eslint規(guī)范狂丝,那么自動(dòng)通過規(guī)范進(jìn)行修復(fù)换淆;

husky是一個(gè)git hook工具,可以幫助我們觸發(fā)git提交的各個(gè)階段:pre-commit美侦、commit-msg产舞、pre-push

這里我們可以使用自動(dòng)配置命令:

npx husky-init && npm install

該命令會(huì)做三件事:

  1. 添加husky項(xiàng)目依賴到package.json中的devDependencies

  2. 在項(xiàng)目目錄下創(chuàng)建 .husky 文件夾,該文件夾中存放hook配置菠剩,也可以手動(dòng)執(zhí)行下面的命令進(jìn)行創(chuàng)建

    npx huksy install
    
  3. package.json中添加一個(gè)腳本

    "prepare": "husky install"
    

接下來易猫,我們需要去完成一個(gè)操作:在進(jìn)行commit時(shí),執(zhí)行package.json中的lint腳本具壮,這時(shí)候就需要修改hook配置了

打開.hucky中的pre-commit配置文件准颓,將原本的npm test改成npm run lint即可

1.5 git commit規(guī)范

  • Commitizen用于編寫規(guī)范的commit message
  • commitlint用于檢查提交的信息是否符合規(guī)范,用于避免提交的時(shí)候是直接git commit -m "xxx"棺妓,而不是通過Commitizen時(shí)的情況

1.5.1 Commitizen

  1. 安裝Commitizen

    npm install commitizen -D
    
  2. 安裝cz-conventional-changelog攘已,并且初始化cz-conventional-changelog

    npx commitizen init cz-conventional-changelog --save-dev --save-exact
    

    該命令會(huì)安裝cz-conventional-changelog并在package.json中進(jìn)行配置

  3. 現(xiàn)在提交代碼就可以使用npx cz提交,提交的message就是規(guī)范的了

    • type

      Type 作用
      feat 新增特性 (feature)
      fix 修復(fù) Bug(bug fix)
      docs 修改文檔 (documentation)
      style 代碼格式修改(white-space, formatting, missing semi colons, etc)
      refactor 代碼重構(gòu)(refactor)
      perf 改善性能(A code change that improves performance)
      test 測(cè)試(when adding missing tests)
      build 變更項(xiàng)目構(gòu)建或外部依賴(例如 scopes: webpack怜跑、gulp样勃、npm 等)
      ci 更改持續(xù)集成軟件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
      chore 變更構(gòu)建流程或輔助工具(比如更改測(cè)試環(huán)境)
      revert 代碼回退

1.5.2 commitlint

  1. 安裝 @commitlint/config-conventional 和 @commitlint/cli

    npm i @commitlint/config-conventional @commitlint/cli -D
    
  2. 在根目錄創(chuàng)建commitlint.config.js文件性芬,配置commitlint

    module.exports = {
      extends: ['@commitlint/config-conventional']
    }
    
  3. 使用husky生成commit-msg文件峡眶,驗(yàn)證提交信息:

    npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
    

二、第三方集成

2.1 vue.config.js配置

vue.config.js有三種配置方式:

  • 方式一:直接通過CLI提供給我們的選項(xiàng)來配置:
    • 比如publicPath:配置應(yīng)用程序部署的子目錄(默認(rèn)是 /植锉,相當(dāng)于部署在 https://www.my-app.com/)辫樱;
    • 比如outputDir:修改輸出的文件夾;
  • 方式二:通過configureWebpack修改webpack的配置:
    • 可以是一個(gè)對(duì)象俊庇,直接會(huì)被合并狮暑;
    • 可以是一個(gè)函數(shù),會(huì)接收一個(gè)config辉饱,可以通過config來修改配置搬男;
  • 方式三:通過chainWebpack修改webpack的配置:
    • 是一個(gè)函數(shù),會(huì)接收一個(gè)基于 webpack-chain 的config對(duì)象彭沼,可以對(duì)配置進(jìn)行修改止后;

示例

const path = require('path')

module.exports = {
  // 配置方式一
  outputDir: './build',
  
  // 配置方式二:對(duì)象形式
  configureWebpack: {
    resolve: {
      alias: {
        views: '@/views'
      }
    }
  }
  
  // 配置方式三:函數(shù)形式
  configureWebpack: (config) => {
    config.resolve.alias = {
      '@': path.resolve(__dirname, 'src'),
      views: '@/views'
    }
  },
      
  // 配置方式四:鏈?zhǔn)秸{(diào)用形式
  chainWebpack: (config) => {
    config.resolve.alias.set('@', path.resolve(__dirname, 'src')).set('views', '@/views')
  }
}

遇到明確沒問題的ESLint提示

比如這里的const path = require('path'),ESLint會(huì)提示使用ES風(fēng)格的import替代溜腐,但是由于是給node用的配置文件译株,必須是commonJS風(fēng)格的,這個(gè)時(shí)候我們需要把這一條ESLint提示禁用掉

vscode中將鼠標(biāo)懸停在提示的代碼處挺益,會(huì)彈出對(duì)應(yīng)的ESLint提示項(xiàng)歉糜,比如這里的提示就是@typescript-eslint/no-var-requires

將它復(fù)制下來,打開.eslintrc.js望众,在rules中添加該配置項(xiàng)匪补,并且值設(shè)為off即可關(guān)閉

rules: {
  '@typescript-eslint/no-var-requires': 'off'
}

2.2 vue-router集成

  1. 安裝vue-router

    npm install vue-router@4
    
  2. 創(chuàng)建文件 -- src/router/index.ts

    index.ts

    import { createRouter, createWebHashHistory } from 'vue-router'
    import { RouteRecordRaw } from 'vue-router'
    
    const routes: RouteRecordRaw[] = [
      {
        path: '/',
        redirect: '/main'
      },
      {
        path: '/main',
        component: () => import('@/views/main/main.vue')
      },
      {
        path: '/login',
        component: () => import('@/views/login/login.vue')
      }
    ]
    
    const router = createRouter({
      routes,
      history: createWebHashHistory()
    })
    
    export default router
    
  3. main.ts中注冊(cè)

    main.ts

    import { createApp } from 'vue'
    import App from './App.vue'
    
    import router from '@/router'
    
    const app = createApp(App)
    app.use(router)
    app.mount('#app')
    
  4. App.vue中配置路由跳轉(zhuǎn)

    <template>
      <div class="app">
        <router-link to="/login">登錄</router-link>
        <router-link to="/main">首頁</router-link>
        <router-view></router-view>
      </div>
    </template>
    

2.3 vuex集成

  1. 安裝vuex

    npm install vuex@next --save
    
  2. 創(chuàng)建文件 -- src/store/index.ts

    index.ts

    import { createStore } from 'vuex'
    
    const store = createStore({
      state() {
        return {
          name: 'plasticine'
        }
      }
    })
    
    export default store
    
  3. main.ts中注冊(cè)

    app.use(store)
    
  4. App.vue中使用

    <h1>{{ $store.state.name }}</h1>
    

2.4 element-plus集成

  1. 安裝element-plus

    npm install element-plus --save
    
  2. 引入element-plus

2.4.1 完整引入

// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

2.4.2按需引入

  1. 安裝兩個(gè)插件

    npm install -D unplugin-vue-components unplugin-auto-import
    
  2. 修改vue.config.js中的Webpack配置

    const AutoImport = require('unplugin-auto-import/webpack')
    const Components = require('unplugin-vue-components/webpack')
    const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
    
    module.exports = {
      configureWebpack: {
        plugins: [
          AutoImport({
            resolvers: [ElementPlusResolver()]
          }),
          Components({
            resolvers: [ElementPlusResolver()]
          })
        ]
      }
    }
    
  3. main.ts中注冊(cè)全局組件

    main.ts

    import {
      ElButton,
      ElTable,
      ElAlert,
      ElAside,
      ElAutocomplete,
      ElAvatar,
      ElBacktop,
      ElBadge
    } from 'element-plus'
    
    const app = createApp(App)
    
    const components = [
      ElButton,
      ElTable,
      ElAlert,
      ElAside,
      ElAutocomplete,
      ElAvatar,
      ElBacktop,
      ElBadge
    ]
    
    for (const cpn of components) {
      app.component(cpn.name, cpn)
    }
    

    這樣的話雖然能用,但是隨著開發(fā)進(jìn)度的進(jìn)行烂翰,引用的組件越來越多夯缺,會(huì)導(dǎo)致main.ts文件過于臃腫,作為入口文件甘耿,應(yīng)當(dāng)盡量只包括主要邏輯踊兜,不應(yīng)該包含過多的邏輯代碼,因此現(xiàn)在我們對(duì)其進(jìn)行抽離

  4. 創(chuàng)建文件 -- src/global/index.ts

    index.ts

    import {
      ElButton,
      ElTable,
      ElAlert,
      ElAside,
      ElAutocomplete,
      ElAvatar,
      ElBacktop,
      ElBadge
    } from 'element-plus'
    import { App } from 'vue'
    
    const components = [
      ElButton,
      ElTable,
      ElAlert,
      ElAside,
      ElAutocomplete,
      ElAvatar,
      ElBacktop,
      ElBadge
    ]
    
    export function registerApp(app: App): void {
      for (const cpn of components) {
        app.component(cpn.name, cpn)
      }
    }
    

    導(dǎo)出一個(gè)函數(shù)registerApp佳恬,在main.ts中只用調(diào)用該函數(shù)即可

    main.ts

    import { createApp } from 'vue'
    import App from './App.vue'
    import { registerApp } from '@/global'
    
    const app = createApp(App)
    
    registerApp(app)
    app.mount('#app')
    

    其實(shí)還可以進(jìn)一步抽離捏境,因?yàn)橐院罂赡苓€要注冊(cè)別的組件,這時(shí)候如果全部注冊(cè)邏輯寫在單個(gè)registerApp函數(shù)里就又會(huì)變得臃腫了毁葱,因此將每個(gè)組件庫的組件注冊(cè)再次抽離成一個(gè)文件垫言,如現(xiàn)在要注冊(cè)element-plus,那么我們就在global目錄下創(chuàng)建一個(gè)register-element.ts倾剿,然后把注冊(cè)邏輯寫在里面筷频,registerApp去調(diào)用即可

  `register-element.ts`

  ```typescript
  import { App } from 'vue'
  import {
    ElButton,
    ElTable,
    ElAlert,
    ElAside,
    ElAutocomplete,
    ElAvatar,
    ElBacktop,
    ElBadge
  } from 'element-plus'
  
  const components = [
    ElButton,
    ElTable,
    ElAlert,
    ElAside,
    ElAutocomplete,
    ElAvatar,
    ElBacktop,
    ElBadge
  ]
  
  export default function (app: App): void {
    for (const cpn of components) {
      app.component(cpn.name, cpn)
    }
  }
  ```

  `src/global/index.ts`

  ```typescript
  import { App } from 'vue'
  import registerElement from './register-element'
  
  export function registerApp(app: App): void {
    registerElement(app)
  }
  ```
  1. App.vue中直接使用

    <el-button>element-plus 按鈕</el-button>
    
  2. 更優(yōu)雅地注冊(cè)

    Vue的app.use()會(huì)默認(rèn)傳入app,因此可以進(jìn)行如下調(diào)整前痘,讓代碼風(fēng)格更加統(tǒng)一

  `main.ts`

  ```typescript
  import { createApp } from 'vue'
  import App from './App.vue'
  
  import { globalRegister } from '@/global'
  
  const app = createApp(App)
  
  app.use(globalRegister)
  app.mount('#app')
  ```

  `src/global/index.ts`

  ```typescript
  import { App } from 'vue'
  import registerElement from './register-element'
  
  export function globalRegister(app: App): void {
    app.use(registerElement)
  }
  ```

2.5 axios集成

2.5.1 安裝axios

npm install axios

2.5.2 創(chuàng)建文件 -- src/service/request/config.ts

該文件用于存放一些axios用到的配置項(xiàng)凛捏,如BASE_URL

/**
 * 生產(chǎn)環(huán)境 -- production
 * 開發(fā)環(huán)境 -- development
 * 測(cè)試環(huán)境 -- test
 */

let BASE_URL = ''
const TIME_OUT = 10000

switch (process.env.NODE_ENV) {
  case 'development':
    BASE_URL = 'http://123.207.32.32:8000'
    break
  case 'production':
    BASE_URL = 'https://www.baidu.com/'
    break
  case 'test':
    BASE_URL = 'https://www.baidu.com/'
    break
}

export { BASE_URL, TIME_OUT }

2.5.3 封裝AxiosInstance

封裝AxiosInstance實(shí)例對(duì)象,主要是添加對(duì)各種攔截器的支持际度,攔截器的粒度細(xì)致到以下三個(gè)階段:

  1. 全局請(qǐng)求響應(yīng)攔截葵袭,對(duì)所有的請(qǐng)求都生效
  2. 實(shí)例請(qǐng)求響應(yīng)攔截,針對(duì)不同的實(shí)例可以設(shè)置不同的請(qǐng)求響應(yīng)攔截
  3. 單獨(dú)請(qǐng)求響應(yīng)攔截乖菱,針對(duì)具體接口設(shè)置相應(yīng)的請(qǐng)求響應(yīng)攔截

要實(shí)現(xiàn)上述攔截器坡锡,需要自己封裝一個(gè)攔截器類型接口,分別對(duì)應(yīng)請(qǐng)求成功處理窒所、請(qǐng)求失敗處理鹉勒、響應(yīng)成功處理、響應(yīng)失敗處理

因此再創(chuàng)建一個(gè)文件吵取,用于存放用到的接口類型 -- src/service/request/type.ts

import { AxiosRequestConfig, AxiosResponse } from 'axios'

export interface WFRequestInterceptors<T = AxiosResponse> {
  requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorCatch?: (error: any) => any
  responseInterceptor?: (res: T) => T
  responseInterceptorCatch?: (error: any) => any
}

export interface WFRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: WFRequestInterceptors<T>
  showLoading?: boolean
}

創(chuàng)建一個(gè)類用于封裝AxiosInstance實(shí)例禽额,這個(gè)類存放在 src/service/request/index.ts中并默認(rèn)導(dǎo)出

src/service/request/index.ts

import axios from 'axios'

import { ElLoading } from 'element-plus'
import type { AxiosInstance } from 'axios'

import { WFRequestConfig, WFRequestInterceptors } from './type'
import { DEAFAULT_LOADING } from './config'
import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'

class WFRequest {
  instance: AxiosInstance
  interceptors?: WFRequestInterceptors
  showLoading: boolean // 處理請(qǐng)求時(shí)是否要顯示加載動(dòng)畫
  loading?: LoadingInstance

  constructor(config: WFRequestConfig) {
    // 創(chuàng)建 axios 實(shí)例
    this.instance = axios.create(config)
    // 保存基本信息
    this.showLoading = config.showLoading ?? DEAFAULT_LOADING
    this.interceptors = config.interceptors

    // ================== 屬于實(shí)例的攔截器 ==================
    // 將請(qǐng)求攔截器注冊(cè)到 axios 實(shí)例中
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestInterceptorCatch
    )
    // 將請(qǐng)求攔截器注冊(cè)到 axios 實(shí)例中
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    )

    // ================== 所有實(shí)例的攔截器 ==================
    // 所有實(shí)例的請(qǐng)求攔截器
    this.instance.interceptors.request.use(
      (config) => {
        console.log('所有的實(shí)例都有的攔截器: 請(qǐng)求成功攔截')

        // 處理加載動(dòng)畫
        if (this.showLoading) {
          this.loading = ElLoading.service({
            lock: true,
            text: '正在請(qǐng)求數(shù)據(jù)......',
            background: 'rgba(0, 0, 0, 0.5)'
          })
        }

        return config
      },
      (err) => {
        console.log('所有的實(shí)例都有的攔截器: 請(qǐng)求成功攔截')
        return err
      }
    )
    // 所有實(shí)例的響應(yīng)攔截器
    this.instance.interceptors.response.use(
      (res) => {
        console.log('所有的實(shí)例都有的攔截器: 響應(yīng)成功攔截')
        // 如果有加載動(dòng)畫則將加載動(dòng)畫移除
        this.loading?.close()

        // 從 res 中提出 data 返回 因?yàn)?data 才是前端真正需要的,其他的東西是 axios 自己封裝的 基本用不到
        const data = res.data

        return data
      },
      (err) => {
        console.log('所有的實(shí)例都有的攔截器: 響應(yīng)失敗攔截')
        this.loading?.close()

        // HTTP 的狀態(tài)碼要在失敗響應(yīng)攔截器中攔截
        if (err.response.status === 404) {
          console.log('404 not found...')
        }

        return err
      }
    )
  }

  request<T>(config: WFRequestConfig<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      // 單個(gè)請(qǐng)求如果配置了請(qǐng)求攔截器 則先執(zhí)行其配置的請(qǐng)求攔截器 再執(zhí)行全局的請(qǐng)求攔截器
      if (config.interceptors?.requestInterceptor) {
        config = config.interceptors.requestInterceptor(config)
      }

      // 判斷是否要顯示 loading
      if (config.showLoading === false) {
        this.showLoading = config.showLoading
      }

      this.instance
        .request<any, T>(config)
        .then((res) => {
          // 如果單次請(qǐng)求配置了響應(yīng)攔截器 則執(zhí)行實(shí)例的響應(yīng)攔截器
          if (config.interceptors?.responseInterceptor) {
            res = config.interceptors.responseInterceptor(res)
          }
          // 將 showLoading 設(shè)置為 true -- 這樣就不會(huì)影響下一個(gè)請(qǐng)求了
          this.showLoading = DEAFAULT_LOADING

          resolve(res)
        })
        .catch((err) => {
          // 將 showLoading 設(shè)置為 true -- 這樣就不會(huì)影響下一個(gè)請(qǐng)求了
          this.showLoading = DEAFAULT_LOADING
          reject(err)

          return err
        })
    })
  }

  get<T>(config: WFRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }

  post<T>(config: WFRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }

  put<T>(config: WFRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'PUT' })
  }

  delete<T>(config: WFRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }

  patch<T>(config: WFRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'PATCH' })
  }
}

export default WFRequest

2.5.3.1使用泛型T的原因

這里使用到的泛型T,意思是在調(diào)用request方法后返回的對(duì)象類型是由axios的AxiosResponse封裝好的T脯倒,即調(diào)用返回對(duì)象的data屬性拿到的就是T類型的對(duì)象实辑,這點(diǎn)可以通過源碼驗(yàn)證:

request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;

而這里調(diào)用request時(shí),傳入的泛型為request<any, T>藻丢,目的是不讓axios幫我們封裝成AxiosResponse實(shí)例剪撬,而是直接返回我們需要的泛型對(duì)象T(會(huì)由Promise封裝)

因此thenres類型就是T,然后再在調(diào)用單個(gè)請(qǐng)求的攔截器的時(shí)候:

export interface WFRequestInterceptors<T = AxiosResponse> {
  ...
  responseInterceptor?: (res: T) => T
}

此時(shí)響應(yīng)攔截器接收到的參數(shù)類型就是T悠反,T默認(rèn)就是AxiosResponse残黑,而一旦我們更改為自己想要的T類型,則不會(huì)再傳入和返回AxiosResponse類型的對(duì)象

之所以要折騰這么一大長(zhǎng)串代碼斋否,是因?yàn)橐獙?shí)現(xiàn)一個(gè)功能:讓調(diào)用接口的時(shí)候得到的返回值是預(yù)先定義好的后端接口會(huì)返回的數(shù)據(jù)格式的接口對(duì)象梨水,這樣在調(diào)用者看來,就能有如下體驗(yàn):

// 可以指定接口返回的對(duì)象類型
interface DataType {
  data: any
  returnCode: string
  success: boolean
}

wfRequest
  .get<DataType>({
    url: '/home/multidata'
  })
  .then((res) => {
    console.log(res)
    console.log(res.data)
    console.log(res.returnCode)
    console.log(res.success)
  })

調(diào)用者在then中拿到的不再是AxiosResponse對(duì)象茵臭,而是調(diào)用get方法時(shí)傳入的DataType泛型對(duì)象

2.5.3.2 service中實(shí)例化封裝好的類

src/service/index.ts中實(shí)例化一個(gè)WFRequest的對(duì)象疫诽,并將其導(dǎo)出以供使用

import WFRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'

const wfRequest = new WFRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  interceptors: {
    requestInterceptor: (config) => {
      // 給該實(shí)例發(fā)起的所有請(qǐng)求攜帶上 token
      const token = 'temp_token'
      if (token) {
        config.headers.Authorization = `Bearer ${token}`
      }
      console.log('單個(gè)實(shí)例請(qǐng)求成功的攔截')

      return config
    },
    requestInterceptorCatch: (err) => {
      console.log('單個(gè)實(shí)例請(qǐng)求失敗的攔截')
      return err
    },
    responseInterceptor: (res) => {
      console.log('響應(yīng)成功的攔截')
      return res
    },
    responseInterceptorCatch: (err) => {
      console.log('響應(yīng)失敗的攔截')
      return err
    }
  }
})

export default wfRequest

2.5.3.3 體驗(yàn)

在項(xiàng)目的main.ts中使用體驗(yàn)一下

import wfRequest from './service'

// 可以指定接口返回的對(duì)象類型
interface DataType {
  data: any
  returnCode: string
  success: boolean
}

wfRequest.get<DataType>({
  url: '/home/multidata',
  interceptors: {
    requestInterceptor: (config) => {
      console.log('單獨(dú)請(qǐng)求的攔截器')
      return config
    },
    responseInterceptor: (res) => {
      console.log('單獨(dú)響應(yīng)的攔截器')
      console.log(res.returnCode)
      console.log(res.success)
      console.log(res.data)
      return res
    }
  }
})
體驗(yàn)封裝好的axios
請(qǐng)求中會(huì)自動(dòng)攜帶上token
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市笼恰,隨后出現(xiàn)的幾起案子踊沸,更是在濱河造成了極大的恐慌,老刑警劉巖社证,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逼龟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡追葡,警方通過查閱死者的電腦和手機(jī)腺律,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宜肉,“玉大人匀钧,你說我怎么就攤上這事∶担” “怎么了之斯?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)遣铝。 經(jīng)常有香客問我佑刷,道長(zhǎng),這世上最難降的妖魔是什么酿炸? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任瘫絮,我火速辦了婚禮,結(jié)果婚禮上填硕,老公的妹妹穿的比我還像新娘麦萤。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布壮莹。 她就那樣靜靜地躺著翅帜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垛孔。 梳的紋絲不亂的頭發(fā)上藕甩,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音周荐,去河邊找鬼。 笑死僵娃,一個(gè)胖子當(dāng)著我的面吹牛概作,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播默怨,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼讯榕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了匙睹?” 一聲冷哼從身側(cè)響起愚屁,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痕檬,沒想到半個(gè)月后霎槐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梦谜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年丘跌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唁桩。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闭树,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荒澡,到底是詐尸還是另有隱情报辱,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布单山,位于F島的核電站碍现,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏饥侵。R本人自食惡果不足惜鸵赫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躏升。 院中可真熱鬧辩棒,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至者吁,卻和暖如春窘俺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背复凳。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工瘤泪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人育八。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓对途,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親髓棋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子实檀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容