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


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

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

因此,今天我將與你分享10個最佳實踐,如果你正在處理大型代碼庫,我建議你參考這些方法。

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

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

🧐 但是這與 Vue.js 大型項目有什么關系呢?一張圖片通常勝過千言萬語,所以我要給你描繪一幅關於我第一次后悔沒有使用它們的畫面。

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

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

然而,如果我從一開始就依賴 slot,情況可能會好多了。最后我重構了所有東西,得到了這個小組件:更容易維護,理解起來更快,更好擴展!

<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 的開發人員所構建的項目確實會對其未來的可維護性產生很大的影響。由於發送的事件更少,代碼更容易理解,而且提供了更大的靈活性,可以在其中顯示任何組件。

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

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譯站


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM