對 Vue 項目團隊開發的一些基本配置封裝分享

本篇文章主要帶來的是 vue 基礎架構 篇,大家都知道, Vue3.0 後 Vue2.0 會有一個終結版出來,也就說明 Vue 迎來了新時代,但是並不是所有項目都能夠一起邁向新時代的輪船。本文主要是承接上篇優化的技巧文章做一個續篇吧,這個續篇主要是針對團隊開發相關的東西,相關插件和庫只是微微帶過,如果本文能夠推動你們的生產線,就點個贊吧。

配置相關類的可以去搜索對應的分享貼,或者看我之前的文章,本文內容較爲貼合團隊開發,非工具鏈分享文,大部分都能引發一些技術層面的思考。

前言

做了什麼?

基本 HTTP 請求做了什麼?

錯誤處理

如下實例,當 HTTP 請求出現錯誤的時候,首先通過 getEnv 獲取當前的開發環境,如果是 dev(開發環境下) 只做一個簡單的 console.log,非開發環境下,則是上報進行異常監聽,對於前後端來說都

function handleError ( error, msg) {
  if (getEnv() === 'dev') {
    tools.log.danger('>>>>>> HTTP Error >>>>>>')
    console.log(error, msg)
  } else {
    Store.dispatch('logs/push', {
      message: msg,
      type: 'danger'
    })
  }
}

RESTFul

相對於一些攔截器來說,都非常的簡單,需要注意的無非就是根據團隊的一些規範制定一些規則,如常見的 code 碼等方法,大部分情況下,如無意外,99% 的接口都是請求成功的,但因爲特殊性,內部會有一個 code 的狀態來定義正反向。同樣的,在操作接口時,一些狀態也是需要和接口的請求方式同步,參考如下:

現如今大部分的接口的規範都使用 RESTful ,如果不知道 RESTful 是什麼,可以看看 @阮一峯 的文章來初步瞭解。RESTful API 最佳實踐 @阮一峯的網絡日誌

狀態碼機制

同樣的 code 中我們也自定義日常開發中的一些狀態碼,當我們需要用到 第三方API 的時候,前後端都需要快速的定位是自身服務的問題,還是其他服務(例如中臺)的問題,因此對接服務我們都自定義了一些 code 來陳述這一類錯誤的處理。可以參考如下,這些其實都是創建在一個對象當中的: 自定義 code

當我們細化一些異常時,這時候是可以劃分的非常細緻的,這裏給出微信的一些參考:

function createHttpService (settings) {
  const service = Axios.create(settings)
  service.interceptors.request.use(
    config => {
      
      const token = localStorage.getItem('access_token')
      config.headers.token = token
      return config
    },
    error => {
      return Promise.reject(error)
    }
  )
  
  service.interceptors.response.use(
    response => {
      console.log(response)
      const { code, message, data } = response.data
      
      if (code >= 30000) {
        console.log('>>> 自定義錯誤信息,全局提示處理', message)
        return data
      }
      
      if (code >= 200 && code < 300) {
        return data
      }

      
      if (code >= 300 && code < 600) {
        return Promise.reject(response.data)
      }
    },
    error => {
      const { status = 404 } = error?.response
      if (Object.prototype.hasOwnProperty.call(codeMessage, status)) {
        handleError(error, codeMessage[status])
      }
      throw error
    }
  )
  return service
}

const http = createHttpService({
  withCredentials,
  timeout,
  baseURL
})

約定式 http 請求

看過我上幾篇文章的文章大家都大致清楚,約定式請求可以很好的簡化請求封裝的複雜度,同樣的當你公司存在小白或者是實習生的話,對於請求的拆封是沒有考慮的,當你交付一個任務,完成並不是等於較爲好的完成。

約定式除了減少新手開發者在團隊中不穩定的代碼因素的同時,也減少了開發時一個個的寫 AxiosPromise 函數的重複行爲。下面是一個基本的接口約定,在 login-api.js 下寫的文件,都將被映射成爲 請求函數

export default {
  getPerson: 'GET /person',
  setPerson: 'POST /person',
  updatePerson: 'PUT /person/:id',
  deletePerson: 'DELETE /person/:id'
}

log.js 打印的結果,團隊開發人員不需要關注函數本身,只需要關注調用。同時, 開發環境下 所有的接口信息都會通過 console.table 輸出到控制檯,在沒有很好的類型推導的情況下,依舊可以快速的調用對應接口來獲取後端數據。

本身 api 函數拆分出來,其實都是一個重複的工作,對開發者成長是毫無意義的。

不同的調用方式

爲了統一的調用,也適當的給出了兩種使用方式,大多數場景下使用都是通用的,第一種方式較爲的保守,其本質上是交由成員來處理任務,實例:

<script>
 
import { useServices } from 'framework'


const { getPerson } = useServices()

export default {
  name: 'home',
  created () {
    
    getPerson().then(res => {
      console.log(res)
      alert('接口請求成功')
    }, err => {
      console.log(err)
      alert('接口請求失敗')
    })
  }
}
</script>

上述實例非常的簡單,相信有一點基礎的同學都可以看得出來,第一種方法非常的普遍,適用於大多數人羣,但是弊端也很明顯,如果每一個接口都需要做一次 then & catch & finally 的話無疑是一個災難,因此第二種方法誕生了,對於新手來說,更加的友好。如下實例:

<script>
 
import { useServices } from 'framework'

const Admin = {
  created () {
    this.getPersonData()
  },
  methods: {
    
    async getPersonData () {
      const [, err] = await useServices('getPerson')
      if (err) {
        alert('接口請求失敗')
      } else {
        alert('接口請求成功')
      }
    }
  }
}
export default Admin
</script>

在原先 api 的基礎上, useServices 爲 Promise 的行爲包裹了一層中間件,當你決定使用非常態請求時,這個 promise中間件 行爲會被觸發。且將 Promise 後的結果形態抽象成爲了一個數組返回出來,那麼在邏輯塊中,我們只需要簡單的通過 async & await 對結果中的數據進行處理,而不必關注無意義的 try catch 和 then catch 。

兼容兩種方式的原因是不同開發者不同習慣問題,有些時候開發者認爲,錯誤的處理還是交由處理人去解決,從而達到錯誤解決目的。

Mixin 數據管理 (model)

有了約定式的請求,很統一的解決我們請求的問題,但隨之而來的就是異步數據的管理問題,很長一段時間中,Vue 開發者都習慣性的將接口請求,數據邏輯處理放在 vue 文件中,比如最常見的 分頁表格數據 , 基礎表單顯示,每一個頁面中都聲明瞭非常多的 total , pageSize , currentPage ,tableData 等字段,並且樂此不疲的反覆 CV,結束忙碌的一天後美滋滋覺得今天又完成了 10 多個頁面。其實細心的同學也發現了,不管你 CV 多少次代碼,對自身的提升是有限度的,無非就是孰能生巧,複製粘貼的速度更加快了,就好比你寫了 4000 次 hello 不代表你有了 4000 個詞彙一般。

因此就產生了封裝自己的表格組件,只需要傳遞很小一部分參數進去(如 HTTP 請求方法),就可以達到渲染表格的實現。同樣的,也有小夥伴們封裝了 Global Mixin 來解決這部分的任務。同樣的,爲了很好的管理數據層,我也在嘗試不同的數據管理,隨着業務邏輯增大,大部分的頁面的異步數據都難以管理,或多或少會和頁面的邏輯數據混淆,過一段時間後,需要將 $data 中的數據解構重新梳理,才能泡通邏輯。

因此,在嘗試不同的解決方案後, mixin 成了首當其衝的方案,它並不像 vuex 一般會在全局生效,而只對混入的頁面生效,所以在簡單的嘗試後,對 mixin 進行了包裝,抽象成爲了一個 model 層,這個 model 層的作用主要是處理菜單級頁面的異步數據的流向打造的,視圖頁面數據在 .vue 中聲明, 後端數據 在 model.js 中。

一個基本的 model.js 它看起來是這樣的

