vue項目的一些最佳實踐提煉和經驗總結

来源:https://www.cnblogs.com/zhouqy/archive/2019/01/23/10311802.html
-Advertisement-
Play Games

項目組織結構 ajax數據請求的封裝和api介面的模塊化管理 第三方庫按需載入 利用less的深度選擇器優雅覆蓋當前頁面UI庫組件的樣式 webpack實時打包進度 vue組件中選項的順序 路由的懶載入 路由模塊拆分化管理 項目組織結構 清晰的項目結構能讓別人開發進來更容易理解,當然,每個人都有一定 ...


  • 項目組織結構
  • ajax數據請求的封裝和api介面的模塊化管理
  • 第三方庫按需載入
  • 利用less的深度選擇器優雅覆蓋當前頁面UI庫組件的樣式
  • webpack實時打包進度
  • vue組件中選項的順序
  • 路由的懶載入
  • 路由模塊拆分化管理

項目組織結構

清晰的項目結構能讓別人開發進來更容易理解,當然,每個人都有一定的代碼風格習慣。但基於vue開發框架的項目,vue-cli腳手架搭建的項目組織結構大同小異。同時,預想到後面的需求變更及功能增加進展得更有效率,下麵截圖是我覺得比較好的項目組織結構:

這個截圖只是針對個人覺得比較通用的vue工程結構,不過這個結構要根據具體的項目情況調整,不必為了模塊化而模塊化。模塊化的優勢就是體現在項目業務比較複雜的情況,如果項目業務邏輯並不複雜,可以適當的刪減部分模塊或文件。

相關說明:

assets: 存放圖片、UI設計的圖標文件

componets:自研的業務型及通用型組件

router:項目的路由管理模塊

store:基於vuex的狀態管理容器,api存放各模塊的數據請求,modules存放將store分割成模塊(module),按官網的說法,每個模塊應該擁有自己的 state、mutation、action、getter,主要是解決應用的所有狀態如果全部集中到一個比較大的store對象,當應用變得非常複雜時,store 對象就有可能變得相當臃腫而難以維護。

例子:

其中的一個模塊configManage.js

import {
  configManageService
} from "../api/index"
// state
const state = {
 accountMenuList:[]
}
// getters
const getters = {
// 菜單
menuTree: state => {
    return state.accountMenuList;
  },
}
// actions
const actions = {
  async GET_ACCOUT_MENU({
    state,
    commit
  }, model) {
    // 參數 state為當前局部狀態,commit響應式改變當前綁定的菜單數據
    const res = await configManageService.getACountMenu(model);
    commit("CHANGE_MENU", res.data);
  }
}
// mutations
const mutations = {
  CHANGE_MENU: (data) => {
    state.accountMenuList = data;
  }
}
export default {
  state,
  getters,
  actions,
  mutations
}

index.js,統一齣口,導出全部的store模塊

import Vue from 'vue'
import Vuex from 'vuex'
import index from './modules/index'
import report from './modules/report'
import createLogger from 'vuex/dist/logger' // 控制台輸出當前變化的某個狀態
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production' // 生產或開發環境打包
export const indexStore = new Vuex.Store({
  modules: {
    report,
    index
  },
  strict: debug, // 按照官網建議,改變state的狀態只能通過getter
  plugins: debug ? [createLogger()] : []
})

style:

存放重寫UI庫的樣式和不同組件公共樣式文件

util:

存放用es6封裝的工具類,http請求類,配置類、校驗類、事件類等

views:

存放各路由模塊頁面

static:

存放全局配置文件,環境功能變數名稱等

iconfont:

存放字體圖標文件

ajax數據請求的封裝和api介面的模塊化管理

基於vue的項目,與後臺請求數據我們通常使用的是axios,它是基於promise的http庫,其提供的優秀的特性被廣泛運用在項目當中,官方已推薦使用axios,放棄原有的vue-resource。

1、axios的封裝,在很多業務場景下用來進行請求的攔截、響應的攔截及請求超時等;

