Web項目實踐@樹洞(前端篇vue3)

前一篇對後端接口開發做了一個簡簡單單介紹,不夠專業,這一篇主要對《樹洞》前端做一個介紹。對於裏面某些細節的部分不再做說明,比如移動端適配,可以參考我之前《樹洞》系列文章。

《樹洞》這次的需求設計並不像最初那麼複雜,主要在於技術實踐。在開始做這件事的時候,需求設計就像電影《解憂雜貨鋪》那樣,分爲兩個部分:一個部分是公開信箋,另一個部分是私有信箋。從功能上,它們只是可見性的區別而已,所以最後只做了公開信箋。

功能需求

和電影不同的是公開信箋並不只是由後臺管理員回覆,而是隨機從用戶中抽取回復人,且只有該用戶擁有回覆權限。所有人都可以看到信箋內容和回覆內容,在設計上是允許對信箋和回覆發表自己的態度、分享,在做的過程中只做了對原信箋的贊同。分享因爲分享設計第三方接口,所以未開發。官網的功能還包括用戶的登錄、註冊、修改密碼、修改個人資料、查看創建內容和查看回復內容。

後臺管理系統功能設計的功能包括對後臺權限的增改查、後臺用戶的增改查、官網用戶的增改查、信箋及用戶對信箋操作數據的查看。起初的設計中這些功能還包括刪除操作,對後臺用戶、官網用戶的啓用、禁用操作、信箋的審覈等等。不過,一個人的精力實在有限,而關鍵的一點是它不能成爲一個商業化的東西。於是不斷的簡化簡化,儘可能去做一些技術上的練習。畢竟平時上班也在不停地重複一些界面功能,沒什麼懸念可言,最終只留下了一些簡單的功能。

技術選型

最開始標題準備寫《Web項目實踐@樹洞(前端篇)》,轉念一想,搞得好像到終點站一樣。然而並不是這樣,《樹洞》項目在早使用的vue2.x,現在使用的vue3.x,之後還會有react、angular,於是加上一個vue3宣示主權。在開始寫這個項目的時候覺得vue3.0還是少了點挑戰,加了個TypeScript,還是感覺少點什麼於是又決定用vite。現在項目寫完了,先做個總結。

首先,雖然標榜使用TypeScript,但是最終邏輯並沒有完全執行TypeScript寫法。簡單地嘗試了一下,寫着寫着有些雞肋的感覺。比如有一個表單,我要先去聲明一個表單裏面的數據類型。再比如傳遞給接口的參數爲number和string的聯合類型,但是在傳參的時候它變成值就是其中一種類型,然而被認爲是null而報錯。最後實在受不了,我全換成了any類型。對於TypeScript的寫法,我再適應一段時間,可能就習慣了。

然後是vite,對於這個工具其實也沒有太多想說的,它的編譯效率不可否認要快不少,只是我總感覺它的模板並不如vue-cli成熟。比如別名,說實話我沒太明白它的配置規則,這個在後面的vite配置再做討論。有一點特別爽的是:在對包進行升級的時候不會像vue-cli受到webpack的約束。

最後,沒有最後。vue整體上是比較簡單的,哪怕是到了vue3,只要vue2比較熟悉,看看vue3遷移指南基本上沒啥大問題。只是vue3對應的生態圈,諸如組件、插件之類的不如vue2豐富。就拿UI庫來說,我最常用的是element和ant-design。ant-design-vue2.x看似已經轉入正式版了,其實它還在不停的變動,而且還有比較的變動。比如useForm在v2.2之前需要單獨引入,在這之後已經集成到了Form。而element-plus截止到我寫這篇文章,還處於beta版或者alpha版(一直在搖擺),這完全不敢用啊。

