加快 Vue.js 开发过程的工具和实践

已发表: 2022-03-10
快速总结 ↬尽管 Vue.js 声称有一个平易近人的极简框架,可以逐步适应,但作为一个 Vue.js 新手开始时,它可能会有点不知所措。 在本文中,我们正在寻找使编写 Vue.js 变得轻而易举的方法。

在本教程中,我们将研究应该采用的实践,应该避免的事情,并仔细研究一些有用的工具,以使编写 Vue.js 更容易。 我将主要关注 Vue 2,因为大多数人和组织仍在使用旧版本。 不过没有理由担心,因为这里提到的大多数事情仍然适用于 Vue 3,因为它只是一个增压和更快的版本。 不过,如果您已经了解 Vue 2 并且只想了解 Vue 3 中的新功能,那么您可以查看迁移指南以了解更多信息。

注意:本文面向希望提高 Vue.js 技能的初学者和经验丰富的开发人员。 JavaScript 和 Vue.js 的基本知识将对您在本教程中的工作方式大有裨益。

基于模块与基于文件的项目结构

让我们从如何按模块构建文件开始,在构建大规模项目时,基于文件的结构可能不是一个好主意,以及如何构建模块以适应业务需求。

当我们使用 Vue.js CLI 新创建一个项目时,我们将获得 Vue.js 团队绘制的默认文件结构。 使用建议的文件结构本身并不是构建项目的坏方法,但是随着项目的增长,您将需要更好的结构,因为您的代码变得聚集并且更难导航和访问文件。

这就是基于模块的项目结构方法发挥作用的地方。

构建项目的一种不好的方法是存储与同一文件夹无关的不同数据,例如根组件文件夹中的通知组件和身份验证组件:

 +-- src/ | +-- assets/ | +-- logo.png | +-- userprofile.png | +-- components | +-- NotificationBar.vue | +-- LoginForm.vue | +-- DashboardInfo.vue | +-- AuthenticationModal.vue | +-- main.js

所以我们要做的是根据业务逻辑和关注点解耦项目,这样我们就有了认证模块、产品模块、服务模块等。 通过这种方式,我们可以确保与该特定功能有关的任何内容都放入模块中,从而使我们的代码更整洁,导航也不那么困难。

 +-- modules/ | +-- AuthModule/ | +-- assets/ | +-- userprofile.png | +-- Components/ | +-- Authentication.vue | +-- login.vue | +-- NotificationModule | +-- assets/ | +-- Alert.png | +-- Components/ | +-- NotificationBar.vue | +-- ProductModule/

组织模块

您可以通过两种方式组织模块:

  1. Vue.js 核心模块,
  2. 应用功能模块。

Vue.js 核心模块可以帮助您进行 Vue.js 开发。 包含公司所需的所有网络请求的服务模块等模块都保存在这个核心模块中,所有相应的网络请求都从这里发出。

根据功能模块化您的应用程序是在您的应用程序中创建更好的文件结构的好方法。 这将允许分离您的关注点,并确保您只处理您或您的团队被分配到的功能。 根据功能进行模块化的另一个优点是它的可维护性和在可能需要对应用程序进行返工的长期避免技术债务的能力。

现在,每当需要添加、删除或更改特定功能的状态时,我们需要做的就是导航到该功能并在不破坏应用程序的情况下进行更改。 这种模块化方法允许在我们的应用程序中进行高效的程序开发和轻松的调试和修改。

例如,分配给您和您的团队的支付功能是实施payout模块的好时机,该模块封装了该功能的所有功能和数据。

 +-- modules/ | +-- payout/ | +-- index.js | +-- assets/ | +-- Components/ | +-- PayOut.vue | +-- UserInfo.vue | +-- store/ | +-- index.js | +-- actions.js | +-- mutations.js | +-- Test/

基于我们上面的支付功能,我们有一个index.js文件来导入和使用仅与支付模块关联的插件。 资产文件夹包含模块的所有资产(图像和样式)。 我们的组件文件夹包含与支付功能相关的组件。 store 文件夹包含我们用于管理此功能状态的操作、突变和 getter。 还有一个测试文件夹可以对此功能进行测试。

跳跃后更多! 继续往下看↓

