构建大型 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 相关文章推荐
TreeView 用法(有代码)(asp.net)
Jul 15 Javascript
jQuery fadeTo方法调整图片的透明度使用介绍
May 06 Javascript
Javascript中call与apply的学习笔记
Sep 22 Javascript
avalon js实现仿google plus图片多张拖动排序附源码下载
Sep 24 Javascript
必备的JS调试技巧汇总
Jul 20 Javascript
支持移动端原生js轮播图
Feb 16 Javascript
self.attachevent is not a function的解决方法
Apr 04 Javascript
vue.js父组件使用外部对象的方法示例
Apr 25 Javascript
JavaScript实现京东购物放大镜和选项卡效果的方法分析
Jul 05 Javascript
react高阶组件添加和删除props
Apr 26 Javascript
layui+jquery支持IE8的表格分页方法
Sep 28 jQuery
JS中自定义事件的使用与触发操作实例分析
Nov 01 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之对抗Web扫描器的脚本技巧
2008/10/01 PHP
PHP 安全检测代码片段(分享)
2013/07/05 PHP
PHP防范SQL注入的具体方法详解(测试通过)
2014/05/09 PHP
ThinkPHP采用实现三级循环代码实例
2014/07/18 PHP
php 从指定数字中获取随机组合的简单方法(推荐)
2017/04/05 PHP
轻轻松松学JS调试(不下载任何工具)
2010/04/14 Javascript
一个网马的tips实现分析
2010/11/28 Javascript
js弹出层包含flash 不能关闭隐藏的2种处理方法
2013/06/17 Javascript
jQuery实现根据类型自动显示和隐藏表单
2015/03/18 Javascript
Javascript中arguments和arguments.callee的区别浅析
2015/04/24 Javascript
jquery+ajax实现注册实时验证实例详解
2015/12/08 Javascript
js动态生成form 并用ajax方式提交的实现方法
2016/09/09 Javascript
JS制作图形验证码实现代码
2020/10/19 Javascript
详解AngularJS脏检查机制及$timeout的妙用
2017/06/19 Javascript
详解vue开发中调用微信jssdk的问题
2019/04/16 Javascript
Angular 中使用 FineReport不显示报表直接打印预览
2019/08/21 Javascript
python读取html中指定元素生成excle文件示例
2014/04/03 Python
python实现数独算法实例
2015/06/09 Python
python 的列表遍历删除实现代码
2020/04/12 Python
Python http接口自动化测试框架实现方法示例
2018/12/06 Python
Python pandas RFM模型应用实例详解
2019/11/20 Python
Python sorted排序方法如何实现
2020/03/31 Python
Python如何创建装饰器时保留函数元信息
2020/08/07 Python
美国二手奢侈品寄售网站:TheRealReal
2016/10/29 全球购物
39美元购买一副眼镜或太阳镜:39DollarGlasses.com
2018/06/17 全球购物
美国第一大药店连锁机构:Walgreens(沃尔格林)
2019/10/10 全球购物
法国隐形眼镜网站:VisionDirect.fr
2020/03/03 全球购物
LINUX下线程,GDI类的解释
2016/12/14 面试题
自荐信格式写作方法有哪些呢
2013/11/20 职场文书
自我鉴定写作要点
2014/01/17 职场文书
加入学生会演讲稿
2014/04/24 职场文书
青年文明号口号
2014/06/17 职场文书
小升初自荐信怎么写
2015/03/26 职场文书
物业工程部经理岗位职责
2015/04/09 职场文书
小学见习报告
2015/06/23 职场文书
Go语言并发编程 sync.Once
2021/10/16 Golang