构建大型 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 相关文章推荐
appendChild() 或 insertBefore()使用与区别介绍
Oct 11 Javascript
浅析node.js中close事件
Nov 26 Javascript
JavaScript控制按钮可用或不可用的方法
Apr 03 Javascript
Javascript递归打印Document层次关系实例分析
May 15 Javascript
JavaScript中的toLocaleLowerCase()方法使用详解
Jun 06 Javascript
javascript实现在指定元素中垂直水平居中
Sep 13 Javascript
jquery对所有input type=text的控件赋值实现方法
Dec 02 Javascript
AngularJs每天学习之总体介绍
Aug 07 Javascript
微信小程序实现左滑动删除效果
Mar 30 Javascript
在node环境下parse Smarty模板的使用示例代码
Nov 15 Javascript
微信小程序wxs实现吸顶效果
Jan 08 Javascript
通过js实现压缩图片上传功能
Feb 25 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
TextArea 控件的最大长度问题(js json)
2009/12/16 Javascript
关于JAVASCRIPT urldecode URL解码的问题
2012/01/08 Javascript
Javascript中找到子元素在父元素内相对位置的代码
2012/07/21 Javascript
ExtJS DOM元素操作经验分享
2013/08/28 Javascript
用Jquery选择器计算table中的某一列某一行的合计
2014/08/13 Javascript
javascript内置对象操作详解
2015/02/04 Javascript
javascript实现将文件保存到本地方法汇总
2015/07/26 Javascript
js时间比较 js计算时间差的简单实现方法
2016/08/26 Javascript
Bootstrap多级菜单的实现代码
2017/05/23 Javascript
详解tween.js的使用教程
2017/09/14 Javascript
js限制输入框只能输入数字(onkeyup触发)
2018/09/28 Javascript
详解jQuery获取特殊属性的值以及设置内容
2018/11/14 jQuery
Vue基于vuex、axios拦截器实现loading效果及axios的安装配置
2019/04/26 Javascript
Vue简单封装axios之解决post请求后端接收不到参数问题
2020/02/16 Javascript
微信小程序 获取手机号 JavaScript解密示例代码详解
2020/05/14 Javascript
微信小程序实现点赞业务
2021/02/10 Javascript
[02:36]DOTA2英雄基础教程 一击致命幻影刺客
2013/12/06 DOTA
linux系统使用python监控apache服务器进程脚本分享
2014/01/15 Python
Python实现的数据结构与算法之快速排序详解
2015/04/22 Python
使用Python生成XML的方法实例
2017/03/21 Python
python使用Matplotlib画饼图
2018/09/25 Python
用Python实现大文本文件切割的方法
2019/01/12 Python
在Mac中PyCharm配置python Anaconda环境过程图解
2020/03/11 Python
Python爬虫获取页面所有URL链接过程详解
2020/06/04 Python
详解python中的异常和文件读写
2021/01/03 Python
Python 求向量的余弦值操作
2021/03/04 Python
NUK奶瓶美国官网:NUK美国
2016/09/26 全球购物
优衣库美国官网:UNIQLO美国
2018/04/14 全球购物
软件测试工程师面试问题精选
2016/10/28 面试题
单身联谊活动方案
2014/01/29 职场文书
学校联谊活动方案
2014/02/15 职场文书
简单通用的简历自我评价
2014/09/21 职场文书
中标通知书范本
2015/04/17 职场文书
如何写观后感
2015/06/19 职场文书
详解CSS中的特指度和层叠问题
2021/07/15 HTML / CSS
js 实现Material UI点击涟漪效果示例
2022/09/23 Javascript