使用自定义指令

Vue.js 中的指令是我们告诉 Vue.js 为我们做某事或表现出某种行为的一种方式。 指令的示例是v-ifv-modelv-for等。在我们的 Vue.js 应用程序中,当我们使用 v-model 之类的东西将数据绑定到表单中的输入时,我们将 Vue.js编写一些 Vue.js 特有的指令。 但是,如果我们想要 Vue.js 提供的指令不允许我们执行的特定操作或行为,那我们该怎么办? 我们可以创建我们所谓的自定义指令。

注册自定义指令和指令挂钩

我们可以通过两种方式注册指令:

  1. 全球范围内
    在我们的main.js文件中。
  2. 本地
    在我们的组件中。

指令中的钩子就像在我们的指令中发生特定操作时触发的方法。 就像创建挂载的钩子生命周期钩子一样,我们提供了在指令中使用的钩子。

假设我们正在构建一个应用程序,并且在我们的一个页面中,我们希望每次导航到它时背景颜色总是改变。 我们将把这个指令命名为colorChange 。 我们可以在指令的帮助下实现这一点。

我们的模板看起来像这样:

 <template> <div v-color-change> <HelloWorld msg="Hello Vue in CodeSandbox!"/> </div> </template>

我们可以看到上面的自定义指令,但是为了让它工作,我们在main.js文件中添加:

 // custom directive Vue.directive("color-change", { bind: function (el) { const random = Math.floor(Math.random() * 900000) + 100000; el.style.backgroundColor = `#${random}` } })

上面的 Vue.js 指令将指令名称作为第一个参数,然后将Object作为第二个参数,控制指令的行为。 bind是我们讨论过的钩子之一,一旦指令绑定到元素就会被调用。 它接受以下参数:

  • el
    这是我们将指令附加到的元素节点。
  • binding
    它包含改变指令行为的有用属性。
  • vnode
    这是 Vue.js 的虚拟节点。

我们创建了一组随机的 6 位数字,以便我们可以使用它来更改背景颜色样式的十六进制代码。

编写自定义指令时的最佳实践

我们已经为上面创建了一个自定义指令,但是我们需要注意一些事情。 除了el ,永远不要修改钩子参数并确保参数是只读的,因为钩子参数是具有本机方法的对象,如果修改可能会导致副作用。 如有必要,使用 Vue.js 数据集在钩子之间共享信息。

如果我们使用 Vue.js 的 CLI 构建,自定义指令应该在main.js文件中,以便所有.vue文件都可以访问它。 您的指令名称应该与该特定指令的作用产生共鸣,对指令功能非常具有描述性。

您可以在我创建的这个代码盒中查看和玩更多代码。 您还可以在 Vue 文档中阅读更多相关信息。

控制更新

Vue.js 反应性系统功能强大,它可以检测需要更新的内容并更新它们,而无需您作为开发人员做任何事情。 例如,每次我们导航到它时都重新渲染一个页面。 有时情况可能会有所不同,因为我们可能会发现自己编写的代码需要我们强制更新。

注意:如果您发现自己需要强制更新,这种情况很少见,那么您可能需要真正了解 Vue 的 Reactivity 以及如何正确使用 props 来传递动态数据。

强制更新发生

大多数情况下,当 vue 数据对象中的值发生变化时,视图会自动重新渲染,但并不总是这样。 我们视图的一个经典案例,不重新渲染是当我们在模板中使用v-for循环数据对象中的一些数据时,我们没有在v-for循环中添加:key值。

 <div v-for="item in itemsArray" :key="item">

这为 Vue.js 提供了一种方法来跟踪每个节点的身份并重新渲染视图以进行任何更改。

一种可能导致我们强制更新的罕见情况是,如果我们有意或无意地使用索引设置了数组项。

 var app = new Vue({ data: { items: ['1', '2'] } }) app.items[1] = '7' //vue does not notice any change

有不同的方法可以强制更新或重新渲染。 有些是非常糟糕的做法,例如使用v-if在页面为true时重新渲染页面,而当页面为 false 时,组件消失且不再存在。 这是一种不好的做法,因为模板永远不会被破坏,而只是隐藏起来直到可以重新使用。

 <template> <div v-if="show"> <button @click="rerender">re-render</button> </div> </template>
 <script> export default { data() { return { show: true, }; }, methods: { rerender() { this.show= false; this.$nextTick(() => { this.show = true; }); } } }; </script>

在上面的代码中, show的状态最初设置为 true,这意味着我们的组件最初是渲染的。 然后,当我们点击按钮时,调用了rerender( ) 函数并将show的状态设置为false ,组件不再渲染。 在下一个滴答,这是一个单一的 DOM 更新周期, show设置为true并且我们的组件再次呈现。 这是一种非常 hacky 的重新渲染方式。

我想谈谈可以做到这一点的两种合法方式:

  1. Vue 的$forceUpdate
  2. 键更改模式。

Vue 的$forceUpdate$forceUpdate forceUpdate 的使用中,子组件不渲染,只有 Vue.js 实例、实例和有槽的子组件。

在全球范围内,我们可以强制更新:

 import Vue from 'vue'; Vue.forceUpdate();

在本地也是如此:

 export default { methods: { methodThatForcesUpdate() { this.$forceUpdate(); } } }

使用比$forceUpdate方法更好的密钥更改模式是解决此问题的另一种方法。 键更改模式更好的原因是它允许 Vue.js 知道哪个组件与特定数据相关联,并且当键更改时,它会破坏旧组件以创建新组件,根据 matthiasg 在此 Github 问题上的说法我撞到了。 您可以使用:key属性让 Vue.js 知道哪个组件附加到特定数据。 当 key 改变时,它会导致 Vue.js 销毁旧组件并创建一个新组件。

 <template> <Child :key="key" /> </template> <script> export default { data() { return { key: 0, }; }, methods: { forceRerender() { this.key += 1; } } } </script>

第三方库和优化

我们几乎不可避免地不会在我们的应用程序中使用第三方库。 如果我们对第三方库视而不见,就会开始出现问题,增加包大小并减慢我们的应用程序。

我最近在一个项目中使用了 Vuetify 组件库,并检查了整个包的大小是缩小了 500kb。 这样的事情可能会成为我们应用程序的瓶颈。 您可以使用webpack-bundle-analyzer检查应用程序的包大小。 您可以通过运行安装它:

 npm install --save-dev webpack-bundle-analyzer

并将其包含在您的 webpack 配置文件中:

 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }

优化 Vue 应用程序的良好实践

  • 我们的主包应该只包含对我们的应用程序至关重要的依赖项,例如vuevuex 。 我们应该避免将在我们的应用程序的特定路由中使用的库放在主包中。
  • 使用组件库时,您可以从库中导入单个组件,而不是导入所有内容。 例如,vuetify:
 <template> <v-app> <v-navigation-drawer app> <!-- --> </v-navigation-drawer> <v-app-bar app> <!-- --> </v-app-bar> </v-app> </template> <script> import { VApp, VNavigationDrawer, VAppBar } from 'vuetify/lib' export default { components: { VApp, VNavigationDrawer, VAppBar, } } </script>

通过执行上述操作,我们减少了包大小和冗余代码,仅使用我们想要在该特定路线中使用的组件。

尽早决定使用 Vuex

我经常发现自己想知道是否应该使用 Vuex 启动一个项目。 有时我只是想开始一个小的副项目,我在没有 Vuex 的情况下启动它来管理我的状态,并且使用 props 的通信开始变得混乱。

那么我们什么时候应该使用 Vuex? 要回答这个问题,我们需要考虑:

  • 项目规模,
  • 代码的简单性,
  • 路由,
  • 涉及的数据集,
  • 组件嵌套。

如果您的应用程序开始增长,则只适合包含 Vuex 来管理应用程序中的状态。 如果您在开始项目时怀疑是否应该使用状态管理器,那么就使用它。 然而,有一种说法是新的 Vue3 组合 API 将替代 vuex。

如何为大型应用程序设置 Vuex

我们在 vuex 存储中有四个组件:

  • 状态:将数据存储在我们的商店中。
  • Getters :检索状态数据。
  • Mutations :用于改变状态数据。
  • 行动:用于提交突变。

