構建大型 Vue.js 項目的10條建議

下面是我在開發大型 Vue 項目時的最佳實踐。這些技巧將幫助你開發更高效、更易於維護和共享的代碼。html

今年作自由職業的時候,我有機會開發了一些大型 Vue 應用程序。我所說的這些項目,Vuex store 超過十個,包含大量的組件(有時候幾百個)和視圖頁面。對我來講這是個頗有益的經驗,由於我發現了不少有意思的模式,可讓代碼擁有更好的伸縮性。我還必須修正一些致使著名的意大利麪條式代碼困境的錯誤實踐。vue

所以,今天我將與你分享10個最佳實踐,若是你正在處理大型代碼庫,我建議你參考這些方法。webpack

1. 使用 slot, 讓組件更強大,也更容易理解

最近我寫了篇關於 Vue.js slot 的文章,它強調了 slot 如何使組件更易於重用和維護,以及爲何應該使用它們。ios

🧐 可是這與 Vue.js 大型項目有什麼關係呢?一張圖片一般賽過千言萬語,因此我要給你描繪一幅關於我第一次後悔沒有使用它們的畫面。git

有一天,我要建立一個 popup。乍一看並無什麼複雜的東西,只是包含了一個標題,一個描述和一些按鈕。因此我所作的就是把一切配置一股腦當作 props 傳進去。最終我定義了三個 prop,用於自定義組件,當用戶單擊按鈕時將發送一個事件。So easy !github

可是,隨着項目的發展,團隊要求咱們在其中展現更多其餘的新內容:表單字段、不一樣的按鈕(取決於它顯示在哪一個頁面上)、卡片、頁腳,以及列表。我覺得,若是繼續使用 prop 來迭代這個組件也沒啥問題。可是我滴個神哪,我是大錯特錯!組件很快變得很是複雜,難以理解,由於它包含了無數的子組件,用了太多的 prop,併發送了一堆的事件。我開始體驗到了可怕的狀況,當你作出一點改動,其餘頁面的某個地方就會崩潰!我彷彿造了一個 Frankenstein 怪人,而不是一個可維護的組件!🤖web

然而,若是我從一開始就依賴 slot,狀況可能會好多了。最後我重構了全部東西,獲得了這個小組件:更容易維護,理解起來更快,更好擴展!vuex

<template>
  <div class="c-base-popup">
    <div v-if="$slot.header" class="c-base-popup__header">
      <slot name="header">
    </div>
    <div v-if="$slot.subheader" class="c-base-popup__subheader">
      <slot name="subheader">
    </div>
    <div class="c-base-popup__body">
      <h1>{{ title }}</h1>
      <p v-if="description">{{ description }}</p>
    </div>
    <div v-if="$slot.actions" class="c-base-popup__actions">
      <slot name="actions">
    </div>
    <div v-if="$slot.footer" class="c-base-popup__footer">
      <slot name="footer">
    </div>
  </div>
</template>

<script>
export default {
  props: {
    description: {
      type: String,
      default: null
    },
    title: {
      type: String,
      required: true
    }
  }
}
</script>

個人觀點是,從經驗來看,那些知道什麼時候使用 slot 的開發人員所構建的項目確實會對其將來的可維護性產生很大的影響。因爲發送的事件更少,代碼更容易理解,並且提供了更大的靈活性,能夠在其中顯示任何組件。npm

️ 敲黑板:根據經驗,當你開始在父組件中複製子組件的 prop 時,你就應該考慮使用 slot 了。json

2. 合理組織 Vuex Store

一般,Vue.js 新手開始瞭解Vuex,由於他們恰好碰到了這兩個問題:

  • 組件樹結構中相隔太遠的組件之間訪問數據
  • 組件銷燬後須要持久化數據

這個時候他們就會建立第一個 Vuex store,學習模塊並開始在應用程序中組織它們。

問題在於,建立模塊時沒有單一的模式能夠遵循。然而,我強烈建議你仔細考慮如何組織它們。據我所見,大多數開發人員更喜歡根據功能來組織它們。例如:

  • Auth.
  • Blog.
  • Inbox.
  • Settings.

就我來講,我發現根據從 API 獲取的數據模型來組織它們更容易理解。例如:

  • Users
  • Teams
  • Messages
  • Widgets
  • Articles

