构建大型 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译站

相关文章
相关标签/搜索