当我们在 Vuex 中使用上述内容时,我们应该记住,无论如何,动作都应该提交突变。 这使我们的开发工具能够跟踪更改并恢复到我们状态的特定时期,并且应该在操作中执行异步操作或业务逻辑。

您可以为每个 Vuex 组件创建一个单独的文件,如下所示:

 ├── services ├── main.js └── store ├── index.js ├── actions.js ├── mutations.js └── Getters.js ├── components

根据特征进行模块化

如果我们的项目是一个非常大的有团队的项目,我们可以根据应用的特点对我们的商店进行模块化。 尤其是当存在包含许多文件和文件夹的复杂且大型项目并且我们只需要一种有组织的方式来处理我们的应用程序的结构时,就会这样做。 我们必须小心处理这件事,否则我们可能弊大于利。 根据特性模块化的简单商店如下所示:

 store/ ├── index.js └── modules/ ├── cart ├── index.js ├── actions.js ├── mutations.js ├── product.js ├── login.js

使用 Vuex 模块时的良好实践

随着我们创建的模块变得越来越复杂,手动导入和组织变得越来越困难。 建议您的模块在模块的根目录中有一个index.js文件,将这些文件一起带入。

确保您的商店中有标准命名模式,因为这将提高可维护性。 您可以使用 camelCase 命名模块,然后使用.store.js扩展名。 示例: CartData.store.js

 modules/ ├── cart.js ├── index.js -> auto export module ├── userProduct.store.js ├── userData.store.js

由于其阻塞行为,与业务逻辑或异步代码相关的代码不应在突变内部运行,而应使用操作。 不直接访问状态对象被认为是最佳实践。 相反,请使用 getter 函数,因为它可以使用mapGetters映射到任何 vue 组件,其行为类似于计算属性,并根据其依赖项缓存 getter 结果。 此外,请确保每个模块都已命名空间,并且不要使用全局状态范围访问它们。

使用 Provide/Inject 方法传递数据

想象一个具有不同组件的应用程序。 我们有父组件,父组件有很多子组件。 从下图中,我们看到我们的子组件 A、B 和 D 是顶级组件,然后我们看到组件 E 嵌套在组件 D 中,组件 F 嵌套在组件 E 中。如果我们有应用程序数据(如用户地址)怎么办?我们要在子组件 A、C 和 F 中使用,而这个用户地址数据在我们的父组件中。

显示如何使用提供/注入将数据向下传递给子组件的图像
父母与孩子沟通的架构。 (大预览)

为此,我们需要:

  • 在父组件(依赖提供者)中提供值。
  • 在组件 F(依赖消费者)中注入值。

在我们的父组件中,我们提供数据:

 app.component('parent-component', { data() { return { user: {name:"Uma Victor", address:"No 33 Rumukwurushi"} } }, provide() { return { userAddress: this.user.address } }, template: ` ... ` })

我们通过返回一个对象来访问组件实例属性,将provide作为函数使用。

在我们的child-f组件中,我们有以下内容:

 app.component('child-f', { inject: ['userAddress'], template: ` <h2>Injected property: {{ this.userAddress }}</h2> ` })

但是,我们注意到如果我们将user.address更改为另一个地址,更改将不会反映在我们的注入值中,这是因为提供给提供/注入的数据最初不是响应式的。 我们可以通过传递一个reactive对象来provide这个问题。 我们必须为我们的用户对象分配一个计算属性。

 app.component('parent-component', { data() { return { user: {name:"Uma Victor", address:"No 33 Rumukwurushi"} } }, provide() { return { userAddress: Vue.computed(() => this.user) } }, template: ` ... ` })

这种模式比使用 Vuex 非常有用且简单。

但是,使用Vue3 和最近的升级,我们现在可以使用上下文提供程序,使我们能够像 vuex 一样在多个组件之间共享数据。

正确使用表单组件的道具

在网络上构建表单是不是每个人都喜欢做的事情之一。 Vue.js 使构建出色的表单变得容易。 为了实现这一点,我们需要知道如何在表单组件中正确使用道具。 在我们有注册、登录或产品页面的传统应用程序中,我们希望有一致的行为和设计。 例如,下面的登录页面。

签到表格的图像
一个简单的登录表格。 (大预览)