element和ant-design這兩個UI庫說不上誰好誰不好。ant-design在功能上稍微要更豐富一些,比如Form組件的useForm、Table組件的columns屬性、Input的pressEnter事件等等。而element在細節上要稍微更注重一些,比如InputNumber的加減按鈕、Select的異步搜索、Image的加載狀態等等。因此,這兩個庫談不上誰更優秀。element還處於不穩定狀態,於是管理後臺使用ant-design。原本打算element和ant-design在管理後臺和官網上各使用一個,後捨棄了做PC,也就捨棄了用element-plus。

開始折騰

npm init vite@latest

首先創建項目,截圖是我最開始搭建項目時所留下,關於vite的相關信息請參考Vite官方文檔

切換目錄到項目下,開始安裝包,然後讓項目跑起來。哎,怎麼跑不起來,是因爲沒腿嗎?

沒腿就讓它長上腿吧。

node node_modules/esbuild/install

項目可以跑起來了,下面我們開始引入UI庫,做一些配置。先說一下配置,vue-cli的配置是在vue.config.js裏面配置,而vite是在vite.config.ts(因爲我是選的是ts模板)裏面。配置項大都與vue-cli差不多,當然也有不同的地方,比如插件,畢竟是不同的工具。再比如我們接着要說的UI庫ant-design-vue的主題定製,我們首先需要引入的不是css而是less文件,然後要開啓javascript,否則會報錯。

按照官網的配置方法配置,結果不生效。需要特別說明一下的是:我源碼中使用的less-loader是10.0,這也會有影響,不同版本的參數可能存在差異。不可否認,我最開始是直接安裝的less和less-loader,它們都是用的最新版,然後我去查官網資料,結果查到:

它告訴我這個配置項已經被廢棄了,是因爲這個原因嗎?當然,並不是,我立即按照官網的版本重新下載包,結果還是有問題。同樣的版本同樣的配方,還是不生效,爲什麼。再去查看vite、ant-design-vue、less-loader文檔,最後發現它一直在玩lessOptions這個配置項。去掉它,搞定,自定義終於生效了。在使用vue-cli的時候,我對sass-loader升級,結果一下子就報錯了,我就發現它和webpack有極強的關聯。

不僅如此,less也一樣,loader、nodejs、UI庫都有可能影響到它們,有時稍不注意一個升級就報錯了。如果我在vite升級,會出現什麼情況?帶着這個疑問,我用npm-check-updates對項目進行升級,結果發現沒什麼問題。於是我用同樣的配方對vant進行配置,等我配置完使用才發現,vant3.x根本不需要,因爲它使用的css4語法中的var進行變量的定義。

css: {
  preprocessorOptions: {
    less: {
      javascriptEnabled: true,
      modifyVars: {
        hack: `
          true;
          @import "${resolve(__dirname, 'src/styles/variables.less')}";
          @import "${resolve(__dirname, 'src/styles/mixins.less')}";
        `
      }
    }
  }
}

到此爲止,我們的項目總該又能跑起來了吧?不,不行,因爲我們在配置less的javascriptEnabled的之前,有這麼一段代碼:

@import '~ant-design-vue/dist/antd.less';

這段代碼會導致程序報錯:

在vite裏面它不認識“~”了,不認識我們就需要去配置這個別名。既然它不認識“~”,同樣也不認識“@”。

然而,這兩個別名的配置讓我有些想不明白,先上代碼:

resolve: {
  alias: [
    { find: '@', replacement: resolve(__dirname, 'src') },
    { find: /^~/, replacement: '' }
  ]
}

一個使用字符串,一個使用正則,必須這樣,否則不生效。“~”使用正則,我能想明白,它是在替換以“~”開頭的路徑。於是我就想用正則來匹配以“@/”開頭的路徑,結果不是我想要的結果。然後我去查看vue-cli的配置,沒發現什麼特別的地方,這讓我很納悶。這隻能留到後面再細細研究了,先把坑留在這兒。