export default {
  namespace: 'Home',
  state: {
    move: {},
    a: 1,
    b: 2
  },
  actions: {
    
    async setUser (state, { payload, set }) {
      const data = await test()
      await set(state, 'move', data)
      return state.move
    }
  }
}

const test = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        user: 'wangly',
        age: 21
      })
    }, 2000)
  })
}

那麼在頁面中,聲明的組件都會被傳入到 useModels 中進行混入,混入後的 Mixin 命名格式已經比較複雜了,這個時候來使用的就不是當前的 this.xxx ,而是統一執行 this.useDispatch 進行處理,並沒有直接去觸發 model methods ,同樣的對所有的 model 狀態都在發生改變,內部會有不同的 loading。

一個簡單的實例

通過一個簡單的實例來模擬一次服務端數據加載,從無到有的過程,純 model.js 控制數據和狀態 通過下面 test 模擬一個數據接口,在 getDesserts 進行獲取,爲了保證閱讀質量,模擬的數據就截斷了,可以參考 vuetifyUI Table Demo 數據。

export default {
  namespace: 'Admin',
  state: {
    mockTabData: [],
    headers: [
      { text: 'Dessert (100g serving)', value: 'name' },
      { text: 'Calories', value: 'calories' },
      { text: 'Fat (g)', value: 'fat' },
      { text: 'Carbs (g)', value: 'carbs' },
      { text: 'Protein (g)', value: 'protein' },
      { text: 'Iron (%)', value: 'iron' }
    ]
  },
  actions: {
    
    async getDesserts (state, { payload, set }) {
      const data = await test()
      console.log(data)
      if (data) {
        state.mockTabData = data
      }
    }
  }
}

const test = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve([
        {
          name: 'Frozen Yogurt',
          calories: 159,
          fat: 6.0,
          carbs: 24,
          protein: 4.0,
          iron: '1%'
        },
        
      ])
    }, 2000)
  })
}

在頁面中,在 created 鉤子中,通過調用 this.useDispatch('Admin/getDesserts') 進行數據獲取,然後將 Admin.headers , Admin.mockTabData 賦值到對應的組件參數上去,同時通過 當前model 方法的副作用進行骨架屏的控制。

所有的 model 方法都會在 data 中生成一個副作用狀態,爲避免衝突,data 中避免定義 model ,以免被 model.js 覆蓋。

<template>
  <div>
    <v-data-table
      v-if="!model['Admin/getDesserts']"
      :headers="Admin.headers"
      :items="Admin.mockTabData"
    ></v-data-table>
    <v-sheet v-else>
      <v-skeleton-loader :boilerplate="false" type="table"></v-skeleton-loader>
    </v-sheet>
  </div>
</template>

<script>
import { useModels } from 'framework'

const Admin = {
  created () {
    this.useDispatch('Admin/getDesserts')
  }
}
export default useModels(Admin, ['Admin'])
</script>

看看效果把,非常的簡單的控制數據響應變化:

該特性實驗中,只作爲參考。性能壓測還在進行中 QAQ。

jsDoc 項目文檔

項目文檔是一個非常重要的事情,不要過度相信自己的代碼,如果業務大的話,3 個月左右的時間,你經手的項目可能就會丟失一部分直觀的記憶,這個時候不論是你繼續維護還是新人繼續維護都是一件非常頭疼的事情,同時需要考慮的是當項目進行到一般,後面有新人加入的時候,龐大的 components , utils , api 都會讓新人感到無從下手的感覺,因此一份文檔就顯得格外珍貴了。那麼有同學問了,我業務都忙不過,還要花時間整理文檔,其他人的事情關我什麼事?

一個好的項目必然會有一個好的文檔,基於這類問題,所以才引入了一個文檔工具來生成文檔,在這個期間,也同時的對文檔進行了改進,更加的貼合 vue 本身,首先就是對文檔語法 @module 進行了改造,同時通過 @page 來聲明頁面,通過 @component 聲明公共組件,如下示例:

<template>
  <div>2222</div>
</template>

<script>
import { useModels, useServices } from 'framework'

const Admin = {
  created () {
    this.getPersonData()
  },
  data: () => ({
    
    discount: false,
    
    currentTab: 1
  }),
  methods: {
    
    async getPersonData () {
      const [, err] = await useServices('getPerson')
      if (err) {
        alert('接口請求失敗')
      } else {
        alert('接口請求成功')
      }
    }
  }
}
export default useModels(Admin, ['Admin'])
</script>

那麼最終通過 yarn doc 命令生成文檔:

yarn doc

效果看上去是下面這樣的 可以看到,現在的一些註釋就已經很規範了,但依舊不完美,主要因素是來自於 jsdoc 文檔的主題問題,如果團隊需要的話,可以自己重構一套出來,也相對來說簡單。

文中實例僅限參考,註釋文檔請移步:jsdoc

自定義開發日誌 log

對於 console 的使用,當時在看 D2Admin 的時候將其克隆了一份過來,對於拋錯的日誌來說,我們並不需要將自身的一些 consle 也進行收集,但是 console 之間也存在等級,如果通過 console.error 進行的話,可能會被捕捉從而傳入給後臺,因此,重寫了一份 log.js 用於開發版和測試版的調試使用。

const log = {}


function typeColor (type = 'default') {
  let color = ''
  switch (type) {
    case 'default':
      color = '#303133'
      break
    case 'primary':
      color = '#409EFF'
      break
    case 'success':
      color = '#67C23A'
      break
    case 'warning':
      color = '#E6A23C'
      break
    case 'danger':
      color = '#F56C6C'
      break
    default:
      break
  }
  return color
}


log.capsule = function (title, info, type = 'primary') {
  console.log(
    `%c ${title} %c ${info} %c`,
    'background:#35495E; padding: 1px; border-radius: 3px 0 0 3px; color: #fff;',
    `background:${typeColor(
      type
    )}; padding: 1px; border-radius: 0 3px 3px 0;  color: #fff;`,
    'background:transparent'
  )
}


log.colorful = function (textArr) {
  console.log(
    `%c${textArr.map(t => t.text || '').join('%c')}`,
    ...textArr.map(t => `color: ${typeColor(t.type)};`)
  )
}

log.default = function (text) {
  log.colorful([{ text }])
}

log.primary = function (text) {
  log.colorful([{ text, type: 'primary' }])
}

log.success = function (text) {
  log.colorful([{ text, type: 'success' }])
}

log.warning = function (text) {
  log.colorful([{ text, type: 'warning' }])
}

log.danger = function (text) {
  log.colorful([{ text, type: 'danger' }])
}

export default log

如下圖效果:

log.default('>>> 我是一些默認提示')
log.primary('>>> 我是一些標記提示')
log.success('>>> 我是一些成功提示')
log.warning('>>> 我是一些警告提示')
log.danger('>>> 我是一些錯誤提示')

組件和頁面管理

在開發過程中,同樣養成一些好習慣對於項目體驗會有很好的幫助,寫代碼就和針線活一樣,細心謹慎,邏輯分明才能學到更多,減少 P0 BUG 的出現,如果你項目不趕,還一直出現同一個問題,感官是非常差的。因此,牢記以下小技巧,希望對你有幫助

頁面文件

在這裏推薦所有的頁面級別都放在一個樹下,目錄菜單使用文件夾且默認視圖爲 index.vue ,名稱都爲小寫駝峯,最好是一句小寫涵蓋如:home , user 。等等,組件統一放在起始頁面的 components 下,且名稱爲大駝峯帶模塊名,如 Admin 下的 Header 組件爲 AdminHeader.vue ,使用時則爲:<admin-header/> ,引入時,統一使用 @/views/admin/components/xxx 引入。菜單在深都是在一級菜單下的東西,帶上頁面名稱是爲了更好的區分,防止組件混淆。

方法導出

很多時候,不同的團隊成員在編寫 utils 時,有使用箭頭函數,也有使用 function 來聲明的,因此,在這裏推薦統一使用 export function 的形式進行 js 的聲明,如下方法:

import asyncAxiosInstance from '@/plugin/createService'
import currentModels from '@/plugin/createModel'

export function getEnv () {
  return process.env.VUE_APP_MODE || 'dev'
}


export function useModels (component, models, isDispatch = true) {
  const current = []
  currentModels.forEach(item => {
    if (models.includes(item.name)) {
      current.push(item.mixin)
    }
  })
  if (isDispatch && current.length > 0) {
    current.push(currentModels[0].mixin)
  }
  console.log(current)
  if (component?.mixins) {
    const preMixin = component.mixins
    component.mixins = current.concat(preMixin)
    return component
  }
  component.mixins = [...current]
  console.log(component)
  return component
}

常用的指令

日常開發中,一些指令能夠達到事半功倍的效果,但是指令需要貼合業務進行,同時設計者在設計時,同樣需要標註文檔,方便團隊成員查看。下面的一些指令推薦在進行註冊掉:

指令資源

使用 SASS 還是 SCSS?

現如今最好的 CSS擴展語言 依舊是 SASS 和 LESS ,兩者大差不差,可以根據團隊需要進行更換,使用起來沒有啥差別。在開發項目中,對於 SASS 我是首先推薦的(非 SCSS),如果沒有熟悉使用 SASS 的同學會覺得非常反人類,但如果你的規範好的話,我想你可以看下下面的這段 SASS 代碼:

@mixin flex ($mod: flex, $justifyContent: center, $alignItems: center, $direction: initial)
  display: $mod
  justify-content: $justifyContent
  align-items: $alignItems
  flex-direction: $direction
// end flex mixin ...

在寫 CSS 時,都建議在末尾加上一段 end 註釋來作爲邏輯符號的完成,用於區分樣式塊的代碼,防止邏輯混亂,當大量的樣式維護較差時,我想 SCSS 給的安全感是比較高的,同理,當維護的好的時候, SASS 無疑是更加簡潔。當然也容易出錯。

兩者殊途同歸,可以根據團隊成員習慣選擇。

@mixin 和 %placeholder

首先, @mixin 適合用來寫一些具有邏輯性的 css ,如最基本的 flex ,可以通過傳遞的 params 進行不同的設置,這是 %placeholder 欠缺的,但是 %placeholder 在靜態樣式的繼承上,可以減少重複 css 的調用,減少重複代碼,運用的多數場景爲:基本卡片樣式統一的組件樣式 等設計稿無偏差的時候使用,因此不需要無腦使用 @mixin ,有時候 %placeholder 更香。

使用一個實例進行比對:

%demo
  color: red
  font-size: 20px
  border: 1px solid red
 // end ...

.test1
  @extend %demo
// end ...

.test2
  @extend %demo
// end ...

.test3
  @extend %demo
複製代碼

如上代碼,使用 %placeholder 最終會生成的樣式是下面這樣的:

.test1, .test2, .test3 {
  color: red
  font-size: 20px
  border: 1px solid red
}

而如果換成 @mixin ,他們的結果是這樣的:

@mixin demo()
  color: red
  font-size: 20px
  border: 1px solid red
// end ...

.test1
  @include demo()
// end ...

.test2
  @@include demo()
// end ...

.test3
  @@include demo()
// end ...

編譯後:

.test1 {
  color: red
  font-size: 20px
  border: 1px solid red
}
.test2 {
  color: red
  font-size: 20px
  border: 1px solid red
}
.test3 {
  color: red
  font-size: 20px
  border: 1px solid red
}

不用我說,你應該知道怎麼用了吧。

ESLint

理想的情況下,大部分前端團隊都會存在有 Eslint ,作爲一個代碼質量的標準,但也僅僅只是一個標準,配合 Git Commit 前置動作可以執行代碼檢閱是否合格來防止低於標準的代碼提交到存儲庫中,這一個動作需要開發者自身養成良好的編碼習慣和代碼質量意識。

如果使用的是 VS CODE 那麼就需要在編譯器中進行配置來開啓規則檢查,當違背了語法警告的同時,會提示如下警告:

不推薦直接 commit 時直接編譯化代碼,Eslint 是幫助開發者代碼成長的,而不是一個表面功夫的工具。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/iFb9EoAtcZpPOYJTExTLMw