尽管 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/
您可以通过两种方式组织模块:
Vue.js 核心模块,
应用功能模块。
Vue.js 核心模块旨在促进您的 Vue.js 开发。包含公司所需的所有网络请求的服务模块等模块都保存在这个核心模块中,所有相应的网络请求都是从这里发出的。
根据功能模块化您的应用程序是在您的应用程序中制作更好的文件结构的好方法。这将允许分离您的关注点,并确保您只在为您或您的团队分配的功能上工作。根据功能模块化的另一个优点是它的可维护性和长期避免技术债务的能力,因为可能需要对应用程序进行返工。
现在,每当需要添加、删除或更改特定功能的状态时,我们所需要做的就是导航到该功能并在不破坏应用程序的情况下进行更改。这种模块化方法允许在我们的应用程序中进行高效的程序开发和轻松的调试和修改。
例如,分配给您和您的团队的支付功能是实施支付模块的好时机,该模块封装了该功能的所有功能和数据。
+-- modules/
| +-- payout/
| +-- index.js
| +-- assets/
| +-- Components/
| +-- PayOut.vue
| +-- UserInfo.vue
| +-- store/
| +-- index.js
| +-- actions.js
| +-- mutations.js
| +-- Test/
基于我们上面的支付功能,我们有一个 index.js 文件来导入和使用仅与支付模块关联的插件。 资产文件夹包含模块的所有资产(图像和样式)。 我们的组件文件夹包含与支付功能相关的组件。 store 文件夹包含我们用于管理此功能状态的操作、更改和获取器。 还有一个测试文件夹可以对这个功能进行测试。
Vue.js 中的指令是我们告诉 Vue.js 为我们做某事或展示某种行为的一种方式。 指令的例子有 v-if、v-model、v-for 等。在我们的 Vue.js 应用程序中,当我们使用类似 v-model 的东西将数据绑定到表单中的输入时,我们给了 Vue.js 编写一些特定于 Vue.js 的指令。 但是,如果我们想要 Vue.js 提供的指令不允许我们做的特定动作或行为,我们该怎么办? 我们可以创建我们所说的自定义指令。
我们可以通过两种方式注册指令:
全局范围内:
在我们的 main.js 文件中。
本地:
在我们的组件中。
指令中的钩子就像在我们的指令中发生特定操作时触发的方法。 就像创建和安装的钩子生命周期钩子一样,我们提供了在我们的指令中使用的钩子。
假设我们正在构建一个应用程序,并且在我们的一个页面中,我们希望每次导航到它时背景颜色总是改变。 我们将把这个指令命名为 colorChange。 我们可以在指令的帮助下实现这一点。
我们的模板看起来像这样:
<template>
<div id="app" 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的重新渲染方式。
我想谈谈可以做到的两种合法方式:
Vue 的 $forceUpdate。
密钥变化模式。
Vue 的 $forceUpdate:在 $forceUpdate 的使用中,子组件不渲染,只渲染 Vue.js 实例,该实例,以及带槽的子组件。
我们可以在全局范围内强制更新:
import Vue from 'vue';
Vue.forceUpdate();
本地也是:
export default {
methods: {
methodThatForcesUpdate() {
this.$forceUpdate();
}
}
}
使用比 $forceUpdate 方法好得多的密钥更改模式是解决此问题的另一种方法。 根据 matthiasg 在这个 Github 问题上的说法,密钥更改模式更好的原因是它允许 Vue.js 知道哪个组件与特定数据相关联,并且当密钥更改时,它会破坏旧组件以创建新组件 我碰到了。 您可以使用 :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、vuex。 我们应该避免将在我们的应用程序中的特定路由中使用的库放在主包中。
使用组件库时,您可以从库中导入单个组件,而不是导入所有组件。 例如,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 的情况下启动它来管理我的状态和使用 props 的通信开始变得混乱。
那么我们什么时候应该使用Vuex呢? 要回答这个问题,我们需要考虑:
项目规模,
代码简单,
路由,
涉及的数据集,
组件嵌套。
如果您的应用程序开始增长,则只适合包含 Vuex 来管理应用程序中的状态。 如果您在开始项目时怀疑是否应该使用状态管理器,那么就使用它。 然而,有一种说法是新的 Vue3 组合 API 是 vuex 的替代品。
我们在 vuex 商店中有四个组件:
State:将数据存储在我们的store中。
Getters:检索状态数据。
Mutations:用于改变状态数据。
Action:用于提交突变。
当我们在 Vuex 中使用上述内容时,我们应该记住,无论发生什么,操作都应该始终提交更改。 这使我们的开发工具能够跟踪更改并恢复到我们状态中的特定时期,并且应该在操作中执行异步操作或业务逻辑。
您可以为每个 Vuex 组件创建一个单独的文件,如下所示:
├── services
├── main.js
└── store
├── index.js
├── actions.js
├── mutations.js
└── Getters.js
├── components
如果我们的项目是一个非常大的团队项目,我们可以根据app的功能来模块化我们的store。 尤其是当有包含许多文件和文件夹的复杂大型项目时,我们只需要一种有组织的方式来处理我们的应用程序的结构时,就会这样做。 我们必须谨慎行事,否则我们可能弊大于利。 根据功能模块化的简单store如下所示:
store/
├── index.js
└── modules/
├── cart
├── index.js
├── actions.js
├── mutations.js
├── product.js
├── login.js
随着我们创建的模块变得越来越复杂,手动导入和组织变得更加困难。 建议您的模块在您的模块的根目录下有一个 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 组件,其行为类似于计算属性,并根据其依赖项缓存 getters 结果。 此外,请确保每个模块都有命名空间,并且不要使用全局状态范围访问它们。
想想一个具有不同组件的应用程序。 我们有父组件,父组件有很多子组件。 从下图中,我们看到我们的子组件 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: `
...
`
})
我们通过返回一个对象来访问组件实例属性来使用提供作为一个函数。
在我们的 child-f 组件中,我们有以下内容:
app.component('child-f', {
inject: ['userAddress'],
template: `
<h2>Injected property: {{ this.userAddress }}</h2>
`
})
但是,我们注意到如果我们将 user.address 更改为另一个地址,更改将不会反映在我们注入的值中,这是因为提供给提供/注入的数据最初不是反应性的。 我们可以通过传递一个反应对象来解决这个问题。 我们必须为我们的用户对象分配一个计算属性。
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 一样在多个组件之间共享数据。
在使用 Vuejs 时,我们可能会遇到一些我们很想实现的功能,但是硬编码可能需要很多时间,或者实现起来有点困难。作为专业的开发人员,我们添加了某些工具和帮助程序库来使事情变得更容易,我们将研究其中的一些。
测试库
在构建大型应用程序时,测试可以发挥至关重要的作用。它帮助我们在与团队合作时避免在开发过程中出现不必要的错误。让我们看看我们可以在 Vue 应用程序及其框架中执行的三种类型的测试。
组件测试
Vue Testing Library, Vue Test Utils.
单元测试
Jest, Mocha.
端到端测试
Nightwatch.js, Cypress.
组件库
组件库是一组可重用的组件,我们可以在我们的应用程序中使用它来使我们的应用程序中的 UI 开发更快、更一致。与 React 和 Angular 一样,Vue 也有自己的一套组件库。其中一些包括:
Vue Material Kit
基于 Material Design 的“Badass”Vue.js UI 套件。它包含 60 多个手工制作的组件。
Buefy
基于 Bulma CSS 框架的轻量级组件库。如果您对 SASS 感到满意,那么使用它就没有问题。
Vuetify
这也是一个材料设计组件框架,可以使用已经制作好的代码脚手架,拥有庞大的社区和定期更新
Quasar
我个人最喜欢的是组件框架。 Quasar 及其高性能前端堆栈允许您为 Web、移动和桌面构建跨平台应用程序。
其他有趣的库#
其他值得注意的库是:
FilePond
这个 Vue.js 库上传您提供的任何图像,并以丝般流畅的体验优化这些图像。
Vuelidate
这个库在处理表单时非常重要,您需要一种方法来验证前端的用户输入。它是一种简单且轻量级的基于模型的验证。
Vue-Clickaway
Vue 没有本机事件侦听器来知道用户何时单击了元素外部,例如下拉列表,这就是 vue-clickaway 存在来检测单击事件的原因。
还有更多的库。可以在 madewithvuejs.com 和 vuejsexamples.com 上查看其中的大量内容。
帮助您编写 Vue # 的有用扩展
扩展是非常有用的工具,它可以在编写 vuejs 时对您的日常工作效率产生很大的影响。在我编写 Vuejs 代码的过程中,我发现以下扩展非常有用:
Vetur
这是我名单上的第一个扩展。在编写 Vuejs 时为我节省了几个小时。它为 Vue.js 提供了特定的突出显示、片段、智能感知、调试等。
Bookmarks
在处理大型项目时,此扩展非常方便,因为您可以在代码中的位置标记和设置书签,并在需要时跳转到该特定位置。
Eslint
如果我们在代码中做错了,Eslint 通过抛出警告来帮助我们轻松地找到编码错误。建议以更漂亮的格式使用它。
Vue.js Extension Pack
此扩展包包含一系列其他扩展,它们将有助于您的 Vue.js 开发,如 Prettier、Vetur、Night Owl 等。
在本教程中,我们查看了一些技巧和工具,可帮助您成为更好的 Vue 开发人员。 我们从一些关于组织项目规模的有用见解和其他需要注意的要点开始,然后我们用工具和扩展来总结它,这些工具和扩展使编写 Vuejs 变得更加容易。
请记住,本文中学到的大部分内容都集中在 Vue.js 2 上,以避免误解。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。