使用代码:

 <template> <div class="form-group"> <form> <label for="email">Your Name</label> <input type="text" class="form-control" placeholder="name" v-model="userData.name" /> <label for="email">Your Email Address</label> <input type="text" class="form-control" placeholder="Email" v-model="userData.email" /> <label for="email">Your Password</label> <input type="text" class="form-control" placeholder="password" v-model="userData.password" /> </form> </div> </template> <script> export default { data() { return { userData: { name: '', email: '', password: '' } } }, } </script>

我们希望有一个BaseInput组件,我们可以将其用于上面的三个表单输入。 我们的BaseInput看起来像这样:

 <template> <div> <label v-if="label">{{ label }}</label> <input type="email" @value="value" @input="updateInput" v-bind="$attrs"> </div> </template> <script> export default { props: { label: { type: String, default: "" }, value: [String, Number] }, methods: { updateInput(event) { this.$emit('input', event.target.value) } } } </script>

我们希望我们的BaseInput接受一个始终是字符串的label prop,如果 Input 有标签,我们会在模板中显示它,如上所示。

当我们填写表单时,会触发updateInput方法。 updateInput方法将输入事件作为参数,它发出一个名称为 Input 的事件,以及有效负载event.target.value ,它是表单中的名称 (John Doe):

 <BaseInput label="Your Name" v-model="userData.name" placeholder="Name"/>

v-model将监听输入事件,然后当它获得它时,它将我们的userData.name设置为它获得的有效负载。

如果我们想为输入设置占位符,我们可能会遇到错误,这是因为在 vue2 中属性总是附加到父级,所以为了解决这个问题,我们将inheritAttrs设置为false并绑定attrs

 <script> export default { inheritAttrs: false, props: { label: { type: String, default: "" }, value: [String, Number] }, methods: { updateInput(event) { this.$emit('input', event.target.value) } } } </script>

到我们希望占位符属性所在的位置。 我们的表单页面代码现在看起来像这样:

 <template> <div class="form-group"> <form> <BaseInput label="Your Name" v-model="userData.name" placeholder="Name"/> <BaseInput label="Your Email Address" v-model="userData.email" placeholder="Email"/> <BaseInput label="Your Password" v-model="userData.password" placeholder="Password"/> </form> </div> </template>

我们终于有了一个独立的可重用表单组件。 您可以使用我制作的代码框中的代码。

注意: Vue3 中的$Attrs Attrs 现在包含所有的侦听器、样式绑定和类。

熟悉 Vue 开发工具

Vue.js Devtools 是一个非常强大的工具,因为它可以帮助我们有效地实时调试我们的应用程序。 当我们使用 Vuex 时它是最强大的,我们必须在我们的应用程序中管理突变和跟踪变化。 大多数 Vue.js 开发人员使用 devtools 作为扩展,但我们也可以将其安装为独立应用程序。

注意: Vue.js 开发工具仅在您构建的开发模式下工作,在生产环境中不起作用,因此其他人无法使用它来检查您的应用程序。

将 Devtools 安装为独立应用程序

您可能想知道为什么我们要为 devtools 安装一个独立的应用程序,而我们可以使用浏览器扩展程序呢? 这是因为当您将其作为独立应用程序安装在本地时,您可以从任何浏览器中使用它。

我们安装它:

 // Globally npm install -g @vue/devtools // or locally npm install --save-dev @vue/devtools

安装完成后,运行:

 vue-devtools

然后在我们的index.html文件中,位于我们的 Vue.js 应用程序根目录的 public 文件夹中,我们添加:

 <script src="https://localhost:8098"></script>

重新加载您的应用程序后,它将自动连接。

我们可以使用 Vue Devtools 进行的一些操作

以下是您可以在 Vue.js DevTools 上执行的一些有用操作。

  • 黑暗主题
    在新的 DevTools 中,现在有一个选项可以在浅色、深色或对比主题之间进行设置。 您可以通过转到全局设置并选择它来执行此操作。
暗模式下的 Vue devtools
暗模式下的 Vue devtools。 (大预览)
  • 时间线
    devtools 中的新时间线显示有关发生的事件的信息,并按时间顺序排列。 它位于检查器和设置视图旁边。
Vue devtools 时间线
Vue devtools 时间线。 (大预览)
  • 格式化组件名称
    您可以选择以 camelCase 或 kebab-case 显示组件名称。

您可以在 vue devtools 中使用许多其他操作。 你可以查看他们的更新日志。

让 Vue 工作更轻松的工具

在使用 Vuejs 时,我们可能会遇到一些我们很想实现的功能,但是硬编码可能需要很多时间,或者实现起来有点困难。 作为专业开发人员,我们添加了某些工具和帮助库以使事情变得更容易,我们将研究其中的一些。

测试库

在构建大型应用程序时,测试可以发挥至关重要的作用。 它可以帮助我们在与团队合作时避免在开发过程中出现不必要的错误。 让我们看看我们可以在 Vue 应用程序及其框架中执行的三种类型的测试。

  • 组件测试
    Vue 测试库,Vue 测试工具。
  • 单元测试
    开玩笑,摩卡。
  • 端到端测试
    Nightwatch.js,赛普拉斯。

组件库

组件库是一组可重用的组件,我们可以在我们的应用程序中使用,以使 UI 开发在我们的应用程序中更快、更一致。 与 React 和 Angular 一样,Vue 也有自己的一套组件库。 其中一些包括:

  • Vue 材料套件
    基于材料设计的“Badass”Vue.js UI 工具包。 它包含 60 多个手工制作的组件。
  • 布菲
    基于 Bulma CSS 框架的轻量级组件库。 如果您对 SASS 感到满意,那么使用它不会有任何问题。
  • 验证
    这也是一个材料设计组件框架,具有已经为代码制作的脚手架的可用性,具有大型社区和定期更新
  • 类星体
    当谈到组件框架时,我个人最喜欢。 Quasar 及其高性能前端堆栈允许您为 Web、移动和桌面构建跨平台应用程序。

其他有趣的图书馆

其他值得注意的库是:

  • 文件池
    这个 Vue.js 库可以上传您提供的任何图像,并以丝般流畅的体验优化这些图像。
  • 维利达
    这个库在处理表单时非常重要,您需要一种在前端验证用户输入的方法。 它是一个简单轻量级的基于模型的验证。
  • vue-Clickaway
    Vue 没有原生事件监听器来知道用户何时点击了元素外部,例如下拉菜单,这就是为什么存在vue-clickaway来检测点击事件的原因。

还有更多的图书馆。 您可以在 madewithvuejs.com 和 vuejsexamples.com 上查看其中的大量内容

帮助您编写 Vue 的有用扩展

扩展是非常有用的工具,它可以在编写 vuejs 时对您的日常工作效率产生很大影响。 在我编写 Vuejs 代码的过程中,我发现以下扩展非常有用:

  • 维特尔
    这是我名单上的第一名分机。 在编写 Vuejs 时节省了我的时间。 它为 Vue.js 提供了特定的突出显示、片段、智能感知、调试等等。
  • 书签
    这个扩展在处理大型项目时非常方便,因为您可以在代码中的位置标记和设置书签,并在需要时跳转到该特定位置。
  • 埃斯林特
    如果我们在代码中做错了什么,Eslint 会通过发出警告来帮助我们轻松找到编码错误。 建议以更漂亮的格式使用它。
  • Vue.js 扩展包
    此扩展包包含一系列有助于 Vue.js 开发的其他扩展,例如 Prettier、Vetur、Night Owl 等。

结论

在本教程中,我们查看了一些技巧和工具来帮助您成为更好的 Vue 开发人员。 我们从一些有用的见解开始,以组织我们的项目以扩大规模和其他值得注意的要点,然后我们使用工具和扩展来完善它,使编写 Vuejs 变得如此容易。

请记住,本文中学到的大部分内容都以 Vue.js 2 为中心,以避免误解。

更多资源

如果您想深入了解我们上面讨论的某些内容,可以查看以下一些有用的链接。

  • “自定义指令”,官方文档
  • “Vue 的反应性”,官方文档
  • “Vue 开发工具”,网站
  • 谈谈 Composition API 与 Vuex
  • 有用的工具 vue javascript 开发 by Timi Omoyeni