// axios請求類,一些基礎化配置
class AjaxRequestModel {
  constructor(model) {
    this.url = model.url || "";
    this.data = model.data || {};
    this.method = model.method || "POST";
    // this.success = model.success || function () {};
    // this.fail = model.fail || function () {};
    // this.slientSuccess = model.slientSuccess || true;
    this.failMsg = model.failMsg || true;
    this.baseUrl = model.baseUrl || window.sysConfig.baseUrl;
    this.loading = model.loading || true;
    // this.setData();
    this.setUrl();
  }
  setData() {
    // let options = {
    //   sessionid: ""
    // };
    this.data = Object.assign({}, this.data);
  }
  setUrl() {
    this.url = this.baseUrl + this.url;
  }
}
// 實例化axios,配置請求超時時間
const axiosInstance = axios.create({
  timeout: 1000 * 20
});
// 封裝ajaxService函數,以更少的代碼處理get、post、delete、put請求方式,同時支持async、await非同步處理方案,返回promise
const ajaxService = param => {
  let model = new AjaxRequestModel(param);
  let o = {
    url: model.url,
    data: model.data,
    method: model.method
  };
  // if (model.loading) {
  //   ak.Msg.showLoading();
  // }
  if (model.method === "GET") {
    o = {
      url: model.url,
      params: model.data,
      method: model.method
    };
  }
  return new Promise((resolve, reject) => {
    axiosInstance
      .request(o)
      .then(res => {
        if (res.data.code === 200 || res.data.code === 0) {
          resolve(res.data);
        } else {
          ak.Msg.toast(res.data.message, "error");
          reject(res.data);
        }
      })
      .catch(err => {
        httpResponseHandle.call(err);
        reject(err);
      });
  });
};

2、在請求的攔截中,可以攜帶用於介面身份驗證的token,配置headers請求頭、提交參數的序列化等