如何選擇取決於你本身。惟一須要記住的是,從長遠來看,一個組織良好的 Vuex store 會造就一個更高效的團隊。它還將使新人在加入團隊時更容易將他們的想法圍繞在你的代碼基礎上。

3. 使用 action 發起 API 調用和提交數據

個人大部分 API 調用(若是不是所有)是在Vuex action 裏面完成的。你可能會問:爲何要這麼作? 🤨
🤷‍♀️ 簡單來講,它們中的大多數獲取的數據須要提交到 store裏去。另外,它們還提供了一層封裝和可重用性,我很喜歡用。還有一些緣由以下:

  • 若是我須要在兩個地方(假設是博客頁面和首頁)獲取文章的第一頁,我只須要用正確的參數調用合適的 dispatcher 就好了。除了 dispatcher 調用,無需重複代碼就能夠完成數據的獲取,commit 到 store 和返回。

  • 若是我須要寫一些避免重複獲取第一頁的邏輯,我就能夠在一個地方完成。這樣作除了會減輕服務器負載,還會加強我對代碼的信心。

  • 我能夠跟蹤這些操做中的大多數 Mixpanel(一個網站用戶行爲分析工具) 事件,這使得分析代碼很是容易維護。我確實有一些應用程序,其中全部的 Mixpanel 調用都是隻在 action 中完成的。我不須要了解哪些數據被跟蹤,哪些沒有,以及何時發送這些信息。以這種工做方式的樂趣簡直沒法言說。

4. 用 mapState,mapGetters,mapMutations 和 mapActions 簡化代碼

一般不須要建立多個計算屬性或方法,只需在組件內部訪問 state/getters 或者調用 actions/mutations 。使用mapStatemapGettersmapMutations 和 mapActions 
能夠幫助你簡化代碼,把來自 store 模塊的數據分組到一塊兒,讓代碼更容易理解。

// NPM
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";

export default {
  computed: {
    // Accessing root properties
    ...mapState("my_module", ["property"]),
    // Accessing getters
    ...mapGetters("my_module", ["property"]),
    // Accessing non-root properties
    ...mapState("my_module", {
      property: state => state.object.nested.property
    })
  },

  methods: {
    // Accessing actions
    ...mapActions("my_module", ["myAction"]),
    // Accessing mutations
    ...mapMutations("my_module", ["myMutation"])
  }
};

有關以上工具方法的全部信息都在 Vuex 官方文檔。🤩

5. 使用 API 工廠

我一般喜歡寫一個this.$api 助手,以便在任何地方調用,獲取後臺 API 資源。在個人項目根目錄有一個api 文件夾,包含了全部相關的類。以下所示(僅部分):

api
├── auth.js
├── notifications.js
└── teams.js

每一個文件都將其類別下的全部 API 資源分組。下面是我在 Nuxt 應用中使用插件初始化這個模式的方法(在標準的 Vue 應用中的過程也相似)。

// PROJECT: API
import Auth from "@/api/auth";
import Teams from "@/api/teams";
import Notifications from "@/api/notifications";

export default (context, inject) => {
  if (process.client) {
    const token = localStorage.getItem("token");
    // Set token when defined
    if (token) {
      context.$axios.setToken(token, "Bearer");
    }
  }
  // Initialize API repositories
  const repositories = {
    auth: Auth(context.$axios),
    teams: Teams(context.$axios),
    notifications: Notifications(context.$axios)
  };
  inject("api", repositories);
};
export default $axios => ({
  forgotPassword(email) {
    return $axios.$post("/auth/password/forgot", { email });
  },

  login(email, password) {
    return $axios.$post("/auth/login", { email, password });
  },

  logout() {
    return $axios.$get("/auth/logout");
  },

  register(payload) {
    return $axios.$post("/auth/register", payload);
  }
});

如今,我能夠簡單地在個人組件或 Vuex action 裏像這樣調用它們:

export default {
  methods: {
    onSubmit() {
      try {
        this.$api.auth.login(this.email, this.password);
      } catch (error) {
        console.error(error);
      }
    }
  }
};

6. 使用 $config 訪問環境變量(在模板裏特別有用)

你的項目可能在一些文件中定義了一些全局配置變量:

config
├── development.json
└── production.json

我喜歡經過 this.$config 助手快速訪問它們,特別是在模板裏。像往常同樣,擴展 Vue 對象很是容易:

// NPM
import Vue from "vue";

// PROJECT: COMMONS
import development from "@/config/development.json";
import production from "@/config/production.json";

if (process.env.NODE_ENV === "production") {
  Vue.prototype.$config = Object.freeze(production);
} else {
  Vue.prototype.$config = Object.freeze(development);
}

7. 按照某個約定來給代碼提交命名

隨着項目的增加,你可能須要按期瀏覽組件的歷史記錄。若是你的團隊沒有遵循相同的約定來命名他們的提交,那麼理解每一個提交將會變得更加困難。

我一直推薦使用 Angular 提交信息指南。我在每一個項目中都遵循它,在不少狀況下,其餘團隊成員很快就會發現遵循它帶來的好處。

遵循這些指導原則能夠獲得更具可讀性的信息,這使得在查看項目歷史記錄時更容易跟蹤提交。簡而言之,它是這樣工做的:

git commit -am "<type>(<scope>): <subject>"

# 舉例
git commit -am "docs(changelog): update changelog to beta.5"
git commit -am "fix(release): need to depend on latest rxjs and zone.js"

看下他們的 README 文件 瞭解更多瞭解更多關於它和相關約定。

8. 項目上線後固定 package 版本

我知道,全部 package 都應該遵循語義化版本規則。但現實狀況是,有些根本沒有遵照。

爲了不由於某個依賴項破壞了整個項目而不得不在半夜醒來,鎖定全部 package 版本可讓你的早晨工做壓力更小。

它的意思很簡單:避免使用帶 ^ 前綴的版本號:

{
  "name": "my project",

  "version": "1.0.0",

  "private": true,

  "dependencies": {
    "axios": "0.19.0",
    "imagemin-mozjpeg": "8.0.0",
    "imagemin-pngquant": "8.0.0",
    "imagemin-svgo": "7.0.0",
    "nuxt": "2.8.1",
  },

  "devDependencies": {
    "autoprefixer": "9.6.1",
    "babel-eslint": "10.0.2",
    "eslint": "6.1.0",
    "eslint-friendly-formatter": "4.0.1",
    "eslint-loader": "2.2.1",
    "eslint-plugin-vue": "5.2.3"
  }
}

9. 在顯示大量數據時使用 Vue Virtual Scroller

當你須要在某個頁面中顯示大量的行,或者須要循環大量的數據時,你可能已經注意到頁面可能會很快變得很是慢。要解決這個問題,你可使用vue-virtual-scoller

npm install vue-virtual-scroller

它將只渲染列表中可見的項,並重用組件和 dom 元素,效率高,性能好。它真的很容易使用,如絲般順滑!✨

<template>
  <RecycleScroller
    class="scroller"
    :items="list"
    :item-size="32"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="user">
      {{ item.name }}
    </div>
  </RecycleScroller>
</template>

10. 跟蹤第三方包的大小

當不少人在同一個項目中工做時,若是沒人關注安裝包的數量,那麼很快就會愈來愈多。爲了不應用程序變慢(特別是在移動網絡變慢的狀況下),我在 VS Code 中使用了 import cost 插件。這樣,我就能夠從個人編輯器中看到導入的模塊庫有多大,而且能夠在它變得太大時檢查出問題。

例如,在最近的一個項目中,整個 lodash 庫被導入(大約有24kB的gzipped)。結果只使用了 cloneDeep 方法。經過 import cost 插件定位到這個問題,咱們是這樣解決的:

npm remove lodash
npm install lodash.clonedeep

cloneDeep 函數能夠在須要的地方引入:

import cloneDeep from "lodash.clonedeep";

️ 要進一步優化,你可使用 Webpack Bundle Analyzer ,用交互式的可縮放地圖可視化文件大小。


Webpack Bundle Analyzer

關於處理大型 Vue 代碼庫,你還有其餘最佳實踐能夠分享的嗎?歡迎在評論區留言。

原文

交流

歡迎關注微信公衆號「1024譯站」,爲你奉上更多技術乾貨。
公衆號:1024譯站

相關文章
相關標籤/搜索