构建大型 Vue.js 项目的10条建议(小结)


Posted in Javascript onNovember 14, 2019

下面是我在开发大型 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 。使用mapState,mapGetters,mapMutations 和 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  ,用交互式的可缩放地图可视化文件大小。

构建大型 Vue.js 项目的10条建议(小结)

关于处理大型 Vue 代码库,你还有其他最佳实践可以分享的吗?欢迎在评论区留言。

原文

喜欢的朋友可以加作者二维码

构建大型 Vue.js 项目的10条建议(小结)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
表头固定(利用jquery实现原理介绍)
Nov 08 Javascript
JavaScript里实用的原生API汇总
May 14 Javascript
AngularJS实现表单手动验证和表单自动验证
Dec 09 Javascript
vue.js从安装到搭建过程详解
Mar 17 Javascript
js 简易版滚动条实例(适用于移动端H5开发)
Jun 26 Javascript
浅谈Vue-cli 命令行工具分析
Nov 22 Javascript
详解如何为你的angular app构建一个第三方库
Dec 07 Javascript
详解VUE里子组件如何获取父组件动态变化的值
Dec 26 Javascript
Node.js 多线程完全指南总结
Mar 27 Javascript
mpvue实现小程序签到金币掉落动画(api实现)
Oct 17 Javascript
详解Typescript 内置的模块导入兼容方式
May 31 Javascript
vue实现修改图片后实时更新
Nov 14 #Javascript
Vue中img的src是动态渲染时不显示的解决
Nov 14 #Javascript
JQuery发送ajax请求时中文乱码问题解决
Nov 14 #jQuery
vue-cli3.X快速创建项目的方法步骤
Nov 14 #Javascript
vue.config.js常用配置详解
Nov 14 #Javascript
解决vue项目中页面调用数据 在数据加载完毕之前出现undefined问题
Nov 14 #Javascript
vue 返回上一页,页面样式错乱的解决
Nov 14 #Javascript
You might like
PHP控制网页过期时间的代码
2008/09/28 PHP
PHP系列学习之日期函数使用介绍
2012/08/18 PHP
使用PHP静态变量当缓存的方法
2013/11/13 PHP
PHP实现采集抓取淘宝网单个商品信息
2015/01/08 PHP
用window.location.href实现刷新另个框架页面
2007/03/07 Javascript
JavaScript中实现块作用域的方法
2010/04/01 Javascript
javascript读写XML实现广告轮换(兼容IE、FF)
2013/08/09 Javascript
js 判断图片是否加载完以及实现图片的预下载
2014/08/14 Javascript
jquery实现图片水平滚动效果代码分享
2015/08/26 Javascript
举例讲解jQuery中可见性过滤选择器的使用
2016/04/18 Javascript
Bootstrap CSS组件之输入框组
2016/12/17 Javascript
JavaScript中最常见的三个面试题解析
2017/03/04 Javascript
Vue 实用分页paging实例代码
2017/04/12 Javascript
Vue.js移动端左滑删除组件的实现代码
2017/09/08 Javascript
jquery动态添加以及遍历option并获取特定样式名称的option方法
2018/01/29 jQuery
如何通过shell脚本自动生成vue文件详解
2019/09/10 Javascript
js原生map实现的方法总结
2020/01/19 Javascript
javascript设计模式 ? 迭代器模式原理与用法实例分析
2020/04/17 Javascript
React Native登录之指纹登录篇的示例代码
2020/11/03 Javascript
python破解zip加密文件的方法
2018/05/31 Python
Django实现微信小程序的登录验证功能并维护登录态
2019/07/04 Python
浅谈Python中的异常和JSON读写数据的实现
2020/02/27 Python
Python的PIL库中getpixel方法的使用
2020/04/09 Python
基于Python实现简单学生管理系统
2020/07/24 Python
python使用建议技巧分享(三)
2020/08/18 Python
深入理解Python变量的数据类型和存储
2021/02/01 Python
详解CSS3中使用gradient实现渐变效果的方法
2015/08/18 HTML / CSS
CheapTickets泰国:廉价航班,查看促销价格并预订机票
2019/12/28 全球购物
使用C#编写创建一个线程的代码
2013/01/22 面试题
制药工程专业毕业生推荐信
2013/12/24 职场文书
党的群众路线教育实践方案
2014/05/11 职场文书
我爱我校演讲稿
2014/05/21 职场文书
学位证书委托书
2014/09/30 职场文书
2014年收银工作总结
2014/11/13 职场文书
公司出差管理制度范本
2015/08/05 职场文书
暑假开始了,你的暑假学习计划写好了吗?
2019/07/04 职场文书