// 請求頭相關配置
axiosInstance.interceptors.request.use(
  function (config) {
    const info = ak.Utils.getSessionStorage("USER_INFO");
    config.headers.common['token'] = info ? info[0].token : "";
    // config.headers.common['Content-Type'] = "application/json";
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);

3、在響應的攔截中,可以進行根據各種狀態碼來進行錯誤的統一處理等

const httpResponseHandle = err => {
  const opt = err.response;
  // 請求超時
  if (err.code === "ECONNABORTED") {
    ak.Msg.toast("請求超時,請稍後再試", "error");
  }
  if (opt.status === 401) {
    ak.Msg.confirm("用戶登錄超時,請重新登錄", () => {
      sessionStorage.removeItem("USER_INFO");
      window.utryVue.$router.replace("/login");
      location.reload();
    });
  } else {
    ak.Msg.toast(opt.data.message, "error");
  }
};

4、api介面模塊化管理,業務邏輯和數據請求分層,這樣可以很方便統一管理我們的介面

如圖,把不同的功能拆分,實現代碼模塊化管理,全部的介面均放在api文件夾下麵。index.js是一個api介面的導出的出口,這樣就可以把api介面根據功能劃分為多個模塊,利於多人協作開發,比如一個人只負責一個模塊的開發等,還能方便每個模塊中介面的命名

index.js:

import report from './report'; // 報表模塊
import accountService from './accountService'; // 登陸、用戶信息相關
// 導出介面
export {
  accountService,
  report
}

API請求service層:

// 報表管理請求模塊,與後臺請求的參數、請求方式、url均看作一個model
import http from "@/util/http.js";
const API_CONTEXT = "sys/"; // 請求的上下文
const report = {
  async getMenuList() {
    let model = {};
    model.url = API_CONTEXT + "category/getCategoryTree";
    model.method = "GET";
    let res = await http.ajaxService(model);
    return res;
  },
  async removeMenu(model) {
    model.data = { ...model };
    model.url = API_CONTEXT + "category/removeCategory";
    let res = await http.ajaxService(model);
    return res;
  }
}
export default report;

組件的業務邏輯層調用方式:

// 說明:async、await的寫法省去了不少的回調,在有些必須請求兩個介面或者兩個介面以上場景下,async、await優勢就顯示出來了
import { reportService } from "../../store/api/index";
async getMenuList() {
      const param = {
        role: ""
      };
      const res = await reportService.getMenuList(param);
      // 下麵代碼返回成功時才執行,錯誤由上面所講的axios封裝ajaxService統一處理
      this.menuList = res.data;
 }

5、如果後期維護需要修改的介面,我們就直接在api.js中找到對應的修改就好了,而不用去每一個頁面查找我們的介面然後再修改會很麻煩,如果修改的量比較大,難免會自測不充分產生bug,直接gg。還有就是如果直接在我們的業務代碼修改介面,一不小心還容易動到我們的業務代碼造成不必要的麻煩

6、處理介面功能變數名稱、埠有多個情況

// 無需前端打包,運維環境快速修改配置,eg:
window.sysConfig = {
  // 運維平臺
  baseUrl: 'http://10.0.33.97:7083/',
  // 租戶平臺
  tenantUrl: 'http://10.0.33.96:7082/'
}
// 區分不同平臺的url地址在http.js文件下的AjaxRequestModel類實例化會統一處理
this.baseUrl = model.baseUrl ? window.sysConfig.baseUrl :  window.sysConfig.tenantUrl

第三方庫按需載入

按需載入是針對某些第三方庫體積比較大的情況下,優化webpack打包後的js體積,減少頁面的載入時間

以echart為例子:

優化前:

// 全導入
import * as echarts from "echarts";

webpack打包後:

優化後(主js體積減少了400kb,同時build編譯打包速度也得到了減少)

import echarts from "echarts/lib/echarts";
// 依賴註入,目前項目只用到折線圖、餅圖和柱形圖,故只需引入對應的模塊即可,tooltip是提示類,title是滑鼠懸停顯示的對應的圖表名稱
import 'echarts/lib/chart/bar';
import 'echarts/lib/chart/line';
import 'echarts/lib/chart/pie';
import 'echarts/lib/component/tooltip';
import 'echarts/lib/component/title';

利用less的深度選擇器優雅覆蓋當前頁面UI庫組件的樣式

vue頁面組件的樣式基本是寫在<style scoped lang="less"></style>中,增加scoped屬性的目的讓其樣式只在當前頁面有效。按照這些寫的方式,編譯後當前標簽會加上類似於[data-v-]這樣的屬性,但是第三方的UI組件庫並沒有編譯為帶[data-v-]這樣的屬性,所以就遇到了當前頁面覆蓋的樣式沒生效的情況,有沒有方法處理這種問題呢。有些小伙伴可能會想到我在公共樣式裡面寫,額外添加類名來覆蓋當前組件的樣式,其實,這也不失為一種方案,但是會引來樣式全局污染和命名可能重名的情況。下麵列舉更簡單粗暴的方式,同時避免了樣式污染和命名衝突的問題:

.menu-tree {
    /deep/ .el-tree-node__content {
      height: 32px;
    }
    /deep/ .is-current .el-tree-node__content {
      background-color: #f2f2fa;
    }
  }

編譯後,預設給menu-tree加上了[data-v-3c93a211]

/deep/深度選擇器支持less或者sass,如果你用的是原生的css,可以用<<<符號

webpack實時打包進度

在項目用jenkins自動化打包前端項目的時候,常常會遇到打包速度慢而體驗很差,在優化減負依賴包的情況下,同時沒有一個測試環境或生產環境當前打包進度捉雞。這裡推薦一個第三方的插件包

progress-bar-webpack-plugin。

用法:
// 需安裝依賴 npm install progress-bar-webpack-plugin --save-dev
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
// 在生產環境webpack配置文件的plugin是加上
new ProgressBarPlugin(), // 打包進度 

vue組件中寫選項的順序

這裡純屬個人觀點,可能有些小伙伴用vue開發不是遵從這個。為什麼要規定組件的寫法順序呢,或者說它是官方要求的規範,不如說是能讓的代碼更加優雅,更易於維護,因為你寫的代碼不僅是你一個人維護。要是一個團隊都按這個規範來,大家在維護代碼的時候認知一樣,那效率就提高了。

組件依賴:

components(自研的子組件或第三方組件)

service(api請求類,其他服務類)

utils(工具類等)

事件傳遞(vue eventBus)

mixins(復用的屬性或方法)

組合:

mixins

組件的屬性、介面:

components

props

本地響應式屬性、狀態:

data

computed

事件註冊:

watch

組件生命周期:

created

mounted

destroyed等

組件的方法:

methods

例子:

// 例子
import utryTree from "@/components/utry-tree/utry-tree.vue";
import { reportService } from "@/store/api/index";
import Validation from "../../util/Validation";
import eventBus from "@/util/eventBus";
import reportMixins from "@/mixins/reportMixins"; export
default { mixins: [], components: { }, props: { menuList: { type: Array, default() { return []; } } }, data(){}, computed:{}, watch:{}, mounted(){}, methods:{}, }

路由的懶載入

有時候,針對有些複雜組件,初始化頁面其實並不需要把全部組件資源載入進來,把業務複雜的組件抽離出來,從而能減少初始化頁面的載入時間

優化前:

import reportManage from '@/views/reportManage/index';
import reportPreview from '@/views/reportManage/reportPreview';
export default [
  { path: 'reportManage/index', name: 'reportManage', component: reportManage },
  { path: 'reportManage/reportPreview',  name: 'reportPreview', component: reportPreview }
];

初始化頁面的載入耗時:

優化後:

import reportManage from '@/views/reportManage/index';
export default [
  { path: 'reportManage/index', name: 'reportManage', component: reportManage },
  { path: 'reportManage/reportPreview', name: 'reportPreview', component: () => import('@/views/reportManage/reportPreview'),
    meta: { keepAlive: false }
  }
];

初始化頁面載入耗時:

時間的差別主要是在js的解析上,主要是是因為初始化頁面沒有載入當前模塊的二次組件的js,等到跳轉到二次頁面再去解析靜態資源,總體優化後初始化頁面的載入時間快了100多毫秒。

路由模塊的拆分化管理

這裡的路由拆分,是指按模塊拆分成不同的路由文件,針對單頁面應用這樣更方便團隊的多人協調同步開發,自己寫的功能模塊互不影響。如果當業務需求多起來的時候,它的優勢就越能體現出來。我們並不想就在一個router.js寫整個工程的路由,這樣會是單文件代碼量龐大而變得很槽糕,同時也會帶來其他同事誤改的問題。

我們在router文件夾下麵創建router.js作為路由的入口文件,其他以router.js尾碼的文件存放著各個模塊的路由。

router.js:

import Vue from "vue";
import Router from "vue-router";
import NProgress from "nprogress"; // 引入nprogress,每次路由變化網頁頂端有個載入條效果
import ak from "@/util/ak.js";
// 業務路由
import login from "@/views/index/login"; // 租戶平臺
import oamLogin from "@/views/index/oamLogin"; // 運維平臺
import indexRouter from "./index.router"; // 首頁相關
import reportManage from "./reportManage.router"; // 報表管理
Vue.use(Router);
// 預設登錄
let routes = [
  {
    path: "/",
    redirect: "login"
  },
  {
    path: "/login",
    name: "login",
    component: login
  },
  {
    path: "/oamLogin",
    name: "oamLogin",
    component: oamLogin
  }
];
routes = routes.concat(
  indexRouter,
  reportManage
);

// router register
const router = new Router({
  routes
});
// 路由相關的攔截操作,在這裡處理,之前有的router相關操作寫在main.js,並不是很友好
router.beforeEach((to, from, next) => {
  // 每次切換頁面時,調用進度條
  NProgress.start();
  // cache機制
  const info = ak.Utils.getSessionStorage("USER_INFO");
  const token = info ? info[0].token : "";
  if (token) {
    next();
  } else {
    if (to.path === "/oamLogin") {
      next();
    } else if (to.path === "/login") {
      next();
    } else {
      next("/login");
    }
  }
});
router.afterEach(() => {
  // 在即將進入新的頁面組件前,關閉掉進度條
  NProgress.done();
}); 

index.router.js:

import home from '@/views/index/home'; 
export default [
  { path: '/index/home', name: 'home', component: home }
];

這裡把首頁的路由放在一個數組裡,然後導出去,有router.js統一引入,並實例化當前路由

 

未完待續......

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 偶然看到公司存儲過程中有個字元串拼接的函數,改bug過程中使用到了,還挺有用的,於是記錄下來方便記憶,幫助有需要的人。 這是我需要整理的串,他是調用了一個存儲過程,傳入組織機構的id和迭代層數,返回來這個組織的id及其所有子節點的id的一個string串 然後關鍵是業務要求一個list 裡面放了好多 ...
  • 在有些時候我們需要他人來連接我們的資料庫,這個時候我們需要用我們本地的IP地址來連接,在連接的過程中可能會出現找不到網路路徑提示40,53的錯誤 解決方案: 1.打開配置管理器 2.點開網路配置,點擊協議,查找TCP/IP 協議 點擊右鍵 打開屬性 3.出現對話框之後,點擊IP地址,修改 "IP3的 ...
  • 資料庫: 結構化查詢語言(Structured Query Language)簡稱SQL; 資料庫管理系統(Database Management System)簡稱DBMS; 資料庫管理員(Database Administration)簡稱DBA,功能是確保DBMS的正常高效運行; 資料庫分體驗 ...
  • INSERT INTO SELECT語句 語句形式為:Insert into Table2(field1,field2,...) select value1,value2,... from Table1 或者:Insert into Table2 select * from Table1 註意:(1 ...
  • 結果: ...
  • 成功安裝Oracle 11g資料庫後,你會發現自己電腦運行速度會變慢,配置較低的電腦甚至出現非常卡的狀況,通過禁止非必須開啟的Oracle服務可以提升電腦的運行速度。那麼,具體該怎麼做呢? 按照win7 64位環境下Oracle 11g R2安裝詳解中的方法成功安裝Oracle 11g後,共有7個服 ...
  • 一、簡單的音頻播放 【項目準備】 ①一個視頻文件,視頻文件的位置 >在res下新建文件夾row >將視頻放入row文件夾中 ②一般音頻播放是不需要一直停留在界面的,所以音頻播放應該放在service中,即使界面被回收,也一直在播放。 【項目結構】 【界面代碼】 【MainActivity.class ...
  • JQ :even選擇器代表著選擇偶數行 JQ :odd 代表選擇奇數行 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...