現在UI庫引進來了,但是任務還沒有結束,vue-routervuex還沒有,得自己引入。這樣的做法無疑在告訴開發者,你可以用你自己喜歡的解決方案。當然vue3幾乎可以用Provide / Inject取代vuex,不管怎麼樣,要用vuex就得自己引入。配置vue-router,我從vue-cli的配置中拷貝了一個過來,結果發現它報錯了:

process不存在?在官方文檔中createWebHashHistory並沒有傳這個參數。本着研究的原則,假如我需要這麼一個環境變量,怎麼辦?vite給出了自己的變量import.meta.env.BASE_URL。

OK路由已經加上,該讓代碼策馬崩騰了!

最後

還沒完呢,界面邏輯是沒問題了,我們還需要請求數據。我使用axios:一般情況下我們只會對正常的數據進行處理,其餘不管是請求和響應時的錯誤只需要做一個統一提示即可。除了響應攔截,我們還要對請求進行攔截,放入token之類的頭部參數,所以需要對axios進行二次封裝。另外,我們還需要對重複請求、路由跳轉的時候未完成的請求進行處理。

首先初始化創建一個axios:

const $axios = axios.create({
  baseURL: '/api',
  timeout: 10000
})

爲了處理重複請求、路由跳轉的時候未完成的請求進行處理,axios提供了cancel方法,我們需要將這些cancel存起來方便處理:

// cancel token存儲
const cancelToken = new Map()

/**
 * 存儲cancel token
 * @param config axios請求配置
 */
function setCancelToken(config: AxiosRequestConfig) {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
    if (!cancelToken.has(url)) {
      cancelToken.set(url, cancel)
    }
  })
}

/**
 * 移除cancel token
 * @param config axios請求配置
 */
function deleteCancelToken(config: AxiosRequestConfig) {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  if (cancelToken.has(url)) {
    cancelToken.get(url)()
    cancelToken.delete(url)
  }
}

/**
 * 清除所有cancel token
 */
export function clearCancelToken() {
  for (const cancel of cancelToken.values()) {
    cancel()
  }
  cancelToken.clear()
}

路由跳轉時需要清除所有未完成的請求,所以要將其暴露出去。最後配置路由攔截,因爲我要統一處理錯誤,所以我做了以下封裝:

/**
 * 錯誤處理,401提示登錄,其餘提示錯誤信息
 * @param error 錯誤數據
 */
function errorHandle(error: any) {
  // 提示處理
}

/**
 * 處理正常響應數據處理錯誤
 * @param response axios響應數據
 * @returns 錯誤信息
 */
function misdataHandle(response: AxiosResponse) {
  const err = {
    status: response.status,
    statusText: response.statusText,
    code: response.data && response.data.code,
    message: response.data ? response.data.message : createError(response.status),
    data: response.data && response.data.data
  }

  // 錯誤處理
  errorHandle(err)

  return err
}

/**
 * 響應異常錯誤處理
 * @param error axios異常錯誤數據
 * @returns 錯誤信息
 */
function exceptionHandle(error: AxiosError) {
  let err: any = {}

  if (error.response) {
    err = misdataHandle(error.response)
  } else if (error.request) {
    err = misdataHandle(error.request)
  } else {
    err.message = error.message || createError()
  }

  // 非cancel token產生的錯誤處理
  if (!axios.isCancel(error)) {
    deleteCancelToken(error.config)
    errorHandle(err)
  }

  return Promise.reject(err)
}

其中createError()是當沒有拿到錯誤信息時,根據狀態碼返回固定的錯誤信息,完整的封裝請參考源碼。然後,在路由中引入clearCancelToken通過路由守衛調用清除未完成的請求,在接口文件引入暴露的axios實例統一管理接口。

具體的業務邏輯就不說了,沒什麼新意,打完收工!

## 代碼倉庫 ##

前後端所有代碼都在一個倉庫不同的分支,代碼拉下來切換分支即可。

倉庫地址:GiteeGithub

##@樹洞系列文章##

Web項目實踐@樹洞(接口篇)

Web項目實踐@樹洞(前端篇vue3)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章