Vue + H5 最佳實踐模板

模板基於 vue-cli4 和 Vant-ui 搭建,進行大型 H5 項目開發最佳實踐方案,讓我們來一探究竟

項目結構

本項目已經為你生成了一個完整的開發框架,下面是整個項目的目錄結構。

<code>├── .github                    # git log
├── plop-templates # 基本模板
├── public # 靜態資源
│ │── favicon.ico # favicon圖標
│ └── index.html # html模板
├── src # 源代碼
│ ├── assets # 靜態資源
│ ├── components # 全局公用組件
│ ├── constants # 常量
│ ├── core # 分層
│ ├── enum # 枚舉
│ ├── filters # 全局 filter
│ ├── icons # 項目所有 svg icons
│ ├── lang # 國際化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store 管理
│ ├── styles # 全局樣式
│ ├── utils # 全局公用方法
│ ├── pages # pages 所有頁面
│ ├── pwa # 漸進式Web應用
│ ├── App.vue # 入口頁面
│ ├── main.js # 入口文件 加載組件 初始化等
│ └── permission.js # 權限管理
├── tests # 測試
├── .editorconfig # 代碼風格
├── .env.xxx # 環境變量配置
├── .eslintrc.js # eslint 配置項
├── .sentryclirc.js # 前端異常日誌監控配置
├── .babel.config # babel 配置

├── plopfile.js # 基本模板配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
...
/<code>

安裝

<code># 克隆項目
git clone https://github.com/push-over/vue-h5-template.git

# 進入項目目錄
cd vue-h5-template

# 安裝依賴
npm install

# 建議不要用 cnpm 安裝 會有各種詭異的bug 可以通過如下操作解決 npm 下載速度慢的問題
npm install --registry=https://registry.npm.taobao.org

# 本地開發 啟動項目
npm start
/<code>

TIP

強烈建議不要用直接使用 cnpm 安裝,會有各種詭異的 bug,可以通過重新指定 registry 來解決 npm 安裝速度慢的問題。若還是不行,可使用 yarn 替代 npm。

Windows 用戶若安裝不成功,很大概率是node-sass安裝失敗,解決方案。

另外因為 node-sass 是依賴 python環境的,如果你之前沒有安裝和配置過的話,需要自行查看一下相關安裝教程。

啟動完成後會自動打開瀏覽器訪問 [http://localhost:9000, 你看到下面的頁面就代表操作成功了。

Vue + H5 最佳實踐模板

接下來你可以修改代碼進行業務開發了,本項目內建了典型業務模板、模擬數據、狀態管理、國際化、全局路由等等各種實用的功能來輔助開發

常用命令

<code># 項目打包
npm run build:xxx

# 自動創建
npm run new

# 規範Git提交
npm run git-cz

# 生成CHANGELOG
npm run genlog
/<code>

分層架構

目前前端的一個開發趨勢是以搭建單頁應用 (SPA) 為主的。當應用體系比較大,或者你的應用業務邏輯足夠複雜的時候,會遇到各種各樣的問題,我們隨便提兩點:

  • 產品需要多人協作時,每個人的代碼風格和對業務的理解不同,導致業務邏輯分佈雜亂無章
  • 對產品的理解停留在頁面驅動層面,導致實現的技術模型與實際業務模型出入較大,當業務需求變動時,技術模型很容易被摧毀

針對上面所遇到的問題,我們以下面這張架構圖做講解:

Vue + H5 最佳實踐模板

其中 視圖層/View 是大家接觸最多的,想必大家都很瞭解,就不在這裡介紹了,重點介紹其他幾個層的含義:

Services 層

Services 層是用來對底層技術進行操作的,例如封裝 AJAX 請求,操作瀏覽器 Cookie、LocaStorage、IndexedDB,操作 Native 提供的能力(如調用攝像頭等),以及建立 Websocket 與後端進行交互等。

Axios 封裝

<code>.....

export default async function(options) {
const { url } = options
const requestOptions = Object.assign({}, options)

try {
const { data, data: { errno, errmsg }} = await instance.request(requestOptions)
if (errno) {
errorReport(url, errmsg, requestOptions, data)
throw new Error(errmsg)
}
return data
} catch (err) {
errorReport(url, err, requestOptions)
throw err
}
}
/<code>

IndexedDB

<code>...

export class DBRequest {
instance

static getInstance() {
if (!this.instance) {
this.instance = new DBRequest()
}
return this.instance
}
async create(options = {}) {
const { name, data } = options
const db = await indexDB(name)
return await db.add(name, data)
}
...
}
/<code>

…….

Entities 層

實體 Entity 是領域驅動設計的核心概念,它是領域服務的載體,它定義了業務中某個個體的屬性和方法。區分一個對象是否是實體,主要是看他是否有唯一的標誌符(例如 id)

Vue + H5 最佳實踐模板

通過上面的代碼可以看到,這裡主要是以實體本身的屬性以及派生屬性為主,當然實體本身也可以具有方法,用於實現屬於實體自身的業務邏輯。

並不是所有的實體都應該按上面那樣封裝成一個類,如果某個實體本身業務邏輯很簡單,就沒有必要進行封裝,例如本模板中的 Test 只是做個演示。

Interactors 層

Interactors 層是負責處理業務邏輯的層,主要是由業務用例組成

<code>import { Request } from '@/utils/request'
import { CARDS } from '@/constants/api/test'

class TestHttpInteractor {
service
constructor(service) {
this.service = service
}
async getTest() {
try {
const options = { url: CARDS }
return await this.service.get(options)
} catch (error) {
throw error
}
}
async createTest(data) {
try {
const optons = { url: CARDS, data }
await this.service.post(optons)
} catch (error) {
throw error
}
}

...
}

const testHttpInteractor = new TestHttpInteractor(Request.getInstance())
export default testHttpInteractor
/<code>

通過上面的代碼可以看到,Sevices 層提供的類的實例主要是通過 Interactors 層的類的構造函數獲取到,這樣就可以達到兩層之間解耦,實現快速切換 service 的目的了,當然這個和依賴注入 DI 還是有些差距的,不過已經滿足了我們的需求。

另外 Interactors 層還可以獲取 Entities 層提供的實體類,將實體類提供的與實體強相關的業務邏輯和 Interactors 層的業務邏輯融合到一起提供給 View 層,例如:

Vue + H5 最佳實踐模板

當然這種分層架構並不是銀彈,其主要適用的場景是:實體關係複雜,而交互相對模式化,例如企業軟件領域。相反實體關係簡單而交互複雜多變就不適合這種分層架構了。

然後需要明確的是,架構和項目文件結構並不是等同的,文件結構是你從視覺上分離應用程序各部分的方式,而架構是從概念上分離應用程序的方式。你可以在很好地保持相同架構的同時,選擇不同的文件結構方式。沒有完美的文件結構,因此請根據項目的不同選擇適合你的文件結構。

佈局

頁面整體佈局是一個產品最外層的框架結構, 這裡使用了 vue-router 路由嵌套, 所以一般情況下,你增加或者修改頁面只會影響 app-main這個主體區域。其它配置在 layout 中的內容如:底部導航都是不會隨著你主體頁面變化而變化的。

<code>/foo                                  /bar
+------------------+ +-----------------+
| layout | | layout |
| +--------------+ | | +-------------+ |
| | foo.vue | | +------------> | | bar.vue | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
/<code>

這裡在 app-main 外部包了一層 keep-alive 主要是為了緩存 的,如不需要可自行去除。

Vue + H5 最佳實踐模板

樣式

CSS Modules

在樣式開發過程中,有兩個問題比較突出:

  • 全局汙染 —— CSS 文件中的選擇器是全局生效的,不同文件中的同名選擇器,根據 build 後生成文件中的先後順序,後面的樣式會將前面的覆蓋;
  • 選擇器複雜 —— 為了避免上面的問題,我們在編寫樣式的時候不得不小心翼翼,類名裡會帶上限制範圍的標示,變得越來越長,多人開發時還很容易導致命名風格混亂,一個元素上使用的選擇器個數也可能越來越多,最終導致難以維護。

好在 vue 為我們提供了 scoped 可以很方便的解決上述問題。 它顧名思義給 css 加了一個域的概念。

<code>/* 編譯前 */
.example {
color: red;
}

/* 編譯後 */
.example[_v-f3f3eg9] {
color: red;
}
/<code>

只要加上 style scoped 這樣 css 就只會作用在當前組件內了。

TIP

使用 scoped 後,父組件的樣式將不會滲透到子組件中。不過一個子組件的根節點會同時受其父組件的 scoped CSS 和子組件的 scoped CSS 的影響。這樣設計是為了讓父組件可以從佈局的角度出發,調整其子組件根元素的樣式。

目錄結構

vue-h5-template 所有全局樣式都在 @/src/styles 目錄下設置

<code>├── styles
│ ├── _animation # 按鈕樣式
│ ├── index.scss # 全局通用樣式
│ ├── _mixin.scss # 全局mixin
│ ├── _transition.scss # 過渡效果
│ └── variables.scss # 全局變量

/<code>

一次完整的與服務器端交互

在 vue-h5-template 中,一個完整的前端 UI 交互到服務端處理流程是這樣的:

  1. UI 組件交互操作
  2. 調用統一管理的 api service 請求函數
  3. 使用封裝的 request.js 發送請求
  4. 獲取服務端返回
  5. 更新 data

request.js

其中,@/src/utils/request.js 是基於 Server目錄的 http 的二次封裝,便於統一處理 POST,GET 等請求方式。具體可以參看項目代碼。

<code>...
export class Request {
instance

static getInstance() {
if (!this.instance) {
this.instance = new Request()
}
return this.instance
}

async post(options = {}) {
const { data } = await service({
method: 'post',
...options
})
return data

}
...
}
/<code>

例子

定義接口地址統一管理 src/constants/api/test.js

<code>export const CARDS = '/admin/cards'
/<code>

請求方法 src/core/interactors/test-interactor.js

<code>async getTest() {
try {
const options = { url: CARDS }
return await this.service.get(options)
} catch (error) {
throw error
}
}
/<code>

請求方式 src/utils/request.js

<code>async get(options = {}) {
const { data } = await service({
method: 'get',
...options
})
return data
}
/<code>

TIP

目錄結構不要糾結,個人習慣而定

頁面使用 src/pages/test/index.vue

<code># 生命週期
async created() {
if (this.id) {
await this.handleGetTest()
}

}

# 請求
async handleGetTest() {
try {
const test = await testInteractor.getTest(this.id)
this.addressInfo = Object.assign({}, test)
} catch (error) {
console.log(error)
}
}
/<code>

可能大家會覺得很繁瑣,這麼多文件容易搞混,重複編寫代碼等等,不要著急,本模板配置了自動生成文件,上述除了視圖/View層這塊需要你手動去編寫代碼,其他的我們都會去一鍵生成,接下來我們就來講講使用方法。

生成所需文件

在開發過程中,無論我們添加頁面也好還是添加組件等等。都需要不停地新建 .vue文件(或者其他框架或者html/js/css文件)

以Vue項目為例, 我們新建一個component 或 view 的時候,需要新建一個.vue文件,然後寫 <template>、/<template>


分享到:


相關文章: