Vue3 秘籍(全)
原文:
zh.annas-archive.org/md5/915E62C558C25E5846A894A1C2157B6C
译者:飞龙
前言
Vue 是一个最小的前端框架,赋予开发人员创建 Web 应用程序、原型、大型企业应用程序、桌面应用程序和移动应用程序的能力。
Vue 3 是 Vue 的完全重写,并对框架的所有核心 API 进行了更改。这次重写改变了用 TypeScript 编写的代码。在 Vue 3 中,我们暴露了所有核心 API,使每个人都有可能使用 Vue。
本书从实现 Vue 3 的新功能到迁移现有 Vue 应用程序到最新版本的方法开始。您将学习如何在 Vue 中使用 TypeScript,并找到解决常见挑战和问题的简洁解决方案,从实现组件、派生物和动画,到构建插件、添加状态管理、路由和开发完整的单页应用程序(SPA)。
本书中使用的一些库、插件和框架可能会在本书编写和您阅读之间接收更新。因此,请注意任何可能导致破坏性变化的 API 更改或版本更改。
本书适合对象
这本书适用于希望了解更多关于 Vue 并希望提高他们的 Vue 技能的 Web 开发人员。我们将从介绍 Vue 3 和 TypeScript 技术开始。在接下来的章节中,读者将了解 Vue 中的新概念及其生态系统插件、UI 框架和高级技巧。
通过从头到尾地阅读本书,您将能够创建一个 Vue 应用程序,使用所有必要的 Vue 插件,并使用顶级 Vue UI 框架。如果您已经熟悉 Vue,您将发现相关的新模式。
本书涵盖内容
第一章,理解 Vue 3 和创建组件,为读者提供了如何使用新的 Vue 3 API 创建自定义 Vue 组件的方法,并使用 Vue 的暴露核心 API 和组合 API。本章还帮助读者将 Vue 2 应用程序初步升级到 Vue 3。
第二章,介绍 TypeScript 和 Vue 生态系统,向读者介绍了 TypeScript 超集以及如何使用它,从基本类型、接口和类型注解开始。读者将准备好使用 Vue CLI、TypeScript 和vue-class-component
开发 Vue 应用程序。
第三章,“数据绑定、表单验证、事件和计算属性”,讨论了基本的 Vue 开发和组件概念,包括v-model
、事件监听器、计算属性和for
循环。读者将介绍 Vuelidate 插件用于表单验证以及如何在 Vue 组件上使用它,以及如何使用vue-devtools
调试 Vue 组件。
第四章,“组件、混合和功能组件”,向读者介绍了使用不同方法构建组件,包括用于内容的自定义插槽、验证的 props、功能组件以及创建用于代码重用的混合。然后,它向读者介绍了一系列不同的方法来访问子组件的数据,创建依赖注入组件和动态注入组件,以及如何延迟加载组件。
第五章,“通过 HTTP 请求从 Web 获取数据”,向读者展示了如何在 JavaScript 上为 HTTP 调用创建 Fetch API 的自定义包装器,如何在 Vue 中使用该包装器,以及如何在 Vue 上实现自定义异步函数。读者还将学习如何在 axios 中替换包装器的 Fetch API,以及如何在 axios 上实现自定义处理程序。
第六章,“使用 vue-router 管理路由”,介绍了 Vue 的路由插件以及如何在 Vue 上使用它为 Vue 应用程序的页面创建路由。它介绍了管理路由路径的过程,路由路径上带有参数的动态路径,页面组件的延迟加载,为路由创建身份验证中间件,以及使用别名和重定向。
第七章,“使用 Vuex 管理应用程序状态”,探讨了 Vue 状态管理插件,帮助读者了解 Vuex 的工作原理以及如何应用于他们的应用程序。本章还为读者提供了创建 Vuex 模块、操作、突变和获取器的配方,并探讨了如何为存储定义基本状态。
第八章,使用过渡和 CSS 为您的应用程序添加动画,通过提供基于 CSS 的自定义动画示例,探讨了 CSS 动画和过渡的基础知识。这些将与 Vue 自定义组件一起使用,以实现一个外观漂亮的应用程序,并为应用程序的用户提供最佳体验。
第九章,使用 UI 框架创建漂亮的应用程序,介绍了流行的 UI 框架。读者将使用 Buefy、Vuetify 和 Ant-Design 构建用户注册表单,并了解它们的设计概念。本章的目的是教会读者如何使用 UI 框架创建一个外观良好的应用程序。
第十章,将应用程序部署到云平台,展示了如何在 Vercel、Netlify 和 Google Firebase 等自定义第三方主机上部署 Vue 应用程序。通过本章的示例,读者将学会如何使用集成的存储库钩子和自动部署功能自动部署他们的应用程序。
第十一章,专业联赛-指令、插件、SSR 和更多,探讨了 Vue 的高级主题,包括模式、最佳实践、如何创建插件和指令,以及如何使用 Quasar 和 Nuxt.js 等高级框架创建应用程序。
为了充分利用本书
Vue 3 beta 是撰写本书时可用的版本。所有的代码将在 GitHub 存储库的最终版本上进行更新:github.com/PacktPublishing/Vue.js-3.0-Cookbook
您需要安装 Node.js 12+,将 Vue CLI 更新到最新版本,并拥有某种良好的代码编辑器。其他要求将在每个示例中介绍。所有软件要求都适用于 Windows、macOS 和 Linux。
要开发 iOS 移动应用程序,您需要一台 macOS 机器以便访问 Xcode 和 iOS 模拟器。以下是总结所有要求的表格:
书中涵盖的软件/硬件 | 操作系统要求 |
---|---|
Vue CLI 4.X | Windows / Linux / macOS |
TypeScript 3.9.X | Windows / Linux / macOS |
Quasar-CLI 1.X | Windows / Linux / macOS |
Nuxt-CLI 3.X.X | Windows / Linux / macOS |
Visual Studio Code 1.4.X 和 IntelliJ WebStorm 2020.2 | Windows / Linux / macOS |
Netlify-CLI | Windows / Linux / macOS |
Vercel-CLI | Windows / Linux / macOS |
Firebase-CLI | Windows / Linux / macOS |
Node.js 12+- | Windows / Linux / macOS |
Python 3 | Windows / Linux / macOS |
Xcode 11.4 和 iOS 模拟器 | macOS |
如果您使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库(链接在下一节中提供)访问代码。这样做将帮助您避免与复制和粘贴代码相关的任何潜在错误。
下载示例代码文件
您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packtpub.com/support注册,直接将文件发送到您的邮箱。
您可以按照以下步骤下载代码文件:
在www.packt.com上登录或注册。
选择“支持”选项卡。
单击“代码下载”。
在搜索框中输入书名,然后按照屏幕上的说明操作。
下载文件后,请确保使用以下最新版本解压缩或提取文件夹:
Windows 下的 WinRAR/7-Zip
Mac 下的 Zipeg/iZip/UnRarX
Linux 下的 7-Zip/PeaZip
该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Vue.js-3.0-Cookbook
。如果代码有更新,将在现有的 GitHub 存储库上进行更新。
我们还有来自我们丰富图书和视频目录的其他代码包,可在**github.com/PacktPublishing/
**上找到。快来看看吧!
使用的约定
本书中使用了许多文本约定。
CodeInText
:表示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。 例如:"将下载的WebStorm-10*.dmg
磁盘映像文件挂载为系统中的另一个磁盘。"
代码块设置如下:
<template>
<header>
<div id="blue-portal" />
</header>
</header>
任何命令行输入或输出都以以下方式编写:
$ npm run serve
粗体:表示新术语,重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会在文本中以这种方式出现。例如:"单击电子邮件按钮,将被重定向到电子邮件注册表单"
警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。
部分
在本书中,您会经常看到几个标题(准备工作,如何做,它是如何工作的,还有更多,和另请参阅)。
为了清晰地说明如何完成食谱,请按照以下部分使用这些部分:
准备工作
本节告诉您在食谱中可以期待什么,并描述如何设置食谱所需的任何软件或初步设置。
如何做…
本节包含了遵循食谱所需的步骤。
它是如何工作的…
本节通常包括对前一节发生的事情的详细解释。
还有更多…
本节包括有关食谱的其他信息,以使您对食谱更加了解。
另请参阅
本节为食谱提供了其他有用信息的链接。
第一章:了解 Vue 3 和创建组件
Vue 3 带来了许多新功能和变化,所有这些都旨在帮助开发并改善框架的整体稳定性、速度和可维护性。借鉴其他框架和库的灵感,Vue 核心团队设法在 API 上实现了很高的抽象水平,现在任何人都可以使用 Vue,无论他们是前端开发人员还是后端开发人员。
在本章中,我们将学习如何将我们的 Vue 项目升级到新版本,以及一些新的 Vue 功能,比如多个根元素,新的属性继承引擎,我们如何在另一个应用程序中使用暴露的响应性 API,以及如何使用新的组合 API 创建组件。
在本章中,您将学习以下内容:
Vue 3 有什么新功能
将您的 Vue 2 应用程序升级到 Vue 3
使用多个根元素创建组件
使用属性继承创建组件
在 Vue 范围之外使用响应性和可观察 API
使用组合 API 创建组件
Vue 3 有什么新功能
你可能想知道一个框架的新版本怎么会在互联网上引起如此大的轰动?想象一下把一辆汽车开上高速公路,做一个完整的 360 度翻滚,然后继续朝着同一个方向全速前进。这将引起一场戏剧性的场面,这正是描述 Vue 将从 2 版本升级到 3 版本的完美方式。
在本章的第一部分,我将向您介绍 Vue 的改进,框架中添加了什么,发生了什么变化,以及它将如何影响您编写 Vue 应用程序的方式。
框架的改进
在这个新版本中,Vue 框架有许多改进;所有这些改进都集中在尽可能使框架更好。以下是一些可能影响用户和开发人员日常开发和使用框架的改进。
底层
外壳看起来和旧的一样,但引擎是一件艺术品。在新版本中,没有留下来自 Vue 2 的代码。核心团队使用 TypeScript 从头开始构建了框架,并重写了一切,以最大程度地提高框架的性能。
选择了 TypeScript 来创建 Vue 核心团队和开源社区更易于维护的代码库,并改进自动完成功能,如 IDE 和代码编辑器提供的IntelliSense或typeahead,无需特殊的插件和扩展。
渲染引擎
对于 Vue 3,使用了一种新的算法开发了一个新的渲染引擎,用于影子 DOM。这个新的渲染引擎默认情况下完全暴露在框架的核心中,无需由框架执行。这使得可以实现一个全新的渲染函数的新实现,可以注入到框架中并替换原始的渲染引擎。
在这个新版本的 Vue 中,从头开始编写了一个新的模板编译器。这个新的编译器使用了一种新的缓存操作和管理渲染元素的新技术,并应用了一种新的提升方法来创建 VNodes。
对于缓存操作,应用了一种新的方法来控制元素的位置,其中元素可以是具有计算数据的动态元素,也可以是对可以被改变的函数的响应。
Vue 核心团队制作了一个浏览器,可以看到新模板编译器如何渲染最终的render
函数。可以在vue-next-template-explorer.netlify.app/
上查看。
暴露的 API
通过所有这些修改,可以在 Vue 应用程序范围之外的文件中渲染所有暴露给使用的 Vue API。可以在 React 应用程序中使用 Vue 响应性或影子 DOM,而无需在 React 应用程序内部渲染 Vue 应用程序。这种可扩展性是将 Vue 转变为更多功能的框架的一种方式,它可以在任何地方使用,不仅仅是在前端开发中。
新的自定义组件
Vue 3 引入了三个新的自定义组件,开发人员可以使用这些组件来解决旧问题。这些组件在 Vue 2 中也存在,但作为第三方插件和扩展。现在它们由 Vue 核心团队制作,并添加到 Vue 核心框架中。
片段
在 Vue 2 中,我们总是需要在单文件组件内部的组件周围有一个父节点。这是由于 Vue 2 的渲染引擎的构造方式所致,需要在每个节点上都有一个根元素。
在 Vue 2 中,我们需要有一个包装元素,封装将要呈现的元素。在这个例子中,我们有一个div
HTML 元素,包装了两个p
HTML 子元素,这样我们就可以在页面上实现多个元素:
<template>
<div>
<p>This is two</p>
<p>children elements</p>
</div> </template>
现在,在 Vue 3 中,可以在单文件组件中声明任意数量的根元素,而无需使用新的 Fragments API 特殊插件,它将处理多个根元素。这有助于为用户保持更清洁的最终代码,而无需为包装元素而创建空壳:
<template>
<p>This is two</p>
<p>root elements</p> </template>
正如我们在 Vue 3 代码中看到的,我们能够有两个根p
HTML 元素,而无需包装元素。
Teleport
Teleport
组件,也称为 Portal 组件,顾名思义,是一个可以使元素从一个组件移动到另一个组件的组件。这一开始可能看起来很奇怪,但它有许多应用,包括对话框、自定义菜单、警报、徽章和许多其他需要出现在特殊位置的自定义 UI。
想象一个标题组件,您希望在组件上放置一个自定义插槽,以便放置组件:
<template>
<header>
<div id="blue-portal" />
</header>
</header>
然后,您想在此标题上显示一个自定义按钮,但您希望从页面上调用此按钮。您只需要执行以下代码:
<template>
<page>
<Teleport to="blue-portal">
<button class="orange-portal">Cake</button>
</Teleport>
</page>
</template>
现在,您的按钮将显示在标题上,但代码将在页面上执行,从而访问页面范围。
悬念
当等待数据的时间比您想要的时间长时,如何为用户显示自定义加载程序?现在这是可能的,而无需自定义代码;Vue 将为您处理。Suspense
组件将管理此过程,在数据加载完成后显示默认视图,并在加载数据时显示备用视图。
您可以编写一个特殊的包装器,如下所示:
<template>
<Suspense>
<template #default>
<data-table />
</template>
<template #fallback>
<loading-gears />
</template>
</Suspense>
</template>
新的 Vue 组合 API 将了解组件的当前状态,因此它将能够区分组件是正在加载还是准备好显示。
API 更改
Vue 3 进行了一些 API 更改,这些更改是为了清理 Vue API 并简化开发而必要的。其中一些是破坏性的更改,另一些是新增的。但不用担心;Vue 2 对象开发并没有被移除,它仍然存在,并将继续使用。这种声明方法是许多开发人员选择 Vue 而不是其他框架的原因之一。
Vue 3 中将出现一些重要的变化,这些变化很重要,需要更多了解。我们将讨论 Vue 3 中将引入的最重要的变化,以及如何处理它们。
在 Vue 3 中,正在引入一种创建组件的新方法——组合 API。这种方法将使您的代码更易于维护,并为您提供更可靠的代码,您将拥有 TypeScript 的全部功能。
一些较小的变化
在 Vue 3 中存在一些较小的变化,需要提及。这些变化涉及我们以前用来编写代码的一种方法,现在在使用 Vue 3 时已经被替换。这并不是一项艰巨的工作,但您需要了解这些变化。
再见过滤器,你好过滤器!Vue 过滤器 API
在 Vue 2 中,我们使用filters
的方式已经不再可用。Vue 过滤器已从 API 中删除。这一变化是为了简化渲染过程并加快速度。最终,所有过滤器都是接收一个字符串并返回一个字符串的函数。
在 Vue 2 中,我们使用filters
如下:
{{ textString | filter }}
现在,在 Vue 3 中,我们只需要传递一个function
来操作string
:
{{ filter(textString) }}
公交车刚刚离开车站!事件总线 API
在 Vue 2 中,我们能够利用全局 Vue 对象的力量创建一个新的 Vue 实例,并使用这个实例作为一个事件总线,可以在组件和函数之间传输消息而不需要任何麻烦。我们只需要发布和订阅事件总线,一切都很完美。
这是在组件之间传输数据的一个好方法,但对于 Vue 框架和组件来说是一种反模式的方法。在 Vue 中,在组件之间传输数据的正确方式是通过父子通信或状态管理,也被称为状态驱动架构。
在 Vue 3 中,$on
、$off
和$once
实例方法已被移除。现在,要使用事件总线策略,建议使用第三方插件或框架,如 mitt(github.com/developit/mitt
)。
不再有全局 Vue——挂载 API
在 Vue 2 中,我们习惯于导入 Vue,并在挂载应用程序之前,使用全局 Vue 实例来添加plugins
、filters
、components
、router
和store
。这是一种很好的技术,我们可以向 Vue 实例添加任何内容,而无需直接附加到挂载的应用程序上。它的工作原理如下:
import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';
Vue.use(Vuex);
const store = new Vuex.store({});
new Vue({
store,
render: (h) => h(App),
}).$mount('#app');
现在,在 Vue 3 中,这是不再可能的。我们需要直接将每个component
、plugin
、store
和router
附加到挂载的实例上:
import { createApp } from 'vue';
import { createStore } from 'vuex';
import App from './App.vue';
const store = createStore({});
createApp(App)
.use(store)
.mount('#app');
使用这种方法,我们可以在同一个全局应用程序中创建不同的 Vue 应用程序,而不会相互干扰。
v-model,v-model,v-model - 多个 v-model
在开发单文件组件时,我们被限制为只能使用一个v-model
指令和一个.sync
选项来进行第二次更新更改。这意味着我们需要使用大量自定义事件发射器和巨大的对象负载来处理组件内的数据。
在这次重大变化中,引入了一个相关的破坏性变化,导致 Vue API 中的model
属性被移除。这个属性用于自定义组件,以前可以做与新的 v-model 指令现在所做的相同的事情。
使用v-model
指令的新方法将改变糖语法的工作方式。在 Vue 2 中,要使用v-model
指令,我们需要创建一个组件,期望接收props
为"value"
,当有变化时,我们需要发出一个'input'
事件,就像下面的代码:
<template>
<input
:value="value"
@input="$emit('input', $event)"
/>
</template>
<script>
export default {
props: {
value: String,
},
}
</script>
在 Vue 3 中,为了使语法糖工作,组件将接收的props
属性和事件发射器将发生变化。现在,组件期望一个名为modelValue
的props
,并发出一个名为'update:modelValue'
的事件,就像下面的代码:
<template>
<input
:modelValue="modelValue"
v-on:['update:modelValue']="$emit('update:modelValue', $event)"
/>
</template>
<script>
export default {
props: {
modelValue: String,
},
}
</script>
但是多个v-model
指令呢?理解v-model
的破坏性变化是了解多个v-model
新方法如何工作的第一步。
要创建多个v-model
组件,我们需要创建各种props
,并使用'update:value'
事件发出值作为模型指令的名称:
<script>
export default {
props: {
name: String,
email: String,
},
methods: {
updateUser(name, email) {
this.$emit('update:name', name);
this.$emit('update:email', email);
}
}
}
</script>
在我们想要使用多个v-model
指令的组件中,使用以下代码:
<template>
<custom-component
v-model:name="name"
v-model:email="email"
/>
</template>
组件将有每个v-model
指令,绑定到子组件正在发出的事件。在这种情况下,子组件发出'update:email'
(父组件)以便能够使用v-model
指令与 email 修饰符。例如,您可以使用v-model:email
来创建组件和数据之间的双向数据绑定。
组合 API
这是 Vue 3 最受期待的功能之一。组合 API 是创建 Vue 组件的一种新方式,以优化的方式编写代码,并在组件中提供完整的 TypeScript 类型检查支持。这种方法以更简单和更高效的方式组织代码。
在这种声明 Vue 组件的新方式中,你只需要一个setup
属性,它将被执行并返回组件执行所需的一切,就像这个例子:
<template>
<p @click="increaseCounter">{{ state.count }}</p> </template> <script> import { reactive, ref } from 'vue'; export default {
setup(){
const state = reactive({
count: ref(0)
}); const increaseCounter = () => {
state.count += 1;
} return { state, increaseCounter }
} } </script>
您将从 Vue 核心导入reactivity
API,以在对象类型数据属性中启用它,例如state
。ref
API 可以使基本类型值(如count
)具有反应性,它是一个数字。
最后,函数可以在setup
函数内部声明,并在返回的对象中传递。然后,所有内容都可以在<template>
部分中访问。
现在,让我们继续进行一些示例。
技术要求
在本章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户!您需要安装一个名为windows-build-tools
的 NPM 包,以便能够安装以下必需的包。为此,请以管理员身份打开 Power Shell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Vue-CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建基本文件
在本章的所有示例中,我们将使用这个基本模板,现在我们将创建它。确保在开始示例之前按照以下步骤创建文件:
在任何文件夹中创建一个新的
.html
文件并打开它。创建一个
html
标签,并添加一个head
HTML 元素作为子元素。在head
HTML 元素内部,添加一个带有src
属性定义为http://unpkg.com/vue@next
的script
HTML 元素:
<html> <head>
<script src="https://unpkg.com/vue@next"></script>
</head>
</html>
- 作为
head
HTML 元素的同级,创建一个body
HTML 元素。在body
HTML 元素内部,添加一个带有属性id
定义为"app"
的div
HTML 元素:
<body>
<div id="app">
</div>
</body>
- 最后,作为
div
HTML 元素的同级,创建一个带有空内容的script
HTML 元素。这将是我们放置示例代码的地方:
<script></script>
将您的 Vue 2 应用程序升级到 Vue 3
将您的项目从 Vue 2 升级到 Vue 3 有时可以自动完成,但在其他情况下,需要手动完成。这取决于您在应用程序中使用 Vue API 的深度。
对于由 Vue-CLI 制作和管理的项目,这个过程将变得更加顺畅,并且与使用自定义框架包装 CLI 的项目相比,将有更加简单的方法。
在这个食谱中,您将学习如何使用 Vue-CLI 升级您的应用程序以及如何手动升级项目和依赖项。
准备工作
这个食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
为了将您的 Vue 2 项目升级到 Vue 3,您将需要将升级分为不同的部分。我们有框架本身的升级,然后是生态系统组件,比如vue-router
和vuex
,最后是将所有内容汇总的捆绑器。
框架升级带来了一些破坏性的变化。本章的Vue 3 中的新内容部分介绍了一些破坏性的变化,还有一些可能出现在更高级的 API 模式中。您必须手动更新并检查您的组件是否适用于框架的升级。
使用 Vue-CLI 升级项目
使用最新版本的 Vue-CLI,您将能够在项目中使用 Vue 3,并且您将能够将当前项目更新到 Vue 3。
要将 Vue-CLI 更新到最新版本,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install @vue/cli-service@latest
手动升级项目
要手动升级项目,您首先需要将项目依赖项升级到它们的最新版本。您不能在 Vue 3 中使用旧版本的 Vue 生态系统插件。要做到这一点,请执行以下步骤:
- 我们需要升级 Vue 框架、ESLint 插件(Vue 依赖的插件)和捆绑器的
vue-loader
。要升级它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install vue@next eslint-plugin-vue@next vue-loader@next
- 我们需要将新的 Vue 单文件组件编译器作为项目的依赖项添加进去。要安装它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install @vue/compiler-sfc@latest
- 如果您在项目中使用单元测试和
@vue/test-utils
包,您还需要升级此依赖项。要升级它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install @vue/test-utils@next @vue/server-test-utils@latest
- 对于 Vue 生态系统插件,如果你使用
vue-router
,你也需要升级它。要升级它,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install vue-router@next
- 如果你的应用程序使用
vuex
作为默认状态管理,你也需要升级它。要升级它,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install vuex@next
更改起始文件
使用新版本的包,我们需要改变我们的起始文件。在使用 Vue-CLI 起始工具创建的 Vue 项目中,你会找到一个名为main.js
或main.ts
的文件。如果你使用 TypeScript,该文件位于src
文件夹中。现在按照以下说明进行操作:
- 打开项目中
src
文件夹中的main.js
文件。在文件顶部,导入包的位置,你会看到以下代码:
import Vue from 'vue';
我们需要将其更改为新的 Vue 暴露的 API 方法。为此,我们需要从 Vue 包中导入createApp
,如下所示:
import { createApp } from 'vue';
从你的代码中移除全局 Vue 静态属性定义的
Vue.config.productionTip
。应该改变你的应用程序的挂载函数。旧的 API 看起来像这样:
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
旧的 API 应该改为新的createApp
API,如下所示:
createApp(App)
.use(router)
.use(store)
.mount('#app')
打开你的
vuex
存储实例化文件(通常,该文件位于src/store
,命名为store.js
或index.js
)。将存储的创建从实例化一个新的
vuex
类改为新的createStore
API。vuex
v3 类的实例化可能看起来像这样:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: { /* ... */ },
mutations: { /* ... */ },
actions: { /* ... */ },
getters: { /* ... */ },
modules: { /* ... */ },
});
你需要用createStore
API 替换它的内容,例如:
import { createStore } from 'vuex';
export default createStore({
state: { /* ... */ },
mutations: { /* ... */ },
actions: { /* ... */ },
getters: { /* ... */ },
modules: { /* ... */ },
});
在
vue-router
生态系统中,我们需要用新的 API 替换路由器创建的旧 API。为此,打开路由器创建文件(在src/router
文件夹中,通常命名为router.js
或index.js
)。最后,在创建文件中,用新的
createRouter
API 替换旧的vue-router
类实例化。vue-router
v3 类的实例化可能看起来像这样:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default new VueRouter({
routes: [{
path: '/',
name: 'HomePage',
component: () => import('pages/home'),
}]
});
你还需要用新的createRouter
和createWebHistory
API 替换new VueRouter
的实例化,就像这个例子一样:
import {
createRouter,
createWebHistory,
} from 'vue-router';
Vue.use(VueRouter);
export default createRouter({
history: createWebHistory(),
routes: [{
path: '/',
name: 'HomePage',
component: () => import('pages/home'),
}]
});
它是如何工作的...
在升级过程中,Vue 为我们提供了两种更新项目的方式。第一种方式是使用 Vue-CLI 插件,它试图自动化几乎所有升级所需的过程和更改。
第二种方法是手动升级项目。这种方法需要开发人员将所有依赖项升级到最新版本,安装新的单文件组件编译器@vue/compiler-sfc
,并将 Vue 应用程序、路由器和存储的入口文件更改为新的 API。
在项目的起始结构更改后,开发人员需要检查组件,看看是否存在任何 Vue 3 破坏性更改,将组件重构为新的 Vue 3 API,并从 Vue 2 中删除已弃用的 API。
创建具有多个根元素的组件
在 Vue 3 中,可以创建具有多个根元素的组件,无需包装元素。这个选项也被称为片段。
在 React 中,这已经很久了,但在 Vue 中,您需要使用自定义的第三方插件,如vue-fragment
(github.com/Thunberg087/vue-fragment
)来使用此功能。
在这个教程中,您将学习如何创建一个具有多个根元素的组件,以及如何将其与<template>
部分和render
函数一起使用。
如何做...
在这个教程中,我们将创建两个多个根元素组件的示例,一个是使用<template>
结构,另一个是使用render
函数。为了做到这一点,这个教程将分为两部分。
使用结构创建组件
为了在我们的示例中使用<template>
结构,我们将使用 Vue 对象的template
属性,我们可以将字符串或模板字符串作为值传递,这将由 Vue 脚本插值并呈现在屏幕上:
使用“创建基本文件”部分的基本示例,创建一个名为template.html
的新文件并打开它。
在空的<script>
HTML 元素中,通过对象解构Vue
全局常量,创建常量defineComponent
和createApp
:
const {
defineComponent, createApp, } = Vue;
- 创建一个名为
component
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,其中有三个属性:data
、methods
和template
:
const component = defineComponent({
data: () => ({}),
methods: {},
template: `` });
- 在
data
属性中,将其定义为一个单例函数,返回一个 JavaScript 对象,其中有一个名为count
的属性,并且默认值为0
:
data: () => ({
count: 0 }),
- 在
methods
属性中,创建一个名为addOne
的属性,这是一个函数,将通过1
增加count
的值:
methods: {
addOne() {
this.count += 1;
}, },
- 在
template
属性中,在模板字符串中,创建一个带有标题的h1
HTML 元素。然后,作为兄弟元素,创建一个带有绑定到click
事件的事件监听器的button
HTML 元素,当执行时触发addOne
函数:
template: `
<h1> This is a Vue 3 Root Element! </h1>
<button @click="addOne"> Pressed {{ count }} times. </button> `
- 最后,调用
createApp
函数,将component
常量作为参数传递。然后,原型链连接mount
函数,并将div
HTML 元素的id
属性("#app")
作为函数的参数:
createApp(component)
.mount('#app');
使用渲染函数创建组件
为了在我们的示例中使用<template>
结构,我们将使用 Vue 对象的template
属性,我们可以将字符串或模板字符串作为值传递,Vue 脚本将对其进行插值处理并在屏幕上呈现:
使用“创建基本文件”部分的基本示例,创建一个名为render.html
的新文件并打开它。
在空的<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,从Vue
全局常量中调用defineComponent
、h
和createApp
方法:
const {
defineComponent,
h, createApp, } = Vue;
- 创建一个名为
component
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,该对象有三个属性:data
、methods
和render
:
const component = defineComponent({
data: () => ({}),
methods: {},
render() {}, });
- 在
data
属性中,将其定义为一个单例函数,返回一个具有名为count
且默认值为0
的 JavaScript 对象:
data: () => ({
count: 0 }),
- 在
methods
属性中,创建一个名为addOne
的属性,它是一个函数,将count
的值增加1
:
methods: {
addOne() {
this.count += 1;
}, },
- 在
render
属性中,执行以下步骤:
创建一个名为h1
的常量,并将其定义为h
函数,将'h1'
作为第一个参数传递,将要使用的标题作为第二个参数。
创建一个名为button
的常量,它将是h
函数,将"button"
作为第一个参数传递,将一个具有onClick
属性且值为this.addOne
的 JavaScript 对象作为第二个参数传递,将button
的内容作为第三个参数。
返回一个数组,第一个值为h1
常量,第二个值为button
常量:
render() {
const h1 = h('h1', 'This is a Vue 3 Root Element!');
const button = h('button', {
onClick: this.addOne,
}, `Pressed ${this.count} times.`); return [
h1,
button,
]; },
- 最后,调用
createApp
函数,将component
常量作为参数传递,原型链连接mount
函数,并将div
HTML 元素的id
属性("#app")
作为函数的参数:
createApp(component)
.mount('#app');
工作原理...
新的 Vue 组件创建 API 需要由一个函数defineComponent
执行,并且作为参数传递的 JavaScript 对象几乎保持与 Vue 2 中的旧结构相同。在示例中,我们使用了相同的属性data
、render
、methods
和template
,这些属性都存在于 Vue 2 中。
在具有<template>
结构的示例中,我们不必创建包装元素来封装应用程序组件的内容,并且可以直接在组件上有两个根元素。
在render
函数示例中,发生了相同的行为,但最终示例使用了新的暴露的h
API,它不再是render
函数的参数。在按钮创建中出现了一个重大变化;我们必须在数据 JavaScript 对象内部使用onClick
属性,而不是on
属性和click
方法。这是因为 Vue 3 的 VNode 的新数据结构。
使用属性继承创建组件。
自 Vue 2 以来,组件上已经可以使用属性继承,但在 Vue 3 中,属性继承变得更好,并且具有更可靠的 API 可用于组件中。
组件中的属性继承是一种模式,它可以更快地开发基于 HTML 元素的自定义组件(例如自定义输入、按钮、文本包装器或链接)。
在这个示例中,我们将创建一个具有属性继承的自定义输入组件,直接应用于input
HTML 元素。
如何做...
在这里,我们将创建一个组件,该组件将在 DOM 树上的选定元素上具有完整的属性继承:
使用创建基本文件部分的基本示例,创建一个名为component.html
的新文件并打开它。
在空的<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,调用Vue
全局常量的defineComponent
和createApp
方法:
const {
defineComponent, createApp, } = Vue;
- 创建一个名为
nameInput
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,具有四个属性:name
、props
、template
和inheritAttrs
。然后,我们将inheritAttrs
的值定义为false
:
const nameInput = defineComponent({
name: 'NameInput',
props: {},
inheritAttrs: false,
template: `` });
- 在
props
属性中,添加一个名为modelValue
的属性,并将其定义为String
:
props: {
modelValue: String, },
- 在模板属性中,在模板字符串内部,我们需要执行以下操作:
创建一个label
HTML 元素,并将一个input
HTML 元素作为子元素添加。
在input
HTML 元素中,将v-bind
指令定义为一个 JavaScript 对象,其中包含this.$attrs
的解构值。
将变量属性value
定义为接收到的 prop 的modelValue
。
将input
属性type
设置为"text"
。
将匿名函数添加到change
事件监听器中,该函数接收一个event
作为参数,然后发出一个名为"update:modeValue"
的事件,载荷为event.target.value
:
template: ` <label>
<input
v-bind="{ ...$attrs, }"
:value="modelValue" type="text" @change="(event) => $emit('update:modelValue',
event.target.value)"
/> </label>`
- 创建一个名为
appComponent
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,其中包含两个属性,data
和template
:
const component = defineComponent({
data: () => ({}),
template: ``, });
- 在
data
属性中,将其定义为一个单例函数,返回一个具有名为name
的属性的 JavaScript 对象,其默认值为''
:
data: () => ({
name: '' }),
- 在模板属性中,在模板字符串中,我们需要执行以下操作:
创建一个NameInput
组件,其中v-model
指令绑定到name
数据属性。
创建一个带有值"border:0; border-bottom: 2px solid red;"
的style
属性。
创建一个带有值"name-input"
的data-test
属性:
template: ` <name-input
v-model="name" style="border:0; border-bottom: 2px solid red;"
data-test="name-input" />`
- 创建一个名为
app
的常量,并将其定义为createApp
函数,将component
常量作为参数传递。然后,调用app.component
函数,将要注册的组件的名称作为第一个参数传递,组件作为第二个参数传递。最后,调用app.mount
函数,将"#app"
作为参数传递:
const app = createApp(component); app.component('NameInput', nameInput); app.mount('#app');
工作原理...
在 Vue 3 中,为了创建一个组件,我们需要执行defineComponent
函数,传递一个 JavaScript 对象作为参数。这个对象保持了几乎与 Vue 2 相同的组件声明结构。在示例中,我们使用了相同的属性,data
,methods
,props
和template
,这些属性都存在于 V2 中。
我们使用inheritAttrs
属性来阻止将属性自动应用于组件上的所有元素,仅将其应用于具有v-bind
指令和解构this.$attrs
对象的元素。
要在 Vue 应用程序中注册组件,我们首先使用createApp
API 创建应用程序,然后执行app.component
函数在应用程序上全局注册组件,然后渲染我们的应用程序。
在 Vue 范围之外使用响应性和可观察 API
在 Vue 3 中,使用暴露的 API,我们可以在不需要创建 Vue 应用程序的情况下使用 Vue reactivity 和 reactive 变量。这使得后端和前端开发人员可以充分利用 Vue reactivity
API。
在这个示例中,我们将使用reactivity
和watch
API 创建一个简单的 JavaScript 动画。
如何做...
在这里,我们将使用 Vue 暴露的reactivity
API 创建一个应用程序,以在屏幕上呈现动画:
使用“创建基本文件”部分中的基本示例,创建一个名为reactivity.html
的新文件并打开它。
在<head>
标签中,添加一个新的<meta>
标签,属性为chartset
定义为"utf-8"
:
<meta charset="utf-8"/>
- 在
<body>
标签中,删除div#app
HTML 元素,并创建一个div
HTML 元素,id
定义为marathon
,style
属性定义为"font-size: 50px;"
:
<div
id="marathon"
style="font-size: 50px;" > </div>
- 在空的
<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,调用Vue
全局常量中的reactivity
和watch
方法:
const {
reactive,
watch, } = Vue;
- 创建一个名为
mod
的常量,定义为一个函数,接收两个参数a
和b
。然后返回一个算术运算,a
模b
:
const mod = (a, b) => (a % b);
- 创建一个名为
maxRoadLength
的常量,其值为50
。然后,创建一个名为competitor
的常量,其值为reactivity
函数,传递一个 JavaScript 对象作为参数,其中position
属性定义为0
,speed
定义为1
:
const maxRoadLength = 50; const competitor = reactive({
position: 0,
speed: 1, });
- 创建一个
watch
函数,传递一个匿名函数作为参数。在函数内部,执行以下操作:
创建一个名为street
的常量,并将其定义为一个大小为maxRoadLength
的Array
,并用*'_'*
*.*填充它。
创建一个名为marathonEl
的常量,并将其定义为 HTML DOM 节点#marathon
。
选择数组索引中competitor.position
的street
元素,并将其定义为*"![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/c8b07311-36a4-4df3-98fd-3b68200deed3.png)"*
,如果competitor.position
是偶数,或者如果数字是奇数,则定义为*"![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/562ed724-a630-4193-a9c6-4e143a9690e2.png)"*
。
将marathonEl.innertHTML
定义为*""*
和street.reverse().join('')
:
本示例中使用的表情符号是跑步的人和行走的人。表情符号图像可能因您的操作系统而异。本示例中呈现的图像是苹果操作系统的表情符号。
watch(() => {
const street = Array(maxRoadLength).fill('_');
const marathonEl = document.getElementById('marathon');
street[competitor.position] = (competitor.position % 2 === 1)
? '![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/c8b07311-36a4-4df3-98fd-3b68200deed3.png)'
: '![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/562ed724-a630-4193-a9c6-4e143a9690e2.png)'; marathonEl.innerHTML = '';
marathonEl.innerHTML = street.reverse().join(''); });
- 创建一个
setInterval
函数,将一个匿名函数作为参数传递。在函数内部,将competitor.position
定义为mod
函数,将competitor.position
加上competitor.speed
作为第一个参数,将maxRoadLength
作为第二个参数:
setInterval(() => {
competitor.position = mod(competitor.position +competitor.speed,
maxRoadLength) }, 100);
它是如何工作的...
使用 Vue 暴露的reactive
和watch
API,我们能够创建一个具有 Vue 框架中的响应性的应用程序,但不使用 Vue 应用程序。
首先,我们创建了一个响应式对象competitor
,它的工作方式与 Vue 的data
属性相同。然后,我们创建了一个watch
函数,它的工作方式与watch
属性相同,但是作为匿名函数使用。在watch
函数中,我们为竞争者开辟了一条跑道,并创建了一个简单的动画,使用两个不同的表情符号,根据在道路上的位置进行更改,以模拟屏幕上的动画。
最后,我们在屏幕上打印了当前的跑步者,并创建了一个setInterval
函数,每 100 毫秒改变竞争者在道路上的位置:
使用组合 API 创建组件
组合 API 是一种编写 Vue 组件的新方法,基于使用函数来组合组件,它使代码的组织和可重用性更好。
这种方法受到 React Hooks 的启发,并引入了创建特殊函数来组合应用程序的技术,这些函数可以在不需要在 Vue 应用程序内部的情况下共享,因为使用了暴露的 Vue API。
在这个示例中,我们将学习如何创建一个外部函数,用于获取用户的地理位置并在屏幕上显示这些数据,使用组合 API。
如何做...
在这里,我们将使用组合 API 创建一个组件,该组件将获取用户的 GPS 位置并在屏幕上显示该信息:
使用“创建基本文件”部分的基本示例,创建一个名为component.html
的新文件并打开它。
在空的<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,从Vue
全局常量中调用createApp
、defineComponent
、setup
、ref
、onMounted
和onUnmounted
方法:
const {
createApp,
defineComponent,
setup,
ref,
onMounted,
onUnmounted, } = Vue;
- 创建一个
fetchLocation
函数,在其中创建一个名为watcher
的let
变量。然后,创建一个名为geoLocation
的常量,并将其定义为navigator.geolocation
。接下来,创建一个名为gpsTime
的常量,并将其定义为ref
函数,将Date.now()
函数作为参数传递。最后,创建一个名为coordinates
的常量,并将其定义为ref
函数,将一个 JavaScript 对象作为参数传递,其中的属性accuracy
、latitude
、longitude
、altitude
、altitudeAccuracy
、heading
和speed
都定义为0
:
function fetchLocation() {
let watcher;
const geoLocation = navigator.geolocation;
const gpsTime = ref(Date.now());
const coordinates = ref({
accuracy: 0,
latitude: 0,
longitude: 0,
altitude: 0,
altitudeAccuracy: 0,
heading: 0,
speed: 0,
}); }
- 然后,在
fetchLocation
函数内部,在常量创建之后,创建一个名为setPosition
的函数,带有一个名为payload
的参数。在函数内部,将gpsTime.value
定义为payload.timestamp
参数,将coordinates.value
定义为payload.coords
参数:
function setPosition(payload) {
gpsTime.value = payload.timestamp
coordinates.value = payload.coords
}
- 在创建
setPosition
函数之后,调用onMounted
函数,将一个匿名函数作为参数传递。在函数内部,检查浏览器是否可用geoLocation
API,并将watcher
定义为geoLocation.watchPostion
函数,将setPosition
函数作为参数传递:
onMounted(() => {
if (geoLocation) watcher = geoLocation.watchPosition(setPosition); });
- 调用
onMounted
函数后,创建一个onUnmounted
函数,将一个匿名函数作为参数传递。在函数内部,检查watcher
是否已定义,然后执行geoLocation.clearWatch
函数,将watcher
作为参数传递:
onUnmounted(() => {
if (watcher) geoLocation.clearWatch(watcher); });
- 最后,在
fetchLocation
函数中,返回一个 JavaScript 对象,并将coordinates
和gpsTime
常量作为属性/值传递:
return {
coordinates,
gpsTime, };
- 创建一个名为
appComponent
的常量,并将其定义为defineComponent
函数,将一个具有setup
和template
属性的 JavaScript 对象作为参数传递:
const appComponent = defineComponent({
setup() {},
template: `` });
- 在
setup
函数中,创建一个常量,这是一个对象解构,包括fetchLocation
函数的coordinates
和gpsTime
属性:
setup() {
const {
coordinates,
gpsTime,
} = fetchLocation(); }
- 在
setup
函数内部,创建另一个名为formatOptions
的常量,并将其定义为一个具有year
、month
、day
、hour
和minute
属性的 JavaScript 对象,其值均为'numeric'
。然后,将属性hour12
定义为true
:
const formatOptions = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
};
- 在创建
formatOptions
常量之后,创建一个名为formatDate
的常量,并将其定义为一个函数,该函数接收一个名为date
的参数。然后,返回一个新的Intl.DateTimeFormat
函数,将navigator.language
作为第一个参数,将formatOption
常量作为第二个参数。然后,原型链连接format
函数,传递date
参数:
const formatDate = (date) => (new
Intl.DateTimeFormat(navigator.language,
formatOptions).format(date));
- 最后,在
setup
函数的末尾,返回一个 JavaScript 对象,其属性定义为coordinates
、gpsTime
和formatDate
常量:
return {
coordinates,
gpsTime,
formatDate };
- 在
template
属性中,进行以下操作:
创建一个带有文本“我的地理位置在{{ formatDate(new Date(gpsTime) }}”的h1
HTML 元素。
创建一个ul
HTML 元素,并添加三个li
HTML 元素作为子元素。
在第一个子元素中,添加文本“纬度:{{ coordinates.latitude }}”。
在第二个子元素中,添加文本“经度:{{ coordinates.longitude }}”。
在第三个子元素中,添加文本“海拔:{{ coordinates.altitude }}”:
template: `
<h1>My Geo Position at {{formatDate(new
Date(gpsTime))}}</h1>
<ul>
<li>Latitude: {{ coordinates.latitude }}</li>
<li>Longitude: {{ coordinates.longitude }}</li>
<li>Altitude: {{ coordinates.altitude }}</li>
</ul> `
- 最后,调用
createApp
函数,传递appComponent
常量作为参数。然后,原型链连接mount
函数,并将div
HTML 元素的id
属性("#app")
作为函数的参数:
createApp(appComponent)
.mount('#app');
它是如何工作的...
在这个示例中,首先我们导入了暴露的 API - createApp
、defineComponent
、setup
、ref
、onMounted
和onUnmounted
- 作为常量,我们将使用它们来创建组件。然后,我们创建了fetchLocation
函数,它负责获取用户的地理位置数据,并将其作为响应式数据返回,当用户更改位置时可以自动更新。
能够获取用户 GPS 位置是因为现代浏览器上存在的navigator.geolocation
API,它能够获取用户当前的 GPS 位置。利用浏览器提供的数据,我们能够用它来定义由 Vue ref
API 创建的变量。
我们使用 Vue 对象声明的setup
函数创建了组件,因此渲染知道我们正在使用新的组合 API 作为组件创建方法。在setup
函数内部,我们导入了fetchLocation
函数的动态变量,并创建了一个方法,用于在模板上使用日期格式化。
然后我们返回导入的变量和过滤器,以便它们可以在模板部分中使用。在模板部分中,我们创建了一个标题,添加了最后一次 GPS 位置的时间,使用过滤器进行格式化,并创建了用户的纬度、经度和海拔的列表。
最后,我们使用createApp
公开的 API 创建了应用程序,并挂载了 Vue 应用程序。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation
找到有关Navigator.geolocation
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。
第二章:介绍 TypeScript 和 Vue 生态系统
TypeScript 是一种基于 Vue 的新语言,在Vue 3上得到了充分支持。现在可以使用类型化的 JSX(也称为 TSX),类型注解,代码的静态验证等等。
Vue 生态系统每天都在变得更加庞大,为了帮助我们,Vue 团队开发了一些工具来改善项目处理和管理。这些工具是 Vue CLI 和 Vue UI,它们是本地 Vue 开发的主要工具。
Vue CLI 工具是每个项目的开始;通过它,你可以选择基本功能或者你之前创建的预设,来创建一个新的 Vue 项目。项目创建后,你可以使用 Vue UI 来管理项目,添加新功能,检查项目的状态,以及几乎可以在命令行界面(CLI)中做的所有事情,还有更多功能。
在这些章节中,你将更多地了解 TypeScript 作为 JavaScript 的扩展,以及如何使用 Vue CLI 工具和 Vue UI 一起来启动和运行整个应用程序。
在本章中,我们将涵盖以下内容:
创建一个 TypeScript 项目
理解 TypeScript
创建你的第一个 TypeScript 类
使用 Vue CLI 创建你的第一个项目
使用 Vue UI 向 Vue CLI 项目添加插件
将 TypeScript 添加到 Vue CLI 项目中
使用vue-class-component
创建你的第一个 TypeScript Vue 组件
使用vue-class-component
创建自定义 mixin
使用vue-class-component
创建自定义函数装饰器
将自定义钩子添加到vue-class-component
将vue-property-decorator
添加到vue-class-component
技术要求
在本章中,我们将使用Node.js,Vue CLI和TypeScript。
注意,Windows 用户需要安装一个名为windows-build-tools
的 npm 包,以便安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
。
要安装Vue CLI工具,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
要安装TypeScript,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),执行以下命令:
> npm install -g typescript
创建一个 TypeScript 项目
TypeScript 是 JavaScript 的类型扩展,在编译时会生成纯 JavaScript 代码。它看起来像是一种新语言,但最终还是 JavaScript。
使用 TypeScript 的优势是什么?主要优势在于类型化的语法,有助于静态检查和代码重构。您仍然可以使用所有 JavaScript 库,并且可以直接使用最新的 ECMAScript 功能进行编程。
编译后,TypeScript 将生成一个纯 JavaScript 文件,可以在任何浏览器、Node.js 或任何能够执行 ECMAScript 3 或更新版本的 JavaScript 引擎上运行。
准备工作
要启动我们的项目,我们需要创建一个npm
项目。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm init -y
您还需要安装 TypeScript,因此打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install typescript --only=dev
如何操作...
环境准备就绪后,我们需要启动我们的 TypeScript 项目。让我们创建一个.ts
文件并进行编译:
- 要启动我们的 TypeScript 项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> tsc --init
这将在我们的文件夹中创建一个tsconfig.json
文件。这是一个编译器设置文件。在这里,您可以定义目标,开发中可用的 JavaScript 库,目标 ECMAScript 版本,模块生成等等。
在为 Web 开发时,不要忘记在tsconfig.json
文件的compilerOption
属性中添加文档对象模型(DOM)库,这样在开发时就可以访问 window 和 document 对象。
- 现在,我们需要创建我们的
index.ts
文件。让我们在index.ts
文件中创建一些简单的代码,以便在终端中记录一个数学计算:
function sum(a: number, b: number): number {
return a + b;
}
const firstNumber: number = 10;
const secondNumber: number = 20;
console.log(sum(firstNumber, secondNumber));
这个函数接收两个参数,a
和b
,它们的类型都设置为number
,并且函数预计返回一个number
。我们创建了两个变量,firstNumber
和secondNumber
,在这种情况下都设置为number
类型——分别是10
和20
,因此,将它们传递给函数是有效的。如果我们将它们设置为其他类型,比如字符串、布尔值、浮点数或数组,编译器会在变量和函数执行的静态类型检查方面抛出错误。
- 现在,我们需要将这段代码编译成 JavaScript 文件。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> tsc ./index.ts
编译后,我们可以在index.js
中看到最终文件。如果我们查看文件内部,最终代码将类似于这样:
function sum(a, b) {
return a + b;
}
var firstNumber = 10;
var secondNumber = 20;
console.log(sum(firstNumber, secondNumber));
你可能会想:“我的类型在哪里?”由于 ECMAScript 是一种动态语言,TypeScript 的类型只存在于超集级别,并不会传递到 JavaScript 文件中。
你的最终 JavaScript 文件将以转译文件的形式存在,其中包含在tsconfig.json
文件中定义的配置。
它是如何工作的...
当我们创建 TypeScript 项目时,一个名为tsconfig.json
的文件会在我们的文件夹中创建。这个文件协调了编译器和开发过程中的静态类型检查的所有规则。所有的开发都基于这个文件中定义的规则。每个环境都依赖于需要导入的特定规则和库。
在开发过程中,我们可以直接为常量、变量、函数参数、返回值等分配类型。这些类型定义可以防止基本类型错误和代码重构。
开发完成并编译项目后,最终产品将是一个纯 JavaScript 文件。由于 JavaScript 的动态类型,这个文件不会有任何类型检查。
这个 JavaScript 文件被转译成目标模型,并在配置文件中定义,所以我们可以无问题地执行它。
另请参阅
您可以在www.typescriptlang.org/docs/home.html
找到有关 TypeScript 的更多信息。
有一个关于从 JavaScript 迁移的指南在www.typescriptlang.org/docs/handbook/migrating-from-javascript.html
。
可以在www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
找到一个关于 TypeScript 的 5 分钟课程。
了解 TypeScript
TypeScript 是一种基于类型的语言。它的很多功能来自于能够使用静态代码分析与 JavaScript。这得益于存在于 TypeScript 环境内的工具。
这些工具包括编译器,在开发过程中和编译后可以提供静态分析,以及 ECMAScript 转译器,可以使您的代码在几乎任何 JavaScript 引擎上运行。
让我们更多地了解这种语言,以及它是如何工作的。
准备好了吗?
首先,我们需要创建一个npm
项目。打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> npm init -y
您还需要安装 TypeScript,因此打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> npm install typescript --only=dev
类型
使用 TypeScript 的主要特性是类型。在本节中,我们将学习有关类型的知识,如何声明它们以及如何使用它们。
这些是静态类型语言中的一些基本类型:
字符串
数字
布尔
数组
元组
枚举
任意
空
对象
让我们谈谈其中一些类型,并展示它们在 TypeScript 中的使用方式。
字符串
JavaScript 中的所有文本数据都将被视为字符串。要声明一个字符串,我们总是需要用双引号(")
或单引号(')
括起来,或者用反引号(
)`,通常称为模板字符串。
在文本中声明模板字符串对 TypeScript 来说不是问题。模板字符串是 ECMAScript 中的一个功能,它使得可以在字符串中添加变量而无需进行连接:
const myText: string = 'My Simple Text';
const myTextAgain: string = "My Simple Text";
const greeting: string = `Welcome back ${myName}!`;
数字
在 JavaScript 中,所有数字都是浮点值。在 TypeScript 中也是如此。这些数字得到了数字类型。除了十六进制和十进制数字外,ECMAScript 2015 中引入的二进制和八进制字面量也被视为数字:
const myAge: number = 31;
const hexNumber: number = 0xf010d;
const binaryNumber: number = 0b1011;
const octalNumber: number = 0o744;
布尔
编程语言中最基本的类型是布尔值——简单的 1 或 0,true 或 false。这被称为布尔:
const isTaskDone: boolean = false;
const isGreaterThen: boolean = 10 > 5;
数组
大多数语言中的一组元素通常被称为数组。在 TypeScript 中,我们可以以两种不同的方式声明它。
最简单的方法就是声明元素的类型,后面跟着[]
(方括号)来表示它是一个声明类型的数组:
const primeNumbers: number[] = [1, 3, 5, 7, 11];
或者,您可以使用Array<type>
声明进行通用声明。这不是最常用的方式,但根据您正在开发的代码,您可能需要使用它:
const switchInstructions: Array<boolean> = [true, false, false, true];
元组
元组是一种具有特定结构的变量类型。在结构上,元组是一个包含两个元素的数组;这两个元素由编译器和用户知道其类型,但这些元素不需要具有相同的类型:
let person: [string, number];
person = ['Heitor', 31];
console.log(`My name is ${person[0]} and I am ${person[1]} years old`);
如果尝试访问已知索引之外的元素,将会收到错误。
枚举
枚举类似于 JavaScript 对象,但它们具有一些特殊的属性,可以帮助开发应用程序。您可以为数字值设置友好的名称,或者为函数可以接受的变量的常量提供更受控制的环境。
可以创建一个数字枚举而不需要任何声明。通过这样做,它将从0
的初始值开始,并以最终索引号的值结束;或者,您可以通过传递枚举值的索引来获取枚举的名称:
enum ErrorLevel {
Info,
Debug,
Warning,
Error,
Critical,
}
console.log(ErrorLevel.Error); // 3
console.log(ErrorLevel[3]); // Error
或者,可以声明一个带有值的枚举。它可以是 TypeScript 编译器将解释其余元素作为第一个元素的增量,或者是一个单独的声明:
enum Color {
Red = '#FF0000',
Blue = '#0000FF',
Green = '#00FF00',
}
enum Languages {
JavaScript = 1,
PHP,
Python,
Java = 10,
Ruby,
Rust,
TypeScript,
}
console.log(Color.Red) // '#FF0000'
console.log(Languages.TypeScript) // 13
任意
由于 JavaScript 是一种动态语言,TypeScript 需要实现一个没有定义值的类型,因此它实现了any类型。any 类型最常用的情况是在使用来自第三方库的值时。在这种情况下,我们知道我们正在放弃类型检查:
let maybeIs: any = 4;
maybeIs = 'a string?';
maybeIs = true;
任何类型的主要用途是当您将传统 JavaScript 项目升级到 TypeScript 时,您可以逐渐向变量和函数添加类型和验证。
空
与 any 相反,void是完全没有类型的。最常用的情况是在不返回任何值的函数中:
function logThis(str: string): void{
console.log(str);
}
使用 void 来对变量进行类型设置是没有意义的,因为它只能被赋值为 undefined 和 null。
对象
TypeScripts 中的对象有一种特殊的声明形式,因为它可以声明为接口,作为直接的对象,或者作为自己的类型。
将对象声明为接口时,您必须在使用之前声明接口,必须传递所有属性,并且需要设置类型:
interface IPerson {
name: string;
age: number;
}
const person: IPerson = {
name: 'Heitor',
age: 31,
};
在将对象作为直接输入传递给函数时,有时是常见的:
function greetingUser(user: {name: string, lastName: string}) {
console.log(`Hello, ${user.name} ${user.lastName}`);
}
最后,它们用于声明对象的类型并重用它:
type Person = {
name: string,
age: number,
};
const person: Person = {
name: 'Heitor',
age: 31,
};
console.log(`My name is ${person.name}, I am ${person.age} years old`);
函数
在 TypeScript 中,最难声明的类型之一是函数。它可以在一个简单的函数链的连接中变得非常复杂。
在 TypeScript 中声明函数是函数将接收的参数和函数将返回的最终类型的组合。
你可以在常量内声明一个简单的函数,就像这样:
const sumOfValues: (a:number, b:number): number = (a: number, b: number): number => a + b;
一个更复杂的函数在常量内声明可以像这样声明:
const complexFunction: (a: number) => (b:number) => number = (a: number): (b: number) => number => (b: number): number => a + b;
当声明一个函数作为普通函数时,其类型方式几乎与常量方式相同,但你不需要声明这些函数是一个函数。以下是一个例子:
function foo(a: number, b:number): number{
return a + b;
}
接口
TypeScript 检查变量的值是否是正确的类型,同样的原则也适用于类、对象或代码之间的合同。这通常被称为“鸭子类型”或“结构子类型”。接口存在是为了填补这个空间并定义这些合同或类型。
让我们尝试通过这个例子来理解一个接口:
function greetingStudent(student: {name: string}){
console.log(`Hello ${student.name}`);
}
const newStudent = {name: 'Heitor'};
greetingStudent(newStudent);
这个函数将知道对象上有一个名为 name 的属性,并且可以调用它是有效的。
我们可以使用接口类型来重写它,以便更好地管理代码:
interface IStudent {
name: string;
course?: string;
readonly university: string;
}
function greetingStudent(student: IStudent){
console.log(`Hello ${student.name}`);
if(student.course){
console.log(`Welcome to the ${student.course}` semester`);
}
}
const newStudent: IStudent = { name: 'Heitor', university: 'UDF' };
greetingStudent(newStudent);
正如你所看到的,我们有一个新的属性叫做course
,在它上面声明了一个?
。这表示这个属性可以是 null 或 undefined。这被称为可选属性。
有一个声明为只读属性的属性。如果我们在变量创建后尝试更改它,我们将收到一个编译错误,因为它使属性变为只读。
装饰器
ECMAScript 6 引入了一个新特性——类。随着这些特性的引入,装饰器的使用也成为了 JavaScript 引擎上的可能。
装饰器提供了一种在类声明及其成员上添加注解和元编程语法的方式。由于它已经在 TC-39 委员会(其中TC代表技术委员会)上处于最终批准状态,TypeScript 编译器已经可以使用它。
要启用它,你可以在tsconfig.json
文件中设置标志:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
装饰器是一种特殊的声明,可以附加到类、方法、存取器属性或参数上。它们以@expression
的形式使用,其中表达式是一个在运行时将被调用的函数。
一个可以应用于类的装饰器的例子可以在以下代码片段中看到:
function classSeal(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
当你创建这个函数时,你是在说构造函数的对象和它的原型将被封闭。
在类内部使用它非常简单:
@classSeal
class Animal {
sound: string;
constructor(sound: string) {
this.sound = sound;
}
emitSound() {
return "The animal says, " + this.sound;
}
}
这些只是一些装饰器及其功能的例子,可以帮助你使用 TypeScript 进行面向对象编程(OOP)的开发。
总结
总之,类型只是在使用 TypeScript 和 JavaScript 进行开发过程中让我们的生活变得更加轻松的一种方式。
因为 JavaScript 是一种动态语言,没有静态类型,TypeScript 中声明的所有类型和接口都严格地只被 TypeScript 使用。这有助于编译器捕捉错误、警告,并且语言服务器可以帮助集成开发环境(IDE)在开发过程中分析你的代码。
这是 TypeScript 的基本介绍,涵盖了有关这种类型语言的基础知识,以及如何理解和使用它。关于它的使用还有很多要学习,比如泛型、模块、命名空间等等。
通过这个介绍,你可以了解新的Vue 3核心是如何工作的,以及如何在项目中使用 TypeScript 的基础知识,并利用项目中的类型语言。
关于 TypeScript,总是有更多的知识可以获取,因为它是建立在 JavaScript 之上的一种不断发展的“语言”,并且拥有一个不断增长的社区。
不要忘记查看 TypeScript 文档,以了解更多信息,以及它如何从现在开始改进你的代码。
另请参阅
你可以在www.typescriptlang.org/docs/handbook/basic-types.html
找到有关 TypeScript 基本类型的更多信息。
你可以在www.typescriptlang.org/docs/handbook/functions.html
找到有关 TypeScript 函数的更多信息。
你可以在www.typescriptlang.org/docs/handbook/enums.html
找到有关 TypeScript 枚举的更多信息。
你可以在www.typescriptlang.org/docs/handbook/advanced-types.html
找到有关 TypeScript 高级类型的更多信息。
你可以在www.typescriptlang.org/docs/handbook/decorators.html
找到有关 TypeScript 装饰器的更多信息。
在rmolinamir.github.io/typescript-cheatsheet/#types
上查看 TypeScript 类型的速查表。
创建你的第一个 TypeScript 类
在 TypeScript 中,没有一种主要的范式来编写程序。你可以选择面向对象的、结构化的,甚至是函数式的。
在大多数情况下,我们会看到使用面向对象编程范例。在这个配方中,我们将学习如何在 TypeScript 中创建一个类,它的继承,接口,以及代码中可以使用的其他属性。
做好准备
要开始我们的项目,我们需要创建一个npm
项目。要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm init -y
您还需要安装 TypeScript**。**要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install typescript --only=dev
如何做到这一点...
在 TypeScript 文件中编写类时,我们首先需要考虑这个类将做什么,这个类可以是什么,它如何通过继承被另一个类扩展,以及它如何在这个过程中受到影响。
想象一下,我们有一个基本的Animal
类。这个类可以有一些基本属性,比如它的name
,它是否发出sound
,它的family
,以及这种动物所吃的基本food chain
。
- 让我们从过程的基础开始,
food chain
。我们需要确保它是一个不可枚举的列表,并且每个使用它的文件最终都有相同的值。我们只需要调用一个常量变量:
export enum FoodChainType {
Carnivorous = 'carnivorous',
Herbivorous = 'herbivorous',
Omnivorous = 'omnivorous',
}
- 现在,我们想为我们的动物制作基本的
interface
。我们知道我们的动物有一个name
,可以发出一个sound
,可以成为一个family
的一部分,并且属于food chain
类别。在类中使用接口,我们在类和将要暴露的内容之间建立了一个合同,有助于开发过程:
interface IAnimal {
name: string;
sound?: string;
family: string;
foodChainType: FoodChainType;
}
- 有了这一切,我们可以制作我们的
Animal
类。每个类都可以有它的构造函数。类构造函数可以很简单,只包含一些变量作为参数,也可以更复杂,有一个对象作为参数。如果你的构造函数将有任何参数,需要一个接口或声明每个参数的类型。在这种情况下,我们的构造函数将是一个对象,只有一个参数,与Animal
相同,所以它将扩展IAnimal
接口:
interface IAnimalConstructor extends IAnimal {
}
- 现在,为了创建我们的类,我们声明了将要使用的接口和枚举。我们将首先声明该类将实现
IBasicAnimal
接口。为此,我们需要添加一些我们的类将具有的公共元素,并也声明这些元素。我们需要实现函数来显示它是什么动物以及它发出什么声音。现在,我们有了一个包含所有动物属性的基本类。它具有类和构造函数的单独接口。食物链的枚举以一种易于阅读的方式声明,因此该库的 JavaScript 导入可以无问题执行:
interface IBasicAnimal extends IAnimal {
whoAmI: () => void;
makeSound: () => void;
}
export class Animal implements IBasicAnimal {
public name: string;
public sound: string;
public family: string;
public foodChainType: FoodChainType;
constructor(params: IAnimalConstructor) {
this.name = params.name;
this.sound = params.sound || '';
this.family = params.family;
this.foodChainType = params.foodChainType;
}
public whoAmI(): void {
console.log(`I am a ${this.name}, my family is ${this.family}.
My diet is ${this.foodChainType}.`);
if (this.sound) {
console.log([...Array(2).fill(this.sound)].join(', '));
}
}
public makeSound(): void {
console.log(this.sound);
}
}
- 让我们用几行代码扩展这个类,将这个
Animal
转换成Dog
:
import {Animal, FoodChainType} from './Animal';
class Dog extends Animal {
constructor() {
super({
name: 'Dog',
sound: 'Wof!',
family: 'Canidae',
foodChainType: FoodChainType.Carnivorous,
});
}n
}
这是一种简单的方式,通过扩展父类并使用父类的子类定义来组成一个几乎与父类具有相同接口的新类。
它是如何工作的...
TypeScript 中的类与 Java 或 C#等语言中的其他类一样工作。编译器在开发和编译过程中评估这些共同的原则。
在这种情况下,我们创建了一个简单的类,其中有一些公共属性是子类固有的。这些变量都是可读的,可以被改变。
还有更多...
在 TypeScript 中,我们有各种可能的类的用法,比如抽象类、特殊修饰符,以及将类用作接口。我们在这里只是涵盖了类的基础知识,为我们提供了一个良好的起点。如果你想深入了解,TypeScript 文档非常有帮助,并且有很多例子可以帮助学习过程。
另请参阅
您可以在www.typescriptlang.org/docs/handbook/classes.html
找到有关 TypeScript 类的更多信息。
在rmolinamir.github.io/typescript-cheatsheet/#classes
上查看 TypeScript 类的速查表。
使用 Vue CLI 创建您的第一个项目
当 Vue 团队意识到开发人员在创建和管理他们的应用程序时遇到问题时,他们看到了一个机会,可以创建一个工具来帮助全世界的开发人员。Vue CLI 项目诞生了。
Vue CLI 工具是一个在终端命令中使用的 CLI 工具,如 Windows PowerShell、Linux Bash 或 macOS Terminal。它被创建为 Vue 开发的起点,开发人员可以开始一个项目并顺利地管理和构建它。Vue CLI 团队在开发时的重点是为开发人员提供更多时间来思考代码,花费更少的时间在工具上,以将他们的代码投入生产,添加新的插件或简单的热模块重载
。
Vue CLI 工具被调整得无需在将其投入生产之前将您的工具代码弹出 CLI 之外。
当版本 3 发布时,Vue UI 项目被添加到 CLI 作为主要功能,将 CLI 命令转换为更完整的可视解决方案,并增加了许多新的功能和改进。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做…
要创建 Vue CLI 项目,请按照以下步骤进行:
- 我们需要打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> vue create my-first-project
- CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,Enter键继续,Spacebar键选择选项:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
有两种方法可以启动一个新项目。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有手动
模式,您可以选择更多的模式、插件、linters 和选项。我们将选择手动
。
现在,我们被问及我们将在项目中需要的功能。这些功能是一些 Vue 插件,如 Vuex 或 Router(Vue-Router)、测试工具、linters 等:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex CSS Pre-processors ❯ Linter / Formatter
Unit Testing ❯ **E2E Testing**
- 对于这个项目,我们将选择
CSS 预处理器
并按Enter继续:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ **Linter / Formatter**
Unit Testing **E2E Testing**
- 可以选择要与 Vue 一起使用的主要层叠样式表(CSS)预处理器——
Sass
、Less
和Stylus
。由您选择哪种最适合您:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) **Less** ❯ Stylus
- 现在是格式化您的代码的时候了。您可以在
AirBnB
、Standard
和Prettier
之间进行选择,并使用基本配置。那些在ESLint
中导入的规则可以随时进行自定义,没有任何问题,并且有一个完美的规则适合您的需求。您知道什么对您最好:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 设置完 linting 规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时进行修复:
? Pick additional lint features: (Use arrow keys) **Lint on save** ❯ Lint and fix on commit
- 在定义所有这些插件、linters 和处理器之后,我们需要选择设置和配置存储的位置。存储它们的最佳位置是在一个专用文件中,但也可以将它们存储在
package.json
文件中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files** In package.json
- 现在,您可以选择是否要将此选择设置为将来项目的预设,以便您无需再次重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
- CLI 将自动创建具有您在第一步中设置的名称的文件夹,安装所有内容并配置项目。
您现在可以浏览和运行项目了。Vue CLI 项目的基本命令如下:
npm run serve
—用于在本地运行开发服务器
npm run build
—用于构建和缩小应用程序以进行部署
npm run lint
—对代码执行 lint
您可以通过 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)执行这些命令。
还有更多...
CLI 内部有一个名为 Vue UI 的工具,可帮助管理 Vue 项目的过程。该工具将处理项目的依赖关系、插件和配置。
Vue UI 工具中的每个npm
脚本都被命名为任务,在这些任务中,您可以获得实时统计数据,例如资产、模块和依赖项的大小;错误或警告的数量;以及更深入的网络数据,以微调您的应用程序。
要进入 Vue UI 界面,您需要在 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)中执行以下命令:
> vue ui
另请参阅
在cli.vuejs.org/guide/
找到有关 Vue CLI 项目的更多信息。
请在cli.vuejs.org/dev-guide/plugin-dev.html
找到有关 Vue CLI 插件开发的更多信息。
使用 Vue UI 向 Vue CLI 项目添加插件
Vue UI 工具是 Vue 开发中最强大的附加工具之一。它可以让开发人员的生活更轻松,同时可以帮助管理 Vue 项目。
准备就绪
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
操作步骤...
首先,我们需要创建我们的 Vue CLI 项目。要找到如何创建 Vue CLI 项目,请查看“使用 Vue CLI 创建您的第一个项目”食谱。我们可以使用上一个食谱中创建的项目,也可以开始一个新项目。现在,按照说明添加插件:
- 打开 Vue UI 界面。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue ui
- 将会出现一个新的浏览器窗口,其中包含Vue UI界面:
在这里,您可以列出您的项目,创建一个新项目,或导入一个现有项目。
- 现在,我们将导入我们创建的插件:
您需要找到您创建的文件夹,然后单击“导入此文件夹”。
- 文件夹导入后,项目的默认仪表板将出现:
在这里,可以通过点击顶部的“自定义”按钮来自定义您的仪表板,添加新的小部件:
- 要添加新插件,必须单击左侧边栏中的“插件”菜单:
您在 Vue CLI 工具中添加的基本插件将已列在此处。
- 现在,我们将添加基本的 Vue 生态系统插件—**vuex **和 vue-router:
- 如果您检查您的代码,您将看到
main.js
文件已更改,并且vuex(store)
和vue-router(router)
插件现在已导入并注入到 Vue 实例中:
工作原理...
Vue UI 插件与npm
或yarn
配合使用,自动安装项目中的软件包,然后在可能的情况下注入 Vue 实例所需的条件。
如果插件是一个可视化、指令或非直接实例化的插件,Vue UI 将安装并管理它,但您需要导入它以在应用程序中使用。
向 Vue CLI 项目添加 TypeScript
在 JavaScript 项目中使用 TypeScript,即使是用于静态类型检查,也是一个好的做法。它有助于最小化项目内部的错误和对象问题。
通过 Vue UI 的帮助向 Vue 项目添加 TypeScript 非常简单,您将能够使用 TypeScript 的 JavaScript 代码。
准备就绪
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下
@vue/cli
@vue/cli-service-global
如何做...
首先,我们需要创建我们的 Vue CLI 项目。要了解如何创建 Vue CLI 项目,请查看“使用 Vue CLI 创建您的第一个项目”食谱。我们可以使用上一个食谱中创建的项目,或者开始一个新项目。
要将 TypeScript 添加到 Vue CLI 项目中,请按照以下步骤进行:
- 打开 Vue UI 界面。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue ui
- 在您的项目中,转到插件管理器,点击+添加插件,然后搜索
@vue/cli-plugin-typescript
:
- 现在,点击页面底部的安装@vue/cli-plugin-typescript 按钮:
- 在插件下载完成之后,在最终安装之前,会要求您进行一些配置设置:
**使用类样式组件语法?**使用 TypeScript 的vue-class-component
插件。
**与 TypeScript 一起使用 Babel(现代模式所需,自动检测的 polyfill,转译 JSX)?**激活 Babel 以在 TypeScript 编译器之外转译 TypeScript。
**使用 ESLint?**将 ESLint 用作.ts
和.tsx
文件的检查器。
**将所有.js 文件转换为.ts 文件?**在安装过程中自动将所有.js
文件转换为.ts
文件。
**允许编译.js 文件?**激活tsconfig.json
标志以接受编译器中的.js
文件。
选择您的选项后,点击完成安装。
现在,您的项目是一个 TypeScript Vue 项目,所有文件都已配置好,准备好进行编码:
它是如何工作的...
Vue UI 作为插件管理器将为您下载为 Vue 制作的 TypeScript 包,并安装和配置它以符合您选择的设置。
您的项目将根据您的规格进行更改和修改,然后准备好进行开发。
另请参阅
在github.com/typescript-eslint/typescript-eslint
找到有关 TypeScript ESLint 的更多信息
在github.com/vuejs/vue-class-component
找到有关vue-class-component
的更多信息。
使用 vue-class-component 创建您的第一个 TypeScript Vue 组件
由于 Vue 组件是基于对象的,并且与 JavaScript 对象的this
关键字有着密切的关系,因此开发 TypeScript 组件会有点混乱。
vue-class-component
插件使用 ECMAScript 装饰器提案将静态类型的值直接传递给 Vue 组件,并使编译器更容易理解发生了什么。
准备工作
这个配方的前提条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
首先,我们需要创建我们的 Vue CLI 项目。我们可以使用上一个配方中创建的项目,或者开始一个新的项目。要了解如何在 Vue CLI 项目中添加 TypeScript,请查看'向 Vue CLI 项目添加 TypeScript'配方。
按照说明使用 Typescript 和vue-class-component
创建你的第一个 Vue 组件:
在src/components
文件夹内创建一个名为Counter.vue
的新文件。
现在,让我们开始制作 Vue 组件的脚本部分。我们将创建一个包含数字数据的类,两个方法——一个用于增加,另一个用于减少——最后,一个计算属性来格式化最终数据:
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class Counter extends Vue {
valueNumber: number = 0;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
increase() {
this.valueNumber += 1;
}
decrease() {
this.valueNumber -= 1;
}
}
</script>
- 现在是时候为这个组件创建模板和渲染了。这个过程与 JavaScript Vue 文件相同。我们将添加增加和减少数值的按钮,并显示格式化的文本:
<template>
<div>
<fieldset>
<legend>{{ formattedNumber }}</legend>
<button @click="increase">Increase</button>
<button @click="decrease">Decrease</button>
</fieldset>
</div>
</template>
- 在
App.vue
文件中,我们需要导入刚刚创建的组件:
<template>
<div id="app">
<Counter />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Counter from './components/Counter.vue';
@Component({
components: {
Counter,
},
})
export default class App extends Vue {
}
</script>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
- 现在,当你在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)上运行
npm run serve
命令时,你将看到你的组件在屏幕上运行和执行:
工作原理...
vue-class-component
插件利用装饰器的新提案来向 TypeScript 类注入和传递一些属性。
这种注入有助于简化使用与 Vue 常见对象相比更符合 TypeScript 语法的组件开发过程。
另请参阅
在github.com/vuejs/vue-class-component
找到更多关于vue-class-component
的信息。
使用 vue-class-component 创建自定义 mixin
在 Vue 中,mixin
是一种在其他 Vue 对象中重用相同代码的方式,就像将mixin
的所有属性混合到组件中一样。
在使用 mixin 时,Vue 首先声明mixin
属性,然后是组件值,因此组件始终是最后且有效的值。此合并以深度模式进行,并且已在框架内声明了特定的方式,但可以通过特殊配置进行更改。
通过使用 mixin,开发人员可以编写小段的代码并在许多组件中重用它们。
这种方法简化了您的工作,并允许您更快地完成任务。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
首先,我们需要创建我们的 Vue CLI 项目。我们可以使用上一个食谱中创建的项目,或者开始一个新项目。要了解如何使用 TypeScript 创建 Vue CLI 项目,请查看'使用 vue-class-component 创建您的第一个 TypeScript Vue 组件'食谱。
在这个食谱中,我们将其分为两个独立的部分。首先,我们将创建计数器组件,然后我们将使用共享的代码来创建 mixin。
创建计数器组件
现在,按照以下说明使用vue-class-component
创建自定义 mixin:
我们需要在src/components
文件夹中创建一个名为CounterByTen.vue
的新组件。
现在,让我们开始制作 Vue 组件的脚本部分。我们将创建一个类,其中将有一个类型为数字的变量和默认值为0
;两种方法,一种是增加10
,另一种是减少10
;最后,一个计算属性来格式化最终数据:
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class CounterByTen extends Vue {
valueNumber: number = 0;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
increase() {
this.valueNumber += 10;
}
decrease() {
this.valueNumber -= 10;
}
}
</script>
- 是时候为这个组件创建模板和渲染了。该过程与 JavaScript Vue 文件相同。我们将添加增加和减少值的按钮以及显示格式化文本的按钮:
<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase By Ten</button>
<button @click="decrease">Decrease By Ten</button>
</fieldset>
</div>
</template>
- 在
App.vue
文件中,我们需要导入刚刚创建的组件:
<template>
<div id="app">
<Counter />
<hr />
<CounterByTen />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Counter from './components/Counter.vue';
import CounterByTen from './components/CounterByTen.vue';
@Component({
components: {
Counter,
CounterByTen,
},
})
export default class App extends Vue {
}
</script>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
提取相似的代码以用于 mixin
由于这两个组件具有相似的代码,我们可以提取这些相似的代码并创建一个 mixin。这个 mixin 可以在这两个组件中导入,它们的行为将是相同的:
在src/mixins
文件夹中创建一个名为defaultNumber.ts
的文件。
为了编写我们的 mixin,我们将从vue-class-component
插件中导入Component
和Vue
修饰符,作为 mixin 的基础。我们需要采用类似的代码并将其放入 mixin 中:
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class DefaultNumber extends Vue {
valueNumber: number = 0;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
}
- 准备好 mixin 后,打开
src/components
文件夹中的Counter.vue
组件并导入它。为此,我们需要从vue-class-component
中导入一个特殊的导出,称为mixins
,并将其与我们想要扩展的 mixin 扩展。这将删除Vue
和Component
装饰器,因为它们已经在 mixin 上声明了:
<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase By Ten</button>
<button @click="decrease">Decrease By Ten</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component, { mixins } from 'vue-class-component';
import DefaultNumber from '../mixins/defaultNumber';
@Component
export default class CounterByTen extends mixins(DefaultNumber) {
increase() {
this.valueNumber += 10;
}
decrease() {
this.valueNumber -= 10;
}
}
</script>
- 现在,当您在终端(macOS 或 Linux)上运行
npm run serve
命令或在命令提示符/PowerShell(Windows)上运行时,您将看到您的组件在屏幕上运行和执行:
它是如何工作的...
使用 TypeScript 使用 mixins 的过程与使用 Vue 对象的过程相同。共享的代码可以拆分成更小的文件,并在组件中调用,以便更轻松地编码。
在使用 TypeScript 和vue-class-component
时,需要在 mixins 上声明Vue
和Component
装饰器,因为将使用 mixin 的类已经具有此扩展,因为它扩展了此 mixin。
我们将相同的代码片段放在两个组件中,然后将其放在一个新文件中,然后在两个组件中调用它。
另请参阅
了解有关vue-class-component
mixins 的更多信息,请访问github.com/vuejs/vue-class-component#using-mixins
。
了解有关 Vue mixins 的更多信息,请访问v3.vuejs.org/guide/mixins.html
使用 vue-class-component 创建自定义函数装饰器
装饰器是在 ECMAScript 2015 中引入的。装饰器是一种高阶函数,它用另一个函数包装一个函数。
这为代码带来了许多新的改进,以及更大的生产力,因为它采用了函数式编程的原则并简化了它。
准备工作
此处的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
首先,我们需要创建我们的 Vue CLI 项目。要了解如何创建 Vue CLI 项目,请查看“使用 Vue CLI 创建您的第一个项目”食谱。我们可以使用上一个食谱中创建的项目,或者开始一个新项目。
按照以下步骤使用vue-class-component
创建自定义函数装饰器:
在src/decorators
文件夹中创建一个名为componentMount.js
的文件。
我们需要从vue-class-component
中导入createDecorator
函数,以便在基于vue-class-component
的组件上使用它,并开始编写我们的装饰器:
import { createDecorator } from 'vue-class-component';
import componentMountLogger from './componentLogger';
export default createDecorator((options) => {
options.mixins = [...options.mixins, componentMountLogger];
});
createDecorator
函数就像 Vue vm *(View-Model)*的扩展,因此它不会有 ECMAScript 装饰器的属性,但会作为 Vue 装饰器的功能。
- 我们需要在我们的装饰器中使用
componentLogger.js
文件。这个函数将获取在“装饰”组件中设置的所有数据值,并对其添加一个监视器。每当它改变时,这个监视器将记录新值和旧值。这个函数只有在调试数据设置为true
时才会执行:
export default {
mounted() {
if (this.debug) {
const componentName = this.name || '';
console.log(`The ${componentName} was mounted
successfully.`);
const dataKeys = Object.keys(this.$data);
if (dataKeys.length) {
console.log('The base data are:');
console.table(dataKeys);
dataKeys.forEach((key) => {
this.$watch(key, (newValue, oldValue) => {
console.log(`The new value for ${key} is:
${newValue}`);
console.log(`The old value for ${key} is:
${oldValue}`);
}, {
deep: true,
});
});
}
}
},
};
- 现在,我们需要将装饰器导入到位于
src/components
文件夹中的Counter.vue
组件文件中,并向其添加调试器数据:
<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase</button>
<button@click="decrease">Decrease</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
import componentMount from '../decorators/componentMount';
@Component
@componentMount
export default class Counter extends Vue {
valueNumber: number = 0;
debug: boolean = true;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
increase() {
this.valueNumber += 1;
}
decrease() {
this.valueNumber -= 1;
}
}
</script>
它是如何工作的...
createDecorator
函数是一个工厂函数,它扩展了 Vue vm(View Model),产生了 Vue 组件的扩展,比如一个 Vue mixin。Vue mixin 是 Vue 组件的一个属性,可以用来在组件之间共享和重用代码。
当我们调用 mixin 时,它将当前组件作为第一个参数的选项(如果它附加到属性,则为键),以及它的索引。
我们添加了一个动态调试器,只有在存在调试数据并且设置为true
时才会附加。这个调试器将记录当前数据,并为数据的更改设置监视器,每次数据更改时都会在控制台上显示日志。
还有更多...
在使用 linters 时,一些规则可能会成为装饰器的问题。因此,明智的做法是仅在出现规则问题的文件上禁用它们,这些规则是代码正常工作所必需的。
例如,在 AirBnB 风格中,no-param-reassign
规则是必需的,因为装饰器使用选项作为传递值的引用。
另请参阅
在github.com/vuejs/vue-class-component#create-custom-decorators
找到有关使用vue-class-component
创建自定义装饰器的更多信息。
在www.typescriptlang.org/docs/handbook/decorators.html
找到有关 ECMAScript 装饰器的更多信息。
向 vue-class-component 添加自定义钩子
在 Vue 中,可以通过插件**应用程序编程接口(API)**向其生命周期添加钩子。最基本的例子是vue-router
与导航守卫,例如beforeRouterEnter
和beforeRouterLeave
函数钩子。
钩子,顾名思义,是每次发生某事时调用的小函数。
您可以利用这些钩子,使它们更加强大,为您的组件添加新的功能,例如检查特殊安全访问、添加搜索引擎优化(SEO)甚至预取数据。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
首先,我们需要创建我们的 Vue CLI 项目。我们可以使用上一个配方中创建的项目,也可以开始一个新项目。要了解如何在 Vue CLI 项目中添加 TypeScript,请查看'向 Vue CLI 项目添加 TypeScript'配方。
现在,按照以下步骤,使用 TypeScript 和vue-class-component
为您的 Vue 项目添加自定义钩子:
- 我们需要将
vue-router
添加到项目中。这可以在创建 Vue CLI 项目时完成,也可以在创建项目后的 Vue UI 界面中完成。
如果提示选择模式,应该运行vue-router
。请注意,选择History选项将在部署时需要特殊的服务器配置。
打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),执行npm run serve
命令,您将看到vue-router
正在工作,并且有两个工作路由器:home
和about
。
让我们开始创建并命名我们的钩子以注册到主应用程序。为此,我们需要在src/classComponentsHooks
文件夹中创建一个vue-router.js
文件:
import Component from 'vue-class-component';
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
]);
- 我们需要将这个文件导入到
main.ts
文件中,因为它需要在应用程序最终构建之前被调用:
import './classComponentsHooks/vue-router';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app');
现在,我们已经在vue-class-component
中注册了这些钩子,并且它们可以在 TypeScript 组件中使用。
我们需要在src/views
文件夹中创建一个名为Secure.vue
的新路由位置。安全页面将有一个输入密码,vuejs
。当用户输入此密码时,路由守卫将授予权限,用户可以看到页面。如果密码错误,用户将被带回到主页。当他们离开页面时,警报将向用户显示一条消息:
<template>
<div class="secure">
<h1>This is an secure page</h1>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Route, RawLocation } from 'vue-router';
type RouteNext = (to?: RawLocation | false | ((vm: Vue) => any) |
void) => void;
@Component
export default class Home extends Vue {
beforeRouteEnter(to: Route, from: Route, next: RouteNext) {
const securePassword = 'vuejs';
const userPassword = prompt('What is the password?');
if (userPassword === securePassword) {
next();
} else if (!userPassword) {
next('/');
}
}
beforeRouteLeave(to: Route, from: Route, next: RouteNext) {
alert('Bye!');
next();
}
}
</script>
- 现在我们的页面已经完成,我们需要将其添加到
router.ts
文件中,以便在 Vue 应用程序中调用它:
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: () => import('./views/About.vue'),
},
{
path: '/secure',
name: 'secure',
component: () => import('./views/Secure.vue'),
},
],
});
- 路由添加完成并创建视图后,最后一步是将链接添加到主
App.vue
文件中,这样我们就会得到一个集成了钩子的组件:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/secure">Secure</router-link>
</div>
<router-view/>
</div>
</template>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
#nav
padding 30px
a
font-weight bold
color #2c3e50
&.router-link-exact-active
color #42b983
</style>
它是如何工作的...
在执行 Vue 应用程序之前,类组件需要了解被添加到 Vue 原型的导航守卫是什么。因此,我们需要在main.ts
文件的第一行导入自定义钩子。
在组件中,通过注册钩子,可以将它们添加为方法,因为vue-class-component
已经将所有这些自定义导入转换为组件装饰器的基本方法。
我们使用了两个vue-router
导航守卫的钩子。这些钩子在每次路由进入或离开时都会被调用。我们没有使用的前两个参数to
和from
是携带有关未来路由和过去路由的信息的参数。
next
函数总是必需的,因为它执行路由更改。如果在函数中没有传递参数,路由将继续使用被调用的路由,但如果想要即时更改路由,可以传递参数来改变用户将要前往的位置。
另请参阅
在router.vuejs.org/guide/advanced/navigation-guards.html
中了解更多关于 vue-router 导航守卫的信息。
在github.com/vuejs/vue-class-component#adding-custom-hooks
中了解更多关于 vue-class-component 钩子的信息。
将 vue-property-decorator 添加到 vue-class-component
Vue 中一些最重要的部分在vue-class-component
中以 TypeScript 装饰器的形式缺失。因此,社区制作了一个名为vue-property-decorator
的库,这个库得到了 Vue 核心团队的全力支持。
这个库引入了一些缺失的部分,如 ECMAScript 提案装饰器,比如props
、watch
、model
、inject
等。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
首先,我们需要创建 Vue CLI 项目。我们可以使用上一个示例中创建的项目,也可以开始一个新项目。要了解如何使用 TypeScript 创建 Vue CLI 项目,请查看'使用 vue-class-component 创建自定义 mixin'示例。
按照以下步骤将vue-property-decorator
添加到 Vue基于类的组件
中:
- 我们需要将
vue-property-decorator
添加到我们的项目中。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install -S vue-property-decorator
- 在组件的 mixin 中,我们将添加一个装饰器来接收一个 prop,这将是我们计算的数字的值:
import {
Vue,
Component,
Prop,
} from 'vue-property-decorator';
@Component
export default class DefaultNumber extends Vue {
valueNumber: number = 0;
@Prop(Number) readonly value: number | undefined;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
}
- 有了这个数字,当值发生变化时,我们需要使观察者向父组件发出事件,并在父组件内部值发生变化时更新值。为此,我们需要在
src/mixins
文件夹内创建一个名为numberWatcher.ts
的新文件:
import {
Watch,
Mixins,
} from 'vue-property-decorator';
import DefaultNumber from './defaultNumber';
export default class NumberWatchers extends Mixins(DefaultNumber) {
@Watch('valueNumber')
onValueNumberChanged(val: number) {
this.$emit('input', val);
}
@Watch('value', { immediate: true })
onValueChanged(val: number) {
this.valueNumber = val;
}
}
在 Vue 中,v-model
指令的工作原理类似于糖语法,它是 Vue $emit
函数和 Vue props
函数的组合。当值发生变化时,组件需要使用'input'
名称进行$emit
,并且组件需要在props
函数中有一个value
键,这将是从父组件传递到子组件的值。
- 随着我们的 mixin 更新,我们的组件也需要更新。首先,我们将更新
Counter.vue
组件,将导入的 mixin 从defaultNumber.ts
文件更改为numberWatcher.ts
:
<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase</button>
<button @click="decrease">Decrease</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component, { mixins } from 'vue-class-component';
import NumberWatcher from '../mixins/numberWatcher';
@Component
export default class Counter extends mixins(NumberWatcher) {
increase() {
this.valueNumber += 1;
}
decrease() {
this.valueNumber -= 1;
}
}
</script>
- 现在,我们将更新
CounterByTen.vue
组件,并添加新创建的 mixin:
<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase By Ten</button>
<button @click="decrease">Decrease By Ten</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component, { mixins } from 'vue-class-component';
import NumberWatcher from '../mixins/numberWatcher';
@Component
export default class CounterByTen extends mixins(NumberWatcher) {
increase() {
this.valueNumber += 10;
}
decrease() {
this.valueNumber -= 10;
}
}
</script>
- 一切就绪后,我们只需要更新
App.vue
组件。这一次,我们将在组件中存储一个变量,该变量将传递给两个子组件,当组件发出更新事件时,此变量将自动更改,也会更新其他组件:
<template>
<div id="app">
<Counter
v-model="amount"
/>
<hr />
<CounterByTen
v-model="amount"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Counter from './components/Counter.vue';
import CounterByTen from './components/CounterByTen.vue';
@Component({
components: {
Counter,
CounterByTen,
},
})
export default class App extends Vue {
amount: number = 0;
}
</script>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
工作原理...
通过在vue-class-components
中注入装饰器,vue-property-decorator
帮助 TypeScript 编译器检查 Vue 代码的类型和静态分析。
我们使用了两个可用的装饰器,@Watch
和@Prop
装饰器。
当我们将代码的常见部分拆分成 mixin 的形式时,流程实现变得更加容易。
父组件向子组件传递了一个属性,传递了初始值和随后更新的值。
这个值在子组件内部进行检查和更新,用于更新计算函数使用的本地变量。当计算完成并且值发生变化时,watcher 会发出一个事件,传递给父组件,父组件更新主变量,循环继续。
还有更多...
还有另一个库,它与vue-property-decorator
相同,但用于vuex
插件,名为vuex-class
。
这个库使用与vue-property-decorator
相同的过程。它在组件中创建一个 inject 装饰器。这些装饰器帮助 TypeScript 编译器在开发过程中检查类型。
您可以在github.com/ktsn/vuex-class/
找到有关这个库的更多信息。
另请参阅
您可以在github.com/kaorun343/vue-property-decorator
找到有关vue-property-decorator
的更多信息。
第三章:数据绑定、表单验证、事件和计算属性
数据是当今世界上最宝贵的资产,知道如何管理它是必须的。在 Vue 中,我们有权利选择如何收集这些数据,按照我们的意愿进行操作,并将其传递到服务器。
在本章中,我们将更多地了解数据处理和数据处理过程、表单验证、数据过滤、如何向用户显示这些数据以及如何以与我们应用程序内部不同的方式呈现它。
我们将学习如何使用vue-devtools
深入了解 Vue 组件并查看我们的数据和应用程序发生了什么。
在本章中,我们将涵盖以下内容:
创建“hello world”组件
创建具有双向数据绑定的输入表单
向元素添加事件侦听器
从输入中删除 v-model
创建动态待办事项列表
创建计算属性并探索其工作原理
使用自定义过滤器显示更清晰的数据和文本
使用 Vuelidate 添加表单验证
为列表创建过滤器和排序器
创建条件过滤以对列表数据进行排序
添加自定义样式和过渡
使用vue-devtools
调试您的应用程序
技术要求
在本章中,我们将使用Node.js和Vue CLI。
注意,Windows 用户需要安装一个名为windows-build-tools
的npm
包,以便安装以下所需的软件包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
。
要安装Vue CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建“hello world”组件
Vue 应用程序是各种组件的组合,由 Vue 框架绑定在一起并进行编排。知道如何制作您的组件很重要。每个组件就像墙上的一块砖,需要以一种方式制作,以便在放置时不需要其他砖块以不同的方式重新塑造。我们将学习如何制作一个基本组件,其中包含一些侧重于组织和清晰代码的重要原则。
准备工作
本教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到这一点...
要启动我们的组件,我们可以使用 Vue CLI 创建我们的 Vue 项目,就像在“使用 Vue CLI 创建您的第一个项目”中学到的那样,或者开始一个新的项目。
要启动一个新的组件,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create my-component
命令行界面(CLI)将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。选择**default
**选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
让我们创建我们的第一个“hello world”组件,按照以下步骤进行:
让我们在src/components
文件夹中创建一个名为CurrentTime.vue
的新文件。
在这个文件中,我们将从组件的<template>
部分开始。它将是一个显示当前日期格式化的阴影框卡:
<template>
<div class='cardBox'>
<div class='container'>
<h2>Today is:</h2>
<h3>{{ getCurrentDate }}</h3>
</div>
</div>
</template>
- 现在,我们需要创建
<script>
部分。我们将从name
属性开始。这将在使用vue-devtools
调试我们的应用程序时使用,也有助于集成开发环境(IDE)。对于getCurrentDate
计算属性,我们将创建一个computed
属性,它将返回当前日期,由Intl
浏览器函数格式化:
<script>
export default {
name: 'CurrentTime',
computed: {
getCurrentDate() {
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(
browserLocale,
{
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
});
return intlDateTime.format(new Date());
}
}
};
</script>
- 为了为我们的框添加样式,我们需要在
src
文件夹中创建一个style.css
文件,然后将cardBox
样式添加到其中:
.cardBox {
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.2);
transition: 0.3s linear;
max-width: 33%;
border-radius: 3px;
margin: 20px;
}
.cardBox:hover {
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.2);
}
.cardBox>.container {
padding: 4px 18px;
}
[class*='col-'] {
display: inline-block;
}
@media only screen and (max-width: 600px) {
[class*='col-'] {
width: 100%;
}
.cardBox {
margin: 20px 0;
}
}
@media only screen and (min-width: 600px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
@media only screen and (min-width: 768px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
@media only screen and (min-width: 992px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
@media only screen and (min-width: 1200px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
- 在
App.vue
文件中,我们需要导入我们的组件才能看到它:
<template>
<div id='app'>
<current-time />
</div>
</template>
<script>
import CurrentTime from './components/CurrentTime.vue';
export default {
name: 'app',
components: {
CurrentTime
}
}
</script>
- 在
main.js
文件中,我们需要导入style.css
文件以包含在 Vue 应用程序中:
import Vue from 'vue';
import App from './App.vue';
import './style.css';
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve
这是您的组件呈现和运行的方式:
它是如何工作的...
Vue 组件几乎与 Node.js 包一样工作。要在代码中使用它,您需要导入组件,然后在要使用的组件的components
属性中声明它。
就像一堵砖墙一样,Vue 应用程序由调用和使用其他组件的组件组成。
对于我们的组件,我们使用了Intl.DateTimeFormat
函数,这是一个本机函数,可用于将日期格式化和解析为声明的位置。为了获得本地格式,我们使用了navigator
全局变量。
另请参见
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。
您可以在v3.vuejs.org/guide/single-file-component.html
找到有关 Vue 组件的更多信息
创建具有双向数据绑定的输入表单
为了收集网页上的数据,我们使用 HTML 表单输入。在 Vue 中,可以使用双向数据绑定方法,其中输入在文档对象模型(DOM)上的值传递到 JavaScript,反之亦然。
这使得 Web 表单更加动态,使您有可能在保存或发送数据回服务器之前管理、格式化和验证数据。
准备就绪
此处的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
操作步骤...
要启动我们的组件,我们可以使用 Vue CLI 创建 Vue 项目,就像在第二章的“使用 Vue CLI 创建第一个项目”中学到的那样,或者使用“创建'hello world'组件”中的项目。
现在,让我们按照以下步骤创建具有双向数据绑定的输入表单:
让我们在src/components
文件夹中创建一个名为TaskInput.vue
的新文件。
在这个文件中,我们将创建一个组件,其中将包含一个文本输入和一个显示文本。这个文本将基于文本输入中键入的内容。在组件的<template>
部分,我们需要创建一个 HTML 输入和一个mustache
变量,用于接收和呈现数据:
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is: {{ task }}</strong>
<input
type='text'
v-model='task'
class='taskInput' />
</div>
</div>
</template>
- 现在,在组件的
<script>
部分,我们将命名它并将任务添加到data
属性中。由于数据始终需要返回一个Object
,我们将使用箭头函数直接返回一个Object
:
<script>
export default {
name: 'TaskInput',
data: () => ({
task: '',
}),
};
</script>
- 我们需要为这个组件添加一些样式。在组件的
<style>
部分,我们需要添加scoped
属性,以便样式仅绑定到组件,不会与其他层叠样式表(CSS)规则混合:
<style scoped>
.tasker{
margin: 20px;
}
.tasker .taskInput {
font-size: 14px;
margin: 0 10px;
border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.75);
}
.tasker button {
border: 1px solid rgba(0, 0, 0, 0.75);
border-radius: 3px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
}
</style>
- 现在,我们需要将这个组件导入到我们的
App.vue
文件中:
<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' />
</div>
</template>
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'app',
components: {
CurrentTime,
TaskInput,
}
}
</script>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
当您创建一个 HTMLinput
元素并为其添加v-model
时,您正在传递一个内置于 Vue 中的指令,该指令检查输入类型并为我们提供输入的糖语法。这处理了变量值和 DOM 的更新。
这个模型被称为双向数据绑定。如果变量被代码改变,DOM 将重新渲染,如果它被 DOM 通过用户输入改变,比如input-form
,那么 JavaScript 代码就可以执行一个函数。
另请参阅
在v3.vuejs.org/guide/forms.html
找到有关表单输入绑定的更多信息
向元素添加事件监听器
在 Vue 中,父子通信最常见的方法是通过 props 和 events。在 JavaScript 中,通常会向 DOM 树的元素添加事件监听器,以便在特定事件上执行函数。在 Vue 中,可以添加监听器并根据需要命名,而不是坚持 JavaScript 引擎上存在的名称。
在这个配方中,我们将学习如何创建自定义事件以及如何发出它们。
准备就绪
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
启动我们的组件,我们可以使用 Vue CLI 创建 Vue 项目,就像在“使用 Vue CLI 创建您的第一个项目”一章中学到的那样,或者使用“使用双向数据绑定创建输入表单”一章中的项目。
按照以下步骤在 Vue 中的元素上添加事件监听器:
创建一个新组件或打开TaskInput.vue
文件。
在<template>
部分,我们将添加一个按钮元素,并使用v-on
指令为按钮点击事件添加事件监听器。我们将从组件中删除{{ task }}
变量,因为从现在开始它将被发出并不再显示在组件上:
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is:</strong>
<input
type='text'
v-model='task'
class='taskInput' />
<button
v-on:click='addTask'>
Add Task
</button>
</div>
</div>
</template>
- 在组件的
<script>
部分,我们需要添加一个处理点击事件的方法。该方法将被命名为addTask
。该方法将触发一个名为add-task
的事件,并将任务发送到数据中。之后,组件上的任务将被重置:
<script>
export default {
name: 'TaskInput',
data: () => ({
task: '',
}),
methods: {
addTask(){
this.$emit('add-task', this.task);
this.task = '';
},
}
};
</script>
- 在
App.vue
文件中,我们需要在组件上添加一个事件监听器绑定。此侦听器将附加到add-task
事件。我们将使用v-on
指令的缩写版本@
。当它被触发时,事件将调用addNewTask
方法,该方法将发送一个警报,说明已添加了一个新任务:
<template>
<div id='app'>
<current-time class='col-4' />
<task-input
class='col-6'
@add-task='addNewTask'
/>
</div>
</template>
- 现在,让我们创建
addNewTask
方法。这将接收任务作为参数,并向用户显示一个警报,显示任务已添加:
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'app',
components: {
CurrentTime,
TaskInput,
},
methods:{
addNewTask(task){
alert(`New task added: ${task}`);
},
},
}
</script>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
HTML 事件通过v-on
事件处理指令由 Vue 读取。当我们将v-on:click
指令附加到按钮时,我们向按钮添加了一个监听器,以便在用户单击按钮时执行一个函数。
该函数在组件方法中声明。该函数在调用时将发出一个事件,表示使用此组件作为子组件的任何组件都可以使用v-on
指令监听它。
另请参阅
您可以在v3.vuejs.org/guide/events.html
找到有关事件处理的更多信息
从输入中删除 v-model
如果我告诉您,在v-model
的魔术背后有很多代码,使我们的魔术糖语法发生?如果我告诉您,兔子洞可以深入到足以控制输入的事件和值发生的一切?
我们将学习如何提取v-model
指令的糖语法,并将其转换为其背后的基本语法。
准备就绪
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到...
要启动我们的组件,我们可以使用 Vue CLI 创建我们的 Vue 项目,就像在第二章*,介绍 TypeScript 和 Vue 生态系统*中学到的那样,或者使用“向元素添加事件侦听器”配方中的项目。
在接下来的步骤中,我们将从输入中删除v-model
指令的语法糖:
打开TaskInput.vue
文件。
在组件的<template>
块中,找到v-model
指令。我们将删除v-model
指令。然后,我们需要向输入添加一个新的绑定,称为v-bind:value
或缩写版本:value
,以及一个事件侦听器到 HTMLinput
元素。我们需要向input
事件添加一个事件侦听器,使用v-on:input
指令或缩写版本@input
。输入绑定将接收任务值作为参数,事件侦听器将接收一个值赋值,其中它将使任务变量等于事件值的值。
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is:</strong>
<input
type='text'
:value='task'
@input='task = $event.target.value'
class='taskInput'
/>
<button v-on:click='addTask'>
Add Task
</button>
</div>
</div>
</template>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
它是如何工作的...
作为一种语法糖,v-model
指令可以自动为您声明绑定和元素的事件侦听器,但副作用是您无法完全控制可以实现的内容。
正如我们所见,绑定的值可以是变量、方法、计算属性或 Vuex getter,例如。对于事件侦听器,它可以是一个函数或一个变量赋值的直接声明。当事件被触发并传递给 Vue 时,$event
变量用于传递事件。在这种情况下,就像在普通 JavaScript 中一样,要捕获输入的值,我们需要使用event.target.value
值。
另请参阅
您可以在v3.vuejs.org/guide/events.html
找到有关事件处理的更多信息
创建一个动态的待办事项列表
每个程序员在学习一种新语言时创建的第一个项目之一就是待办事项列表。这样做可以让我们更多地了解围绕状态和数据操作的语言过程。
我们将使用 Vue 制作我们的待办事项列表。我们将使用我们在之前的配方中学到和创建的内容。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
制作待办事项应用程序涉及一些基本原则——它必须有一个任务列表;这些任务可以标记为已完成和未完成,并且列表可以进行过滤和排序。现在,我们将学习如何将任务添加到任务列表中。
要启动我们的组件,我们可以使用 Vue CLI 创建 Vue 项目,就像在第二章*,介绍 TypeScript 和 Vue 生态系统*中学到的“使用 Vue CLI 创建你的第一个项目”一节中所述,或者使用“从输入中删除 v-model”一节中的项目。
现在,按照以下步骤使用 Vue 和之前的方法创建一个动态的待办事项列表:
- 在
App.vue
文件中,我们将创建我们的任务数组。每当TaskInput.vue
组件发出消息时,这个任务将被填充。我们将向这个数组添加一个带有任务和创建任务的当前日期的对象。目前,任务完成时的日期将是未定义的。为了做到这一点,在组件的<script>
部分,我们需要创建一个接收任务并将该任务与当前日期添加到taskList
数组中的方法:
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput,
},
data: () => ({
taskList: [],
}),
methods:{
addNewTask(task){
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined,
})
},
},
}
</script>
- 现在,我们需要在
<template>
部分呈现这个列表。我们将使用 Vue 的v-for
指令来迭代任务列表。当我们将这个指令与数组一起使用时,它会给我们访问两个属性——项目本身和项目的索引。我们将使用项目来呈现它,使用索引来创建元素的键以进行呈现。我们需要添加一个复选框,当选中时,调用一个函数来改变任务的状态和任务完成时的显示:
<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' @add-task='addNewTask' />
<div class='col-12'>
<div class='cardBox'>
<div class='container'>
<h2>My Tasks</h2>
<ul class='taskList'>
<li
v-for='(taskItem, index) in taskList'
:key='`${index}_${Math.random()}`'
>
<input type='checkbox'
:checked='!!taskItem.finishedAt'
@input='changeStatus(index)'
/>
{{ taskItem.task }}
<span v-if='taskItem.finishedAt'>
{{ taskItem.finishedAt }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
始终重要的是要记住迭代器中的键必须是唯一的。这是因为呈现函数需要知道哪些元素已经改变。在这个例子中,我们将Math.random()
函数添加到索引中以生成一个唯一的键,因为当元素的数量减少时,数组的前几个元素的索引始终是相同的数字。
- 我们需要在
App.vue
的methods
属性上创建changeStatus
函数。这个函数将接收任务的索引作为参数,然后去改变任务数组中的finishedAt
属性,这是我们标记任务完成的标志:
changeStatus(taskIndex){
const task = this.taskList[taskIndex];
if(task.finishedAt){
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
- 现在,我们需要将任务文本添加到屏幕的左侧。在组件的
<style>
部分,我们将使其具有作用域并添加自定义类:
<style scoped>
.taskList li{
text-align: left;
}
</style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染并运行的地方:
工作原理...
当我们从组件接收到发射的消息时,我们用更多的数据来填充消息,并将其推送到本地数组变量中。
在模板中,我们遍历这个数组,使其成为任务列表。这显示了我们需要做的任务,标记任务完成时的复选框以及任务完成的时间。
当用户点击复选框时,它会执行一个函数,将当前任务标记为已完成。如果任务已经完成,函数将把finishedAt
属性设置为undefined
。
另请参阅
您可以在v3.vuejs.org/guide/list.html#mapping-an-array-to-elements-with-v-for
找到有关列表渲染的更多信息
您可以在v3.vuejs.org/guide/conditional.html#v-if
找到有关条件渲染的更多信息
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
找到有关Math.random
的更多信息。
创建计算属性并了解其工作原理
想象一下,每次您必须获取操作过的数据时,都需要执行一个函数。想象一下,您需要获取需要经过一些处理的特定数据,并且每次都需要通过函数执行它。这种类型的工作不容易维护。计算属性存在是为了解决这些问题。使用计算属性可以更容易地获取需要预处理甚至缓存的数据,而无需执行任何其他外部记忆函数。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
操作步骤...
我们将继续我们的待办事项项目,或者您可以按照第二章中学到的'使用 Vue CLI 创建您的第一个项目'中的步骤创建一个新的 Vue 项目,介绍 TypeScript 和 Vue 生态系统。
现在,按照以下步骤创建计算属性并了解其工作原理:
- 在
App.vue
文件的<script>
部分,我们将在data
和method
之间添加一个名为computed
的新属性。这是computed
属性将被放置的地方。我们将创建一个名为displayList
的新计算属性,它将用于在模板上呈现最终列表:
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: []
}),
computed: {
displayList(){
return this.taskList;
},
},
methods: {
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskIndex){
const task = this.taskList[taskIndex];
if(task.finishedAt){
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
displayList
属性目前只返回变量的缓存值,而不是直接的变量本身。
- 现在,在
<template>
部分,我们需要更改列表的获取位置:
<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' @add-task='addNewTask' />
<div class='col-12'>
<div class='cardBox'>
<div class='container'>
<h2>My Tasks</h2>
<ul class='taskList'>
<li
v-for='(taskItem, index) in displayList'
:key='`${index}_${Math.random()}`'
>
<input type='checkbox'
:checked='!!taskItem.finishedAt'
@input='changeStatus(index)'
/>
{{ taskItem.task }}
<span v-if='taskItem.finishedAt'>
{{ taskItem.finishedAt }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
- 要运行服务器并查看组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
它是如何工作的...
使用computed
属性将值传递到模板时,该值现在被缓存。这意味着只有在值更新时才会触发渲染过程。同时,我们确保模板不使用变量进行渲染,因此它不能在模板上更改,因为它是变量的缓存副本。
使用这个过程,我们可以获得最佳性能,因为我们不会浪费处理时间重新渲染对数据显示没有影响的更改的 DOM 树。这是因为如果有什么变化,而结果是相同的,computed
属性会缓存结果并不会更新最终结果。
另请参阅
您可以在v3.vuejs.org/guide/computed.html
找到有关计算属性的更多信息。
使用自定义过滤器显示更清晰的数据和文本
有时您可能会发现用户甚至您自己无法阅读 Unix 时间戳或其他DateTime
格式。我们如何解决这个问题?在 Vue 中渲染数据时,可以使用我们称之为过滤器的东西。
想象一系列管道,数据通过这些管道流动。数据以一种形式进入每个管道,以另一种形式退出。这就是 Vue 中的过滤器的样子。您可以在同一变量上放置一系列过滤器,因此它会被格式化,重塑,并最终以不同的数据显示,而代码保持不变。在这些管道中,初始变量的代码是不可变的。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
我们将继续我们的待办事项列表项目,或者您可以像在第二章中学到的那样,使用 Vue CLI 创建一个新的 Vue 项目,介绍 TypeScript 和 Vue 生态系统。
按照以下步骤创建您的第一个自定义 Vue 过滤器:
- 在
App.vue
文件的<script>
部分,在方法中,创建一个formatDate
函数。此函数将接收value
作为参数并进入过滤器管道。我们可以检查value
是否是一个数字,因为我们知道我们的时间是基于 Unix 时间戳格式的。如果它是一个数字,我们将根据当前浏览器位置进行格式化,并返回该格式化的值。如果传递的值不是一个数字,我们只需返回传递的值。
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: []
}),
computed: {
displayList() {
return this.taskList;
}
},
methods: {
formatDate(value) {
if (!value) return '';
if (typeof value !== 'number') return value;
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(
browserLocale,
{
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
});
return intlDateTime.format(new Date(value));
},
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskIndex) {
const task = this.taskList[taskIndex];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
- 在组件的
<template>
部分,我们需要将变量传递给过滤器方法。为此,我们需要找到taskItem.finishedAt
属性,并将其作为formatDate
方法的参数。我们将添加一些文本来表示任务在日期开始时已完成:
<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' @add-task='addNewTask' />
<div class='col-12'>
<div class='cardBox'>
<div class='container'>
<h2>My Tasks</h2>
<ul class='taskList'>
<li
v-for='(taskItem, index) in displayList'
:key='`${index}_${Math.random()}`'
>
<input type='checkbox'
:checked='!!taskItem.finishedAt'
@input='changeStatus(index)'
/>
{{ taskItem.task }}
<span v-if='taskItem.finishedAt'> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
- 要运行服务器并查看组件,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve
这是您的组件呈现并运行的样子:
工作原理...
过滤器是接收一个值并必须返回一个值以在文件的<template>
部分显示或在 Vue 属性中使用的方法。
当我们将值传递给formatDate
方法时,我们知道它是一个有效的 Unix 时间戳,因此可以调用新的Date
类构造函数,将value
作为参数传递,因为 Unix 时间戳是一个有效的日期构造函数。
我们过滤器背后的代码是Intl.DateTimeFormat
函数,这是一个原生函数,可用于格式化和解析日期到声明的位置。为了获得本地格式,我们使用navigator
全局变量。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。
使用 Vuelidate 添加表单验证
最初,JavaScript 仅用于在将 HTML 表单发送到服务器之前验证这些表单;我们没有任何 JavaScript 框架或今天我们拥有的 JavaScript 生态系统。然而,有一件事仍然是相同的:在将表单发送到服务器之前,应该首先由 JavaScript 引擎进行表单验证。
我们将学习如何使用 Vue 生态系统中最受欢迎的库之一,在发送之前验证我们的输入表单。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
怎么做...
我们将继续我们的待办事项项目,或者您可以使用 Vue CLI 创建一个新的 Vue 项目,就像在第二章的“使用 Vue CLI 创建您的第一个项目”中学到的那样。
现在,按照以下步骤将表单验证添加到您的 Vue 项目和表单组件中:
- 要安装Vuelidate,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install vuelidate --save
- 要将 Vuelidate 插件添加到 Vue 应用程序中,我们需要在
src
文件夹中的main.js
文件中导入并添加它:
import Vue from 'vue';
import App from './App.vue';
import Vuelidate from 'vuelidate';
import './style.css';
Vue.config.productionTip = false
Vue.use(Vuelidate);
new Vue({
render: h => h(App),
}).$mount('#app')
- 在
TaskInput.vue
文件中,我们将向 Vue 对象添加一个新属性。这个属性由安装的新插件解释。在对象的末尾,我们将添加validations
属性,并在该属性内添加模型的名称。模型是插件将检查验证的数据或计算属性的直接名称:
<script>
export default {
name: 'TaskInput',
data: () => ({
task: ''
}),
methods: {
addTask() {
this.$emit('add-task', this.task);
this.task = '';
}
},
validations: {
task: {}
}
};
</script>
- 现在,我们需要导入已经存在于我们想要使用的插件上的规则,这些规则将是
required
和minLength
。导入后,我们将这些规则添加到模型中:
<script>
import { required, minLength } from 'vuelidate/lib/validators';
export default {
name: 'TaskInput',
data: () => ({
task: ''
}),
methods: {
addTask() {
this.$emit('add-task', this.task);
this.task = '';
}
},
validations: {
task: {
required,
minLength: minLength(5),
}
}
};
</script>
- 现在,我们需要在发出事件之前添加验证。我们将使用
$touch
内置函数告诉插件该字段已被用户触摸,并进行验证。如果有任何字段与用户有任何交互,插件将相应地设置标志。如果没有错误,我们将发出事件,并使用$reset
函数重置验证。为此,我们将更改addTask
方法:
addTask() {
this.$v.task.$touch();
if (this.$v.task.$error) return false;
this.$emit('add-task', this.task);
this.task = '';
this.$v.task.$reset();
return true;
}
- 为了提醒用户字段上存在一些错误,我们将使输入更改样式为完整的红色边框,并具有红色文本。为此,我们需要在输入字段上创建一个条件类。这将直接附加到模型的
$error
属性上:
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is:</strong>
<input
type='text'
:value='task'
@input='task = $event.target.value'
class='taskInput'
:class="$v.task.$error ? 'fieldError' : ''"
/>
<button v-on:click='addTask'>Add Task</button>
</div>
</div>
</template>
- 对于类,我们可以在
src
文件夹中的style.css
文件中创建一个fieldError
类:
.fieldError {
border: 2px solid red !important;
color: red;
border-radius: 3px;
}
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> **npm run serve**
这是您的组件呈现并运行的方式:
它是如何工作的...
安装后,Vuelidate 插件会向 Vue 原型添加一个新的$v
属性,并在 Vue 对象中检查一个名为validations
的新对象属性。当定义了此属性并具有一些规则时,插件会在每次更新时检查模型的规则。
使用这个新的 Vue 原型,我们可以在我们的代码中检查我们定义的规则内的错误,并执行函数来告诉插件该字段已被用户触摸以标记为脏字段或重置它。使用这些功能,我们能够根据我们在任务模型上定义的规则添加一个新的条件类。
任务模型是必需的,并且至少有五个字符。如果不满足这些规则,插件将标记模型有错误。我们获取此错误并使用它来向用户显示任务字段有活动错误。当用户满足要求时,错误的显示消失,事件可以被触发。
另请参阅
您可以在vuelidate.netlify.com/
找到有关 Vuelidate 的更多信息。
您可以在v3.vuejs.org/guide/class-and-style.html
找到有关类和样式绑定的更多信息
为列表创建过滤器和排序器
在处理列表时,通常会发现自己有原始数据。有时,您需要对这些数据进行过滤,以便用户可以阅读。为此,我们需要一组计算属性来形成最终的过滤器和排序器。
在这个示例中,我们将学习如何创建一个简单的过滤器和排序器,来控制我们最初的待办任务列表。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
我们将继续我们的待办事项列表项目,或者您可以像在第二章中学到的那样,使用 Vue CLI 创建一个新的 Vue 项目,介绍 TypeScript 和 Vue 生态系统。
按照以下步骤添加一组过滤器和排序到您的列表中:
- 在
App.vue
文件的<script>
部分,我们将添加新的计算属性;这些将用于排序和过滤。我们将添加三个新的计算属性,baseList
,filteredList
和sortedList
。baseList
属性将是我们的第一个操作。我们将通过Array.map
向任务列表添加一个id
属性。由于 JavaScript 数组从零开始,我们将在数组的索引上添加1
。filteredList
属性将过滤baseList
属性,并返回未完成的任务,sortedList
属性将对filteredList
属性进行排序,以便最后添加的id
属性将首先显示给用户:
<script>
import CurrentTime from "./components/CurrentTime.vue";
import TaskInput from "./components/TaskInput";
export default {
name: "TodoApp",
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: [],
}),
computed: {
baseList() {
return [...this.taskList]
.map((t, index) => ({
...t,
id: index + 1
}));
},
filteredList() {
return [...this.baseList]
.filter(t => !t.finishedAt);
},
sortedList() {
return [...this.filteredList]
.sort((a, b) => b.id - a.id);
},
displayList() {
return this.sortedList;
}
},
methods: {
formatDate(value) {
if (!value) return "";
if (typeof value !== "number") return value;
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(browserLocale, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric"
});
return intlDateTime.format(new Date(value));
},
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskIndex) {
const task = this.taskList[taskIndex];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
- 在
<template>
部分,我们将Task ID
添加为指示器,并更改changeStatus
方法发送参数的方式。因为现在索引是可变的,我们不能将其用作变量;它只是数组上的临时索引。我们需要使用任务id
:
<template>
<div id="app">
<current-time class="col-4" />
<task-input class="col-6" @add-task="addNewTask" />
<div class="col-12">
<div class="cardBox">
<div class="container">
<h2>My Tasks</h2>
<ul class="taskList">
<li
v-for="(taskItem, index) in displayList"
:key="`${index}_${Math.random()}`"
>
<input type="checkbox"
:checked="!!taskItem.finishedAt"
@input="changeStatus(taskItem.id)"
/>
#{{ taskItem.id }} - {{ taskItem.task }}
<span v-if="taskItem.finishedAt"> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
- 在
changeStatus
方法中,我们也需要更新我们的函数。由于索引现在从1
开始,我们需要将数组的索引减一,以获取更新前元素的真实索引:
changeStatus(taskId) {
const task = this.taskList[taskId - 1];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve
这是您的组件呈现和运行的方式:
它是如何工作的...
computed
属性一起作为列表的缓存工作,并确保对元素的操作没有副作用:
在baseList
属性中,我们创建了一个新数组,其中包含相同的任务,但为任务添加了一个新的id
属性。
在filteredList
属性中,我们取出了baseList
属性,并且只返回了未完成的任务。
在sortedList
属性上,我们按照它们的 ID,按降序对filteredList
属性上的任务进行排序。
当所有操作完成时,displayList
属性将返回被操作的数据的结果。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
找到有关Array.prototype.map
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
找到有关Array.prototype.filter
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
找到有关Array.prototype.sort
的更多信息。
创建条件过滤器以对列表数据进行排序
完成上一个食谱后,您的数据应该已经被过滤和排序,但您可能需要检查过滤后的数据或需要更改排序方式。在这个食谱中,我们将学习如何创建条件过滤器并对列表上的数据进行排序。
使用一些基本原则,可以收集信息并以许多不同的方式显示它。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
我们将继续我们的待办事项列表项目,或者您可以按照在第二章中学到的,在 Vue CLI 中创建一个新的 Vue 项目,介绍 TypeScript 和 Vue 生态系统。
现在,按照以下步骤添加条件过滤器以对列表数据进行排序:
- 在
App.vue
文件的<script>
部分,我们将更新computed
属性,filteredList
,sortedList
和displayList
。我们需要向我们的项目添加三个新变量,hideDone
,reverse
和sortById
。所有三个变量都将是布尔变量,并且默认值为false
。filteredList
属性将检查hideDone
变量是否为true
。如果是,它将具有相同的行为,但如果不是,它将显示整个列表而不进行任何过滤。sortedList
属性将检查sortById
变量是否为true
。如果是,它将具有相同的行为,但如果不是,它将按任务完成日期对列表进行排序。displayList
属性将检查reverse
变量是否为true
。如果是,它将颠倒显示的列表,但如果不是,它将具有相同的行为:
<script>
import CurrentTime from "./components/CurrentTime.vue";
import TaskInput from "./components/TaskInput";
export default {
name: "TodoApp",
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: [],
hideDone: false,
reverse: false,
sortById: false,
}),
computed: {
baseList() {
return [...this.taskList]
.map((t, index) => ({
...t,
id: index + 1
}));
},
filteredList() {
return this.hideDone
? [...this.baseList]
.filter(t => !t.finishedAt)
: [...this.baseList];
},
sortedList() {
return [...this.filteredList]
.sort((a, b) => (
this.sortById
? b.id - a.id
: (a.finishedAt || 0) - (b.finishedAt || 0)
));
},
displayList() {
const taskList = [...this.sortedList];
return this.reverse
? taskList.reverse()
: taskList;
}
},
methods: {
formatDate(value) {
if (!value) return "";
if (typeof value !== "number") return value;
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(browserLocale, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric"
});
return intlDateTime.format(new Date(value));
},
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskId) {
const task = this.taskList[taskId - 1];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
- 在
<template>
部分,我们需要为这些变量添加控制器。我们将创建三个复选框,直接通过v-model
指令与变量链接:
<template>
<div id="app">
<current-time class="col-4" />
<task-input class="col-6" @add-task="addNewTask" />
<div class="col-12">
<div class="cardBox">
<div class="container">
<h2>My Tasks</h2>
<hr />
<div class="col-4">
<input
v-model="hideDone"
type="checkbox"
id="hideDone"
name="hideDone"
/>
<label for="hideDone">
Hide Done Tasks
</label>
</div>
<div class="col-4">
<input
v-model="reverse"
type="checkbox"
id="reverse"
name="reverse"
/>
<label for="reverse">
Reverse Order
</label>
</div>
<div class="col-4">
<input
v-model="sortById"
type="checkbox"
id="sortById"
name="sortById"
/>
<label for="sortById">
Sort By Id
</label>
</div>
<ul class="taskList">
<li
v-for="(taskItem, index) in displayList"
:key="`${index}_${Math.random()}`"
>
<input type="checkbox"
:checked="!!taskItem.finishedAt"
@input="changeStatus(taskItem.id)"
/>
#{{ taskItem.id }} - {{ taskItem.task }}
<span v-if="taskItem.finishedAt"> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现和运行的方式:
工作原理...
computed
属性一起作为列表的缓存工作,并确保在元素操作中没有任何副作用。通过条件处理,可以通过变量更改过滤和排序规则,并且显示会实时更新:
在filteredList
属性处,我们取出了baseList
属性,并返回了未完成的任务。当hideDone
变量为false
时,我们返回整个列表而不进行任何过滤。
在sortedList
属性处,我们对filteredList
属性上的任务进行了排序。当sortById
变量为true
时,列表按 ID 降序排序;当为false
时,按任务完成时间升序排序。
在displayList
属性处,当reverse
变量为true
时,最终列表被颠倒。
当所有操作都完成时,displayList
属性返回了被操作的数据的结果。
这些computed
属性由用户屏幕上的复选框控制,因此用户可以完全控制他们可以看到什么以及如何看到它。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
找到有关Array.prototype.map
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
找到有关Array.prototype.filter
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
找到有关Array.prototype.sort
的更多信息。
添加自定义样式和过渡效果
在组件中添加样式是一个很好的做法,因为它可以让用户更清楚地看到发生了什么。通过这样做,您可以向用户显示视觉响应,也可以为您的应用程序提供更好的体验。
在这个示例中,我们将学习如何添加一种新的条件类绑定。我们将使用 CSS 效果与每个新的 Vue 更新带来的重新渲染相结合。
准备就绪
此处的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
我们将继续我们的待办事项清单项目,或者您可以像在第二章“使用 Vue CLI 创建您的第一个项目”中学到的那样,使用 Vue CLI 创建一个新的 Vue 项目。
按照以下步骤为您的组件添加自定义样式和过渡效果:
- 在
App.vue
文件中,我们将为已完成的任务的列表项添加一个条件类:
<template>
<div id="app">
<current-time class="col-4" />
<task-input class="col-6" @add-task="addNewTask" />
<div class="col-12">
<div class="cardBox">
<div class="container">
<h2>My Tasks</h2>
<hr />
<div class="col-4">
<input
v-model="hideDone"
type="checkbox"
id="hideDone"
name="hideDone"
/>
<label for="hideDone">
Hide Done Tasks
</label>
</div>
<div class="col-4">
<input
v-model="reverse"
type="checkbox"
id="reverse"
name="reverse"
/>
<label for="reverse">
Reverse Order
</label>
</div>
<div class="col-4">
<input
v-model="sortById"
type="checkbox"
id="sortById"
name="sortById"
/>
<label for="sortById">
Sort By Id
</label>
</div>
<ul class="taskList">
<li
v-for="(taskItem, index) in displayList"
:key="`${index}_${Math.random()}`"
:class="!!taskItem.finishedAt ? 'taskDone' : ''"
>
<input type="checkbox"
:checked="!!taskItem.finishedAt"
@input="changeStatus(taskItem.id)"
/>
#{{ taskItem.id }} - {{ taskItem.task }}
<span v-if="taskItem.finishedAt"> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
- 在组件的
<style>
部分,我们将为taskDone
的 CSS 类创建 CSS 样式表类。我们需要让列表项之间有一个分隔符;然后,我们将使列表具有条纹样式;当它们被标记为完成时,背景将发生变化。要在行之间添加分隔符和条纹列表或斑马样式,我们需要添加一个 CSS 样式表规则,适用于我们列表的每个even nth-child
:
<style scoped>
.taskList li {
list-style: none;
text-align: left;
padding: 5px 10px;
border-bottom: 1px solid rgba(0,0,0,0.15);
}
.taskList li:last-child {
border-bottom: 0px;
}
.taskList li:nth-child(even){
background-color: rgba(0,0,0,0.05);
}
</style>
- 在
<style>
部分的末尾添加 CSS 动画关键帧,指示背景颜色变化,并将此动画应用于.taskDone
CSS 类,以在任务完成时添加背景效果
<style scoped>
.taskList li {
list-style: none;
text-align: left;
padding: 5px 10px;
border-bottom: 1px solid rgba(0,0,0,0.15);
}
.taskList li:last-child {
border-bottom: 0px;
}
.taskList li:nth-child(even){
background-color: rgba(0,0,0,0.05);
}
@keyframes colorChange {
from{
background-color: inherit;
}
to{
background-color: rgba(0, 160, 24, 0.577);
}
}
.taskList li.taskDone{
animation: colorChange 1s ease;
background-color: rgba(0, 160, 24, 0.577);
}
</style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现和运行的地方:
它是如何工作的...
每当我们的应用程序中的新项目被标记为已完成时,displayList
属性都会更新并触发组件的重新渲染。
因此,我们的taskDone
CSS 类附加了一个在渲染时执行的动画,显示绿色背景。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations
找到有关 CSS 动画的更多信息。
您可以在v3.vuejs.org/guide/class-and-style.html
找到有关类和样式绑定的更多信息
使用 vue-devtools 调试您的应用程序
vue-devtools
对于每个 Vue 开发人员都是必不可少的。这个工具向我们展示了 Vue 组件、路由、事件和 vuex 的深度。
借助vue-devtools
扩展程序,可以调试我们的应用程序,在更改代码之前尝试新数据,执行函数而无需直接在代码中调用它们,等等。
在这个配方中,我们将学习如何使用 devtools 找到有关您的应用程序的更多信息,以及如何使用它来帮助您的调试过程。
准备就绪
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
您需要在浏览器中安装vue-devtools
扩展程序:
Chrome 扩展程序-bit.ly/chrome-vue-devtools
Firefox 扩展程序-bit.ly/firefox-vue-devtools
如何做...
我们将继续进行待办事项列表项目,或者您可以按照第二章*,介绍 TypeScript 和 Vue 生态系统*中学到的内容,使用 Vue CLI 创建一个新的 Vue 项目。
在开发任何 Vue 应用程序时,始终最好使用vue-devtools
进行开发。
按照这些步骤来了解如何使用vue-devtools
以及如何正确调试 Vue 应用程序:
- 要进入
vue-devtools
,首先需要在浏览器中安装它,所以请查看本教程的“准备就绪”部分,获取 Chrome 或 Firefox 的扩展链接。在 Vue 开发应用程序中,进入浏览器开发者检查器模式。一个名为 Vue 的新标签页必须出现:
- 您首先看到的是组件标签页。该标签页显示了您的应用程序组件树。如果单击组件,您将能够查看所有可用数据,计算属性,以及由插件(如
vuelidate
,vue-router
或vuex
)注入的额外数据。您可以编辑数据以实时查看应用程序中的更改:
- 第二个标签页是用于vuex 开发的。该标签页将显示变化的历史记录、当前状态和 getter。可以检查每个变化传递的有效负载,并进行时间旅行变化,以在 vuex 状态中“回到过去”:
- 第三个标签页专门用于应用程序中的事件发射器。在此处显示了应用程序中发射的所有事件。您可以通过单击事件来检查发射的事件。您可以查看事件的名称、类型,事件的来源(在本例中是一个组件),以及有效负载:
- 第四个标签页专门用于vue-router插件。在那里,您可以查看导航历史记录,以及传递给新路由的所有元数据。您可以查看应用程序中所有可用的路由:
- 第五个标签页是性能标签页。在这里,您可以检查组件的加载时间,应用程序运行的每秒帧数,以及实时发生的事件。第一张截图显示了当前应用程序的每秒帧数,以及所选组件的每秒帧数:
第二张截图显示了组件生命周期钩子的性能,以及执行每个钩子所需的时间:
- 第六个标签是您的设置标签;在这里,您可以管理扩展程序,更改外观,内部行为以及在 Vue 插件中的行为方式:
- 最后一个标签是
vue-devtools
的刷新按钮。有时,当hot-module-reload
发生或者应用程序组件树中发生一些复杂事件时,扩展程序可能会失去对发生情况的跟踪。这个按钮强制扩展程序重新加载并再次读取 Vue 应用程序状态。
另请参阅
您可以在github.com/vuejs/vue-devtools
找到有关vue-devtools
的更多信息。
第四章:组件,混合和功能组件
构建 Vue 应用程序就像拼图一样。拼图的每一块都是一个组件,每一块都有一个插槽要填充。
组件在 Vue 开发中扮演着重要的角色。在 Vue 中,您的代码的每一部分都将是一个组件——它可以是布局,页面,容器或按钮,但最终,它都是一个组件。学习如何与它们交互并重用它们是清理代码和提高 Vue 应用性能的关键。组件是最终会在屏幕上呈现出某些东西的代码,无论大小如何。
在本章中,我们将学习如何制作一个可视化组件,可以在许多地方重复使用。我们将使用插槽将数据放入我们的组件中,为了严格快速的渲染,创建功能性组件,实现父子组件之间的直接通信,最后,看看如何异步加载您的组件。
让我们把所有这些部分放在一起,创建一个美丽的拼图,即 Vue 应用程序。
在本章中,我们将涵盖以下配方:
创建一个可视化模板组件
使用插槽和命名插槽将数据放入您的组件中
将数据传递给您的组件并验证数据
创建功能性组件
访问您的子组件数据
创建一个动态注入的组件
创建一个依赖注入组件
创建一个组件mixin
延迟加载您的组件
技术要求
在本章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的 NPM 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
npm install -g windows-build-tools
要安装Vue-CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个可视化模板组件
组件可以是数据驱动的,无状态的,有状态的,或者是一个简单的可视化组件。但是什么是可视化组件?可视化组件是一个只有一个目的的组件:可视化操作。
一个可视化组件可以有一个简单的带有一些div
HTML 元素的作用域 CSS,或者它可以是一个更复杂的组件,可以实时计算元素在屏幕上的位置。
我们将创建一个遵循 Material Design 指南的卡片包装组件。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用带有 Vue-CLI 的 Vue 项目,就像我们在第二章中的“使用 Vue CLI 创建您的第一个项目”食谱中所做的那样,介绍 TypeScript 和 Vue 生态系统**,或者我们可以开始一个新的项目。
要启动一个新项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create visual-component
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
现在,让我们按照这些步骤创建一个可视化模板组件:
让我们在src/components
文件夹中创建一个名为MaterialCardBox.vue
的新文件。
在这个文件中,我们将从组件的模板开始。我们需要为卡片创建一个框。通过使用 Material Design 指南,这个框将有阴影和圆角:
<template>
<div class="cardBox elevation_2">
<div class="section">
This is a Material Card Box
</div>
</div>
</template>
- 在我们组件的
<script>
部分中,我们将只添加我们的基本名称:
<script>
export default {
name: 'MaterialCardBox',
};
</script>
- 我们需要创建我们的高程 CSS 样式表规则。为此,请在
style
文件夹中创建一个名为elevation.css
的文件。在那里,我们将创建从0
到24
的高程,以遵循 Material Design 指南上的所有高程:
.elevation_0 {
border: 1px solid rgba(0, 0, 0, 0.12);
}
.elevation_1 {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2),
0 1px 1px rgba(0, 0, 0, 0.14),
0 2px 1px -1px rgba(0, 0, 0, 0.12);
}
.elevation_2 {
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2),
0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.elevation_3 {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2),
0 3px 4px rgba(0, 0, 0, 0.14),
0 3px 3px -2px rgba(0, 0, 0, 0.12);
}
.elevation_4 {
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2),
0 4px 5px rgba(0, 0, 0, 0.14),
0 1px 10px rgba(0, 0, 0, 0.12);
}
.elevation_5 {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 5px 8px rgba(0, 0, 0, 0.14),
0 1px 14px rgba(0, 0, 0, 0.12);
}
.elevation_6 {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 6px 10px rgba(0, 0, 0, 0.14),
0 1px 18px rgba(0, 0, 0, 0.12);
}
.elevation_7 {
box-shadow: 0 4px 5px -2px rgba(0, 0, 0, 0.2),
0 7px 10px 1px rgba(0, 0, 0, 0.14),
0 2px 16px 1px rgba(0, 0, 0, 0.12);
}
.elevation_8 {
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.elevation_9 {
box-shadow: 0 5px 6px -3px rgba(0, 0, 0, 0.2),
0 9px 12px 1px rgba(0, 0, 0, 0.14),
0 3px 16px 2px rgba(0, 0, 0, 0.12);
}
.elevation_10 {
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, 0.2),
0 10px 14px 1px rgba(0, 0, 0, 0.14),
0 4px 18px 3px rgba(0, 0, 0, 0.12);
}
.elevation_11 {
box-shadow: 0 6px 7px -4px rgba(0, 0, 0, 0.2),
0 11px 15px 1px rgba(0, 0, 0, 0.14),
0 4px 20px 3px rgba(0, 0, 0, 0.12);
}
.elevation_12 {
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 12px 17px 2px rgba(0, 0, 0, 0.14),
0 5px 22px 4px rgba(0, 0, 0, 0.12);
}
.elevation_13 {
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
}
.elevation_14 {
box-shadow: 0 7px 9px -4px rgba(0, 0, 0, 0.2),
0 14px 21px 2px rgba(0, 0, 0, 0.14),
0 5px 26px 4px rgba(0, 0, 0, 0.12);
}
.elevation_15 {
box-shadow: 0 8px 9px -5px rgba(0, 0, 0, 0.2),
0 15px 22px 2px rgba(0, 0, 0, 0.14),
0 6px 28px 5px rgba(0, 0, 0, 0.12);
}
.elevation_16 {
box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2),
0 16px 24px 2px rgba(0, 0, 0, 0.14),
0 6px 30px 5px rgba(0, 0, 0, 0.12);
}
.elevation_17 {
box-shadow: 0 8px 11px -5px rgba(0, 0, 0, 0.2),
0 17px 26px 2px rgba(0, 0, 0, 0.14),
0 6px 32px 5px rgba(0, 0, 0, 0.12);
}
.elevation_18 {
box-shadow: 0 9px 11px -5px rgba(0, 0, 0, 0.2),
0 18px 28px 2px rgba(0, 0, 0, 0.14),
0 7px 34px 6px rgba(0, 0, 0, 0.12);
}
.elevation_19 {
box-shadow: 0 9px 12px -6px rgba(0, 0, 0, 0.2),
0 19px 29px 2px rgba(0, 0, 0, 0.14),
0 7px 36px 6px rgba(0, 0, 0, 0.12);
}
.elevation_20 {
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, 0.2),
0 20px 31px 3px rgba(0, 0, 0, 0.14),
0 8px 38px 7px rgba(0, 0, 0, 0.12);
}
.elevation_21 {
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, 0.2),
0 21px 33px 3px rgba(0, 0, 0, 0.14),
0 8px 40px 7px rgba(0, 0, 0, 0.12);
}
.elevation_22 {
box-shadow: 0 10px 14px -6px rgba(0, 0, 0, 0.2),
0 22px 35px 3px rgba(0, 0, 0, 0.14),
0 8px 42px 7px rgba(0, 0, 0, 0.12);
}
.elevation_23 {
box-shadow: 0 11px 14px -7px rgba(0, 0, 0, 0.2),
0 23px 36px 3px rgba(0, 0, 0, 0.14),
0 9px 44px 8px rgba(0, 0, 0, 0.12);
}
.elevation_24 {
box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2),
0 24px 38px 3px rgba(0, 0, 0, 0.14),
0 9px 46px 8px rgba(0, 0, 0, 0.12);
}
- 为了在组件的
<style>
部分中设置样式,我们需要在<style>
标签内设置scoped
属性,以确保视觉样式不会干扰应用程序中的任何其他组件。我们将使这张卡遵循 Material Design 指南。我们需要导入Roboto
字体系列并将其应用于将包装在此组件内的所有元素:
<style scoped>
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap');
@import '../style/elevation.css';
*{
font-family: 'Roboto', sans-serif;
}
.cardBox{
width: 100%;
max-width: 300px;
background-color: #fff;
position: relative;
display: inline-block;
border-radius: 0.25rem;
}
.cardBox > .section {
padding: 1rem;
position: relative;
}
</style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
可视化组件是一个将包装任何组件并使用自定义样式放置包装数据的组件。由于此组件与其他组件混合,它可以形成一个新的组件,而无需在代码中重新应用或重写任何样式。
参见
您可以在vue-loader.vuejs.org/guide/scoped-css.html#child-component-root-elements
找到有关作用域 CSS 的更多信息。
您可以在material.io/components/cards/
找到有关 Material Design 卡片的更多信息。
在fonts.google.com/specimen/Roboto
上查看 Roboto 字体系列。
使用插槽和命名插槽在组件中放置数据
有时候,拼图的一些部分会丢失,你会发现自己有一个空白的地方。想象一下,你可以用自己制作的一块填充那个空白的地方,而不是原来随拼图盒子一起的那块。这是 Vue 插槽的一个粗略类比。
Vue 插槽就像是组件中的开放空间,其他组件可以用文本、HTML 元素或其他 Vue 组件填充。您可以在组件中声明插槽的位置和行为方式。
使用这种技术,您可以创建一个组件,并在需要时轻松自定义它。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们可以像在第二章的使用 Vue CLI 创建您的第一个项目中那样使用 Vue-CLI 创建我们的 Vue 项目,或者使用创建可视化模板组件中的项目。
按照以下说明在组件中创建插槽和命名插槽:
让我们打开组件文件夹中的名为MaterialCardBox.vue
的文件。
在组件的<template>
部分,我们需要在卡片上添加四个主要部分。这些部分基于 Material Design 卡片解剖学,分别是header
、media
、main section
和action
区域。我们将使用默认插槽来放置main section
,其余部分都将是命名作用域。对于一些命名插槽,我们将添加一个备用配置,如果用户没有在插槽上选择任何设置,将显示该配置:
<template>
<div class="cardBox elevation_2">
<div class="header">
<slot
v-if="$slots.header"
name="header"
/>
<div v-else>
<h1 class="cardHeader cardText">
Card Header
</h1>
<h2 class="cardSubHeader cardText">
Card Sub Header
</h2>
</div>
</div>
<div class="media">
<slot
v-if="$slots.media"
name="media"
/>
<img
v-else
src="https://via.placeholder.com/350x250"
>
</div>
<div
v-if="$slots.default"
class="section cardText"
:class="{
noBottomPadding: $slots.action,
halfPaddingTop: $slots.media,
}"
>
<slot />
</div>
<div
v-if="$slots.action"
class="action"
>
<slot name="action" />
</div>
</div>
</template>
- 现在,我们需要为组件创建文本 CSS 样式表规则。在
style
文件夹中,创建一个名为cardStyles.css
的新文件,在那里我们将添加卡片文本和标题的规则:
h1, h2, h3, h4, h5, h6{
margin: 0;
}
.cardText{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-decoration: inherit;
text-transform: inherit;
font-size: 0.875rem;
line-height: 1.375rem;
letter-spacing: 0.0071428571em;
}
h1.cardHeader{
font-size: 1.25rem;
line-height: 2rem;
font-weight: 500;
letter-spacing: .0125em;
}
h2.cardSubHeader{
font-size: .875rem;
line-height: 1.25rem;
font-weight: 400;
letter-spacing: .0178571429em;
opacity: .6;
}
- 在组件的
<style>
部分,我们需要创建一些 CSS 样式表来遵循我们的设计指南的规则:
<style scoped>
@import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap");
@import "../style/elevation.css";
@import "../style/cardStyles.css";
* {
font-family: "Roboto", sans-serif;
}
.cardBox {
width: 100%;
max-width: 300px;
border-radius: 0.25rem;
background-color: #fff;
position: relative;
display: inline-block;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2), 0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.cardBox > .header {
padding: 1rem;
position: relative;
display: block;
}
.cardBox > .media {
overflow: hidden;
position: relative;
display: block;
max-width: 100%;
}
.cardBox > .section {
padding: 1rem;
position: relative;
margin-bottom: 1.5rem;
display: block;
}
.cardBox > .action {
padding: 0.5rem;
position: relative;
display: block;
}
.cardBox > .action > *:not(:first-child) {
margin-left: 0.4rem;
}
.noBottomPadding {
padding-bottom: 0 !important;
}
.halfPaddingTop {
padding-top: 0.5rem !important;
}
</style>
- 在
src
文件夹中的App.vue
文件中,我们需要向这些插槽添加元素。这些元素将被添加到每个命名插槽和默认插槽中。我们将更改文件的<template>
部分中的组件。要添加命名插槽,我们需要使用一个名为v-slot:
的指令,然后是我们想要使用的插槽的名称:
<template>
<div id="app">
<MaterialCardBox>
<template v-slot:header>
<strong>Card Title</strong><br>
<span>Card Sub-Title</span>
</template>
<template v-slot:media>
<img src="https://via.placeholder.com/350x150">
</template>
<p>Main Section</p>
<template v-slot:action>
<button>Action Button</button>
<button>Action Button</button>
</template>
</MaterialCardBox>
</div>
</template>
对于默认插槽,我们不需要使用指令;它只需要包装在组件中,以放置在组件的<slot />
部分中。
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染并运行的方式:
它是如何工作的...
插槽是可以放置任何可以呈现到 DOM 中的地方。我们选择插槽的位置,并告诉组件在接收到任何信息时在何处呈现。
在这个教程中,我们使用了命名插槽,它们旨在与需要多个插槽的组件一起使用。要在 Vue 单文件(.vue
)的<template>
部分中向该组件放置任何信息,您需要添加v-slot:
指令,以便 Vue 能够知道在何处放置传递下来的信息。
另请参阅
您可以在vuejs.org/v2/guide/components-slots.html
找到有关 Vue 插槽的更多信息。
您可以在material.io/components/cards/#anatomy
找到有关 Material Design 卡片解剖的更多信息。
向您的组件传递数据并验证数据
您现在知道如何通过插槽将数据放入组件中,但这些插槽是为 HTML DOM 元素或 Vue 组件而设计的。有时,您需要传递诸如字符串、数组、布尔值甚至对象之类的数据。
整个应用程序就像一个拼图,其中每个部分都是一个组件。组件之间的通信是其中的重要部分。向组件传递数据的可能性是连接拼图的第一步,然后验证数据是连接这些部分的最后一步。
在这个教程中,我们将学习如何向组件传递数据并验证传递给组件的数据。
准备工作
先决条件如下:
- Node.js 12+
Node.js 所需的全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以像在第二章“介绍 TypeScript 和 Vue 生态系统”中的使用 Vue CLI 创建您的第一个项目食谱中那样,使用 Vue-CLI 创建我们的 Vue 项目,或者使用使用插槽和命名插槽将数据放入组件食谱中的项目。
按照这些说明将数据传递给组件并进行验证:
让我们在src/components
文件夹中打开名为MaterialCardBox.vue
的文件。
在组件的<script>
部分,我们创建一个名为props
的新属性。该属性接收组件数据,该数据可以用于视觉操作、代码内的变量或需要执行的函数。在此属性中,我们需要声明属性的名称、类型、是否必需以及验证函数。此函数将在运行时执行,以验证传递的属性是否有效:
<script>
export default {
name: 'MaterialCardBox',
inheritAttrs: false,
props: {
header: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
subHeader: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
mainText: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
showMedia: {
type: Boolean,
required: false,
default: false,
validator: v => typeof v === 'boolean',
},
imgSrc: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
showActions: {
type: Boolean,
required: false,
default: false,
validator: v => typeof v === 'boolean',
},
elevation: {
type: Number,
required: false,
default: 2,
validator: v => typeof v === 'number',
},
},
computed: {},
};
</script>
- 在组件的
<script>
部分的computed
属性中,我们需要创建一组用于呈现卡片的视觉操作规则。这些规则将是showMediaContent
、showActionsButtons
、showHeader
和cardElevation
。每个规则将检查接收到的props
和$slots
对象,以查看是否需要呈现相关的卡片部分:
computed: {
showMediaContent() {
return (this.$slots.media || this.imgSrc) && this.showMedia;
},
showActionsButtons() {
return this.showActions && this.$slots.action;
},
showHeader() {
return this.$slots.header || (this.header || this.subHeader);
},
showMainContent() {
return this.$slots.default || this.mainText;
},
cardElevation() {
return `elevation_${parseInt(this.elevation, 10)}`;
},
},
- 在添加了视觉操作规则之后,我们需要将创建的规则添加到组件的
<template>
部分。它们将影响我们卡片的外观和行为。例如,如果没有定义头部插槽,并且定义了头部属性,我们将显示备用头部。该头部是通过props
传递下来的数据:
<template>
<div
class="cardBox"
:class="cardElevation"
>
<div
v-if="showHeader"
class="header"
>
<slot
v-if="$slots.header"
name="header"
/>
<div v-else>
<h1 class="cardHeader cardText">
{{ header }}
</h1>
<h2 class="cardSubHeader cardText">
{{ subHeader }}
</h2>
</div>
</div>
<div
v-if="showMediaContent"
class="media"
>
<slot
v-if="$slots.media"
name="media"
/>
<img
v-else
:src="imgSrc"
>
</div>
<div
v-if="showMainContent"
class="section cardText"
:class="{
noBottomPadding: $slots.action,
halfPaddingTop: $slots.media,
}"
>
<slot v-if="$slots.default" />
<p
v-else
class="cardText"
>
{{ mainText }}
</p>
</div>
<div
v-if="showActionsButtons"
class="action"
>
<slot
v-if="$slots.action"
name="action"
/>
</div>
</div>
</template>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行:
工作原理...
每个 Vue 组件都是一个具有渲染函数的 JavaScript 对象。当需要在 HTML DOM 中呈现它时,将调用此渲染函数。单文件组件是该对象的抽象。
当我们声明我们的组件具有可以传递的唯一 props 时,它为其他组件或 JavaScript 打开了一个小门,以便将信息放入我们的组件中。然后,我们可以在组件内使用这些值来渲染数据,进行一些计算或制定可视规则。
在我们的情况下,使用单文件组件,我们将这些规则作为 HTML 属性传递,因为 vue-template-compiler
将获取这些属性并将它们转换为 JavaScript 对象。
当这些值传递给我们的组件时,Vue 首先检查传递的属性是否与正确的类型匹配,然后我们在每个值上执行我们的验证函数,以查看它是否与我们期望的匹配。
完成所有这些后,组件的生命周期将继续,我们可以渲染我们的组件。
另请参阅
您可以在 vuejs.org/v2/guide/components-props.html
找到有关 props
的更多信息。
您可以在 vue-loader.vuejs.org/guide/
找到有关 vue-template-compiler
的更多信息。
创建功能组件
功能组件的美妙之处在于它们的简单性。它们是无状态组件,没有任何数据、计算属性,甚至没有生命周期。它们只是在传递的数据发生变化时调用的渲染函数。
您可能想知道这有什么用。嗯,功能组件是 UI 组件的完美伴侣,这些组件不需要在内部保留任何数据,或者只是渲染组件,不需要任何数据操作的可视组件。
顾名思义,它们是简单的函数组件,除了渲染函数外没有其他内容。它们是组件的精简版本,专门用于性能渲染和可视元素。
准备工作
先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,请使用 Vue-CLI 创建您的 Vue 项目,就像我们在第二章“引入 TypeScript 和 Vue 生态系统”中的食谱“使用 Vue CLI 创建您的第一个项目”中所做的那样,或者使用“将数据传递给您的组件并验证数据”的食谱中的项目。
现在,按照以下说明创建一个 Vue 功能组件:
在src/components
文件夹中创建一个名为MaterialButton.vue
的新文件。
在这个组件中,我们需要验证我们将接收的 prop 是否是有效的颜色。为此,在项目中安装is-color
模块。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save is-color
- 在我们组件的
<script>
部分,我们需要创建功能组件将接收的props
对象。由于功能组件只是一个没有状态的渲染函数,<script>
部分被简化为props
、injections
和slots
。将有四个props
对象:backgroundColor
、textColor
、isRound
和isFlat
。在安装组件时,这些不是必需的,因为我们在props
中定义了默认值:
<script>
import isColor from 'is-color';
export default {
name: 'MaterialButton',
props: {
backgroundColor: {
type: String,
required: false,
default: '#fff',
validator: v => typeof v === 'string' && isColor(v),
},
textColor: {
type: String,
required: false,
default: '#000',
validator: v => typeof v === 'string' && isColor(v),
},
isRound: {
type: Boolean,
required: false,
default: false,
},
isFlat: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
- 在我们组件的
<template>
部分,我们首先需要向<template>
标签添加functional
属性,以指示vue-template-compiler
这个组件是一个功能组件。我们需要创建一个按钮 HTML 元素,带有基本的class
属性按钮和一个基于props
对象接收的动态class
属性。与普通组件不同,我们需要指定props
属性以使用功能组件。对于按钮的样式,我们需要创建一个基于props
的动态style
属性。为了直接将所有事件监听器传递给父组件,我们可以调用v-on
指令并传递listeners
属性。这将绑定所有事件监听器,而无需声明每一个。在按钮内部,我们将添加一个用于视觉增强的div
HTML 元素,并添加<slot>
,文本将放置在其中:
<template functional>
<button
tabindex="0"
class="button"
:class="{
round: props.isRound,
isFlat: props.isFlat,
}"
:style="{
background: props.backgroundColor,
color: props.textColor
}"
v-on="listeners"
>
<div
tabindex="-1"
class="button_focus_helper"
/>
<slot/>
</button>
</template>
- 现在,让我们把它弄得漂亮一点。在组件的
<style>
部分,我们需要为这个按钮创建所有的 CSS 样式表规则。我们需要向<style>
添加scoped
属性,以便所有的 CSS 样式表规则不会影响我们应用程序中的任何其他元素:
<style scoped>
.button {
user-select: none;
position: relative;
outline: 0;
border: 0;
border-radius: 0.25rem;
vertical-align: middle;
cursor: pointer;
padding: 4px 16px;
font-size: 14px;
line-height: 1.718em;
text-decoration: none;
color: inherit;
background: transparent;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
min-height: 2.572em;
font-weight: 500;
text-transform: uppercase;
}
.button:not(.isFlat){
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2),
0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.button:not(.isFlat):focus:before,
.button:not(.isFlat):active:before,
.button:not(.isFlat):hover:before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: inherit;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
}
.button:not(.isFlat):focus:before,
.button:not(.isFlat):active:before,
.button:not(.isFlat):hover:before {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 5px 8px rgba(0, 0, 0, 0.14),
0 1px 14px rgba(0, 0, 0, 0.12);
}
.button_focus_helper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
border-radius: inherit;
outline: 0;
opacity: 0;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5, 1),
opacity 0.4s cubic-bezier(0.25, 0.8, 0.5, 1);
}
.button_focus_helper:after, .button_focus_helper:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
border-radius: inherit;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5, 1),
opacity 0.6s cubic-bezier(0.25, 0.8, 0.5, 1);
}
.button_focus_helper:before {
background: #000;
}
.button_focus_helper:after {
background: #fff;
}
.button:focus .button_focus_helper:before,
.button:hover .button_focus_helper:before {
opacity: .1;
}
.button:focus .button_focus_helper:after,
.button:hover .button_focus_helper:after {
opacity: .6;
}
.button:focus .button_focus_helper,
.button:hover .button_focus_helper {
opacity: 0.2;
}
.round {
border-radius: 50%;
}
</style>
- 运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染并运行的地方:
它是如何工作的...
功能组件就像一个渲染函数一样简单。它们没有任何类型的数据、函数或者对外部世界的访问。
它们最初作为 JavaScript 对象render()
函数在 Vue 中引入;后来,它们被添加到了vue-template-compiler
中,用于 Vue 单文件应用程序。
功能组件通过接收两个参数来工作:createElement
和context
。正如我们在单文件中看到的,我们只能访问元素,因为它们不在 JavaScript 对象的this
属性中。这是因为当上下文传递给渲染函数时,就没有this
属性。
功能组件在 Vue 上提供了最快的渲染速度,因为它不依赖于组件的生命周期来检查渲染;它只是在数据更改时每次渲染。
另请参阅
您可以在vuejs.org/v2/guide/render-function.html#Functional-Components
找到有关功能组件的更多信息。
您可以在www.npmjs.com/package/is-color
找到有关is-color
模块的更多信息。
访问您的子组件数据
通常,父子通信是通过事件或 props 来完成的。但有时,您需要访问存在于子组件或父组件函数中的数据、函数或计算属性。
Vue 提供了一种双向交互的方式,打开了通信和事件的大门,例如 props 和事件监听器。
还有另一种访问组件之间数据的方式:通过直接访问。这可以通过在单文件组件中使用模板时使用特殊属性,或者在 JavaScript 中直接调用对象来完成。这种方法被一些人认为有点懒惰,但有时确实没有其他方法可以做到这一点。
准备工作
先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动您的组件,请使用 Vue-CLI 创建您的 Vue 项目,就像我们在第二章的'使用 Vue CLI 创建您的第一个项目'食谱中所做的那样,介绍 TypeScript 和 Vue 生态系统,或者使用'创建功能组件'食谱中的项目。
我们将把这个教程分成四个部分。前三部分将涵盖新组件的创建——StarRatingInput
、StarRatingDisplay
和StarRating
——最后一部分将涵盖数据和函数访问的父子直接操作。
创建星级评分输入
我们将创建一个基于五星级评分系统的星级评分输入。
按照以下步骤创建自定义星级评分输入:
在src/components
文件夹中创建一个名为StarRatingInput.vue
的新文件。
在组件的<script>
部分,在props
属性中创建一个maxRating
属性,它是一个数字,非必需,并且默认值为5
。在data
属性中,我们需要创建我们的rating
属性,其默认值为0
。在methods
属性中,我们需要创建三个方法:updateRating
、emitFinalVoting
和getStarName
。updateRating
方法将保存评分到数据中,emitFinalVoting
将调用updateRating
并通过final-vote
事件将评分传递给父组件,getStarName
将接收一个值并返回星级的图标名称。
<script>
export default {
name: 'StarRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
data: () => ({
rating: 0,
}),
methods: {
updateRating(value) {
this.rating = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rating);
},
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
- 在组件的
<template>
部分,我们需要创建一个<slot>
组件来放置星级评分之前的文本。我们将根据通过props
属性接收到的maxRating
值创建一个动态星级列表。创建的每个星级都将在mouseenter
、focus
和click
事件上附加一个监听器。当触发mouseenter
和focus
时,将调用updateRating
方法,而click
将调用emitFinalVote
方法。
<template>
<div class="starRating">
<span class="rateThis">
<slot />
</span>
<ul>
<li
v-for="rate in maxRating"
:key="rate"
@mouseenter="updateRating(rate)"
@click="emitFinalVote(rate)"
@focus="updateRating(rate)"
>
<i class="material-icons">
{{ getStarName(rate) }}
</i>
</li>
</ul>
</div>
</template>
- 我们需要将 Material Design 图标导入我们的应用程序。在
styles
文件夹中创建一个名为materialIcons.css
的新样式文件,并添加font-family
的 CSS 样式规则。
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-
Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons' !important;
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
- 打开
main.js
文件,并将创建的样式表导入其中。css-loader
将处理 JavaScript 文件中导入的.css
文件的处理。这将有助于开发,因为您不需要在其他地方重新导入文件。
import Vue from 'vue';
import App from './App.vue';
import './style/materialIcons.css';
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
- 为了给我们的组件添加样式,我们将在
src/style
文件夹中创建一个名为starRating.css
的通用样式文件。在那里,我们将添加StarRatingDisplay
和StarRatingInput
组件之间共享的通用样式。
.starRating {
user-select: none;
display: flex;
flex-direction: row;
}
.starRating * {
line-height: 0.9rem;
}
.starRating .material-icons {
font-size: .9rem !important;
color: orange;
}
ul {
display: inline-block;
padding: 0;
margin: 0;
}
ul > li {
list-style: none;
float: left;
}
- 在组件的
<style>
部分,我们需要创建所有的 CSS 样式表规则。然后,在位于src/components
文件夹中的StarRatingInput.vue
组件文件上,我们需要向<style>
添加scoped
属性,以便所有的 CSS 样式表规则不会影响应用程序中的任何其他元素。在这里,我们将导入我们创建的通用样式,并为输入添加新样式:
<style scoped>
@import '../style/starRating.css';
.starRating {
justify-content: space-between;
}
.starRating * {
line-height: 1.7rem;
}
.starRating .material-icons {
font-size: 1.6rem !important;
}
.rateThis {
display: inline-block;
color: rgba(0, 0, 0, .65);
font-size: 1rem;
}
</style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的样子:
创建 StarRatingDisplay 组件
现在我们有了输入,我们需要一种方法来向用户显示所选的选择。按照以下步骤创建StarRatingDisplay
组件:
在src/components
文件夹中创建一个名为StarRatingDisplay.vue
的新组件。
在组件的<script>
部分,在props
属性中,我们需要创建三个新属性:maxRating
,rating
和votes
。它们三个都将是数字,非必需的,并且有默认值。在methods
属性中,我们需要创建一个名为getStarName
的新方法,它将接收一个值并返回星星的图标名称:
<script>
export default {
name: 'StarRatingDisplay',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
- 在
<template>
中,我们需要根据通过props
属性接收到的maxRating
值创建一个动态星星列表。在列表之后,我们需要显示我们收到的投票数,如果我们收到任何投票,我们也会显示它们:
<template>
<div class="starRating">
<ul>
<li
v-for="rate in maxRating"
:key="rate"
>
<i class="material-icons">
{{ getStarName(rate) }}
</i>
</li>
</ul>
<span class="rating">
{{ rating }}
</span>
<span
v-if="votes"
class="votes"
>
({{ votes }})
</span>
</div>
</template>
- 在组件的
<style>
部分,我们需要创建所有的 CSS 样式表规则。我们需要向<style>
添加scoped
属性,以便所有的 CSS 样式表规则不会影响应用程序中的任何其他元素。在这里,我们将导入我们创建的通用样式,并为显示添加新样式:
<style scoped>
@import '../style/starRating.css';
.rating, .votes {
display: inline-block;
color: rgba(0,0,0, .65);
font-size: .75rem;
margin-left: .4rem;
}
</style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的样子:
创建 StarRating 组件
创建输入和显示后,我们需要将两者合并到一个单独的组件中。这个组件将是我们在应用程序中使用的最终组件。
按照以下步骤创建最终的StarRating
组件:
在src/components
文件夹中创建一个名为StarRating.vue
的新文件。
在组件的<script>
部分,我们需要导入StarRatingDisplay
和StarRatingInput
组件。在props
属性中,我们需要创建三个新属性:maxRating
,rating
和votes
。它们三个都将是数字,非必需的,并且有一个默认值。在data
属性中,我们需要创建我们的rating
属性,其默认值为0
,并且一个名为voted
的属性,其默认值为false
。在methods
属性中,我们需要添加一个名为vote
的新方法,它将接收rank
作为参数。它将把rating
定义为接收到的值,并将voted
组件的内部变量定义为true
:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
- 在
<template>
部分,我们将放置两个组件,显示评分的输入:
<template>
<div>
<StarRatingInput
v-if="!voted"
:max-rating="maxRating"
@final-vote="vote"
>
Rate this Place
</StarRatingInput>
<StarRatingDisplay
v-else
:max-rating="maxRating"
:rating="rating || rank"
:votes="votes"
/>
</div>
</template>
子组件上的数据操作
现在我们所有的组件都准备好了,我们需要将它们添加到我们的应用程序中。基本应用程序将访问子组件,并将评分设置为 5 星。
现在,按照以下步骤来理解和操作子组件中的数据:
在App.vue
文件中,在组件的<template>
部分,删除MaterialCardBox
组件的main-text
属性,并将其放置为组件的默认插槽。
在放置的文本之前,我们将添加StarRating
组件。我们将为其添加一个ref
属性。此属性将指示 Vue 将此组件直接链接到组件的this
对象中的一个特殊属性。在操作按钮中,我们将为点击事件添加监听器——一个用于resetVote
,另一个用于forceVote
。
<template>
<div id="app">
<MaterialCardBox
header="Material Card Header"
sub-header="Card Sub Header"
show-media
show-actions
img-src="https://picsum.photos/300/200"
>
<p>
<StarRating
ref="starRating"
/>
</p>
<p>
The path of the righteous man is beset on all sides by the
iniquities of the selfish and the tyranny of evil men.
</p>
<template v-slot:action>
<MaterialButton
background-color="#027be3"
text-color="#fff"
@click="resetVote"
>
Reset
</MaterialButton>
<MaterialButton
background-color="#26a69a"
text-color="#fff"
is-flat
@click="forceVote"
>
Rate 5 Stars
</MaterialButton>
</template>
</MaterialCardBox>
</div>
</template>
- 在组件的
<script>
部分,我们将创建一个methods
属性,并添加两个新方法:resetVote
和forceVote
。这些方法将访问StarRating
组件并重置数据或将数据设置为 5 星投票:
<script>
import MaterialCardBox from './components/MaterialCardBox.vue';
import MaterialButton from './components/MaterialButton.vue';
import StarRating from './components/StarRating.vue';
export default {
name: 'App',
components: {
StarRating,
MaterialButton,
MaterialCardBox,
},
methods: {
resetVote() {
this.$refs.starRating.rank = 0;
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.rank = 5;
this.$refs.starRating.voted = true;
},
},
};
</script>
它是如何工作的...
当ref
属性添加到组件时,Vue 会将对所引用元素的链接添加到 JavaScript 的this
属性对象内的$refs
属性中。从那里,您可以完全访问组件。
这种方法通常用于操作 HTML DOM 元素,而无需调用文档查询选择器函数。
然而,此属性的主要功能是直接访问 Vue 组件,使您能够执行函数并查看组件的计算属性、变量和更改的变量,就像从外部完全访问组件一样。
还有更多...
与父组件可以访问子组件的方式相同,子组件可以通过在this
对象上调用$parent
来访问父组件。事件可以通过调用$root
属性来访问 Vue 应用程序的根元素。
另请参阅
您可以在vuejs.org/v2/guide/components-edge-cases.html#Accessing-the-Parent-Component-Instance
找到有关父子通信的更多信息。
创建动态注入组件
有些情况下,您的组件可以由您收到的变量类型或数据类型来定义;然后,您需要在不需要设置大量 Vue v-if
、v-else-if
和v-else
指令的情况下即时更改组件。
在这些情况下,最好的做法是使用动态组件,当计算属性或函数可以定义要呈现的组件时,并且决定是实时进行的。
如果有两个响应,这些决策有时可能很简单,但在长的 switch case 中可能会更复杂,其中可能有一长串可能要使用的组件。
准备工作
先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用 Vue-CLI 创建我们的 Vue 项目,就像我们在第二章中的'使用 Vue CLI 创建您的第一个项目'配方中所做的那样,介绍 TypeScript 和 Vue 生态系统,或者使用'访问您的子组件数据'配方中的项目。
按照以下步骤创建动态注入组件:
打开StarRating.vue
组件。
在组件的<script>
部分,我们需要创建一个带有名为starComponent
的新计算值的computed
属性。此值将检查用户是否已投票。如果他们没有,它将返回StarRatingInput
组件;否则,它将返回StarRatingDisplay
组件:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
- 在组件的
<template>
部分,我们将删除现有组件,并用一个名为<component>
的特殊组件替换它们。这个特殊组件有一个命名属性,您可以指向任何返回有效 Vue 组件的地方。在我们的例子中,我们将指向计算属性starComponent
。我们将把从这两个组件中定义的所有绑定 props 放在这个新组件中,包括放在<slot>
中的文本:
<template>
<component
:is="starComponent"
:max-rating="maxRating"
:rating="rating || rank"
:votes="votes"
@final-vote="vote"
>
Rate this Place
</component>
</template>
工作原理...
使用 Vue 特殊的<component>
组件,我们声明了根据计算属性设置的规则应该呈现什么组件。
作为通用组件,您总是需要确保每个可以呈现的组件都存在。最好的方法是使用v-bind
指令与需要定义的 props 和规则,但也可以直接在组件上定义,因为它将作为 prop 传递下去。
另请参阅
您可以在vuejs.org/v2/guide/components.html#Dynamic-Components
找到有关动态组件的更多信息。
创建依赖注入组件
直接从子组件或父组件访问数据而不知道它们是否存在可能非常危险。
在 Vue 中,可以使您的组件的行为像一个接口,并拥有一个在开发过程中不会改变的常见和抽象函数。依赖注入的过程是开发世界中的一个常见范例,并且也已经在 Vue 中实现。
使用内部 Vue 依赖注入有一些利弊,但在开发时,确保子组件知道父组件的期望总是一个好方法。
准备工作
先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以像在第二章中的“使用 Vue CLI 创建您的第一个项目”中那样使用 Vue-CLI 创建我们的 Vue 项目,或者使用“创建动态注入组件”中的项目。
现在,按照以下步骤创建一个依赖注入组件:
打开StarRating.vue
组件。
在组件的<script>
部分,添加一个名为provide
的新属性。在我们的情况下,我们将只添加一个键值来检查组件是否是特定组件的子级。在属性中创建一个对象,其中包含starRating
键和true
值:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
provide: {
starRating: true,
},
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
打开StarRatingDisplay.vue
文件。
在组件的<script>
部分,我们将添加一个名为inject
的新属性。此属性将接收一个名为starRating
的键的对象,值将是一个具有default()
函数的对象。如果此组件不是StarRating
组件的子级,则此函数将记录错误:
<script>
export default {
name: 'StarRatingDisplay',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingDisplay need to be a child of
StarRating');
},
},
},
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
打开StarRatingInput.vue
文件。
在组件的<script>
部分,我们将添加一个名为inject
的新属性。此属性将接收一个名为starRating
的键的对象,值将是一个具有default()
函数的对象。如果此组件不是StarRating
组件的子级,则此函数将记录错误:
<script>
export default {
name: 'StarRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingInput need to be a child of
StarRating');
},
},
},
data: () => ({
rating: 0,
}),
methods: {
updateRating(value) {
this.rating = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rating);
},
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
它是如何工作的...
在运行时,Vue 将检查StarRatingDisplay
和StarRatingInput
组件中的starRating
的注入属性,如果父组件未提供此值,则将在控制台上记录错误。
使用组件注入通常用于在绑定组件之间保持共同接口的方式,例如菜单和项目。项目可能需要存储在菜单中的某些功能或数据,或者我们可能需要检查它是否是菜单的子级。
依赖注入的主要缺点是共享元素上不再具有响应性。因此,它主要用于共享功能或检查组件链接。
另请参阅
您可以在vuejs.org/v2/guide/components-edge-cases.html#Dependency-Injection
找到有关组件依赖注入的更多信息。
创建一个组件混合
有时您会发现自己一遍又一遍地重写相同的代码。但是,有一种方法可以防止这种情况,并使自己更加高效。
您可以使用所谓的mixin
,这是 Vue 中的一个特殊代码导入,它将外部代码部分连接到当前组件。
准备工作
先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用 Vue-CLI 创建我们的 Vue 项目,就像我们在第二章中的'使用 Vue CLI 创建您的第一个项目'中所做的那样,或者使用'创建依赖注入组件'食谱中的项目。
让我们按照以下步骤创建一个组件mixin
:
打开StarRating.vue
组件。
在<script>
部分,我们需要将props
属性提取到一个名为starRatingDisplay.js
的新文件中,我们需要在mixins
文件夹中创建这个新文件。这个新文件将是我们的第一个mixin
,并且看起来像这样:
export default {
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
};
- 回到
StarRating.vue
组件,我们需要导入这个新创建的文件,并将其添加到一个名为mixin
的新属性中:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
import StarRatingDisplayMixin from '../mixins/starRatingDisplay';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
mixins: [StarRatingDisplayMixin],
provide: {
starRating: true,
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
现在,我们将打开StarRatingDisplay.vue
文件。
在<script>
部分,我们将inject
属性提取到一个名为starRatingChild.js
的新文件中,该文件将被创建在mixins
文件夹中。这将是我们inject
属性的mixin
:
export default {
inject: {
starRating: {
default() {
console.error('StarRatingDisplay need to be a child of
StarRating');
},
},
},
};
- 在
StarRatingDisplay.vue
文件中,在<script>
部分,我们将提取methods
属性到一个名为starRatingName.js
的新文件中,该文件将被创建在mixins
文件夹中。这将是我们getStarName
方法的mixin
:
export default {
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
- 回到
StarRatingDisplay.vue
文件,我们需要导入这些新创建的文件,并将它们添加到一个名为mixin
的新属性中:
<script>
import StarRatingDisplayMixin from '../mixins/starRatingDisplay';
import StarRatingNameMixin from '../mixins/starRatingName';
import StarRatingChildMixin from '../mixins/starRatingChild';
export default {
name: 'StarRatingDisplay',
mixins: [
StarRatingDisplayMixin,
StarRatingNameMixin,
StarRatingChildMixin,
],
};
</script>
打开StarRatingInput.vue
文件。
在<script>
部分,我们移除inject
属性,并将props
属性提取到一个名为starRatingBase.js
的新文件中,该文件将被创建在mixins
文件夹中。这将是我们props
属性的mixin
:
export default {
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
},
};
- 回到
StarRatingInput.vue
文件,我们需要将rating
数据属性重命名为rank
,并且在getStarName
方法中,我们需要添加一个新的常量,该常量将接收rating
属性或rank
数据。最后,我们需要导入starRatingChild
mixin
和starRatingBase
mixin
:
<script>
import StarRatingBaseMixin from '../mixins/starRatingBase';
import StarRatingChildMixin from '../mixins/starRatingChild';
export default {
name: 'StarRatingInput',
mixins: [
StarRatingBaseMixin,
StarRatingChildMixin,
],
data: () => ({
rank: 0,
}),
methods: {
updateRating(value) {
this.rank = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rank);
},
getStarName(rate) {
const rating = (this.rating || this.rank);
if (rate <= rating) {
return 'star';
}
if (Math.fround((rate - rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
它是如何工作的...
mixins
的工作原理就像对象合并一样,但确保不要用导入的属性替换组件中已经存在的属性。
mixins
属性的顺序也很重要,因为它们将被检查并作为for
循环导入,所以最后一个mixin
不会改变任何祖先的属性。
在这里,我们将我们的代码中的许多重复部分拆分成了四个不同的小 JavaScript 文件,这样更容易维护并提高了生产力,而无需重写代码。
另请参阅
您可以在vuejs.org/v2/guide/mixins.html
找到有关 mixins 的更多信息。
惰性加载您的组件
webpack
和 Vue 天生就是一对。当使用webpack
作为 Vue 项目的打包工具时,可以使组件在需要时或异步加载。这通常被称为惰性加载。
准备工作
先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以像在第二章中的'使用 Vue CLI 创建您的第一个项目'配方中那样使用 Vue-CLI 创建我们的 Vue 项目,或者使用'创建组件 mixin'配方中的项目。
现在,按照以下步骤使用惰性加载技术导入您的组件:
打开App.vue
文件。
在组件的<script>
部分,我们将在脚本顶部获取导入并将它们转换为每个组件的惰性加载函数:
<script>
export default {
name: 'App',
components: {
StarRating: () => import('./components/StarRating.vue'),
MaterialButton: () => import('./components/MaterialButton.vue'),
MaterialCardBox: () =>
import('./components/MaterialCardBox.vue'),
},
methods: {
resetVote() {
this.$refs.starRating.rank = 0;
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.rank = 5;
this.$refs.starRating.voted = true;
},
},
};
</script>
它是如何工作的...
当我们声明一个为每个组件返回import()
函数的函数时,webpack
知道这个导入函数将进行代码拆分,并且它将使组件成为捆绑包中的一个新文件。
import()
函数是由 TC39 提出的一个模块加载语法的建议。这个函数的基本功能是异步加载任何声明为模块的文件,避免了在第一次加载时放置所有文件的需要。
另请参阅
您可以在vuejs.org/v2/guide/components-dynamic-async.html#Async-Components
找到有关异步组件的更多信息。
您可以在github.com/tc39/proposal-dynamic-import
找到有关 TC39 动态导入的更多信息。
第五章:通过 HTTP 请求从网络获取数据
数据现在是日常生活的一部分。如果没有数据,你就不会读到这本书,也不会试图了解更多关于 Vue 的知识。
了解如何在应用程序中获取和发送数据是开发人员的要求,而不仅仅是一个额外的技能。学习的最佳方式是通过实践,并找出它在幕后是如何完成的。
在这一章中,我们将学习如何使用 Fetch API 和当前最流行的 API 库axios
来构建自己的 API 数据操作。
在这一章中,我们将涵盖以下的配方:
创建一个 Fetch API 的 HTTP 客户端包装器
创建一个随机猫图片或 GIF 组件
使用MirageJS
创建本地虚拟 JSON API 服务器
使用axios
作为新的 HTTP 客户端
创建不同的axios
实例
为axios
创建请求和响应拦截器
使用axios
和Vuesax
创建 CRUD 接口
技术要求
在这一章中,我们将使用 Node.js 和 Vue CLI。
注意,Windows 用户!你需要安装一个名为windows-build-tools
的 NPM 包,以便能够安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Vue CLI,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个 Fetch API 的 HTTP 客户端包装器
Fetch API 是旧的XMLHttpRequest
的子代。它有一个改进的 API 和一个基于Promises
的新而强大的功能集。
Fetch API 既简单又基于两个对象Request
和Response
的通用定义,使其可以在浏览器中的任何地方使用。浏览器的 Fetch API 也可以在window
或service worker
中执行。对于这个 API 的使用没有限制。
在这个配方中,我们将学习如何创建一个 Fetch API 的包装器,使 API 调用更简单。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们可以使用在第二章中创建的使用 Vue CLI 创建的 Vue 项目,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
创建包装器
首先,我们需要创建一个新的 API 包装器来在这个教程中使用。这将是我们将在所有 HTTP 方法中使用的主要文件。
让我们按照以下步骤创建基本包装器:
在src/http
文件夹中创建一个名为baseFetch.js
的新文件。
我们将创建一个异步函数,它将作为参数接收url
,method
和options
的三个变量。这将是一个柯里化函数,第二个函数将接收type
作为参数:
export default async (url, method, options = {}) => {
let httpRequest;
if (method.toUpperCase() === 'GET') {
httpRequest = await fetch(url, {
cache: 'reload',
...options,
});
} else {
httpRequest = fetch(url, {
method: method.toUpperCase(),
cache: 'reload',
...options,
});
}
return (type) => {
switch (type.toLocaleLowerCase()) {
case 'json':
return httpRequest.json();
case 'blob':
return httpRequest.blob();
case 'text':
return httpRequest.text();
case 'formdata':
return httpRequest.formData();
default:
return httpRequest.arrayBuffer();
}
}; };
创建 API 方法
现在我们需要制作我们的 HTTP 方法函数。这些函数将使用包装器来执行浏览器的 Fetch API 并返回响应。
按照以下步骤创建每一个 API 方法调用:
在src/http
文件夹中创建一个名为fetchApi.js
的新文件。
我们需要从我们在第一步创建的文件中导入baseHttp
:
import baseHttp from './baseFetch';
现在在接下来的部分,我们将创建我们包装器中可用的每一个 HTTP 方法。
GET 方法函数
在这些步骤中,我们将创建HTTP GET方法。按照以下每一条指示来创建getHttp
函数:
创建一个名为getHttp
的常量。
定义一个常量作为一个异步函数,接收三个参数,url
,type
和options
。type
参数将默认值为'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
,'get'
作为第二个参数,options
作为第三个参数,并立即执行带有我们收到的type
参数的函数:
export const getHttp = async (url, type = 'json', options) => (await
baseHttp(url, 'get', options))(type);
POST 方法函数
在这部分,我们将创建HTTP POST方法。按照以下步骤来创建postHttp
函数:
创建一个名为postHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
参数和'post'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
参数执行返回的函数。body
通常是 JSON 或 JavaScript 对象。如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const postHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'post',
{
body,
...options,
}))(type);
PUT 方法函数
现在我们正在创建一个HTTP PUT方法。使用以下步骤创建putHttp
函数:
创建一个名为putHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'put'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
参数执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const putHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'put',
{
body,
...options,
}))(type);
PATCH 方法函数
是时候创建一个HTTP PATCH方法了。按照以下步骤创建patchHttp
函数:
创建一个名为patchHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'patch'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const patchHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'patch',
{
body,
...options,
}))(type);
更新方法函数
在这一部分,我们正在创建一个HTTP UPDATE方法。按照以下步骤创建updateHttp
函数:
创建一个名为updateHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'update'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const updateHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'update',
{
body,
...options,
}))(type);
DELETE 方法函数
在这最后一步,我们将创建一个DELETE HTTP方法。按照以下步骤创建deleteHttp
函数:
创建一个名为deleteHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。类型参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'delete'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const deleteHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'delete',
{
body,
...options,
}))(type);
它是如何工作的...
在这个教程中,我们为window
元素上呈现的Fetch
API 创建了一个包装器。这个包装器由一个柯里化和闭包函数组成,第一个函数接收 Fetch API 的 URL 数据、方法和选项,而结果函数是 Fetch API 的响应转换器。
在包装器中,函数的第一部分将创建我们的fetch
请求。在那里,我们需要检查它是否是GET方法,所以我们只需要用url
参数执行它并省略其他参数。函数的第二部分负责将fetch
响应转换。它将在type
参数之间切换,并根据正确的参数执行检索函数。
要接收请求的最终数据,您始终需要在请求之后调用响应翻译器,就像以下示例中一样:
getHttp('https://jsonplaceholder.typicode.com/todos/1', 'json').then((response) => { console.log(response)); }
这将从 URL 获取数据,并将响应转换为 JSON/JavaScript 对象。
我们制作的第二部分是方法翻译器。我们为每个 REST 动词制作了函数,以便更轻松地使用。 GET 动词没有能力传递任何body
,但所有其他动词都能够在请求中传递body
。
另请参阅
你可以在developer.mozilla.org/en-US/docs/Web/API/Fetch_API
找到有关 Fetch API 的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
找到有关 FormData 的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/API/Body/body
找到有关 Fetch 响应主体的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/API/Headers
找到有关标头的更多信息。
您可以在developer.mozilla.org/
en-US/docs/Web/API/Request找到有关请求的更多信息。
创建一个随机猫图像或 GIF 组件
众所周知,互联网上有许多猫的 GIF 和视频。我相信如果我们删除所有与猫有关的内容,我们将会出现网络黑屏。
了解有关 Fetch API 以及如何在组件内使用它的最佳方法是制作一个随机猫图像或 GIF 组件。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用在“*将 Fetch API 包装为 HTTP 客户端”配方中使用的 Vue 项目和 Vue CLI,或者我们可以启动一个新的项目。
要启动新的项目,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows),并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
创建组件
在本教程中,我们将使用第四章组件、混合和功能组件中创建的组件进行视觉元素。您也可以使用简单的 HTML 元素来实现相同的结果。
我们将把这个组件的创建分为三个步骤:<script>
、<template>
和<style>
。
单文件组件<script>
部分
按照以下步骤创建单文件组件的<script>
部分:
在src/components
文件夹中创建一个名为RandomCat.vue
的新文件并打开它。
从我们在'将 Fetch API 包装为 HTTP 客户端创建包装器'教程中制作的fetchApi
包装器中导入getHttp
函数:
import { getHttp } from '../http/fetchApi';
- 在
component
属性中异步导入MaterialButton
和MaterialCardBox
组件:
components: {
MaterialButton: () => import('./MaterialButton.vue'),
MaterialCardBox: () => import('./MaterialCardBox.vue'), },
- 在
data
属性中,我们需要创建一个名为kittyImage
的新数据值,默认为空字符串:
data: () => ({
kittyImage: '', }),
- 在
methods
属性中,我们需要创建getImage
方法,它将以Blob
的形式获取图片,并将其作为URL.createObjectURL
返回。我们还需要创建newCatImage
方法,它将获取一张新的猫的静态图片,以及newCatGif
方法,它将获取一个新的猫的 GIF:
methods: {
async getImage(url) {
return URL.createObjectURL(await getHttp(url, 'blob'));
},
async newCatImage() {
this.kittyImage = await this.getImage('https://cataas.com/cat');
},
async newCatGif() {
this.kittyImage = await
this.getImage('https://cataas.com/cat/gif');
},
},
- 在
beforeMount
生命周期钩子中,我们需要将其设置为异步,并执行newCatImage
方法:
async beforeMount() {
await this.newCatImage();
},
单文件组件<template>
部分
按照以下步骤创建单文件组件的<template>
部分:
- 首先,我们需要添加带有标题和副标题的
MaterialCardBox
组件,激活media
和action
部分,并为media
和action
创建<template>
命名插槽:
<MaterialCardBox
header="Cat as a Service"
sub-header="Random Cat Image"
show-media
show-actions >
<template
v-slot:media> </template>
<template v-slot:action> </template> </MaterialCardBox>
- 在
<template>
中名为media
的插槽中,我们需要添加一个<img>
元素,它将接收一个 URIBlob
,当kittyImage
变量中有任何数据时,它将显示出来,否则将显示一个加载图标:
<img
v-if="kittyImage"
alt="Meow!"
:src="kittyImage"
style="width: 300px;" >
<p v-else style="text-align: center">
<i class="material-icons">
cached
</i>
</p>
- 在
<template>
中名为action
的插槽中,我们将创建两个按钮,一个用于获取猫的图片,另一个用于获取猫的 GIF,两者都将在@click
指令上有一个事件监听器,调用一个函数来获取相应的图片:
<MaterialButton
background-color="#4ba3c7"
text-color="#fff"
@click="newCatImage" >
<i class="material-icons">
pets
</i> Cat Image
</MaterialButton> <MaterialButton
background-color="#005b9f"
text-color="#fff"
@click="newCatGif" >
<i class="material-icons">
pets
</i> Cat GIF
</MaterialButton>
单文件组件<style>
部分
在组件的<style>
部分中,我们需要设置body font-size
以便基于rem
和em
进行 CSS 样式计算:
<style>
body {
font-size: 14px;
} </style>
启动和运行您的新组件
按照以下步骤将您的组件添加到 Vue 应用程序中:
在src
文件夹中的App.vue
文件中打开。
在components
属性中,异步导入RandomCat.vue
组件:
<script> export default { name: 'App',
components: {
RandomCat: () => import('./components/RandomCat'),
}, }; </script>
- 在文件的
<template>
部分中,声明导入的组件:
<template>
<div id="app">
<random-cat />
</div> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
使用getHttp
包装器,组件能够获取 URL 并将其作为Blob
类型检索出来。有了这个响应,我们可以使用URL.createObjectUrl
导航方法,并将Blob
作为参数传递,以获取一个有效的图像 URL,该 URL 可以用作src
属性。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
找到有关URL.createObjectUrl
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/API/Body/blob
找到有关Blob
响应类型的更多信息。
使用 MirageJS 创建您的虚假 JSON API 服务器
为了测试、开发或设计而伪造数据总是一个问题。在开发阶段展示应用程序时,您需要有一个大的 JSON 或者制作一个自定义服务器来处理任何数据更改。
现在有一种方法可以帮助开发人员和 UI 设计师在不需要编写外部服务器的情况下实现这一点 - 一个名为 MirageJS 的新工具,它是在浏览器上运行的服务器模拟器。
在这个配方中,我们将学习如何使用 MirageJS 作为模拟服务器并在其上执行 HTTP 请求。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用我们在“将 Fetch API 作为 HTTP 客户端创建包装器”配方中使用的 Vue 项目和 Vue CLI,或者我们可以启动一个新的项目。
要启动一个新项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create visual-component
CLI 将询问一些问题,这将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
创建模拟服务器
在这个配方中,我们将使用在“将 Fetch API 包装为 HTTP 客户端的创建包装器”配方中制作的fetchApi
包装器的getHttp
函数。
通过下一步和部分来创建您的MirageJS
模拟服务器:
将MirageJS
服务器安装到您的软件包中。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save miragejs
本配方使用的版本是 0.1.32。注意MirageJS
的任何更改,因为目前还没有当前的 LTS 版本。
现在在接下来的部分中,我们将创建每一个由 MirageJS 服务器模拟的 HTTP 方法。
创建模拟数据库
在这一部分,我们将创建一个MirageJS
数据库,用于存储临时数据。按照以下步骤创建它:
在src/server
文件夹中创建一个名为db.js
的新文件,用于初始加载的数据。
我们需要为这个文件创建一个 JavaScript 对象作为默认导出,其中包含我们希望服务器具有的初始数据:
export default {
users: [
{ name: 'Heitor Ramon Ribeiro',
email: '[email protected]',
age: 31,
country: 'Brazil',
active: true,
},
], };
创建 GET 路由函数
在这一部分,我们将创建一个由MirageJS
服务器模拟的HTTP GET方法。按照以下步骤创建它:
对于GET方法,我们需要在src/server
文件夹中创建一个名为get.js
的新文件。
对于这个配方,我们将创建一个通用的getFrom
函数,该函数接收一个键作为参数并返回一个函数。这个返回的函数返回一个直接指向本地数据库的指定键:
export const getFrom = key => ({ db }) => db[key]; export default {
getFrom, };
创建 POST 路由函数
在这一部分,我们将创建HTTP POST方法,这将由MirageJS
服务器模拟。按照以下步骤创建它:
对于POST方法,我们需要在src/server
文件夹中创建一个名为post.js
的新文件。
对于这个配方,我们将创建一个通用的postFrom
函数,该函数接收一个键作为参数并返回一个函数。这个返回的函数将解析 HTTP 请求体的data
属性,并返回服务器模式的内部函数,将数据插入数据库。使用key
参数,模式知道我们正在处理哪个表:
export const postFrom = key => (schema, request) => {
const { data } = typeof request.requestBody === 'string'
? JSON.parse(request.requestBody)
: request.requestBody; return schema.db[key].insert(data); }; export default {
postFrom, };
创建 PATCH 路由函数
在本节中,我们将创建MirageJS
服务器模拟的HTTP PATCH方法。按照以下步骤创建它:
对于PATCH方法,我们需要在src/server
文件夹中创建一个名为patch.js
的新文件。
对于这个配方,我们将制作一个通用的patchFrom
函数,该函数接收一个键作为参数并返回一个函数。返回的函数将解析 HTTP 请求体的data
属性,并返回一个服务器模式的内部函数,该函数更新具有与数据一起传递的id
属性的特定对象。使用key
参数,模式知道我们正在处理哪个表:
export const patchFrom = key => (schema, request) => {
const { data } = typeof request.requestBody === 'string'
? JSON.parse(request.requestBody)
: request.requestBody; return schema.db[key].update(data.id, data); }; export default {
patchFrom, };
创建 DELETE 路由函数
在本节中,我们将创建MirageJS
服务器模拟的HTTP DELETE方法。按照以下步骤创建它:
对于DELETE方法,我们需要在src/server
文件夹中创建一个名为delete.js
的新文件。
对于这个配方,我们将制作一个通用的patchFrom
函数,该函数接收一个键作为参数并返回一个函数。返回的函数将解析 HTTP 请求体的data
属性,并返回一个服务器模式的内部函数,该函数删除具有通过路由REST参数传递给服务器的id
属性的特定对象。使用key
参数,模式知道我们正在处理哪个表:
export const deleteFrom = key => (schema, request) =>
schema.db[key].remove(request.params.id); export default {
deleteFrom, };
创建服务器
在本节中,我们将创建MirageJS
服务器和可用的路由。按照以下步骤创建服务器:
在src/server
文件夹中创建一个名为server.js
的新文件。
接下来,我们需要导入Server
类,baseData
和路由方法:
import { Server } from 'miragejs'; import baseData from './db'; import { getFrom } from './get'; import { postFrom } from './post'; import { patchFrom } from './patch'; import { deleteFrom } from './delete';
- 创建一个全局变量
server
,并将此变量设置为Server
类的新执行:
window.server = new Server({});
- 在
Server
类的构造选项中,添加一个名为seeds
的新属性。此属性是一个接收服务器(srv
)作为参数并执行srv.db.loadData
函数传递baseDate
作为参数的函数:
seeds(srv) {
srv.db.loadData({ ...baseData }); },
- 现在我们需要添加相同的构造选项到一个名为
routes
的新属性中,它将创建模拟服务器路由。这个属性是一个函数,在函数体内,我们需要设置模拟服务器的namespace
和服务器响应的毫秒延迟。将有四个路由。对于创建路由,我们将创建一个名为/users
的新路由,监听POST方法。对于读取路由,我们将创建一个名为/users
的新路由,监听GET方法。对于更新路由,我们将创建一个名为/users/:id
的新路由,监听PATCH方法,最后,对于删除路由,我们将创建一个名为/users
的新路由,监听DELETE方法:
routes() {
this.namespace = 'api'; this.timing = 750; this.get('/users', getFrom('users')); this.post('/users', postFrom('users')); this.patch('/users/:id', patchFrom('users')); this.delete('/users/:id', deleteFrom('users'));
},
添加到应用程序
在这一部分,我们将MirageJS
服务器添加到 Vue 应用程序中。按照以下步骤使服务器对您的 Vue 应用程序可用:
在src
文件夹中打开main.js
文件。
我们需要将服务器声明为第一个导入声明,这样它就可以在应用程序的初始加载时可用:
import './server/server'; import Vue from 'vue'; import App from './App.vue'; Vue.config.productionTip = false; new Vue({
render: h => h(App), }).$mount('#app');
创建组件
现在我们有了服务器,我们需要测试它。为了这样做,我们将创建一个简单的应用程序,运行每个 HTTP 方法,并显示每个调用的结果。
在接下来的部分,我们将创建一个简单的 Vue 应用程序。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下步骤创建它:
在src
文件夹中打开App.vue
文件。
从我们在'将 Fetch API 包装为 HTTP 客户端的创建包装器'中制作的fetchHttp
包装器中导入getHttp
,postHttp
,patchHttp
和deleteHTTP
方法:
import {
getHttp,
postHttp,
patchHttp,
deleteHttp, } from './http/fetchApi';
- 在
data
属性中,我们需要创建三个新属性来使用,response
,userData
和userId
:
data: () => ({
response: undefined,
userData: '',
userId: undefined,
}),
- 在
methods
属性中,我们需要创建四个新方法,getAllUsers
,createUser
,updateUser
和deleteUser
:
methods: {
async getAllUsers() {
},
async createUser() {
},
async updateUser() {
},
async deleteUser() {
}, },
- 在
getAllUsers
方法中,我们将设置响应数据属性为api/users
路由的getHttp
函数的结果:
async getAllUsers() {
this.response = await getHttp(`${window.location.href}api/users`); },
- 在
createUser
方法中,我们将接收一个data
参数,这将是一个对象,我们将把它传递给api/users
路由上的postHttp
,然后执行getAllUsers
方法:
async createUser(data) {
await postHttp(`${window.location.href}api/users`, { data });
await this.getAllUsers(); },
- 对于
updateUser
方法,我们将接收一个data
参数,这将是一个对象,我们将其传递给patchHttp
,在api/users/:id
路由上使用对象上的id
属性作为路由上的:id
。之后,我们将执行getAllUsers
方法:
async updateUser(data) {
await patchHttp(`${window.location.href}api/users/${data.id}`,
{ data });
await this.getAllUsers(); },
- 最后,在
deleteUser
方法中,我们接收用户id
作为参数,该参数是一个数字,然后我们将其传递给deleteHttp
,在api/users/:id
路由上使用 ID 作为:id
。之后,我们执行getAllUsers
方法:
async deleteUser(id) {
await deleteHttp(`${window.location.href}api/users/${id}`, {}, 'text');
await this.getAllUsers(); },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下步骤创建它:
- 在模板的顶部,我们需要添加
response
属性,包裹在一个<pre>
HTML 元素中:
<h3>Response</h3> <pre>{{ response }}</pre>
- 对于用户的创建和更新,我们需要创建一个带有
v-model
指令绑定到userData
属性的textarea
HTML 输入:
<hr/> <h1> Create / Update User </h1> <label for="userData">
User JSON:
<textarea
id="userData"
v-model="userData"
rows="10"
cols="40"
style="display: block;"
></textarea> </label>
- 要发送这些数据,我们需要创建两个按钮,两者都在单击事件上绑定了事件侦听器,使用
@click
指令分别指向createUser
和updateUser
,并在执行时传递userData
:
<button
style="margin: 20px;"
@click="createUser(JSON.parse(userData))" >
Create User
</button> <button
style="margin: 20px;"
@click="updateUser(JSON.parse(userData))" >
Update User
</button>
- 要执行DELETE方法,我们需要创建一个类型为
number
的输入 HTML 元素,并将v-model
指令绑定到userId
属性:
<h1> Delete User </h1> <label for="userData">
User Id:
<input type="number" step="1" v-model="userId"> </label>
- 最后,要执行此操作,我们需要创建一个按钮,该按钮将在单击事件上绑定一个事件侦听器,使用
@click
指令,将其指向deleteUser
方法,并在执行时传递userId
属性:
<button
style="margin: 20px;"
@click="deleteUser(userId)" >
Delete User
</button>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
MirageJS
的工作原理类似于拦截应用程序上发生的每个 HTTP 请求的拦截器。服务器拦截浏览器上的所有**XHR (XMLHttpRequest)**执行,并检查路由,以查看它是否与服务器创建的任何一个路由匹配。如果匹配,服务器将根据相应的路由执行函数。
作为具有基本 CRUD 功能的简单 REST 服务器,服务器具有类似模式的数据库结构,有助于创建用于存储数据的虚拟数据库。
另请参阅
您可以在github.com/miragejs/miragejs
找到有关 MirageJS 的更多信息。
使用 axios 作为新的 HTTP 客户端
当您需要一个用于 HTTP 请求的库时,毫无疑问axios
是您应该选择的。这个库被超过 150 万个开源项目和无数个闭源项目使用,是 HTTP 库之王。
它构建成适用于大多数浏览器,并提供了最完整的选项集之一-您可以自定义请求中的一切。
在这个食谱中,我们将学习如何将我们的 Fetch API 包装器更改为axios
并开始围绕它工作。
准备就绪
这个食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用在'使用 MirageJS 创建您的虚拟 JSON API 服务器'食谱中制作的 Vue 项目,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,Enter键继续,Spacebar键选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
从 Fetch API 更改为 Axios
在接下来的步骤中,我们将为 HTTP 包装器中使用的 Fetch API 更改为axios
库。按照以下步骤正确更改它:
- 在您的包中安装
axios
。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save axios
此食谱中使用的版本是 0.19.0。注意axios
的更改,因为该库尚无 LTS 版本。
打开src/http
文件夹中的baseFetch.js
文件。
简化该方法,使其接收三个参数,url
、method
和options
,并返回一个axios
方法,使用传递给实例构造函数的方法调用 HTTP 请求:
import axios from 'axios';
export default async (url, method, options = {}) => axios({
method: method.toUpperCase(),
url,
...options,
});
更改 GET 方法函数
在这部分中,我们正在更改HTTP GET方法。按照以下说明更改getHttp
函数:
打开src/http
文件夹中的fetchApi.js
文件。
在getHttp
函数中,我们将添加一个新的参数 param,并删除柯里化函数:
export const getHttp = async (
url,
params,
options, ) => baseHttp(url,
'get',
{
...options,
params,
});
更改 POST 方法函数
在这部分中,我们正在更改HTTP POST方法。按照以下说明更改postHttp
函数:
打开http
文件夹中的fetchApi.js
文件。
在postHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const postHttp = async (
url,
data,
options, ) => baseHttp(url,
'post',
{
data,
...options,
});
更改 PUT 方法函数
在这部分,我们正在更改HTTP PUT方法。按照以下说明更改putHttp
函数:
打开http
文件夹内的fetchApi.js
文件。
在putHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const putHttp = async (
url,
data,
options, ) => baseHttp(url,
'put',
{
data,
...options,
});
更改 PATCH 方法函数
在这部分,我们正在更改HTTP PATCH方法。按照以下说明更改patchHttp
函数:
打开http
文件夹内的fetchApi.js
文件。
在patchHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const patchHttp = async (
url,
data,
options, ) => baseHttp(url,
'patch',
{
data,
...options,
});
更改 UPDATE 方法函数
在这部分,我们正在更改HTTP UPDATE方法。按照以下说明更改updateHttp
函数:
打开http
文件夹内的fetchApi.js
文件。
在updateHttp
函数中,我们将添加一个新的参数 param,并删除柯里化函数:
export const updateHttp = async (
url,
data,
options, ) => baseHttp(url,
'update',
{
data,
...options,
});
更改 DELETE 方法函数
在这部分,我们正在更改HTTP DELETE方法。按照以下说明更改deleteHttp
函数:
打开http
文件夹内的fetchApi.js
文件。
在deleteHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const deleteHttp = async (
url,
data,
options, ) => baseHttp(url,
'delete',
{
data,
...options,
});
更改组件
在这部分,我们将改变组件与新函数的工作方式。按照以下说明正确更改它:
打开src
文件夹内的App.vue
文件。
在getAllUsers
方法中,我们需要更改响应的定义方式,因为axios
给我们提供了一个完全不同的响应对象,而不是 Fetch API:
async getAllUsers() {
const { data } = await getHttp(`${window.location.href}api/users`);
this.response = data;
},
- 在
deleteUser
方法中,我们可以直接将 URL 作为参数传递:
async deleteUser(id) {
await deleteHttp(`${window.location.href}api/users/${id}`);
await this.getAllUsers();
},
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
它是如何工作的...
当我们为 Fetch API 创建包装器时,我们使用了一种将 API 抽象成另一个接口的技术,这使得从 Fetch API 更改到axios
库成为可能。通过这样做,我们能够改进方法并简化函数的调用和处理方式。例如,GET 方法现在可以接收一个名为params的新参数,这些参数是 URL 查询参数的对象,将自动注入到 URL 中。
我们还必须更改响应的解释方式,因为axios
比 Fetch API 具有更健壮和完整的响应对象,后者只返回获取的响应本身。
另请参阅
您可以在github.com/axios/axios
找到有关axios
的更多信息。
创建不同的 axios 实例
使用axios
时,您可以运行多个实例,而它们互不干扰。例如,您可以有一个指向版本 1 的用户 API 的实例,另一个指向版本 2 的支付 API,两者共享相同的命名空间。
在这里,我们将学习如何创建各种axios
实例,因此您可以在不受问题或干扰的情况下使用尽可能多的 API 命名空间。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
操作步骤...
要启动我们的组件,我们可以使用在“使用 axios 作为新的 HTTP 客户端”食谱中使用的 Vue CLI 创建的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的实例,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,Enter键继续,Spacebar键选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
更改 HTTP 函数
创建多个axios
实例时,调用axios
库的过程会发生变化。因此,我们需要更改 HTTP 包装器实例化axios
库的方式。
在接下来的部分中,我们将改变 HTTP 包装器与创建新的axios
实例的工作方式,并使其可用于应用程序。
更改 HTTP Fetch 包装器
在接下来的步骤中,我们将创建一个新的自定义axios
实例,该实例将用于 HTTP 包装器。按照以下说明将新实例添加到应用程序中:
在src/http
文件夹中打开baseFetch.js
文件。
我们需要创建一个名为createAxios
的新工厂函数,以便每次执行时生成一个新的axios
实例:
export function createAxios(options = {}) {
return axios.create({
...options,
}); }
- 现在,我们需要创建
localApi
常量,其值将是createAxios
工厂的执行结果:
const localApi = createAxios();
- 对于
JSONPlaceHolder
,我们将创建一个名为jsonPlaceholderApi
的常量,该常量将被导出,其值将是createAxios
工厂的执行。我们还将传递一个对象作为参数,其中包含定义的baseURL
属性:
export const jsonPlaceholderApi = createAxios({
baseURL: 'https://jsonplaceholder.typicode.com/', });
- 在
export default
函数中,我们需要从axios
更改为localApi
:
export default async (url, method, options = {}) => localApi({
method: method.toUpperCase(),
url,
...options, });
更改 HTTP 方法函数
在这部分,我们将改变 HTTP 方法如何与新的axios
实例一起工作。按照说明正确执行:
在src/http
文件夹中打开fetchApi.js
文件。
我们将从baseFetch
导入jsonPlaceholderApi
函数作为额外导入的值:
import baseHttp, { jsonPlaceholderApi } from './baseFetch';
- 我们需要创建一个名为
getTodos
的新常量,该常量将被导出。此常量将是一个函数,将接收userId
作为参数,并返回axios
的 GET 函数,其中userId
参数将在名为params
的属性的配置对象中接收:
export const getTodos = async userId => jsonPlaceholderApi.get('todos',
{
params: {
userId,
},
});
更改 MirageJS 服务器
在这部分,我们将更改MirageJS
服务器如何与新创建的axios
实例一起工作。按照说明正确执行:
在src/server
文件夹中打开server.js
文件。
在构造函数对象的routes
属性上,我们需要添加一个passthrough
声明,这将指示 MirageJS 不会拦截对该 URL 的所有调用:
import { Server } from 'miragejs'; import baseData from './db'; import { getFrom } from './get'; import { postFrom } from './post'; import { patchFrom } from './patch'; import { deleteFrom } from './delete'; window.server = new Server({
seeds(srv) {
srv.db.loadData({ ...baseData });
}, routes() {
this.passthrough();
this.passthrough('https://jsonplaceholder.typicode.com/**'); this.namespace = 'api'; this.timing = 750; this.get('/users', getFrom('users')); this.post('/users', postFrom('users')); this.patch('/users/:id', patchFrom('users')); this.delete('/users/:id', deleteFrom('users'));
}, });
更改组件
在包装函数、MirageJS
服务器方法和 HTTP 方法更改后,我们需要将组件更改为已实现的新库。
在接下来的部分,我们将更改组件以匹配已实现的新库。
单文件组件<script>
部分
在这部分,我们将更改单文件组件的<script>
部分。按照以下步骤执行:
在src
文件夹中打开App.vue
文件。
我们需要按以下方式导入新的getTodos
函数:
import {
getHttp,
postHttp,
patchHttp,
deleteHttp,
getTodos, } from './http/fetchApi';
- 在
Vue
对象的data
属性中,我们需要创建一个名为userTodo
的新属性,其默认值为一个空数组:
data: () => ({
response: undefined,
userData: '',
userId: undefined,
userTodo: [], }),
- 在
methods
属性中,我们需要创建一个名为getUserTodo
的新方法,该方法接收userId
参数。此方法将获取用户的待办事项列表,并将响应属性分配给userTodo
属性:
async getUserTodo(userId) {
this.userTodo = await getTodos(userId); },
单文件组件<template>
部分
在这部分,我们将更改单文件组件的<template>
部分。按照以下步骤执行:
在src
文件夹中打开App.vue
文件。
在模板底部,我们需要创建一个新的input
HTML 元素,使用v-model
指令绑定到userId
属性:
<h1> Get User ToDos </h1> <label for="userData">
User Id:
<input type="number" step="1" v-model="userId"> </label>
- 要获取项目列表,我们需要创建一个按钮,该按钮绑定了点击事件的事件监听器,使用
@click
指令,目标是getUserTodo
,并在执行中传递userId
:
<button
style="margin: 20px;"
@click="getUserTodo(userId)" >
Fetch Data
</button>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的地方:
它是如何工作的...
当我们创建axios
的新实例时,会创建一个新对象,并定义新的配置、标头、拦截器和操纵器。这是因为axios
声明create
函数与new Class
相同。它是相同的接口但不同的对象。
利用这个可能性,我们能够创建两个连接驱动程序,一个用于本地 API,另一个用于JSONPlaceHolder
API,它有一个不同的baseURL
。
由于 MirageJS 服务器集成,所有 HTTP 请求都被 MirageJS 拦截,因此我们需要在路由构造函数中添加一个指令,指示 MirageJS 不会拦截的路由。
另请参阅
您可以在jsonplaceholder.typicode.com/
找到有关 JSONPlaceHolder API 的更多信息。
您可以在github.com/axios/axios#creating-an-instance
找到有关axios
实例的更多信息。
您可以在github.com/miragejs/miragejs
找到有关 MirageJS 的更多信息。
为 axios 创建请求和响应拦截器
在我们的应用程序中使用axios
作为主要的 HTTP 操作器,允许我们使用请求和响应拦截器。这些用于在将数据发送到服务器之前或在接收数据时操纵数据,然后将其发送回 JavaScript 代码之前对其进行操纵。
拦截器最常用的方式是在 JWT 令牌验证和刷新接收特定错误或 API 错误操纵的请求时使用。
在这个示例中,我们将学习如何创建一个请求拦截器来检查POST、PATCH和DELETE方法以及一个响应错误操纵器。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用我们在'创建不同的 axios 实例'食谱中制作的 Vue CLI 项目,也可以启动一个新的项目。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
创建拦截器
在接下来的步骤中,我们将创建一个axios
拦截器,它将作为中间件工作。按照说明正确执行:
- 安装
Sweet Alert
包。要做到这一点,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save sweetalert2
在src/http
文件夹中创建一个名为interceptors.js
的新文件并打开它。
然后,我们导入 Sweet Alert 包:
import Swal from 'sweetalert2';
- 我们需要创建一个包含将被拦截的POST方法的数组的常量:
const postMethods = ['post', 'patch'];
- 我们需要创建一个名为
requestInterceptor
的函数并导出它。这个函数将接收一个参数config
,它是一个axios
配置对象。我们需要检查请求方法是否包含在我们之前创建的数组中,以及数据主体的data
属性是否有一个id
属性。如果任何检查未通过,我们将抛出一个Error
,否则,我们将返回config
:
export function requestInterceptor(config) {
if (
postMethods.includes(config.method.toLocaleLowerCase()) &&
Object.prototype.hasOwnProperty.call('id', config.data.data) &&
!config.data.data.id)
{
throw new Error('You need to pass an ID for this request');
} return config; }
- 对于响应拦截器,我们需要创建一个名为
responseInterceptor
的新函数,它返回响应,因为我们在这个拦截器中不会改变任何东西:
export function responseInterceptor(response) {
return response; }
- 为了捕获错误,我们需要创建一个
errorInterceptor
函数,它将被导出。这个函数接收一个error
作为参数,并显示一个sweetalert2
警报错误消息,并返回一个带有error
的Promise.reject
:
export function errorInterceptor(error) {
Swal.fire({
type: 'error',
title: 'Error!',
text: error.message,
}); return Promise.reject(error); }
将拦截器添加到 HTTP 方法函数中
在接下来的步骤中,我们将向 HTTP 方法函数添加axios
拦截器。按照以下步骤正确执行:
在src/http
文件夹中打开baseFetch.js
文件。
我们需要导入刚刚创建的三个拦截器:
import {
errorInterceptor,
requestInterceptor,
responseInterceptor, } from './interceptors';
- 在创建
localApi
实例之后,我们声明了请求和响应拦截器的使用:
localApi.interceptors
.request.use(requestInterceptor, errorInterceptor); localApi.interceptors
.response.use(responseInterceptor, errorInterceptor);
- 在创建
jsonPlaceholderApi
实例之后,我们声明了请求和响应拦截器的使用:
jsonPlaceholderApi.interceptors
.request.use(requestInterceptor, errorInterceptor); jsonPlaceholderApi.interceptors
.response.use(responseInterceptor, errorInterceptor);
工作原理...
axios
执行的每个请求都会通过拦截器集中的任何一个。响应也是一样。如果在拦截器上抛出任何错误,它将自动传递给错误处理程序,因此请求根本不会被执行,或者响应将作为错误发送到 JavaScript 代码。
我们检查了每个POST,PATCH和DELETE方法所做的每个请求,以查看在请求体数据中是否有id
属性。如果没有,我们向用户抛出错误,告诉他们需要为请求传递一个 ID。
另请参阅
您可以在sweetalert2.github.io
找到有关 Sweet Alert 2 的更多信息。
您可以在**github.com/axios/axios#interceptors
**找到有关axios
请求拦截器的更多信息。
使用 Axios 和 Vuesax 创建 CRUD 界面
在处理数据时,我们总是需要做一些事情:CRUD 过程。无论您正在开发什么类型的应用程序,都需要 CRUD 界面以便在服务器上输入和操作任何数据,管理面板,应用程序的后端,甚至客户端。
在这里,我们将学习如何使用Vuesax
框架和axios
进行 HTTP 请求来创建一个简单的 CRUD 界面。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,请使用我们在“为 axios 创建请求和响应拦截器”配方中使用的 Vue CLI 的 Vue 项目,或者启动一个新项目。
要启动一个新项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
将 Vuesax 添加到应用程序
在接下来的步骤中,我们将介绍如何将Vuesax
UI 库添加到您的 Vue 应用程序中。按照这些说明正确执行:
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save vuesax material-icons
在src
文件夹中创建一个名为style.css
的文件并打开它。
导入vuesax
,material-icon
和Open Sans
字体样式表:
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,
800i&display=swap'); @import url('~vuesax/dist/vuesax.css'); @import url('~material-icons/iconfont/material-icons.css'); * {
font-family: 'Open Sans', sans-serif; }
打开src
文件夹中的main.js
文件。
导入style.css
文件和Vuesax
。之后,您需要通知 Vue 使用Vuesax
:
import './server/server'; import Vue from 'vue'; import App from './App.vue'; import Vuesax from 'vuesax'; import './style.css'; Vue.use(Vuesax); Vue.config.productionTip = false; new Vue({
render: h => h(App), }).$mount('#app');
创建组件路由
我们将在五个部分中继续这个过程:List
,Create
,Read
,Update
和Delete
。我们的应用将是一个动态组件应用程序,因此我们将创建五个组件,每个部分一个。这些组件将类似于我们的页面。
首先,我们需要将App.vue
更改为我们的主路由管理器,并创建一个用于更改组件的混合器。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
导入将在此处创建的每个组件:
import List from './components/list'; import Create from './components/create'; import View from './components/view'; import Update from './components/update';
- 在
data
属性中,创建两个新值:componentIs
默认值为'list'
,userId
默认值为0
:
data: () => ({
componentIs: 'list',
userId: 0, }),
- 我们需要向 Vue 对象添加一个名为
provide
的新属性。该属性将是一个函数,因此提供给组件的值可以是响应式的:
provide () {
const base = {}; Object.defineProperty(base, 'userId', {
enumerable: true,
get: () => Number(this.userId),
}); return base; },
- 在
computed
属性中,我们需要创建一个名为component
的新属性。这将是一个 switch case,根据componentIs
属性返回我们的组件:
computed: {
component() {
switch (this.componentIs) {
case 'list':
return List;
case 'create':
return Create;
case 'view':
return View;
case 'edit':
return Update;
default:
return undefined;
}
} },
- 最后,在方法中,我们需要创建一个
changeComponent
方法,将当前组件更新为新组件:
methods: {
changeComponent(payload) {
this.componentIs = payload.component;
this.userId = Number(payload.userId);
}, },
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 在
div#app
HTML 元素中,我们需要添加一个vs-row
组件:
<div id="app">
<vs-row></vs-row> </div>
- 在
vs-row
组件中,我们需要添加一个vs-col
组件,具有以下属性:vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12"> </vs-col>
- 最后,在
vs-col
组件内部,我们将添加一个动态组件,该组件具有一个is
属性,指向计算属性component
,并将事件侦听器指向将执行changeComponent
方法的"change-component"
事件:
<component
:is="component"
@change-component="changeComponent" />
创建路由混合器
在这部分,我们将创建组件混合器,以便在其他组件中重复使用。按照以下说明正确创建组件:
在src/mixin
文件夹中创建一个名为changeComponent.js
的新文件并打开它。
这个混合器将有一个名为changeComponent
的方法,它将发出一个名为'change-component'
的事件,其中包含需要呈现的新组件的名称和userId
:
export default {
methods: {
changeComponent(component, userId = 0) {
this.$emit('change-component', { component, userId });
},
}
}
创建列表组件
列表组件将是索引组件。它将列出应用程序中的用户,并具有其他 CRUD 操作的所有链接。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
在src/components
文件夹中创建一个名为list.vue
的新文件并打开它。
从fetchApi
导入getHttp
和deleteHttp
,以及changeComponent
混合器:
import {
getHttp,
deleteHttp,
} from '../http/fetchApi';
import changeComponent from '../mixin/changeComponent';
- 在组件的
mixins
属性中,我们需要添加导入的changeComponent
混合器:
mixins: [changeComponent],
- 在组件的
data
属性中,我们添加了一个名为userList
的新属性,其默认为空数组:
data: () => ({
userList: [], }),
- 对于方法,我们创建了
getAllUsers
和deleteUsers
方法。在getAllUsers
方法中,我们获取用户列表,并将userList
的值设置为getHttp
函数执行的响应。deleteUser
方法将执行deleteHttp
函数,然后执行getAllUsers
方法:
methods: {
async getAllUsers() {
const { data } = await getHttp(`${window.location.href}api/users`);
this.userList = data;
},
async deleteUser(id) {
await deleteHttp(`${window.location.href}api/users/${id}`);
await this.getAllUsers();
}, }
- 最后,我们将
beforeMount
生命周期钩子异步化,调用getAllUsers
方法:
async beforeMount() {
await this.getAllUsers(); },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 创建一个带有
style
属性定义为margin: 20px
的vs-card
组件:
<vs-card style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内部,为header
创建一个动态的<template>
,其中包含一个<h3>
标签和您的标题:
<template slot="header">
<h3>
Users
</h3> </template>
- 之后,创建一个
vs-row
组件,其中包含一个vs-col
组件,具有以下属性:vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内部,我们需要创建一个vs-table
组件。该组件将具有指向userList
变量的data
属性,并将search
,stripe
和pagination
属性定义为 true。max-items
属性将被定义为10
,style
属性将具有width: 100%; padding: 20px;
的值:
<vs-table
:data="userList"
search
stripe pagination max-items="10"
style="width: 100%; padding: 20px;" ></vs-table>
- 对于表头,我们需要创建一个名为
thead
的动态 <template>
,并为每一列创建一个带有 sort-key
属性定义为相应对象键属性和显示为您想要的名称的 vs-th
组件:
<template slot="thead">
<vs-th sort-key="id">
#
</vs-th>
<vs-th sort-key="name">
Name
</vs-th>
<vs-th sort-key="email">
Email
</vs-th>
<vs-th sort-key="country">
Country
</vs-th>
<vs-th sort-key="phone">
Phone
</vs-th>
<vs-th sort-key="Birthday">
Birthday
</vs-th>
<vs-th>
Actions
</vs-th> </template>
- 对于表格主体,我们需要创建一个动态的
<template>
,其中定义了一个 slot-scope
属性作为 data
属性。在这个 <template>
中,我们需要创建一个 vs-tr
组件,它将迭代数据属性,并为表格头部设置的每一列创建一个 vs-td
组件。每个 vs-td
组件都有一个设置为相应列数据对象属性的数据属性,并且内容将是相同的数据渲染。最后一列是操作列,将有三个按钮,一个用于读取,另一个用于更新,最后一个用于删除。读取按钮将在 "click"
事件上有一个指向 changeComponent
的事件监听器,更新按钮也是如此。删除按钮的 "click"
事件监听器将指向 deleteUser
方法:
<template slot-scope="{data}">
<vs-tr :key="index" v-for="(tr, index) in data">
<vs-td :data="data[index].id">
{{data[index].id}}
</vs-td>
<vs-td :data="data[index].name">
{{data[index].name}}
</vs-td>
<vs-td :data="data[index].email">
<a :href="`mailto:${data[index].email}`">
{{data[index].email}}
</a>
</vs-td>
<vs-td :data="data[index].country">
{{data[index].country}}
</vs-td>
<vs-td :data="data[index].phone">
{{data[index].phone}}
</vs-td>
<vs-td :data="data[index].birthday">
{{data[index].birthday}}
</vs-td>
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeComponent('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeComponent('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/>
</vs-td>
</vs-tr> </template>
- 最后,在卡片页脚中,我们需要创建一个名为
footer
的动态 <template>
。在这个 <template>
中,我们将添加一个带有 vs-justify
属性定义为 flex-start
的 vs-row
组件,并插入一个带有 color
属性定义为 primary
、type
属性定义为 filled
、icon
属性定义为 fiber_new
和 size
属性定义为 small
的 vs-button
。@click
事件监听器将以参数 'create'
和 0
目标 changeComponent
方法:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="fiber_new"
size="small"
@click="changeComponent('create', 0)"
>
Create User
</vs-button>
</vs-row> </template>
单文件组件 <style> 部分
在这部分,我们将创建单文件组件的 <style>
部分。按照以下说明正确创建组件:
- 为
vs-button
组件类创建一个边距声明:
<style scoped>
.vs-button {
margin-left: 5px;
} </style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染和运行的地方:
创建一个通用的用户表单组件
在接下来的部分,我们将创建一个通用的用户表单组件,它将被其他组件使用。这个组件被认为是通用的,因为它是一个可以被任何人使用的组件。
单文件组件 <script> 部分
在这部分,我们将创建单文件组件的 <script>
部分。按照以下说明正确创建组件:
在src/components
文件夹中创建一个名为userForm.vue
的新文件并打开它。
在 Vue 的props
属性中,创建两个名为value
和disabled
的新属性,都是对象,并具有type
、required
和default
三个属性。对于value
属性,type
将是Object
,required
将是false
,default
将是返回一个对象的工厂。对于disabled
属性,type
将是Boolean
,required
将是false
,default
也将是false
:
props: {
value: {
type: Object,
required: false,
default: () => {
},
},
disabled: {
type: Boolean,
required: false,
default: false,
} },
- 在
data
属性中,我们需要添加一个名为tmpForm
的新值,其默认值为一个空对象:
data: () => ({
tmpForm: {}, }),
- 在 Vue 的
watch
属性中,我们需要为tmpForm
和value
创建处理程序。对于tmpForm
watcher,我们将添加一个handler
函数,它将在每次更改时发出一个'input'
事件,带有新的value
,并将deep
属性添加为true
。最后,在value
watcher 中,我们将添加一个handler
函数,它将将tmpForm
的值设置为新的value
。我们还需要将deep
和immediate
属性定义为true
:
watch: {
tmpForm: {
handler(value) {
this.$emit('input', value);
},
deep: true,
},
value: {
handler(value) {
this.tmpForm = value;
},
deep: true,
immediate: true,
} },
在使用 watchers 时,声明deep
属性使 watcher 检查数组或对象的深层更改,而immediate
属性在组件创建时立即执行 watcher。
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 对于输入包装器,我们需要创建一个
vs-row
组件。在vs-row
组件内部,我们将为我们的用户表单创建每个输入:
<vs-row></vs-row>
- 对于名称输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.name
,disabled
属性绑定到disabled
props,label
定义为'Name'
,placeholder
定义为'User Name'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.name"
:disabled="disabled"
label="Name"
placeholder="User Name"
class="inputMargin full-width"
/> </vs-col>
- 对于电子邮件输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.email
,disabled
属性绑定到disabled
属性,label
定义为'Email'
,placeholder
定义为'User Email'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.email"
:disabled="disabled"
label="Email"
placeholder="User Email"
class="inputMargin full-width"
/> </vs-col>
- 对于国家输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.country
,disabled
属性绑定到disabled
属性,label
定义为'Country'
,placeholder
定义为'User Country'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.country"
:disabled="disabled"
label="Country"
placeholder="User Country"
class="inputMargin full-width"
/> </vs-col>
- 对于电话输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.phone
,disabled
属性绑定到disabled
属性,label
定义为'Phone'
,placeholder
定义为'User Phone'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.phone"
:disabled="disabled"
label="Phone"
placeholder="User Phone"
class="inputMargin full-width"
/> </vs-col>
- 对于生日输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.birthday
,disabled
属性绑定到disabled
属性,label
定义为'Birthday'
,placeholder
定义为'User Birthday'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.birthday"
:disabled="disabled"
label="Birthday"
placeholder="User Birthday"
class="inputMargin full-width"
/> </vs-col>
单文件组件<style>
部分
在这部分,我们将创建单文件组件的<style>
部分。按照以下说明正确创建组件:
创建一个名为inputMargin
的新的作用域类,其中margin
属性定义为15px
:
<style>
.inputMargin {
margin: 15px;
} </style>
创建创建用户组件
要开始我们的用户操作过程,我们需要创建一个初始的基本用户表单,以便在View
、Create
和Update
组件之间共享。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
在src/components
文件夹中创建一个名为create.vue
的新文件并打开它。
从fetchApi
中导入UserForm
组件、changeComponent
混合和postHttp
:
import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent'; import { postHttp } from '../http/fetchApi';
- 在
data
属性中,我们将添加一个userData
对象,其中name
、email
、birthday
、country
和phone
属性都定义为空字符串:
data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
mixins
属性中,我们需要添加changeComponent
:
mixins: [changeComponent],
- 在 Vue 的
components
属性中,添加UserForm
组件:
components: {
UserForm, },
- 在
methods
属性中,我们需要创建createUser
方法,该方法将使用userData
属性上的数据,并在服务器上创建一个新用户,然后将用户重定向到用户列表:
methods: {
async createUser() {
await postHttp(`${window.location.href}api/users`, {
data: {
...this.userData,
}
});
this.changeComponent('list', 0);
}, },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 创建一个
vs-card
组件,其中style
属性定义为margin: 20px
:
<vs-card
style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内部,为header
创建一个动态的<template>
插槽,其中包含一个<h3>
标签和您的标题:
<template slot="header">
<h3>
Create User
</h3> </template>
- 之后,创建一个
vs-row
组件,其中包含一个vs-col
组件,其属性为vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内部,我们将添加user-form
组件,并将v-model
指令绑定到userData
上:
<user-form
v-model="userData" />
- 最后,在卡片页脚中,我们需要为
footer
创建一个动态的<template>
插槽。在这个<template>
中,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个将用于创建用户,其属性为color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
。@click
事件监听器将针对createUser
方法,第二个vs-button
组件将用于取消此操作并返回到用户列表。它的属性为color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
,style
定义为margin-left: 5px
,@click
事件监听器将目标设置为changeComponent
方法,参数为'list'
和0
:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="save"
size="small"
@click="createUser"
>
Create User
</vs-button>
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Cancel
</vs-button>
</vs-row> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的地方:
视图组件
在接下来的部分中,我们将创建可视化组件。此组件仅用于查看用户信息。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/components
文件夹中创建名为view.vue
的文件并打开它。
导入UserForm
组件,changeComponent
mixin 和fetchApi
中的getHttp
:
import {
getHttp, } from '../http/fetchApi'; import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent';
- 在
data
属性中,我们将添加一个userData
对象,其中name
,email
,birthday
,country
和phone
属性都定义为空字符串:
data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
mixins
属性中,我们需要添加changeComponent
mixin:
mixins: [changeComponent],
- 在 Vue 的
inject
属性中,我们需要声明'userId'
属性:
inject: ['userId'],
- 在 Vue 的
components
属性中,添加UserForm
组件:
components: {
UserForm, },
- 对于方法,我们将创建
getUserById
方法。此方法将通过当前 ID 获取用户数据,并将userData
值设置为getHttp
函数执行的响应:
methods: {
async getUserById() {
const { data } = await getHttp(`${window.location.href}api/users/${this.userId}`);
this.userData = data;
}, }
- 在
beforeMount
生命周期钩子中,我们将使其异步,调用getUserById
方法:
async beforeMount() {
await this.getUserById(); },
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 创建一个带有
style
属性定义为margin: 20px
的vs-card
组件:
<vs-card
style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内,为header
创建一个动态的<template>
,并添加一个<h3>
标签和您的标题:
<template slot="header">
<h3>
View User
</h3> </template>
- 之后,创建一个带有
vs-col
组件的vs-row
组件,其中vs-type
属性定义为flex
,vs-justify
属性定义为left
,vs-align
属性定义为left
,vs-w
属性定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内,我们将添加UserForm
组件,并将v-model
指令绑定到userData
,并将disabled
属性设置为true
:
<user-form
v-model="userData"
disabled />
- 最后,在卡片页脚中,我们需要为
footer
创建一个动态的<template>
。在这个<template>
中,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个是用于取消此操作并返回到用户列表的。它将具有color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
的属性,以及@click
事件监听器目标为changeComponent
方法,参数为'list'
和0
。第二个vs-button
组件将用于编辑用户,并具有color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
,style
定义为margin-left: 5px
,以及@click
事件监听器目标为changeComponent
方法,参数为'list'
和注入的userId
:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="arrow_back"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Back
</vs-button>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
style="margin-left: 5px"
@click="changeComponent('edit', userId)"
>
Edit User
</vs-button>
</vs-row> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
您的组件已呈现并运行:
更新用户组件
我们刚刚查看了用户数据,现在我们想要更新它。我们需要制作一个新的组件,几乎与查看组件相同,但具有更新用户的方法并启用表单。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/components
文件夹中创建名为update.vue
的文件并打开它。
从fetchApi
中导入UserForm
组件、changeComponent
mixin 以及getHttp
和patchHttp
函数:
import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent'; import {
getHttp,
patchHttp, } from '../http/fetchApi';
- 在
data
属性中,我们将添加一个userData
对象,其中包含name
、email
、birthday
、country
和phone
属性,全部定义为空字符串:
data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
mixins
属性中,我们需要添加changeComponent
mixin:
mixins: [changeComponent],
- 在 Vue 的
inject
属性中,我们需要声明'userId'
属性:
inject: ['userId'],
- 在 Vue 的
components
属性中,添加UserForm
组件:
components: {
UserForm, },
- 对于方法,我们将创建两个:
getUserById
和updateUser
。getUserById
方法将通过当前 ID 获取用户数据,并将userData
值设置为getHttp
函数执行的响应,而updateUser
将通过patchHttp
函数将当前userDate
发送到服务器,并重定向回用户列表:
methods: {
async getUserById() {
const { data } = await
getHttp(`${window.location.href}api/users/${this.userId}`);
this.userData = data;
},
async updateUser() {
await patchHttp
(`${window.location.href}api/users/${this.userData.id}`, {
data: {
...this.userData,
}
});
this.changeComponent('list', 0);
}, },
- 在
beforeMount
生命周期钩子上,我们将使其异步化,调用getUserById
方法:
async beforeMount() { await this.getUserById(); },
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 创建一个带有
style
属性定义为margin: 20px
的vs-card
组件:
<vs-card
style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内部,为header
创建一个动态的<template>
命名插槽,其中包含一个<h3>
标签和您的标题:
<template slot="header">
<h3>
Update User
</h3> </template>
- 之后,创建一个带有
vs-col
组件的vs-row
组件,其中vs-type
属性定义为flex
,vs-justify
属性定义为left
,vs-align
属性定义为left
,vs-w
属性定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内部,我们将添加UserForm
组件,其中v-model
指令绑定到userData
,并将disabled
属性设置为true
:
<user-form
v-model="userData"
disabled />
- 最后,在卡片页脚中,我们需要为
footer
创建一个动态的<template>
命名插槽。在<template>
内,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个将用于创建用户,并具有color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
的属性,并且@click
事件监听器指向updateUser
方法。第二个vs-button
组件将用于取消此操作并返回到用户列表。它将具有color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
,style
定义为margin-left: 5px
的属性,并且@click
事件监听器指向changeComponent
方法,参数为'list'
和0
:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="save"
size="small"
@click="updateUser"
>
Update User
</vs-button>
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Cancel
</vs-button>
</vs-row> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染并运行的情况:
它是如何工作的...
我们创建的 CRUD 接口就像一个路由应用程序,有三个路由,即索引或列表、查看和编辑路由。每个路由都有自己的屏幕和组件,具有分离的逻辑功能。
我们创建了一个抽象的UserForm
组件,该组件用于View
和Update
组件。这个抽象组件可以在许多其他组件中使用,因为它不需要任何基本逻辑来工作;它就像一个由几个输入组成的输入框。
使用 Vue 的 provide/inject API,我们能够以可观察的方式将userId
传递给每个组件,这意味着当变量更新时,组件会接收到更新后的变量。这是无法通过普通的 Vue API 实现的,因此我们必须使用Object.defineProperty
并使用provide
属性作为返回最终对象的工厂函数。
另请参阅
您可以在lusaxweb.github.io/vuesax/
找到有关Vuesax
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
找到有关Object.defineProperty
的更多信息。
您可以在vuejs.org/v2/guide/components-edge-cases.html
找到有关 Vue provide/inject API 的更多信息。
第六章:使用 vue-router 管理路由
您的应用程序的主要部分之一是路由管理。在这里,可以将无限的组件组合在一个地方。
路由能够协调组件的渲染,并根据 URL 指示应用程序应该在哪里。有许多方法可以增加vue-router
的定制化。您可以添加路由守卫来检查特定路由是否可由访问级别导航,或在进入路由之前获取数据以管理应用程序中的错误。
在本章中,您将学习如何创建应用程序路由、动态路由、别名和信任路由,以及嵌套路由视图。我们还将看看如何管理错误,创建路由守卫,并延迟加载您的页面。
在本章中,我们将涵盖以下教程:
创建一个简单的路由
创建一个程序化导航
创建一个动态路由路径
创建一个路由别名
创建一个路由重定向
创建一个嵌套路由视图
创建一个 404 错误页面
创建一个身份验证中间件
异步延迟加载您的页面
技术要求
在本章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的 npm 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Vue-CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个简单的路由
在您的应用程序中,您可以创建无限组合的路由,可以导向任意数量的页面和组件。
vue-router
是这个组合的维护者。我们需要使用它来设置如何创建路径并为我们的访问者建立路由的指令。
在这个教程中,我们将学习如何创建一个初始路由,该路由将引导到不同的组件。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做…
创建一个 Vue-CLI 项目,按照以下步骤进行:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create initial-routes
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。
有两种方法可以启动新项目。默认方法是基本的 Babel 和 ESLint 项目,没有任何插件或配置,还有手动
模式,您可以选择更多模式、插件、代码检查器和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint**)** ❯ **Manually select features**
- 现在我们被问及项目中想要的功能。这些功能包括一些 Vue 插件,如 Vuex 或 Vue Router(Vue-Router)、测试器、代码检查器等。选择
Babel
、Router
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router
Vuex CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 现在 Vue-CLI 会询问您是否要在路由管理中使用历史模式。我们会选择
Y
(是):
? Use history mode for router? (Requires proper server setup for
index fallback in production) (Y**/n) y**
- 继续此过程,选择一个代码检查器和格式化程序。在我们的情况下,我们将选择
ESLint + Airbnb config
:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 设置完代码检查规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、代码检查器和处理器之后,我们需要选择设置和配置存储的位置。最佳存储位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
- 现在您可以选择是否要将此选择作为将来项目的预设,这样您就不需要再次重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
我们的步骤将分为五个部分:
创建NavigationBar
组件
创建联系页面
创建关于页面
更改应用程序的主要组件
创建路由
让我们开始吧。
创建 NavigationBar 组件
现在我们将创建将在我们的应用程序中使用的NavigationBar
组件。
单文件组件<script>部分
在这一部分,我们将创建单文件组件的<script>部分。按照这些说明正确创建组件:
在src/components
文件夹中创建一个navigationBar.vue
文件并打开它。
创建组件的默认export
对象,具有 Vue 属性name
:
<script> export default {
name: 'NavigationBar', }; </script>
单文件组件部分
在这一部分,我们将创建单文件组件的部分。按照这些说明正确创建组件:
- 创建一个带有
id
属性定义为"nav"
的div
HTML 元素,并在其中创建三个RouterLink
组件。这些组件将指向Home
、About
和Contact
路由。在RouterLink
组件中,我们将添加一个to
属性,分别定义为每个组件的路由,并将文本内容定义为菜单的名称:
<div id="nav">
<router-link to="/">
Home
</router-link> |
<router-link to="/about">
About
</router-link> |
<router-link to="/contact">
Contact
</router-link> </div>
创建联系页面
我们需要确保当用户输入/contact
URL 时,联系页面会被渲染。为此,我们需要创建一个单文件组件,用作联系页面。
单文件组件 <script> 部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/views
文件夹中,创建一个名为contact.vue
的新文件并打开它。
创建组件的默认export
对象,其中包含 Vue 属性name
:
<script> export default {
name: 'ContactPage', }; </script>
单文件组件 部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
创建一个div
HTML 元素,其中class
属性定义为"contact"
。
在<h1>
HTML 元素内部,添加一个显示当前页面的文本内容:
<template>
<div class="contact">
<h1>This is a contact page</h1>
</div> </template>
创建关于页面
我们需要确保当用户输入/about
URL 时,关于页面会被渲染。在接下来的小节中,我们将为关于页面创建单文件组件。
单文件组件 <script> 部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/views
文件夹中,创建一个名为About.vue
的新文件并打开它。
创建组件的默认导出对象,其中包含 Vue 属性name
:
<script> export default {
name: 'AboutPage', }; </script>
单文件组件 部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
创建一个div
HTML 元素,其中class
属性定义为"about"
。
在其中,放置一个带有显示当前页面文本内容的<h1>
元素:
<template>
<div class="about">
<h1>This is an about page</h1>
</div> </template>
更改应用程序的主要组件
创建页面和导航栏后,我们需要更改应用程序的主要组件,以便能够渲染路由并在顶部拥有导航栏。
单文件组件 <script> 部分
在这部分中,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
导入NavigationBar
组件:
import NavigationBar from './components/navigationBar.vue';
- 在 Vue 的
components
属性中,声明导入的NavigationBar
:
export default {
components: { NavigationBar }, };
单文件组件<template>
部分
在这部分中,我们将创建单文件组件的<template>
部分。在div
HTML 元素内,添加NavigationBar
组件和RouterView
组件:
<template>
<div id="app">
<navigation-bar />
<router-view/>
</div> </template>
创建路由
现在我们需要在应用程序中使路由可用。为此,首先需要声明路由和路由将呈现的组件。按照以下步骤正确创建 Vue 应用程序路由:
在src/router
文件夹中,打开index.js
文件。
导入Contact
组件页面:
import Vue from 'vue'; import VueRouter from 'vue-router'; import Home from '../views/Home.vue'; import Contact from '../views/contact.vue';
- 在
routes
数组中,我们需要创建一个新的route
对象。该对象将具有path
属性定义为'/contact'
,name
定义为'contact'
,并且component
指向导入的Contact
组件:
{
path: '/contact',
name: 'contact',
component: Contact, },
要运行服务器并查看组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的地方:
工作原理...
当将vue-router
添加到 Vue 作为插件时,它开始监视window.location.pathname
和其他 URL 属性的更改,以检查当前 URL 在浏览器上的权重与路由配置中的 URL 列表的匹配情况。
在这种情况下,我们使用直接 URL 和非动态 URL。因此,vue-router
插件只需要检查 URL 路径的直接匹配,而不需要将可能的匹配与正则表达式验证器进行比较。
匹配 URL 后,router-view
组件充当动态组件,并呈现我们在vue-router
配置中定义的组件。
另请参阅
您可以在router.vuejs.org/.
找到有关vue-router
的更多信息。
您可以在cli.vuejs.org/.
找到有关 Vue CLI 的更多信息。
创建程序化导航
使用vue-router
时,还可以通过函数执行来更改应用程序的当前路由,而无需特殊的vue-router
组件来创建链接。
使用程序化导航,您可以确保所有路由重定向可以在代码的任何位置执行。使用此方法可以使用特殊的路由方向,例如传递参数和使用路由名称进行导航。
在这个食谱中,我们将学习如何执行程序化导航函数,以及它提供的额外可能性。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用在“创建简单路由”中创建的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create route-project
选择手动功能并将Router
作为所需功能添加,如“如何做...”部分和“创建简单路由”食谱中所示。
我们的食谱将分为两部分:
更改应用程序的主要组件
更改联系视图
让我们开始吧。
更改应用程序的主要组件
我们将从App.vue
文件开始。我们将添加一个在超时后执行的程序化导航函数,该函数将添加到组件生命周期钩子中。
单文件组件<script>
部分
在这部分中,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
添加一个mounted
属性:
mounted() {}
- 在
mounted
属性中,我们需要添加一个setTimeout
函数,该函数将执行$router.push
函数。当执行时,此函数将接收一个 JavaScript 对象作为参数,其中包含两个属性,name
和params
:
mounted() {
setTimeout(() => {
this.$router.push({
name: 'contact',
params: {
name: 'Heitor Ribeiro',
age: 31,
}, }); }, 5000); },
更改联系视图
在联系视图上,我们需要添加一个事件侦听器,该侦听器将抓取路由更改并执行操作。
单文件组件<script>
部分
在这部分中,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/views
文件夹中打开contact.vue
。
添加一个新的mounted
属性:
mounted() {}
- 在此属性中,我们将添加一个验证,检查
$route.params
对象上是否有任何参数,并显示具有该$route.params
的alert
:
mounted() {
if (Object.keys(this.$route.params).length) {
alert(`Hey! I've got some parameter!
${JSON.stringify(this.$route.params)}`);
} },
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现和运行的方式:
它是如何工作的...
当执行$router.push
函数时,告诉vue-router
改变应用程序所在的位置,在这个过程中,您将向新的路由器传递一些参数,这些参数将替换当前路由。在这些参数中,有一个名为params
的属性,它将一组参数发送到新的路由器。
当进入这个新的路由器时,我们将从路由器内部调用的所有参数都将在$route.params
对象中可用;在那里,我们可以在我们的视图或组件中使用它。
还有更多...
在程序化导航中,可以通过$router.push
函数导航到路由器,并将它们添加到浏览器历史记录中,但也可以使用其他函数。
$router.replace
函数将替换用户导航历史记录为新的历史记录,使其无法返回到上一页。
$router.go
用于以步骤方式移动用户导航历史记录。要前进,您需要传递正数,要后退,您需要传递负数。
参见
您可以在router.vuejs.org/guide/essentials/navigation.html
找到有关vue-router
程序化导航的更多信息。
创建动态路由器路径
向您的应用程序添加路由是必不可少的,但有时您需要的不仅仅是简单的路由。在这个食谱中,我们将看看动态路由是如何发挥作用的。通过动态路由,您可以定义可以通过 URL 设置的自定义变量,并且您的应用程序可以从这些变量开始定义。
在这个食谱中,我们将学习如何在 CRUD 列表上使用动态路由器路径。
准备就绪
这个食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在第五章中完成的 Vue 项目和 Vue-CLI,通过 HTTP 请求从 Web 获取数据中的'使用 axios 和 Vuesax 创建 CRUD 界面'食谱。在接下来的步骤中,我们将通过 Vue UI 仪表板向项目添加vue-router
:
- 首先,您需要打开
vue ui
。要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
在那里,您需要通过定位项目文件夹来导入项目。导入vue ui
后,您将被重定向到仪表板。
通过转到插件管理页面并单击“添加 vue-router”按钮,将vue-router
添加到插件中。然后,单击“继续”按钮。
Vue-CLI 将自动为我们在项目上安装和配置 vue-router。现在我们需要为列表,视图和编辑页面创建每个视图。
要开始视图开发,我们将首先进入用户列表路由。在每个路由中,我们将解构我们之前制作的旧组件,并将其重新创建为视图。
我们的步骤将分为八个部分:
更改应用程序的主要组件
更改路由 mixin
Axios 实例配置
用户列表视图
用户创建视图
用户信息视图
用户更新视图
创建动态路由
让我们开始吧。
更改应用程序的主要组件
添加 vue-router 插件后,App.vue
将发生变化。我们需要撤销安装vue-router
所做的更改。这是因为当vue-ui
添加vue-router
插件时,它会更改App.vue
,添加我们不需要的示例代码。
单文件组件部分
在这部分中,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
删除所有内容,只留下div#app
HTML 元素和router-view
组件:
<template>
<div id="app">
<router-view/>
</div> </template>
更改路由 mixin
在上一个步骤中,我们使用了一个changeComponent
mixin。现在我们要使用路由,我们需要将此 mixin 更改为changeRoute
mixin 并更改其行为。在接下来的步骤中,我们将更改 mixin 的工作方式,以便能够更改路由而不是组件:
在src/mixin
文件夹中,将changeComponent.js
重命名为changeRoute.js
并打开它。
我们将删除changeComponent
方法并创建一个名为changeRoute
的新方法。这个新方法将接收两个参数,name
和id
。name
参数是路由名称,在vue-router
配置中设置,id
将是我们将在路由更改中传递的用户 id 参数。此方法将执行$router.push
,将这些参数作为参数传递:
export default {
methods: {
async changeRoute(name, id = 0) {
await this.$router.push({
name,
params: {
id,
},
});
},
} }
Axios 实例配置
要在 MirageJS 服务器中获取数据,我们需要在 axios 实例中定义一些选项。现在,在以下步骤中,我们将配置 axios 实例以与新的路由系统一起工作:
在src/http
文件夹中,打开baseFetch.js
文件。
在axios
的localApi
实例的创建者中,我们需要添加一个options
对象,传递baseURL
属性。这个baseURL
将是当前浏览器导航的 URL:
const localApi = createAxios({
baseURL: `${document.location.protocol}//${document.location.host}`, });
用户列表视图
为了创建我们的视图,我们将从list.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
将list.vue
文件从components
移动到views
文件夹,并将其重命名为List.vue
。
删除旧的changeComponent
mixin 导入,并导入新的changeRoute
mixin:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
getAllUsers
和deleteUser
方法中,我们需要从getHttp
和deleteHttp
函数参数中删除${window.location.href}
:
methods: {
async getAllUsers() {
const { data } = await getHttp(`api/users`);
this.userList = data;
},
async deleteUser(id) {
await deleteHttp(`api/users/${id}`);
await this.getAllUsers();
}, }
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包装VsCard
组件及其子内容。VsCol
组件将vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在操作按钮上,我们将把
changeComponent
函数改为changeRoute
:
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeRoute('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeRoute('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/> </vs-td>
- 在
VsCard
的页脚处,我们需要将操作按钮的changeComponent
方法改为changeRoute
方法:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="fiber_new"
size="small"
@click="changeRoute('create')"
>
Create User
</vs-button>
</vs-row> </template>
用户创建视图
为了创建我们的视图,我们将从create.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
将create.vue
文件从components
移动到views
文件夹,并将其重命名为Create.vue
。
删除旧的changeComponent
mixin 导入,并导入新的changeRoute
mixin:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
getUserById
方法中,我们需要从postHttp
函数的 URL 中移除${window.location.href}
,并将changeComponent
函数更改为changeRoute
:
async createUser() {
await postHttp(`/api/users`, {
data: {
...this.userData,
}
});
this.changeRoute('list'); },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将定义vs-type
属性为'flex'
,vs-justify
属性为'left'
,vs-align
属性为'left'
,vs-w
属性为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在
VsCard
的页脚上,我们需要将Cancel
按钮的changeComponent
函数更改为changeRoute
:
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Cancel
</vs-button>
用户信息视图
为了创建我们的视图,我们将从view.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
将view.vue
文件从src/components
移动到src/views
文件夹,并将其重命名为View.vue
。
移除旧的changeComponent
混入导入,并导入新的changeRoute
:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
component
对象中创建一个新的computed
属性,属性为userId
,它将返回$route.params.id
:
computed: {
userId() {
return this.$route.params.id;
}, },
- 在
getUserById
方法中,我们需要从getHttp
函数的 URL 中移除${window.location.href}
:
methods: {
async getUserById() {
const { data } = await getHttp(`api/users/${this.userId}`);
this.userData = data;
}, }
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将定义vs-type
属性为'flex'
,vs-justify
属性为'left'
,vs-align
属性为'left'
,vs-w
属性为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在
VsCard
的页脚上,我们需要将返回按钮的changeComponent
函数更改为changeRoute
:
<vs-button
color="primary"
type="filled"
icon="arrow_back"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Back
</vs-button>
用户更新视图
为了创建我们的视图,我们将从update.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
将update.vue
文件从src/components
移动到src/views
文件夹,并将其重命名为Edit.vue
。
移除旧的changeComponent
混入导入,并导入新的changeRoute
混入:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
component
对象中创建一个新的computed
属性,具有userId
属性,它将返回$route.params.id
:
computed: {
userId() {
return this.$route.params.id;
}, },
- 在
getUserById
和updateUser
方法中,我们需要移除
从getHttp
和patchHttp
函数的 URL 中删除${window.location.href}
,并将changeComponent
函数改为changeRoute
:
methods: {
async getUserById() {
const { data } = await getHttp(`api/users/${this.userId}`);
this.userData = data;
},
async updateUser() {
await patchHttp(`api/users/${this.userData.id}`, {
data: {
...this.userData,
}
});
this.changeRoute('list');
}, },
单文件组件的部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在
VsCard
的页脚上,我们需要把Cancel
按钮的changeComponent
函数改为changeRoute
:
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Cancel
</vs-button>
创建动态路由
现在,我们已经创建了页面视图,我们需要创建路由并使其接受参数,将它们转换为动态路由。在接下来的步骤中,我们将创建应用程序的动态路由:
打开src/router
文件夹中的index.js
。
首先,我们需要导入四个新页面 - List
,View
,Edit
,Create
和Update
:
import List from '@/views/List.vue'; import View from '@/views/View.vue'; import Edit from '@/views/Edit.vue'; import Create from '@/views/Create.vue';
在routes
数组上,我们将为每个导入的页面添加一个新的路由对象。在这个对象中,将有三个属性:name
,path
和component
。
对于list
路由,我们将把name
定义为'list'
,path
定义为'/'
,并把component
定义为导入的List
组件:
{
path: '/',
name: 'list',
component: List, },
- 在
view
路由上,我们将把name
定义为'view'
,path
定义为'/view/:id'
,并把component
定义为导入的View
组件:
{
path: '/view/:id',
name: 'view',
component: View, },
- 在
edit
路由上,我们将把name
定义为'edit'
,path
定义为'/edit/:id'
,并把component
定义为导入的Edit
组件:
{
path: '/edit/:id',
name: 'edit',
component: Edit, },
- 最后,在
create
路由上,我们将把name
定义为'create'
,path
定义为'/create'
,并把component
定义为导入的Create
组件:
{
path: '/create',
name: 'create',
component: Create, },
- 当创建
VueRouter
时,我们将添加mode
选项属性,并将其设置为'history'
:
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes });
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染和运行的方式:
- 列表视图路由 -
/
将是您的用户列表页面,包含应用程序中所有用户的列表以及查看、编辑和删除用户的按钮,以及创建新用户的按钮:
- 用户视图路由 -
/view/:id
将是您的用户查看页面,您可以在此页面查看用户信息,例如用户的姓名、电子邮件、国家、生日和电话号码:
- 用户编辑路由 -
/update/:id
将是您的用户编辑页面,您可以在此页面编辑用户信息,更改用户的姓名、电子邮件、国家、生日和电话号码:
- 创建用户路由别名 -
/update/:id
将是您的用户创建页面,您可以在系统上创建新用户:
它是如何工作的...
当创建vue-router
并将路由传递进行匹配时,路由分析会根据每个路由的权重定义的正则表达式来寻找最佳匹配路由。
当定义路由并在其路径中有一个变量时,您需要在变量参数之前添加:
。此参数通过$route.params
属性传递给组件。
另请参阅
您可以在router.vuejs.org/guide/essentials/dynamic-matching.html
找到有关动态路由匹配的更多信息。
创建路由别名
每个应用程序都是一个活生生的有机体-它不断发展、突变和变化。有时,这些进化可以通过路由更改的形式来实现,以获得更好的命名或废弃的服务。在vue-router
中,可以使所有这些更改对用户不可见,因此当他们使用旧链接时,仍然可以访问应用程序。
在这个教程中,我们将学习如何为我们的应用程序创建路由别名并使用它。
准备工作
此教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到这一点...
要启动我们的组件,我们将使用在“创建动态路由路径”配方中完成的 Vue 项目,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动功能,并按照“如何做…”部分的指示,将router
添加为必需功能。
现在,在以下步骤中,我们将创建路由别名:
在src/router
文件夹中打开index.js
。
在list
对象中,我们将把path
属性从'/'
改为'/user'
,并为alias
属性设置为'/'
:
{
path: '/user',
name: 'list',
alias: '/',
component: List, },
- 在
view
对象中,我们将把path
属性从'/view/:id'
改为'/user/:id'
,并将alias
属性设置为'/view/:id'
:
{
path: '/user/:id',
name: 'view',
alias: '/view/:id',
component: View, },
- 在
edit
对象中,我们将把path
属性从'/edit/:id'
改为'/user/edit/:id'
,并将alias
属性设置为'/edit/:id'
。
{
path: '/user/edit/:id',
name: 'edit',
alias: '/edit/:id',
component: Edit, },
- 最后,在
create
对象中,我们将把path
属性从'/create'
改为'/user/create'
,并将alias
属性设置为'/create'
:
{
path: '/user/create',
name: 'create',
alias: '/create',
component: Create, },
工作原理…
当用户进入您的应用程序时,vue-router
将尝试将路径与用户尝试访问的路径匹配。如果路由对象中有一个名为alias
的属性,则vue-router
将使用此属性在幕后维护旧路由,并使用别名路由。如果找到别名,则渲染该别名的组件,并且路由器保持为别名,不向用户显示更改,使其透明。
在我们的场景中,我们对应用程序进行了转换,现在处理所有在/user
命名空间上调用的用户,但仍保持旧的 URL 结构,以便如果旧访问者尝试访问网站,他们将能够正常使用应用程序。
另请参阅
您可以在router.vuejs.org/guide/essentials/redirect-and-alias.html#alias
找到有关vue-router
别名的更多信息。
创建路由重定向
路由重定向几乎与路由别名相同,但主要区别在于用户确实被重定向到新的 URL。使用此过程,您可以管理如何加载新路由。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在'创建路由别名'配方中完成的 Vue-CLI 中的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动功能并将Router
作为必需的功能添加到'如何做...'步骤中的'创建一个简单路由'配方中。
现在,在这些步骤中,我们将创建路由重定向规则:
打开src/router
文件夹中的index.js
。
在routes
数组的末尾插入一个新的路由对象。这个对象将有两个属性,path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/create-new-user'
,在redirect
中,用户将被重定向到的路径,在这种情况下是'/user/create'
。
{
path: '/create-new-user',
redirect: '/user/create', },
- 创建一个新对象,这个对象将有两个属性,
path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/users'
,在redirect
中,我们将创建一个具有名为name
的属性的对象,并将值设置为'list'
。
{
path: '/users',
redirect: {
name: 'list',
}, },
- 创建一个新对象。这个对象将有两个属性,
path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/my-user/:id?'
,在redirect
中,我们将创建一个函数,该函数将接收一个参数to
,这是当前路由的对象。我们需要检查路由中是否存在用户 ID,以便将用户重定向到编辑页面。否则,我们将把他们重定向到用户列表。
{
path: '/my-user/:id?',
redirect(to) {
if (to.params.id) {
return '/user/:id';
}
return '/user';
}, },
- 最后,在最后,我们将创建一个具有两个属性,
path
和redirect
的路由对象。在path
属性中,我们需要定义用户将输入的路径,'/*'
,在redirect
中,我们需要将redirect
属性定义为'/'
。
{
path: '*',
redirect: '/', },
请记住,具有'*'
的最后一个路由将始终是在用户尝试输入的 URL 中没有匹配时呈现的路由。
它是如何工作的...
当我们将redirect
定义为一个新的路由时,它的工作方式类似于别名,但是redirect
属性可以接收三种类型的参数:一个字符串,当重定向到路由本身时,对象,当使用其他参数重定向时,例如路由的名称,最后但并非最不重要的是函数类型,redirect
可以处理并返回前两个对象中的一个,以便用户可以被重定向。
另请参阅
您可以在router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect
找到有关vue-router
重定向的更多信息。
创建嵌套路由视图
在vue-router
中,嵌套路由就像是路由的命名空间,您可以在同一个路由内拥有多个级别的路由,使用基本视图作为主视图,并在其中呈现嵌套路由。
在多模块应用程序中,这用于处理像 CRUD 这样的路由,其中您将拥有一个基本路由,而子路由将是 CRUD 视图。
在这个配方中,您将学习如何创建嵌套路由。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何操作...
要启动我们的组件,我们将使用在“创建动态路由路径”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动功能,并在“如何操作...”部分中将Router
添加为必需功能,如“创建简单路由”配方中所示。
我们的配方将分为两部分:
在布局上创建router-view
更改路由文件
让我们开始吧。
在布局上创建router-view
在使用带有子路由的vue-router
时,我们需要创建主视图,它将具有一个名为RouterView
的特殊组件。此组件将在您呈现的布局或页面内呈现当前路由。
现在,在接下来的步骤中,我们将为页面创建布局:
在src/views
文件夹中,我们需要创建一个名为user
的新文件夹,并将Create
、Edit
、List
、和View
页面移动到这个新文件夹中。
在user
文件夹中创建一个名为Index.vue
的新文件并打开它。
在单文件组件<template>
部分中,添加一个router-view
组件:
<template>
<router-view/>
</template>
<script>
export default {
name: 'User',
}
</script>
更改路由文件
我们将创建一个新文件来管理用户的特定路由,这将帮助我们维护代码并使其更清晰。
用户路由
在接下来的步骤中,我们将为用户创建路由:
在src/router
文件夹中创建一个名为user.js
的新文件。
导入Index
、List
、View
、Edit
和Create
视图:
import Index from '@/views/user/Index.vue'; import List from '@/views/user/List.vue'; import View from '@/views/user/View.vue'; import Edit from '@/views/user/Edit.vue'; import Create from '@/views/user/Create.vue';
- 创建一个数组,并将其设置为文件的默认导出。在这个数组中,添加一个
route
对象,具有四个属性-path
,name
,component
和children
。将path
属性设置为'/user'
,将name
属性定义为'user'
,将component
定义为导入的Index
组件,最后,将children
属性定义为空数组:
export default [
{ path: '/user',
name: 'user',
component: Index,
children: [],
}, ]
- 在
children
属性中,添加一个新的路由对象,具有三个属性-path
,name
和component
。将path
定义为''
,name
定义为'list'
,最后,将component
属性定义为导入的List
组件:
{
path: '',
name: 'list',
component: List, },
- 为视图路由创建一个路由对象,并使用与上一个
route
对象相同的结构。将path
属性定义为':id'
,将name
定义为'view'
,将component
定义为导入的View
组件:
{
path: ':id',
name: 'view',
component: View, },
- 为
edit
路由创建一个路由对象,并使用与上一个route
对象相同的结构。将path
属性定义为'edit/:id'
,将name
定义为'edit'
,将component
定义为导入的Edit
组件:
{
path: 'edit/:id',
name: 'edit',
component: Edit, },
- 为
create
路由创建一个路由对象,使用与上一个route
对象相同的结构。将path
属性定义为'create'
,将name
定义为'create'
,将component
定义为导入的Create
组件:
{
path: 'create',
name: 'create',
component: Create, },
路由管理器
在以下步骤中,我们将创建路由管理器,该管理器将控制应用程序中的所有路由:
在src/router
文件夹中打开index.js
。
在src/router
文件夹中导入新创建的user.js
文件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user';
- 在
routes
数组中,将导入的UserRoutes
作为解构数组添加:
const routes = [
...UserRoutes,
{
path: '*',
redirect: '/user',
}, ];
工作原理...
vue-router
提供了使用子路由作为当前视图或布局的内部组件的能力。这使得可以创建具有特殊布局文件的初始路由,并通过RouterView
组件在此布局中呈现子组件。
这种技术通常用于在应用程序中定义布局并为模块设置命名空间,其中父路由可以具有一组特定顺序,这些顺序将对其每个子路由都可用。
另请参阅
您可以在router.vuejs.org/guide/essentials/nested-routes.html#nested-routes
找到有关嵌套路由的更多信息。
创建 404 错误页面
有时您的用户可能会尝试输入旧链接或输入拼写错误,无法到达正确的路由,这应该直接导致找不到错误。
在这个配方中,您将学习如何在vue-router
中处理 404 错误。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在“创建嵌套路由视图”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create http-project
选择手动功能并将Router
添加为所需功能,如“如何做...”部分在“创建简单路由”配方中所示。
我们的配方将分为两部分:
创建NotFound
视图
更改路由文件
让我们开始吧。
创建 NotFound 视图
我们需要创建一个新的视图,当应用程序上没有匹配的路由时,将显示给用户。这个页面将是一个简单的通用页面。
单文件组件部分
在这部分中,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
在src/views
文件夹中,创建一个名为NotFound.vue
的新文件并打开它。
创建一个VsRow
组件,在其中创建四个VsCol
组件。所有这些组件都将具有属性vs-w
定义为12
和class
定义为text-center
:
<vs-row>
<vs-col vs-w="12" class="text-center">
<!-- Icon --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Title --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Text --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Button --> </vs-col> </vs-row>
- 在第一个
VsCol
组件中,我们将添加一个VsIcon
组件,并将属性 icon 定义为sentiment_dissatisfied
,并将size
定义为large
:
<vs-icon
icon="sentiment_dissatisfied"
size="large" />
- 在第二个
VsCol
组件中,我们将为页面添加一个标题:
<h1>Oops!</h1>
- 在第三个
VsCol
组件中,我们需要创建将放置在页面上的文本:
<h3>The page you are looking for are not here anymore...</h3>
- 最后,在第四个
VsCol
组件上,我们将添加VsButton
组件。此按钮将具有属性type
定义为relief
和to
定义为'/'
:
<vs-button
type="relief"
to="/" >
Back to Home...
</vs-button>
单文件组件<style>部分
在这部分中,我们将创建单文件组件的<style>
部分。按照这些说明正确创建组件:
- 在
<style>
标签中添加scoped
标签:
<style scoped> </style>
- 创建一个名为
.text-center
的新规则,其中text-align
属性定义为center
,margin-bottom
定义为20px;
:
.text-center {
text-align: center;
margin-bottom: 20px; }
更改路由文件
创建视图后,我们需要将其添加到路由并使其对用户可用。为此,我们需要将视图路由添加到路由管理器中。
在这些步骤中,我们将更改路由管理器,以添加新的错误页面:
在src/router
文件夹中打开index.js
。
导入NotFound
组件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user'; import NotFound from '@/views/NotFound';
- 在
routes
数组中,在UserRoutes
之后,添加一个新的route
对象,具有两个属性,path
和redirect
。将path
属性定义为'/'
,将redirect
属性定义为'/user'
:
{
path: '/',
redirect: '/user' },
- 对于未找到的页面,我们需要创建一个新的路由对象,该对象需要放在
routes
数组的最后位置。这个路由对象将有两个属性,path
和component
。path
属性将被定义为'*'
,component
将被定义为导入的NotFound
视图:
{
path: '*',
component: NotFound, },
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
vue-router
尝试找到用户想要访问的 URL 的最佳匹配;如果没有匹配项,vue-router
将使用'*'
路径作为这些情况的默认值,其中*
表示用户输入的不在路由列表中的任何值。
因为在vue-router
中匹配的过程是由路由的权重决定的,所以我们需要将错误页面放在最底部,这样vue-router
在实际调用NotFound
路由之前需要通过每个可能的路由。
另请参阅
您可以在router.vuejs.org/guide/essentials/history-mode.html#caveat
找到有关处理 vue-router 历史模式中的 404 错误的更多信息。
创建和应用身份验证中间件
在vue-router
中,可以创建路由守卫-每次路由更改时运行的函数。这些守卫被用作路由管理过程中的中间件。通常将它们用作身份验证中间件或会话验证器。
在这个示例中,我们将学习如何创建身份验证中间件,向我们的路由添加元数据以使它们受限制,并创建登录页面。
准备工作
这个示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到...
要启动我们的组件,我们将使用在“创建 404 错误页面”配方中使用的 Vue-CLI 的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动特性,并在“如何做...”部分中添加Router
作为必需特性,如“创建简单路由”配方中所示。
我们的配方将分为三个部分:
创建身份验证中间件
向路由添加元数据和中间件
将中间件附加到 vue-router 并创建登录页面
让我们开始。
创建登录视图
登录视图将是用户在未经过身份验证时看到的页面。我们将构建一个简单的页面,里面有两个输入框 - 一个卡片和一个按钮。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
在src/views
文件夹中,创建一个名为Login.vue
的新文件并打开它。
创建一个包含username
、password
和error
的data
属性:
data: () => ({
username: '',
password: '',
error: false, }),
- 然后创建一个名为
userSignIn
的方法的methods
属性。此方法将验证username
和password
数据是否完整。如果是,它将在sessionStorage
中创建一个名为'auth'
的新密钥,其中包含username
数据的加密字符串化 JSON。然后,将error
设置为false
并执行$router.replace
将用户重定向到用户列表'/user'
。如果任何字段未通过任何验证,该方法将将错误定义为true
并返回false
:
methods: {
userSignIn() {
if (this.username && this.password) {
window.sessionStorage.setItem('auth',
window.btoa(JSON.stringify({
username: this.username
})
) );
this.error = false;
this.$router.replace('/user');
}
this.error = true;
return false;
}, }
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 创建一个带有
VsRow
组件的div.container
HTML 元素。VsRow
组件将具有属性vs-align
定义为"center"
和vs-justify
定义为"center"
:
<div class="container">
<vs-row
vs-align="center"
vs-justify="center"
>
</vs-row>
</div>
- 在
VsRow
组件内部,添加一个带有属性vs-lg
定义为4
,vs-sm
定义为6
和vs-xs
定义为10
的VsCol
组件。然后,在VsCol
组件内部,我们将创建一个带有style
属性定义为margin: 20px;
的VsCard
组件:
<vs-col
vs-lg="4"
vs-sm="6"
vs-xs="10" >
<vs-card
style="margin: 20px;"
>
</vs-card>
</vs-col>
- 在
VsCard
组件内部,创建一个带有名称为header
的slot
的动态<template>
,一个h3
HTML 元素和您的标题:
<template slot="header">
<h3>
User Login
</h3> </template>
- 之后,创建一个
VsRow
组件,其中属性vs-align
定义为"center"
,vs-justify
定义为"center"
,并在其中放置两个VsCol
组件,其中属性vs-w
定义为12
:
<vs-row
vs-align="center"
vs-justify="center" >
<vs-col vs-w="12">
</vs-col>
<vs-col vs-w="12">
</vs-col>
</vs-row>
- 在第一个
VsCol
组件上,我们将添加一个VsInput
组件,其中属性danger
定义为数据error
的值,danger-text
定义为错误时显示的文本,label
定义为"Username"
,placeholder
定义为"Username or e-mail"
,并且v-model
指令绑定到username
:
<vs-input
:danger="error"
danger-text="Check your username or email"
label="Username"
placeholder="Username or e-mail"
v-model="username" />
- 在第二个
VsCol
组件中,我们将添加一个VsInput
组件,其中属性danger
定义为数据error
的值,danger-text
定义为错误时显示的文本,label
定义为"Password"
,type
定义为password
,placeholder
定义为"Your password"
,并且v-model
指令绑定到password
:
<vs-input
:danger="error"
label="Password"
type="password"
danger-text="Check your password"
placeholder="Your password"
v-model="password" />
- 最后,在卡片页脚中,我们需要创建一个动态的
<template>
,其中包含名为footer
的插槽。在这个<template>
中,我们将添加一个VsRow
组件,其中vs-justify
属性定义为flex-start
,并插入一个VsButton
,其中属性color
定义为success
,type
定义为filled
,icon
定义为account_circle
,size
定义为small
,并且@click
事件监听器指向userSignIn
方法:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="account_circle"
size="small"
@click="userSignIn"
>
Sign-in
</vs-button>
</vs-row> </template>
单文件组件<style>部分
在这部分,我们将创建单文件组件的<style>
部分。按照以下说明正确创建组件:
- 首先,我们需要使这个部分具有作用域,这样 CSS 规则就不会影响应用程序的任何其他组件:
<style scoped></style>
- 然后,我们需要为
container
类和VsInput
组件添加规则:
<style scoped>
.container {
height: 100vh;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
} .vs-input {
margin: 5px;
} </style>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
创建中间件
所有vue-router
中间件也可以称为导航守卫,并且它们可以附加到应用程序路由更改上。这些更改有一些钩子,您可以将其应用于您的中间件。身份验证中间件在路由更改之前发生,因此我们可以处理一切并将用户发送到正确的路由。
在src/router
文件夹中,创建一个名为middleware
的新文件夹,然后创建并打开一个名为authentication.js
的新文件。
在这个文件中,我们将创建一个默认的export
函数,它将有三个函数参数 - to
,from
和next
。to
和from
参数是对象,next
参数是一个回调函数:
export default (to, from, next) => { };
- 我们需要检查我们被重定向到的路由是否具有设置为
true
的经过身份验证的meta
属性,并且我们是否有一个具有'auth'
键的sessionStorage
项。如果通过了这些验证,我们可以执行next
回调:
if (to.meta.authenticated && sessionStorage.getItem('auth')) {
return next(); }
- 然后,如果第一个验证没有通过,我们需要检查我们将用户重定向到的路由是否具有经过身份验证的
meta
属性,并检查它是否为false
值。如果验证通过,我们将执行next
回调:
if (!to.meta.authenticated) {
return next(); }
- 最后,如果我们的任何验证都没有通过,执行
next
回调,传递'/login'
作为参数:
next('/login');
将元数据和中间件添加到路由器
创建完我们的中间件后,我们需要定义哪些路由将被验证,哪些路由不会被验证。然后我们需要将中间件导入到路由器中,并在执行时定义它:
在src/router
文件夹中打开user.js
。
在每个route
对象中,添加一个名为meta
的新属性。这个属性将是一个对象,具有一个经过身份验证的key
和一个值定义为true
。我们需要对每个路由都这样做 - 即使是子路由:
import Index from '@/views/user/Index.vue'; import List from '@/views/user/List.vue'; import View from '@/views/user/View.vue'; import Edit from '@/views/user/Edit.vue'; import Create from '@/views/user/Create.vue'; export default [
{ path: '/user',
name: 'user',
component: Index,
meta: {
authenticated: true,
},
children: [
{ path: '',
name: 'list',
component: List,
meta: {
authenticated: true,
},
},
{
path: ':id',
name: 'view',
component: View,
meta: {
authenticated: true,
},
},
{
path: 'edit/:id',
name: 'edit',
component: Edit,
meta: {
authenticated: true,
},
},
{
path: 'create',
name: 'create',
component: Create,
meta: {
authenticated: true,
},
},
],
}, ]
在src/router
文件夹中打开index.js
。
导入新创建的中间件和Login
视图组件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user'; import NotFound from '@/views/NotFound'; import Login from '@/views/Login'; import AuthenticationMiddleware from './middleware/authentication';
- 为登录页面视图创建一个新的
route
对象。这个路由对象将path
设置为'/login'
,name
定义为'login'
,component
定义为Login
,并且meta
属性将具有authenticated
键,其值设置为false
:
{
path: '/login',
name: 'login',
component: Login,
meta: {
authenticated: false,
}, },
- 在错误处理路由上,我们将定义
meta
属性authenticated
为false
,因为登录视图是一个公共路由:
{
path: '*',
component: NotFound,
meta: {
authenticated: false,
}, },
- 最后,在创建了
router
构造函数之后,我们需要在beforeEach
执行中注入中间件:
router.beforeEach(AuthenticationMiddleware);
它是如何工作的...
路由守卫作为中间件工作;它们在vue-router
进程的每个生命周期中都有一个钩子被执行。对于这个示例,我们选择了beforeEach
钩子来添加我们的中间件。
在这个钩子中,我们检查用户是否经过了身份验证,以及用户是否需要身份验证才能导航到该路由。在检查了这些变量之后,我们通过将用户发送到他们需要的路由来继续这个过程。
另请参阅
您可以在router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
找到有关 vue-router 路由守卫的更多信息。
异步加载您的页面
组件可以在需要时加载,路由也可以。使用vue-router
的惰性加载技术可以在应用程序中进行更多的代码拆分和更小的最终捆绑包。
在这个配方中,我们将学习如何转换路由以异步加载它们。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“创建身份验证中间件”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以启动一个新的项目。
要启动新的路由管理器,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create http-project
选择手动功能,并将Router
添加为所需的功能,如“如何做...”部分和“创建简单路由”配方中所示。
我们的配方将分为两部分:
更新路由管理器
更新用户路由
让我们开始吧。
更新路由管理器
要更新路由管理器,请按照以下说明进行操作:
在src/router
文件夹中打开index.js
文件。
在每个具有component
属性的路由中,我们将把组件的直接赋值转换为一个新函数。这将是一个返回 webpack 的import()
方法的箭头函数:
{
path: '/login',
name: 'login',
component: () => import('@/views/Login'),
meta: {
authenticated: false,
}, },
- 在每个具有
component
属性的route
对象上重复此过程。
更新用户路由
要更新用户路由,请按照以下说明进行操作:
在src/router
文件夹中打开user.js
文件。
在每个具有component
属性的路由中,我们将把组件的直接赋值转换为一个新函数。这将是一个返回 webpack 的import()
方法的箭头函数。
{
path: '/user',
name: 'user',
component: () => import('@/views/user/Index.vue'),
meta: {
authenticated: true,
},
children: [], },
- 在每个具有
component
属性的route
对象上重复此过程。
它是如何工作的...
在 ECMAScript 中,当我们使用export default
方法时,export
和import
是具有预定义值的对象。这意味着当我们import
一个新组件时,该组件已经指向该文件的default export
。
为了进行延迟加载过程,我们需要传递一个在运行时执行的函数,该函数的返回值将是 webpack 在捆绑过程中分割的代码的一部分。
当我们在vue-router
中调用这个函数时,vue-router
不直接导入组件,而是进行验证检查,确保当前组件导入是一个需要执行的函数。在函数执行后,响应被用作将显示在用户屏幕上的组件。
由于 webpack 的import()
方法是异步的,这个过程可以与其他代码执行同时进行,而不会干扰或阻塞 JavaScript 虚拟机的主线程。
另请参阅
您可以在router.vuejs.org/guide/advanced/lazy-loading.html
找到有关vue-router
延迟加载的更多信息。
您可以在webpack.js.org/guides/code-splitting/
找到有关webpack
代码拆分的更多信息。
您可以在github.com/tc39/proposal-dynamic-import
找到有关 ECMAScript 动态导入提案的更多信息。
第七章:使用 Vuex 管理应用程序状态
在兄弟组件之间传输数据可能非常容易,但想象一下让一系列组件对任何数据变化做出反应。你需要在事件总线中触发一个事件,或者通过所有父组件发送事件,直到它到达事件链的顶部,然后再一路发送到所需的组件;这个过程可能非常繁琐和痛苦。如果你正在开发一个大型应用程序,这个过程是不可持续的。
Flux 库是为了帮助这个过程而开发的,其想法是将反应性带出组件边界,因为 Vuex 能够维护数据的唯一真相来源,并且同时也是你制定业务规则的地方。
在这一章中,我们将学习如何使用 Vuex,开发我们的存储,将其应用到我们的组件,并对其进行命名空间,以便我们可以在同一个存储中拥有不同的 Vuex 模块。
在这一章中,我们将涵盖以下的配方:
创建一个简单的 Vuex 存储
创建和理解 Vuex 状态
创建和理解 Vuex 变化
创建和理解 Vuex 操作
创建和理解 Vuex 获取器
使用 Vuex 创建一个动态组件
为开发添加热模块重新加载
创建一个 Vuex 模块
技术要求
在这一章中,我们将使用Node.js和Vue-CLI。
注意,Windows 用户,你需要安装一个名为windows-build-tools
的 NPM 包,以便安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Vue-CLI,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个简单的 Vuex 存储
在应用程序中创建一个唯一的真相来源可以让你简化数据流,使数据的反应性流向另一个视角,你不再受限于父子关系。数据现在可以存储在一个地方,每个人都可以获取或请求数据。
在这个配方中,我们将学习如何安装 Vuex 库并创建我们的第一个单一存储,以及如何使用反应式操作和数据获取器来操作它。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要创建一个 Vue-CLI 项目,请按照以下步骤进行:
- 我们需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> vue create initial-vuex
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用Spacebar选择选项。
有两种方法可以启动新项目。默认方法是基本的babel
和eslint
项目,没有任何插件或配置,还有手动
模式,您可以选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys)
default (babel, eslint)
❯ Manually select features
- 现在我们被问及项目中需要的功能。这些功能包括一些 Vue 插件,如 Vuex 或 Router(
Vue-Router
),测试工具,代码检查工具等。选择Babel
,Router
,Vuex
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router ❯ Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 继续此过程,选择一个代码检查工具和格式化工具。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 设置了代码检查规则后,我们需要定义它们何时应用于您的代码。它们可以在“保存时”应用,也可以在“提交时”修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、代码检查工具和处理器之后,我们需要选择设置和配置的存储位置。最好的存储位置是专用文件,但也可以将它们存储在
package.json
文件中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ **In dedicated config files****In package.json**
- 现在您可以选择是否将此选择作为将来项目的预设,这样您就不需要再次重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
我们的步骤将分为两部分:
创建 store
使用 Vuex 创建响应式组件
让我们开始吧。
创建 store
现在您已经有了包含 Vuex 库的项目,我们需要创建我们的第一个 store。在接下来的步骤中,我们将创建 Vuex store:
打开src/store
文件夹中的index.js
。
在state
属性中,添加一个名为counter
的新键,并将值设置为0
:
state: {
counter: 0,
},
- 在
mutations
属性中,添加两个新函数,increment
和decrement
。这两个函数都将有一个state
参数,这是当前的 Vuexstate
对象。increment
函数将counter
增加1
,而decrement
函数将counter
减少1
:
mutations: {
increment: (state) => {
state.counter += 1;
},
decrement: (state) => {
state.counter -= 1;
},
},
- 最后,在
actions
属性中,添加两个新函数,increment
和decrement
。这两个函数都将有一个解构参数commit
,它是调用 Vuex mutation 的函数。在每个函数中,我们将执行commit
函数,将当前函数的名称作为字符串参数传递:
actions: {
increment({ commit }) {
commit('increment');
},
decrement({ commit }) {
commit('decrement');
},
},
使用 Vuex 创建响应式组件
现在您已经定义了您的 Vuex 存储,您需要与之交互。我们将创建一个响应式组件,它将在屏幕上显示当前状态的counter
,并显示两个按钮,一个用于增加counter
,另一个用于减少counter
。
单文件组件<script>部分
在这里,我们将编写单文件组件的<script>
部分:
从src
文件夹中打开App.vue
文件。
在文件中创建<script>
部分,使用export default
对象:
<script>
export default {}; </script>
- 在新创建的对象中,添加 Vue
computed
属性,属性名为counter
。在这个属性中,我们需要返回当前的$store.state.counter
:
computed: {
counter() {
return this.$store.state.counter;
}, },
- 最后,在 Vue
methods
属性中创建两个函数,increment
和decrement
。这两个函数都将执行一个带有函数名称作为字符串参数的$store.dispatch
:
methods: {
increment() {
this.$store.dispatch('increment');
},
decrement() {
this.$store.dispatch('decrement');
}, },
单文件组件部分
让我们编写单文件组件的<template>
部分:
在src
文件夹中打开App.vue
文件。
在<template>
部分中,删除div#app
内的所有内容。
创建一个包含计数器变量的h1
HTML 元素。
创建一个带有@click
指令的事件监听器的按钮,调用increment
函数,并将+
作为标签:
<button @click="increment">+</button>
- 创建一个带有
@click
指令的事件监听器的按钮,调用decrement
函数,并将-
作为标签:
<button @click="decrement">-</button>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
当您声明 Vuex 存储时,您需要创建三个主要属性,state
,mutations
和actions
。这些属性作为一个单一的结构,通过注入的$store
原型或导出的store
变量绑定到 Vue 应用程序。
state
是一个集中的对象,保存您的信息并使其可供mutation
、actions
或组件使用。改变state
始终需要通过mutation
执行的同步函数。
mutation
是一个同步函数,可以改变state
并且是可追踪的,因此在开发时,可以通过 Vuex 存储中执行的所有mutations
进行时间旅行。
action
是一个异步函数,可用于保存业务逻辑、API 调用、分派其他actions
和执行mutations
。这些函数是 Vuex 存储中任何更改的常见入口点。
Vuex 存储的简单表示可以在此图表中看到:
另请参阅
您可以在vuex.vuejs.org/
找到有关 Vuex 的更多信息。
创建和理解 Vuex 状态
Vuex 状态似乎很容易理解。但是,随着数据变得更加深入和嵌套,其复杂性和可维护性可能变得更加复杂。
在本配方中,我们将学习如何创建一个 Vuex 状态,该状态可以在渐进式 Web 应用程序(PWA)/**单页面应用程序(SPA)和服务器端渲染(SSR)**的情景中使用,而无需任何问题。
准备就绪
本配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在第六章中使用的 Vue-CLI 的 Vue 项目,或者我们可以开始一个新的项目。
要启动一个新项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create vuex-store
选择手动功能,根据'如何做...'部分的指示,将Router
和Vuex
添加为必需功能。
我们的配方将分为两部分:
通过vue ui
添加 Vuex
创建Vuex
状态
让我们开始吧。
通过 vue ui 添加 Vuex
当导入通过 Vue-CLI 创建的旧项目时,可以通过vue ui
界面自动添加 Vuex,而无需任何努力。我们将学习如何向旧项目添加 Vuex 库,以便继续开发该项目。
在接下来的步骤中,我们将使用vue ui
界面添加 Vuex。
- 在项目文件夹中,通过在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)上执行以下命令来打开
vue ui
:
> vue ui
- 选择你正在工作的正确项目。在右侧边栏中,点击插件菜单图标:
- 在插件页面的顶部工具栏上,点击“添加 vuex”按钮。这将触发一个弹出模态窗口,然后点击“继续”按钮完成在应用程序上安装 Vuex:
- 将 Vuex 添加到我们的应用程序将改变应用程序的结构。首先,我们会注意到
src
文件夹中有一个名为store
的新文件夹,在main.js
文件中,它被添加到了导入和在 Vue 应用程序中注入store
:
import './server/server'; import Vue from 'vue'; import App from './App.vue'; import Vuesax from 'vuesax'; import './style.css'; import router from './router' import store from './store' Vue.use(Vuesax); Vue.config.productionTip = false; new Vue({
router,
store,
render: h => h(App) }).$mount('#app');
创建 Vuex 状态
为了将数据保存在 Vuex 中,您需要有一个初始状态,在用户进入您的应用程序时加载并定义为默认状态。在这里,我们将学习如何创建 Vuex 状态并将其用作单例,以便 Vuex 可以在 SPA 和 SSR 页面中使用:
现在我们将创建一个可以在 SSR 和 SPA 中使用的 Vuex 存储:
在src/store
文件夹中,创建一个名为user
的新文件夹,在这个文件夹里创建一个名为state.js
的新文件。
创建一个新的generateState
函数。这个函数将返回一个 JavaScript 对象,有三个主要属性,data
,loading
和error
。data
属性将是一个 JavaScript 对象,其中有一个名为usersList
的属性,默认为空数组,以及一个名为userData
的属性,其中包含用户的默认对象。loading
属性将默认设置为布尔值false
,error
将有一个默认值初始化为null
:
const generateState = () => ({
data: {
usersList: [],
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
},
},
loading: false,
error: null, });
- 创建函数后,我们将在文件末尾创建一个
export default
对象,它将是一个 JavaScript 对象,并且我们将解构generateState
函数的返回值:
export default { ...generateState() };
在user
文件夹中创建一个名为index.js
的新文件并打开它。
导入新创建的state
:
import state from './state';
- 在文件末尾,创建一个
export default
文件作为 JavaScript 对象。在这个对象中,我们将添加导入的state
:
export default {
state, };
打开src/store
文件夹中的index.js
文件。
从user
文件夹中导入index.js
文件:
import Vue from 'vue'; import Vuex from 'vuex'; import UserStore from './user';
- 在创建一个新的 Vuex store 的
export default
函数中,我们将删除其中的所有属性,并将导入的UserStore
解构对象放入Vuex.Store
参数中:
export default new Vuex.Store({
...UserStore, })
工作原理...
当使用vue ui
将 Vuex 作为插件添加时,vue ui
将自动添加所需的文件,并导入所有需要的内容。这是创建 Vuex store
的初始阶段。
首先是创建一个专门管理state
的文件,我们可以使用它来分离store
中状态的开始过程以及如何初始化状态。
在这种情况下,我们使用一个函数来生成每次调用时都会生成一个全新的state
。这是一个很好的做法,因为在 SSR 环境中,服务器的state
始终是相同的,我们需要为每个新连接创建一个新的state
。
在创建state
之后,我们需要创建一个默认文件来导出将在user
文件夹中创建的 Vuex 文件。这个文件是对将在文件夹中创建的所有文件(state
,actions
,mutation
和getters
)的简单导入。导入后,我们导出一个带有所需的 Vuex 属性名称的对象,state
,actions
,mutations
和getters
。
最后,在 Vuex 的store
中,我们导入了一个文件,将所有内容聚合并解构到我们的 store 中进行初始化。
还有更多...
Vuex
state 是应用程序中的唯一数据源,它就像一个全局数据管理器,不应直接更改。这是因为我们需要防止数据的同时变异。为了避免这种情况,我们总是需要通过 mutations 来改变我们的 state,因为这些函数是同步的,并由 Vuex 控制。
另请参阅
在vuex.vuejs.org/guide/state.html
找到有关 Vuex state 的更多信息。
创建和理解 Vuex mutations
当 Vuex 发生变化时,我们需要一种以异步形式执行这种变化并跟踪它的方式,以便在第一个变化完成之前不会执行另一个变化。
在这种情况下,我们需要 mutations,这些是仅负责改变应用程序状态的函数。
在这个示例中,我们将学习如何创建 Vuex mutations 以及最佳实践。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在'创建和理解 Vuex 状态'食谱中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create vuex-store
选择手动功能,根据'如何做...'部分的指示,添加Router
和Vuex
作为必需功能。
现在我们创建一个 Vuex mutation 和基本类型的 mutation:
在src/store
文件夹内的user
文件夹中创建一个名为types.js
的新文件,并打开它。
在这个文件中,我们将创建一个export default
的 JavaScript 对象,其中包含一组键,这些键将是我们 mutations 的名称。这些键将是LOADING
、ERROR
、SET_USER_LIST
、SET_USER_DATA
、UPDATE_USER
和REMOVE_USER
:
export default {
LOADING: 'LOADING',
ERROR: 'ERROR',
SET_USER_LIST: 'SET_USER_LIST',
SET_USER_DATA: 'SET_USER_DATA',
UPDATE_USER: 'UPDATE_USER',
REMOVE_USER: 'REMOVE_USER', }
在user
文件夹中创建一个名为mutations.js
的新文件,并打开它。
导入新创建的types.js
文件:
import MT from './types';
- 创建一个名为
setLoading
的新函数,它将接收 Vuex state
作为参数,并在执行时将状态的 loading 属性定义为true
。
const setLoading = state => {
state.loading = true; };
- 创建一个名为
setError
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state
的loading
属性设置为false
,将error
属性设置为接收到的payload
参数:
const setError = (state, payload) => {
state.loading = false;
state.error = payload; };
- 创建一个名为
setUserList
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state.data
的usersList
属性定义为接收到的payload
参数,将state
的loading
属性设置为false
,将error
属性设置为null
:
const setUserList = (state, payload) => {
state.data.usersList = payload;
state.loading = false;
state.error = null; };
- 创建一个名为
setUserData
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state.data
的userData
属性定义为接收到的payload
参数,将state
的loading
属性设置为false
,将error
属性设置为null
:
const setUserData = (state, payload) => {
state.data.userData = payload;
state.loading = false;
state.error = null; };
- 创建一个名为
updateUser
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将更新state.data
的usersList
属性中的用户数据,将state
的loading
属性定义为false
,将error
属性定义为null
:
const updateUser = (state, payload) => {
const userIndex = state.data.usersList.findIndex(u => u.id ===
payload.id);
if (userIndex > -1) {
state.data.usersList[userIndex] = payload;
}
state.loading = false;
state.error = null; };
- 创建一个名为
removeUser
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将从state.data
的usersList
属性中删除用户数据,将state
的loading
属性定义为false
,将error
属性定义为null
:
const removeUser = (state, payload) => {
const userIndex = state.data.usersList.findIndex(u => u.id ===
payload);
if (userIndex > -1) {
state.data.usersList.splice(userIndex, 1);
}
state.loading = false;
state.error = null; };
- 最后,创建一个
export default
对象,其中键是我们在types.js
文件中创建的类型,并将每个键定义为我们创建的函数:
export default {
[MT.LOADING]: setLoading,
[MT.ERROR]: setError,
[MT.SET_USER_LIST]: setUserList,
[MT.SET_USER_DATA]: setUserData,
[MT.UPDATE_USER]: updateUser,
[MT.REMOVE_USER]: removeUser, }
打开user
文件夹中的index.js
文件。
导入新创建的mutations.js
文件,并将其添加到export default
JavaScript 对象中:
import state from './state';
import mutations from './mutations';
export default {
state,
mutations,
};
它是如何工作的...
每个mutation
都是一个将作为commit
调用的函数,并且在 Vuex 存储中具有标识符。这个标识符是导出的 JavaScript 对象中的mutation
键。在这个示例中,我们创建了一个文件,将所有标识符作为对象值保存,以便在我们的代码中作为常量使用。
这种模式有助于我们开发需要知道每个mutation
名称的 Vuex actions
。
在导出mutation
JavaScript 对象时,我们使用常量作为键,相应的函数作为其值,这样 Vuex 存储在调用时可以执行正确的函数。
另请参阅
在vuex.vuejs.org/guide/mutations.html
找到有关 Vuex mutations 的更多信息。
创建和理解 Vuex getters
从Vuex
中访问数据可以通过状态本身完成,这可能非常危险,或者通过 getters 完成。Getters 就像是可以预处理并传递数据而不触及或干扰 Vuex 存储状态的数据。
Getter 背后的整个理念是可以编写自定义函数,可以在需要时从状态中提取数据的单一位置,以便获得所需的数据。
在这个示例中,我们将学习如何创建一个 Vuex getter 和一个可以作为高阶函数使用的动态 getter。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“创建和理解 Vuex mutations”示例中使用的 Vue 项目与 Vue-CLI,或者我们可以启动一个新的项目。
要启动一个新的项目,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能,并根据“如何做...”部分中的指示添加 Router 和Vuex
作为需要的功能。
在接下来的步骤中,我们将创建 Vuex 的 getter:
在src/store/user
文件夹中创建一个名为getters.js
的新文件。
创建一个名为getUsersList
的新函数,并返回state.data.usersList
属性:
function getUsersList(state) {
return state.data.usersList;
}
在getter
函数中,函数将始终接收到的第一个参数是 Vuex store
的当前state
。
- 创建一个名为
getUserData
的新函数,并返回state.data.userData
属性:
function getUserData(state) {
return state.data.userData; }
- 创建一个名为
getUserById
的新函数,并返回另一个函数,该函数接收userId
作为参数。这个返回函数将返回与接收到的userId
相匹配的state.data.usersList
的搜索结果:
function getUserById(state) {
return (userId) => {
return state.data.usersList.find(u => u.id === userId);
} }
- 创建一个名为
isLoading
的新函数,并返回state.loading
属性:
function isLoading(state) {
return state.loading;
}
- 创建一个名为
hasError
的新函数,并返回state.error
属性:
function hasError(state) {
return state.error;
}
- 最后,创建一个带有所有创建的函数作为属性的
export default
JavaScript 对象:
export default {
getUsersList,
getUserData,
getUserById,
isLoading,
hasError, };
在src/store/user
文件夹中打开index.js
文件。
导入新创建的getters.js
文件,并将其添加到默认导出的 JavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';
export default {
state,
mutations,
getters,
};
它是如何工作的...
Getter 就像是从对象中获取的 GET 函数,是静态缓存函数-只有在state
发生变化时才会改变返回值。但是,如果将返回值作为高阶函数添加,就可以赋予它更多的功能,使用更复杂的算法并提供特定的数据。
在这个示例中,我们创建了两种类型的 getter:最基本的,返回简单数据,以及高阶函数,需要作为函数调用以检索所需的值。
还有更多...
使用带有业务逻辑的 getter 是收集更多状态数据的好方法。这是一个很好的模式,因为在较大的项目中,它可以帮助其他开发人员更好地理解每个 GET 函数中发生了什么以及它在幕后是如何工作的。
您始终需要记住,getter 是同步函数,并对状态变化具有反应性,因此 getter 上的数据是被记忆和缓存的,直到单一的真相源接收到提交并更改它。
参见
您可以在vuex.vuejs.org/guide/getters.html
找到有关 Vuex getters 的更多信息。
创建和理解 Vuex actions
你已经准备好了所有的状态,你的数据集,现在你需要从外部来源获取新数据或者在你的应用程序中改变这些数据。这就是操作发挥作用的地方。
操作负责在应用程序和外部世界之间的通信中编排过程。控制数据何时需要在状态上进行变异并返回给操作的调用者。
通常,操作是通过组件或视图进行调度,但有时操作可以调度另一个操作,以在应用程序中创建一系列操作。
在这个配方中,我们将学习如何在我们的应用程序中创建所需的操作,以定义用户列表,更新用户和删除用户。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“创建和理解 Vuex getters”配方中使用的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能,并根据“如何做...”部分和“创建一个简单的 Vuex 存储”配方中指示的要求,添加Router
和Vuex
作为必需的功能。
现在按照以下步骤创建 Vuex 操作:
在src/store/user
文件夹中创建一个名为actions.js
的新文件。
从fetchApi
包装器中导入变异类型文件(types.js
)和getHttp
,patchHttp
,postHttp
和deleteHttp
函数:
import {
getHttp,
patchHttp,
deleteHttp,
postHttp,
} from '@/http/fetchApi';
import MT from './types';
- 创建一个名为
createUser
的新的异步
函数,它接收解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,并将userData
作为第二个参数,用于创建用户。添加一个try/catch
语句,在try
上下文中。首先,我们执行commit(MT.LOADING)
,然后我们从 API 中获取用户列表,最后,执行commit(MT.SET_USER_DATA, data)
,将用户列表传递给被突变。如果我们收到异常并进入Catch
语句,我们将执行commit(MT.ERROR, error)
,将收到的错误传递给state
:
async function createUser({ commit }, userData) {
try {
commit(MT.LOADING);
await postHttp(`/api/users`, {
data: {
...userData,
}
});
commit(MT.SET_USER_DATA, userData);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
fetchUsersList
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中获取用户列表,最后执行commit(MT.SET_USER_LIST, data)
,将用户列表传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function fetchUsersList({ commit }) {
try {
commit(MT.LOADING);
const { data } = await getHttp(`api/users`);
commit(MT.SET_USER_LIST, data);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
fetchUsersData
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的将要获取的userId
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中获取用户列表,最后执行commit(MT.SET_USER_DATA, data)
,将用户列表传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function fetchUserData({ commit }, userId) {
try {
commit(MT.LOADING);
const { data } = await getHttp(`api/users/${userId}`);
commit(MT.SET_USER_DATA, data);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
updateUser
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的payload
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后将用户数据提交到 API,最后执行commit(MT.UPDATE_USER, payload)
,将新的用户数据传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function updateUser({ commit }, payload) {
try {
commit(MT.LOADING);
await patchHttp(`api/users/${payload.id}`, {
data: {
...payload,
}
});
commit(MT.UPDATE_USER, payload);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
removeUser
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的userId
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中删除用户数据,最后执行commit(MT.REMOVE_USER, userId)
,将userId
传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function removeUser({ commit }, userId) {
try {
commit(MT.LOADING);
await deleteHttp(`api/users/${userId}`);
commit(MT.REMOVE_USER, userId);
} catch (error) {
commit(MT.ERROR, error);
} }
- 最后,我们将创建一个默认导出的 JavaScript 对象,其中包含所有创建的函数作为属性:
export default {
createUser,
fetchUsersList,
fetchUserData,
updateUser,
removeUser, }
- 在
src/store/user
文件夹的index.js
中导入新创建的actions.js
文件,并将其添加到export default
JavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';
import actions from './actions';
export default {
state,
mutations,
getters,
actions,
};
它是如何工作的...
操作是所有 Vuex 生命周期更改的初始化程序。当分发时,操作可以执行一个 mutation commit,或者另一个操作 dispatch,甚至是对服务器的 API 调用。
在我们的情况下,我们将我们的 API 调用放在了 actions 中,因此当异步函数返回时,我们可以执行 commit 并将状态设置为函数的结果。
另请参阅
在vuex.vuejs.org/guide/actions.html
找到有关 Vuex 操作的更多信息。
使用 Vuex 创建动态组件
将 Vuex 与 Vue 组件结合使用,可以在多个组件之间实现响应性,而无需直接进行父子通信,并分担组件的责任。
使用这种方法允许开发人员增强应用程序的规模,无需将数据状态存储在组件本身,而是使用单一真相作为整个应用程序的存储。
在这个配方中,我们将使用最后的配方来改进一个应用程序,其中使用了父子通信,并将其作为整个应用程序中可用的单一真相。
准备就绪
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要创建我们的动态组件,我们将把组件从有状态转换为无状态,并提取一些可以制作成新组件的部分。
我们将使用在“创建和理解 Vuex 操作”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create vuex-store
选择手动功能,并根据“如何做...”部分中“创建简单的 Vuex 存储”配方中的指示添加Router
和Vuex
作为所需功能。
我们的配方将分为五个部分:
创建用户列表组件
编辑用户列表页面
编辑用户视图页面
编辑用户视图页面
编辑用户创建页面
让我们开始吧。
创建用户列表组件
因为 Vuex 给了我们在应用程序中拥有单一数据源的能力,我们可以为我们的应用程序创建一个新的组件,该组件将处理用户列表并触发从服务器获取用户列表的 Vuex 操作。这个组件可以是无状态的,并且可以自行执行Vuex
操作。
单文件组件<script>
部分
让我们编写单文件组件的<script>
部分:
在src/components
文件夹中创建一个名为userList.vue
的新文件。
从src/mixin
文件夹导入changeRouterMixin
:
import changeRouteMixin from '@/mixin/changeRoute';
- 创建一个
export default
的 JavaScript 对象,并添加一个名为mixin
的新 Vue 属性,其默认值为一个数组。将导入的changeRouteMixin
添加到这个数组中:
mixins: [changeRouteMixin],
- 创建一个名为
computed
的新 Vue 属性。在这个属性中,创建一个名为userList
的新值。这个属性将是一个返回 Vuex 存储器 gettergetUsersList
的函数:
computed: {
userList() {
return this.$store.getters.getUsersList;
}, },
单文件组件<template>
部分
在这里,我们将编写单文件组件的<template>
部分:
打开views
文件夹内users
文件夹中的List.vue
文件,并复制VsTable
组件的内容和组件。
打开src/components
文件夹中的userList.vue
文件。
将你从List.vue
文件中复制的内容粘贴到<template>
部分中:
<template>
<vs-table
:data="userList"
search
stripe pagination max-items="10"
style="width: 100%; padding: 20px;"
>
<template slot="thead">
<vs-th sort-key="name">
#
</vs-th>
<vs-th sort-key="name">
Name
</vs-th>
<vs-th sort-key="email">
Email
</vs-th>
<vs-th sort-key="country">
Country
</vs-th>
<vs-th sort-key="phone">
Phone
</vs-th>
<vs-th sort-key="Birthday">
Birthday
</vs-th>
<vs-th>
Actions
</vs-th>
</template>
<template slot-scope="{data}">
<vs-tr :key="index" v-for="(tr, index) in data">
<vs-td :data="data[index].id">
{{data[index].id}}
</vs-td>
<vs-td :data="data[index].name">
{{data[index].name}}
</vs-td>
<vs-td :data="data[index].email">
<a :href="`mailto:${data[index].email}`">
{{data[index].email}}
</a>
</vs-td>
<vs-td :data="data[index].country">
{{data[index].country}}
</vs-td>
<vs-td :data="data[index].phone">
{{data[index].phone}}
</vs-td>
<vs-td :data="data[index].birthday">
{{data[index].birthday}}
</vs-td>
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeRoute('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeRoute('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/>
</vs-td>
</vs-tr>
</template>
</vs-table> </template>
编辑用户列表页面
现在我们已经将用户列表提取到一个新的组件中,我们需要导入这个组件并移除旧的 VsTable,它使我们的视图混乱。
单文件组件<script>
部分
在这一步中,我们将编写单文件组件的<script>
部分:
打开views
文件夹内users
文件夹中的List.vue
文件。
从components
文件夹导入新创建的用户列表组件:
import changeRouteMixin from '@/mixin/changeRoute'; import UserTableList from '@/components/userList';
- 在
export default
的 JavaScript 对象中,添加一个名为components
的新属性。将该属性声明为 JavaScript 对象,并将导入的UserTableList
组件添加到对象中:
components: { UserTableList },
- 在
methods
属性中,在getAllUsers
函数中,我们需要更改内容以在调用时执行一个 Vuex 分发。这个方法将执行fetchUsersList
的 Vuex 操作:
async getAllUsers() {
this.$store.dispatch('fetchUsersList'); },
- 最后,在
deleteUser
函数中,我们需要更改内容以在调用时执行一个 Vuex 分发。这个方法将执行removeUser
的 Vuex 操作,并将userId
作为参数传递:
async deleteUser(id) {
this.$store.dispatch('removeUser', id);
await this.getAllUsers(); },
单文件组件<template>
部分
让我们编写单文件组件的<template>
部分:
在view
文件夹内的users
文件夹中打开List.vue
文件。
用新导入的UserTableList
替换VsTable
组件及其内容:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<user-table-list /> </vs-col>
编辑用户视图页面
现在我们可以将 Vuex 添加到用户视图页面。我们将添加 Vuex 操作和获取器来操作数据,并从页面中提取管理责任。
单文件组件的<script>
部分
现在你要创建单文件组件的<script>
部分:
从view
文件夹内的users
文件夹中打开View.vue
文件。
删除 Vue 的data
属性。
在 Vue 的computed
属性中,添加userData
,返回一个 Vuex 的 getter,getUserData
:
userData() {
return this.$store.getters.getUserData; },
- 最后,在
getUserById
方法中,将内容更改为调度一个 Vuex 操作fetchUserData
,传递计算的userId
属性作为参数:
async getUserById() {
await this.$store.dispatch('fetchUserData', this.userId); },
单文件组件的<template>
部分
是时候编写单文件组件的<template>
部分了:
在view
文件夹内的users
文件夹中打开View.vue
文件。
在 UserForm 组件中,将v-model
指令更改为:value
指令:
<user-form
:value="userData"
disabled />
当使用只读值,或者需要删除v-model
指令的语法糖时,可以将输入值声明为:value
指令,并将值更改事件声明为@input
事件监听器。
编辑用户编辑页面
我们需要编辑我们的用户。在上一个示例中,我们使用了一个有状态的页面,并在页面内执行了所有操作。我们将状态转换为临时状态,并在 Vuex 操作上执行 API 调用。
单文件组件的<script>
部分
在这里,我们将创建单文件组件的<script>
部分:
在view
文件夹内的users
文件夹中打开Edit.vue
文件。
在 Vue 的data
属性中,将数据的名称从userData
更改为tmpUserData
:
data: () => ({
tmpUserData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
computed
属性中,添加一个名为userData
的新属性,它将返回 Vuex 的 gettergetUserData
:
userData() {
return this.$store.getters.getUserData; }
- 添加一个名为
watch
的新 Vue 属性,并添加一个名为userData
的新属性,它将是一个 JavaScript 对象。在这个对象中,添加三个属性,handler
,immediate
和deep
。handler
属性将是一个接收名为newData
的参数的函数,它将tmpUserData
设置为这个参数。immediate
和deep
属性都是设置为true
的布尔属性:
watch: {
userData: {
handler(newData) {
this.tmpUserData = newData;
},
immediate: true,
deep: true,
} },
- 在 Vue 的
methods
属性中,我们需要更改getUserById
的内容以调度名为fetchUserData
的 Vuex 动作,并将computed
属性userId
作为参数传递:
async getUserById() {
await this.$store.dispatch('fetchUserData', this.userId); },
- 在
updateUser
方法中,更改内容以调度名为updateUser
的 Vuex 动作,并将tmpUserData
作为参数传递:
async updateUser() {
await this.$store.dispatch('updateUser', this.tmpUserData);
this.changeRoute('list'); },
单文件组件部分
在这部分,我们将编写单文件组件的<template>
部分:
在view
文件夹内的users
文件夹中打开Edit.vue
。
将UserForm
组件的v-model
指令的目标更改为tmpUserData
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12"
style="margin: 20px" >
<user-form
v-model="tmpUserData"
/> </vs-col>
编辑用户创建页面
对于用户创建页面,更改将是最小的,因为它只执行 API 调用。我们需要添加 Vuex 动作调度。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在view
文件夹内的users
文件夹中打开Create.vue
文件。
更改createUser
方法的内容以调度名为createUser
的 Vuex 动作,并将userData
作为参数传递:
async createUser() {
await this.$store.dispatch('createUser', this.userData);
this.changeRoute('list'); },
它是如何工作的...
在所有四个页面中,我们进行了更改,将业务逻辑或 API 调用从页面中移除到 Vuex 存储,并尝试使其对于数据的维护责任更少。
因此,我们可以将一段代码放入一个新组件中,该组件可以放置在应用程序的任何位置,并且将显示当前用户列表,而不受实例化它的容器的任何限制。
这种模式有助于我们开发更突出的应用程序,其中需要的组件不那么业务导向,而更专注于它们的任务。
另请参阅
您可以在vuex.vuejs.org/guide/structure.html
找到有关 Vuex 应用程序结构的更多信息。
为开发添加热模块重载
热模块重载(HMR)是一种用于加快应用程序开发的技术,您无需刷新整个页面即可获取您刚刚在编辑器上更改的新代码。HMR 将仅更改和刷新您在编辑器上更新的部分。
在所有 Vue-CLI 项目或基于 Vue 的框架(如 Quasar Framework)中,HMR 存在于应用程序的呈现中。因此,每当您更改任何文件,该文件是 Vue 组件并且正在呈现时,应用程序将在运行时将旧代码替换为新代码。
在这个教程中,我们将学习如何向 Vuex 存储添加 HMR,并能够在不需要刷新整个应用程序的情况下更改 Vuex 存储。
准备工作
此教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“使用 Vuex 创建动态组件”中使用的 Vue 项目和 Vue-CLI,或者我们可以启动一个新的项目。
要启动一个新的项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能,将Router
和Vuex
添加为所需功能,如“如何做...”部分和“创建简单的 Vuex 存储”教程中所示。
在接下来的步骤中,我们将向 Vuex 添加 HMR:
打开src/store
文件夹中的index.js
文件。
将export default
转换为一个名为store
的常量,并使其可导出:
export const store = new Vuex.Store({
...UserStore, });
- 检查 webpack
hot-module-reload
插件是否处于活动状态:
if (module.hot) {}
- 创建一个名为
hmr
的新常量,其中包含user
文件夹中index.js
,getters.js
,actions.js
和mutations.js
文件的路径:
const hmr = [
'./user',
'./user/getters',
'./user/actions',
'./user/mutations', ];
- 创建一个名为
reloadCallback
的新函数。在这个函数中,创建三个常量getters
,actions
和mutations
。每个常量将指向user
文件夹中的等效文件,并调用store.hotUpdate
函数,将一个对象作为参数传递,其中包含您创建的常量的值:
const reloadCallback = () => {
const getters = require('./user/getters').default;
const actions = require('./user/actions').default;
const mutations = require('./user/mutations').default; store.hotUpdate({
getters,
actions,
mutations,
}) };
由于文件的 Babel 输出,您需要在使用 webpack require
函数动态导入的文件末尾添加.default
。
- 执行 webpack HMR 的
accept
函数,将hmr
常量作为第一个参数传递,将reloadCallback
作为第二个参数传递:
module.hot.accept(hmr, reloadCallback);
- 最后,默认导出创建的
store
:
export default store;
它是如何工作的...
Vuex 存储支持使用 webpack HMR 插件的 API 进行 HMR。
当它可用时,我们创建一个可能需要更新的文件列表,以便 webpack 可以意识到这些文件的任何更新。当这些文件中的任何一个被更新时,将执行您创建的特殊回调。这个回调是使 Vuex 能够完全更新或更改更新文件的行为的回调。
另请参阅
您可以在vuex.vuejs.org/guide/hot-reload.html
找到有关 Vuex 热重载的更多信息。
您可以在 webpack.js.org/guides/hot-module-replacement/
找到有关 webpack HMR 的更多信息。
创建一个 Vuex 模块
随着我们的应用程序的增长,在单个对象中工作可能非常危险。项目的可维护性和每次更改可能产生的风险都会变得更糟。
Vuex 有一种叫做模块的方法,可以帮助我们将存储分成不同的存储分支。这些分支或模块中的每一个都有不同的状态、变化、获取器和操作。这种模式有助于开发,并减少了向应用程序添加新功能的风险。
在这个教程中,我们将学习如何创建一个模块以及如何与之一起工作,将其分成专用分支。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在“使用 Vuex 创建动态组件”中使用的 Vue 项目和 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能并将 Router
和 Vuex
添加为必需功能,如“如何做...”部分和“创建简单的 Vuex 存储”教程中所示。
我们的教程将分为两个部分:
创建新的认证模块
向 Vuex 添加模块
让我们开始吧。
创建新的认证模块
首先,我们需要创建一个新的 Vuex
模块。这个示例模块将被称为 authentication
,并将存储用户的凭据数据。
在这些步骤中,我们将为 Vuex
创建 authentication
模块:
在 src/store
文件夹中创建一个名为 authentication
的新文件夹。
在这个新创建的文件夹中,创建一个名为 state.js
的新文件,并打开它。
创建一个名为 generateState
的函数,它将返回一个具有 data.username
、data.token
、data.expiresAt
、loading
和 error
属性的 JavaScript 对象:
const generateState = () => ({
data: {
username: '',
token: '',
expiresAt: null,
},
loading: false,
error: null, });
- 在文件末尾创建一个
export default
对象。这个对象将是一个 JavaScript 对象。我们将解构 generateState
函数的返回值:
export default { ...generateState() };
在 src/store
文件夹中的 authentication
文件夹中创建一个名为 index.js
的新文件,并打开它。
导入新创建的 state.js
文件:
import state from './state';
- 在文件末尾创建一个
export default
对象。这个对象将是一个 JavaScript 对象。添加一个名为namespaced
的新属性,其值设置为true
,并添加导入的state
:
export default {
namespaced: true,
state, };
将模块添加到 Vuex
现在我们已经创建了我们的模块,我们将把它们添加到 Vuex 存储中。我们可以将新模块与旧代码集成在一起。这不是问题,因为 Vuex 将把新模块处理为一个命名空间对象,具有完全独立的 Vuex 存储。
现在,在这些步骤中,我们将把创建的模块添加到 Vuex 中:
打开src/store
文件夹中的index.js
文件。
从authentication
文件夹中导入index.js
文件:
import Vue from 'vue'; import Vuex from 'vuex'; import UserStore from './user'; import Authentication from './authentication';
- 在
Vuex.Store
函数中,添加一个名为modules
的新属性,这是一个 JavaScript 对象。然后添加导入的User
和Authentication
模块:
export default new Vuex.Store({
...UserStore,
modules: { Authentication,
} })
工作原理...
模块的工作方式类似于单一的 Vuex 存储,但在同一个 Vuex 单一的数据源中。这有助于开发更大规模的应用程序,因为你可以维护和处理更复杂的结构,而无需在同一个文件中检查问题。
与此同时,可以使用模块和普通的 Vuex 存储,从传统应用程序迁移,这样你就不必从头开始重写所有内容才能使用模块结构。
在我们的情况下,我们添加了一个名为authentication
的新模块,只有一个状态存在于存储中,并继续使用旧的用户 Vuex 存储,这样将来我们可以将用户存储重构为一个新模块,并将其分离成更具体的、面向领域的架构。
另请参阅
您可以在vuex.vuejs.org/guide/modules.html
找到有关 Vuex 模块的更多信息。
第八章:使用过渡和 CSS 为您的应用程序添加动画
为了使应用程序更加动态并吸引用户的全部注意力,使用动画是至关重要的。如今,CSS 动画出现在提示、横幅、通知甚至输入字段中。
有些情况下,您需要创建特殊的动画,称为过渡,并完全控制页面上发生的事情。为此,您必须使用自定义组件,并让框架帮助您渲染应用程序。
使用 Vue,我们可以使用两个自定义组件来帮助我们在应用程序中创建动画和过渡,这些组件是Transition
和TransitionGroup
。
在这一章中,我们将学习如何创建 CSS 动画,使用 Animate.css 框架创建自定义过渡,使用Transition
组件钩子执行自定义函数,创建在组件渲染时执行的动画,为组和列表创建动画和过渡,创建可重用的自定义过渡组件,并在组件之间创建无缝过渡。
在这一章中,我们将涵盖以下内容:
创建你的第一个 CSS 动画
使用 Animate.css 创建自定义过渡类
使用自定义钩子创建交易
在页面渲染时创建动画
为列表和组创建动画
创建自定义过渡组件
在元素之间创建无缝过渡
技术要求
在这一章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户!您需要安装一个名为windows-build-tools
的 NPM 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行
> npm install -g windows-build-tools
命令。
要安装Vue-CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建基础项目
在这一章中,我们将使用此项目作为每个配方的基础。在这里,我将指导您如何创建基础项目:
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create {replace-with-recipe-name}
- Vue-CLI 将要求您选择预设;使用空格键选择
手动选择功能
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
- 现在,Vue-CLI 将询问您希望安装哪些功能。您需要选择
CSS 预处理器
作为默认功能之外的附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 继续这个过程,选择一个 linter 和 formatter。在我们的情况下,我们将选择“ESLint + Airbnb 配置”:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能。在我们的情况下,选择“保存时进行 lint”和“提交时进行 lint 和修复”:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择要放置 linter 和 formatter 配置文件的位置。在我们的情况下,我们将选择“在专用配置文件中”:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
- 最后,CLI 会询问您是否要保存未来项目的设置;选择
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
- 从创建的项目中,打开位于
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分,删除HelloWorld
组件。添加一个data
属性,并将其定义为一个返回具有名为display
的属性和默认值为true
的 JavaScript 对象的单例函数:
<script> export default {
name: 'App',
data: () => ({
display: true,
}), }; </script>
- 在单文件组件的
<template>
部分,删除HelloWorld
组件,并添加一个带有文本Toggle
的button
HTML 元素。在img
HTML 元素中,添加一个绑定到display
变量的v-if
指令。最后,在button
HTML 元素中,添加一个click
事件。在事件监听器中,将值定义为一个将display
变量设置为display
变量的否定的匿名函数:
<template>
<div id="app">
<button @click="display = !display">
Toggle
</button>
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </div> </template>
有了这些说明,我们可以为本章中的每个示例创建一个基础项目。
创建您的第一个 CSS 动画
借助 CSS,我们可以在不需要手动通过 JavaScript 编程 DOM 元素的更改的情况下为我们的应用程序添加动画。使用专门用于控制动画的特殊 CSS 属性,我们可以实现美丽的动画和过渡效果。
要使用 Vue 中可用的动画,当将动画应用于单个元素时,我们需要使用一个名为Transition
的组件,或者当处理组件列表时,需要使用一个名为TransitionGroup
的组件。
在这个示例中,我们将学习如何创建一个 CSS 动画,并将此动画应用于 Vue 应用程序中的单个元素。
准备工作
以下是此示例的先决条件:
Node.js 12+
一个名为cssanimation
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为这个示例创建一个名为cssanimation
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 打开
App.vue
文件。在单文件组件的<template>
部分中,使用Transaction
组件包装img
HTML 元素。在Transaction
组件中,添加一个name
属性,其值为"image"
:
<transition name="image">
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
- 在单文件组件的
<style>
部分中,创建一个.image-enter-active
类,其中包含一个值为bounce-in .5s
的animation
属性。然后,创建一个.image-leave-active
类,其中包含一个值为bounce-in .5s reverse
的animation
属性:
.image-enter-active {
animation: bounce-in .5s; } .image-leave-active {
animation: bounce-in .5s reverse; }
- 最后,创建一个
@keyframes bounce-in
CSS 规则。在其中,执行以下操作:
创建一个0%
规则,其中包含属性变换和值为scale(0)
。
创建一个50%
规则,其中包含属性变换和值为scale(1.5)
。
创建一个100%
规则,其中包含属性变换和值为scale(1)
:
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
} }
完成此操作后,当第一次按下切换按钮时,您的图像将放大并消失。再次按下时,动画完成后,图像将放大并保持正确的比例:
它是如何工作的...
首先,我们将 Vue 动画包装器添加到我们想要添加过渡效果的元素上,然后添加将在过渡中使用的 CSS 类的名称。
Transition
组件使用预先制作的命名空间来要求必须存在的 CSS 类。这些是-enter-active
,用于组件进入屏幕时,以及-leave-active
,用于组件离开屏幕时。
然后,在<style>
中创建 CSS 类,用于元素离开和进入屏幕的过渡,以及bounce-in
动画的keyframe
规则集,以定义其行为方式。
另请参阅
您可以在v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions
找到有关使用 Vue 类进行基于类的动画和过渡的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/CSS/@keyframes
找到有关 CSS 关键帧的更多信息。
使用 Animate.css 创建自定义过渡类
在Transition
组件中,可以定义在每个过渡步骤中使用的 CSS 类。通过使用此属性,我们可以使Transition
组件在过渡动画中使用 Animate.css。
在这个示例中,我们将学习如何在组件中使用 Animate.css 类与Transition
组件,以创建自定义过渡效果。
准备工作
以下是此示例的先决条件:
Node.js 12+
一个名为animatecss
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为此示例创建一个名为animatecss
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 在项目文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令安装 Animate.css 框架:
> npm install [email protected]
- 打开
src
文件夹中的main.js
文件并导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 打开
src
文件夹中的App.vue
文件,并将Transition
组件添加为img
HTML 元素的包装器。在Transition
组件中,添加一个名为enter-active-class
的属性,并将其定义为"animated bounceInLeft"
。然后,添加另一个名为leave-active-class
的属性,并将其定义为"animated bounceOutLeft"
:
<transition
enter-active-class="animated bounceInLeft"
leave-active-class="animated bounceOutLeft" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
完成此操作后,当第一次按下切换按钮时,您的图像将向左滑出并消失。再次按下时,它将从左侧滑入:
工作原理...
Transition
组件最多可以接收六个属性,可以为交易的每个步骤设置自定义类。这些属性是enter-class
、enter-active-class
、enter-to-class
、leave-class
、leave-active-class
和leave-to-class
。在这个示例中,我们使用了enter-active-class
和leave-active-class
;这些属性定义了元素在屏幕上可见或离开屏幕时的自定义类。
为了使用自定义动画,我们使用了 Animate.css 框架,该框架提供了预先制作并准备好供使用的自定义 CSS 动画。我们使用了bounceInLeft
和bounceOutLeft
来使元素从屏幕中滑入和滑出。
还有更多...
您可以尝试更改enter-active-class
和leave-active-class
属性的类,以查看 CSS 动画在浏览器上的行为。
您可以在 Animate.css 文档中找到可用类的完整列表animate.style/
。
另请参阅
您可以在v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions
找到有关基于类的动画和 Vue 类的过渡的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
使用自定义钩子创建交易
Transaction
组件具有每个动画生命周期的自定义事件发射器。这些可以用于附加自定义函数和方法,以在动画周期完成时执行。
我们可以使用这些方法在页面交易完成或按钮动画结束后执行数据获取,从而按特定顺序链接动画,这些动画需要根据动态数据依次执行。
在这个配方中,我们将学习如何使用Transaction
组件的自定义事件发射器来执行自定义方法。
准备就绪
以下是此配方的先决条件:
Node.js 12+
名为transactionhooks
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到...
使用基础项目,为此配方创建一个名为transactionhooks
的新项目,并打开项目文件夹。现在,按照以下步骤:
- 在项目文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令以安装 Animate.css 框架:
> npm install [email protected]
- 在
src
文件夹中打开main.js
文件并导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 在
src
文件夹中打开App.vue
文件。在单文件组件的<script>
部分,在数据属性中,在单例函数中,添加一个名为status
的新属性,并将其值定义为"appeared"
:
data: () => ({
display: true,
status: 'appeared', }),
- 创建一个
methods
属性,并将其定义为 JavaScript 对象。在对象内部,添加两个名为onEnter
和onLeave
的属性。在onEnter
属性中,将其定义为一个函数,并在其中将数据status
变量设置为"appeared"
。在onLeave
属性中,将其定义为一个函数,并在其中将数据status
变量设置为"disappeared"
:
methods: {
onEnter() {
this.status = 'appeared';
},
onLeave() {
this.status = 'disappeared';
}, },
- 在单文件组件的
<template>
部分,添加一个Transition
组件作为img
HTML 元素的包装器。在Transition
组件中,执行以下操作:
添加一个名为enter-active-class
的属性,并将其定义为"animated rotateIn"
。
添加另一个名为leave-active-class
的属性,并将其定义为"animated rotateOut"
。
添加事件监听器after-enter
绑定并将其附加到onEnter
方法。
添加事件监听器after-leave
绑定并将其附加到onLeave
方法:
<transition
enter-active-class="animated rotateIn"
leave-active-class="animated rotateOut"
@after-enter="onEnter"
@after-leave="onLeave" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
- 创建一个
h1
HTML 元素作为Transition
组件的兄弟元素,并添加文本The image {{ status }}
:
<h1>The image {{ status }}</h1>
现在,当点击按钮时,文本将在动画完成时更改。 它将显示动画完成后出现的图像和动画完成后消失的图像:
工作原理...
Transition
组件有八个自定义钩子。 这些钩子由 CSS 动画触发,当它们被触发时,它们会发出自定义事件,可以被父组件使用。 这些自定义事件是before-enter
,enter
,after-enter
,enter-cancelled
,before-leave
,leave
,after-leave
和leave-cancelled
。
在使用after-enter
和after-leave
钩子时,当 CSS 动画完成时,屏幕上的文本会相应地更改为每个钩子的事件监听器上定义的函数。
另请参阅
您可以在v3.vuejs.org/guide/transitions-enterleave.html#javascript-hooks
找到有关转换钩子的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
在页面渲染时创建动画
在页面渲染时使用页面转换动画或自定义动画是常见的,有时需要引起应用程序用户的注意。
在 Vue 应用程序中可以创建此效果,无需刷新页面或重新渲染屏幕上的所有元素。 您可以使用Transition
组件或TransitionGroup
组件来实现这一点。
在本教程中,我们将学习如何使用Transition
组件,以便在页面渲染时触发动画。
准备工作
本文档的先决条件如下:
Node.js 12+
一个名为transactionappear
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为本教程创建一个名为transactionappear
的新项目,并打开项目文件夹。 现在,按照以下步骤:
- 在项目文件夹中,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows),并执行以下命令以安装 Animate.css 框架:
> npm install [email protected]
- 在
src
文件夹中的main.js
文件中导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 在
src
文件夹中的App.vue
文件中,为img
HTML 元素添加一个Transition
组件作为包装器。在Transition
组件中,执行以下操作:
添加一个名为appear-active-class
的属性,并将其定义为"animated jackInTheBox"
.
添加一个名为enter-active-class
的属性,并将其定义为"animated jackInTheBox"
。
添加另一个名为leave-active-class
的属性,并将其定义为"animated rollOut"
。
添加appear
属性并将其定义为true
:
<transition
appear
appear-active-class="animated jackInTheBox"
enter-active-class="animated jackInTheBox"
leave-active-class="animated rollOut" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
页面打开时,Vue 标志将像一个爆米花盒一样摇晃,并在动画完成后保持静止:
工作原理...
Transition
组件有一个特殊属性叫做appear
,当启用时,使元素在屏幕上呈现时触发动画。该属性带有三个控制动画 CSS 类的属性,分别为appear-class
、appear-to-class
和appear-active-class
。
还有四个与此属性一起执行的自定义钩子,分别为before-appear
、appear
、after-appear
和appear-cancelled
。
在我们的案例中,当组件在屏幕上呈现时,我们使组件执行 Animate.css 框架中的jackInTheBox
动画。
另请参阅
您可以在v3.vuejs.org/guide/transitions-enterleave.html#transitions-on-initial-render
找到有关初始渲染过渡的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
为列表和组创建动画
有一些动画需要在一组元素或列表中执行。这些动画需要包装在TransitionGroup
元素中才能工作。
该组件具有一些与Transition
组件中相同的属性,但要使其工作,您必须为子元素和特定于该组件的组件定义一组特殊指令。
在此示例中,我们将创建一个动态图像列表,当用户点击相应按钮时将添加图像。这将在图像出现在屏幕上时执行动画。
准备工作
以下是此示例的先决条件:
Node.js 12+
一个名为transactiongroup
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为此示例创建一个名为transactiongroup
的新项目并打开项目文件夹。现在,按照以下步骤进行:
- 在项目文件夹中,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows),并执行以下命令来安装 Animate.css 框架:
> npm install [email protected]
- 打开
src
文件夹中的main.js
文件并导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 打开
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分中,在data
单例上返回一个名为count
且值为0
的属性:
data: () => ({
count: 0, }),
- 在单文件组件的
<template>
部分中,删除div#app
HTML 元素内的所有内容。然后,作为div#app
HTML 元素的子元素,创建一个带有tag
属性定义为"ul"
和enter-active-class
属性定义为"animated zoomIn"
的TransitionGroup
组件:
<div id="app">
<transition-group
tag="ul"
enter-active-class="animated zoomIn"
></transition-group>
</div>
- 作为
TransitionGroup
组件的子元素,创建一个带有v-for
指令的li
HTML 元素,对count
变量进行迭代,定义一个名为key
的变量属性,其值为i
,并定义一个style
属性,其值为"float: left"
。作为li
HTML 组件的子元素,创建一个带有src
属性定义为"https://picsum.photos/100"
的img
HTML 组件:
<li
v-for="i in count"
:key="i"
style="float: left" >
<img src="https://picsum.photos/100"/> </li>
- 然后,作为
TransitionGroup
组件的兄弟元素,创建一个带有style
属性定义为"clear: both"
的hr
HTML 元素:
<hr style="clear: both"/>
- 最后,作为
hr
HTML 元素的兄弟,创建一个带有click
事件的button
HTML 元素,将1
添加到当前的count
变量,并将文本设置为Increase
:
<button
@click="count = count + 1" >
Increase
</button>
现在,当用户点击相应的按钮以增加列表时,它将向列表添加一个新项目,并触发放大动画:
它是如何工作的...
TransitionGroup
元素通过tag
属性中声明的标记创建一个包装器元素。这将通过检查子元素的唯一键来管理将触发动画的自定义元素。因此,TransitionGroup
组件内的所有子元素都需要声明一个key
并且必须是唯一的。
在我们的案例中,我们使用ul
和li
HTML 元素创建了一个 HTML 列表,其中TransitionGroup
是使用ul
标签定义的,子元素是使用li
HTML 元素定义的。然后,我们对数字进行了虚拟迭代。这意味着将有一个项目列表,并根据该列表上的项目数量在屏幕上显示图像。
为了增加我们的列表,我们创建了一个button
HTML 元素,每次按下时都会将count
变量的计数增加一。
另请参阅
您可以在v3.vuejs.org/guide/transitions-list.html#reusable-transitions
找到有关转换组的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
创建自定义过渡组件
使用框架创建应用程序很好,因为您可以创建可重用的组件和可共享的代码。使用这种模式对简化应用程序的开发非常有帮助。
创建可重用的过渡组件与创建可重用组件相同,可以使用更简单的方法,因为它可以与函数渲染一起使用,而不是正常的渲染方法。
在本教程中,我们将学习如何创建一个可在我们的应用程序中使用的可重用的函数组件。
准备工作
本章的先决条件如下:
Node.js 12+
一个名为customtransition
的 Vue-CLI 基本项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基本项目,为本教程创建一个名为customtransition
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 在项目文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令来安装 Animate.css 框架:
> npm install [email protected]
- 在
src
文件夹中的main.js
文件中导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 在
src/components
文件夹中创建一个名为CustomTransition.vue
的新文件,并打开它。在单文件组件的<template>
部分,添加functional
属性以启用组件的函数渲染。然后,创建一个Transition
组件,将appear
变量属性定义为props.appear
。将enter-active-class
属性定义为"animated slideInLeft"
,将leave-active-class
属性定义为"animated slideOutRight"
。最后,在Transition
组件内部,添加一个<slot>
占位符:
<template functional>
<transition
:appear="props.appear"
enter-active-class="animated slideInLeft"
leave-active-class="animated slideOutRight"
>
<slot />
</transition> </template>
- 在
src
文件夹中的App.vue
文件中打开。在单文件组件的<script>
部分,导入新创建的CustomTransition
组件。在 Vue 对象上,添加一个名为components
的新属性,将其定义为 JavaScript 对象,并添加导入的CustomTransition
组件:
<script> import CustomTransition from './components/CustomTransition.vue'; export default {
name: 'App',
components: {
CustomTransition,
}, data: () => ({
display: true,
}), }; </script>
- 最后,在单文件组件的
<template>
部分,使用CustomTransition
组件包装img
HTML 元素:
<custom-transition>
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </custom-transition>
使用这个自定义组件,可以在不需要在组件上重新声明Transition
组件和过渡 CSS 类的情况下重用过渡:
它是如何工作的...
首先,我们使用函数组件方法创建了一个自定义组件,不需要声明单文件组件的<script>
部分。
在这个自定义组件中,我们使用Transaction
组件作为基础组件。然后,我们使用注入的函数上下文prop.appear
定义了appear
属性,并为过渡添加了动画类,使其在组件呈现时从左侧滑入,销毁时从右侧滑出。
然后,在主应用程序中,我们使用这个自定义组件来包装img
HTML 元素,并使其作为Transition
组件工作。
另请参阅
您可以在v3.vuejs.org/guide/transitions-list.html#reusable-transitions
找到有关可重用过渡组件的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
在元素之间创建无缝过渡
当两个组件之间有动画和过渡时,它们需要是无缝的,这样用户在将组件放置在屏幕上时就看不到 DOM 抖动和重绘。为了实现这一点,我们可以使用Transition
组件和过渡模式属性来定义过渡的方式。
在这个示例中,我们将使用Transition
组件和过渡模式属性来创建图像之间的过渡动画。
准备工作
本章的先决条件如下:
Node.js 12+
一个名为seamlesstransition
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为这个示例创建一个名为seamlesstransition
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 在
src
文件夹中打开App.vue
文件。在单文件组件的<style>
部分,创建一个名为.rotate-enter-active,.rotate-leave-active
的属性,并将transition
CSS 样式属性定义为transform .8s ease-in-out;
。然后,创建一个名为.rotate-enter,.rotate-leave-active
的属性,并将transform
CSS 样式属性定义为rotate( -180deg );
,并将transition
定义为.8s ease-in-out;
:
.rotate-enter-active, .rotate-leave-active {
transition: transform .8s ease-in-out; } .rotate-enter, .rotate-leave-active {
transform: rotate( -180deg );
transition: transform .8s ease-in-out; }
- 在单文件组件的
<template>
部分,用Transition
组件包裹img
HTML 元素。然后,将name
属性定义为rotate
,mode
属性定义为out-in
:
<transition
name="rotate"
mode="out-in" ></transition>
- 在
Transition
组件中,对img
HTML 元素添加一个key
属性,并将其定义为up
。然后,添加另一个img
HTML 元素并添加一个v-else
指令。添加一个key
属性并将其定义为down
,添加一个src
属性并将其定义为"./assets/logo.png"
,最后添加一个style
属性并将其定义为"transform: rotate(180deg)"
:
<img
v-if="display"
key="up"
src="./assets/logo.png"> <img
v-else
key="down"
src="./assets/logo.png"
style="transform: rotate(180deg)" >
当用户切换元素时,将执行离开动画,然后在完成后,进入动画将立即开始。这样就可以实现旧元素和新元素之间的无缝过渡:
它是如何工作的...
Transition
组件有一个特殊的属性叫做mode
,在这里可以定义元素过渡动画的行为。这种行为将创建一组规则,控制动画步骤在Transition
组件内部的发生方式。在组件中可以使用"in-out"
或"out-in"
模式:
在"in-out"
行为中,新元素的过渡将首先发生,当它完成后,旧元素的过渡将开始。
在"out-in"
行为中,旧元素的过渡将首先发生,然后新元素的过渡将开始。
在我们的案例中,我们创建了一个动画,将 Vue 标志旋转到倒置状态。然后,为了处理这个无缝的变化,我们使用了"out-in"
模式,这样新元素只会在旧元素完成过渡后才会显示出来。
另请参阅
您可以在v3.vuejs.org/guide/transitions-enterleave.html
找到有关过渡模式的更多信息。
第九章:使用 UI 框架创建漂亮的应用程序
使用 UI 框架和库是提高生产力并帮助应用程序开发的好方法。您可以更多地专注于代码,而不是设计。
学习如何使用这些框架意味着您知道这些框架的行为和工作原理。这将有助于您在将来开发应用程序或框架的过程中。
在这里,您将学习在创建用户注册表单和页面所需的所有组件时,使用框架的更多用法。在本章中,我们将学习如何使用 Buefy、Vuetify 和 Ant-Design 创建布局、页面和表单。
在本章中,我们将涵盖以下示例:
使用 Buefy 创建页面、布局和用户表单
使用 Vuetify 创建页面、布局和用户表单
使用 Ant-Design 创建页面、布局和用户表单
技术要求
在这一章中,我们将使用 Node.js 和 Vue-CLI。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的npm
包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装Vue-CLI
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
使用 Buefy 创建页面、布局和用户表单
Bulma 是最早用于快速原型设计和 Web 开发的框架之一,它不需要附加 JavaScript 库。所有需要编码的特殊组件都是使用框架的开发人员的责任。
随着 JavaScript 框架的出现和围绕 Bulma 框架创建的社区,为 Vue 创建了一个包装器。这个包装器承担了 JavaScript 组件开发的所有责任,并为开发人员提供了在其应用程序中使用 Bulma 框架的完整解决方案,而无需重新发明轮子。
在这个示例中,我们将学习如何在 Vue 中使用 Buefy 框架,以及如何创建布局、页面和用户注册表单。
准备工作
本示例的先决条件如下:
Node.js 12+
一个 Vue-CLI 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要使用 Buefy 框架创建一个 Vue-CLI 项目,我们首先需要创建一个 Vue-CLI 项目,然后将 Buefy 框架添加到项目中。我们将把这个步骤分为四个部分:创建 Vue-CLI 项目,将 Buefy 框架添加到项目中,创建布局和页面,最后创建用户注册表单。
创建 Vue-CLI 项目
在这里,我们将创建用于此示例的 Vue-CLI 项目。这个项目将具有自定义设置,以便能够与 Buefy 框架一起工作:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create bulma-vue
- Vue-CLI 会要求您选择一个预设 - 选择
手动选择功能
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在 Vue-CLI 会要求选择功能,您需要在默认功能之上选择
CSS 预处理器
作为附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter Unit Testing E2E Testing
- 在这里,Vue-CLI 会询问您想要使用哪种 CSS 预处理器;选择
Sass/SCSS(使用 node-sass)
:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) ❯ Sass/SCSS (with node-sass) Less **Stylus**
- 继续这个过程,选择一个 linter 和格式化程序。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能(这里是
在提交时进行 Lint 和修复
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择您想要放置 linter 和格式化程序配置文件的位置(这里是
在专用配置文件中
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
- 最后,Vue-CLI 会询问您是否要保存设置以供将来使用;您应该选择
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
将 Buefy 添加到 Vue-CLI 项目中
要在 Vue 项目中使用 Bulma,我们将使用 Buefy UI 库。这个库是 Bulma 框架的一个包装器,并提供了所有原始框架可用的组件以及一些额外的组件来使用:
- 在为 Vue-CLI 项目创建的文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue add buefy
- Vue-CLI 会询问您是否要选择一个样式来使用 Buefy;我们将选择
scss
:
? Add Buefy style? (Use arrow keys)
none
css
❯ scss
- 然后,Vue-CLI 会询问您是否要包括 Material Design 图标;对于这个项目,我们不会使用它们:
? Include Material Design Icons? (y/N) n
- 现在 Vue-CLI 会询问您是否要包括 Font Awesome 图标;我们将把它们添加到项目中:
? Include Font Awesome Icons? (y/N) y
使用 Buefy 创建布局和页面
要创建一个页面,我们需要创建一个布局结构和页面的基本组件,比如页眉菜单、页脚和页面的主要部分。
创建页眉菜单组件
在我们的设计中,我们将有一个页眉菜单,其中包含链接和呼吁行动按钮的基本组合:
在src/components
文件夹中创建一个名为top-menu.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为TopMenu
:
<script> export default {
name: 'TopMenu', }; </script>
- 在单文件组件的
<template>
部分,创建一个带有section
类的section
HTML 元素,并添加一个带有container
类的子div
HTML 元素:
<section class="section">
<div class="container">
</div>
</section>
- 现在,在
div.container
HTML 元素的子级中创建一个b-navbar
组件,并添加一个具有命名插槽brand
的template
占位符组件作为子级。在其中,添加一个带有href
属性定义为#
的b-navbar-item
组件,并添加一个img
HTML 元素作为子级:
<b-navbar>
<template slot="brand">
<b-navbar-item href="#">
<img src="https://raw.githubusercontent.com/buefy/buefy/dev
/static/img/buefy-logo.png"
alt="Lightweight UI components for Vue.js based on Bulma"
>
</b-navbar-item>
</template> </b-navbar>
- 在这个
template
占位符之后,创建另一个具有命名插槽start
的template
占位符。在其中,创建两个带有href
属性定义为#
的b-navbar-item
组件。作为同级组件,创建一个带有label
属性定义为Info
的b-navbar-dropdown
组件。在这个组件中,添加两个带有href
属性定义为#
的b-navbar-item
组件作为子级:
<template slot="start">
<b-navbar-item href="#">
Home
</b-navbar-item>
<b-navbar-item href="#">
Documentation
</b-navbar-item>
<b-navbar-dropdown label="Info">
<b-navbar-item href="#">
About
</b-navbar-item>
<b-navbar-item href="#">
Contact
</b-navbar-item>
</b-navbar-dropdown> </template>
- 最后,在
template
中创建另一个具有命名插槽end
的占位符。创建一个b-navbar-item
组件作为子组件,tag
属性定义为div
,并在该组件的子级中添加一个带有buttons
类的div
HTML 元素。在div
HTML 元素中,创建一个带有button is-primary
类的a
HTML 元素,以及另一个带有button is-light
类的a
HTML 元素:
<template slot="end">
<b-navbar-item tag="div">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light">
Log in
</a>
</div>
</b-navbar-item> </template>
创建英雄区组件
我们将创建一个英雄区组件。英雄组件是一个全宽的横幅,为用户提供页面上的视觉信息:
在src/components
文件夹中创建一个名为hero-section.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为HeroSection
:
<script> export default {
name: 'HeroSection', }; </script>
- 在单文件组件的
<template>
部分,创建一个带有hero is-primary
类的section
HTML 元素,然后添加一个带有hero-body
类的div
HTML 元素作为子级:
<section class="hero is-primary">
<div class="hero-body">
</div>
</section>
- 在
div.hero-body
HTML 元素内部,创建一个带有container
类的子div
HTML 元素。然后,添加一个带有title
类的h1
HTML 元素作为子级,以及一个带有subtitle
类的h2
HTML 元素:
<div class="container">
<h1 class="title">
user Registration
</h1>
<h2 class="subtitle">
Main user registration form
</h2> </div>
创建页脚组件
我们将在布局中使用的最终组件是页脚组件。这将显示为我们页面的页脚:
在src/components
文件夹中创建一个名为Footer.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为FooterSection
:
<script> export default {
name: 'FooterSection', }; </script>
- 在单文件组件的
<template>
部分,创建一个带有footer
类的footer
HTML 元素,然后添加一个带有content has-text-centered
类的div
HTML 元素:
<footer class="footer">
<div class="content has-text-centered">
</div>
</footer>
- 在
div.content
HTML 元素内,创建一个p
HTML 元素作为初始页脚行,并创建第二个p
HTML 元素作为第二行:
<p>
<strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy
Thomas</a>
| <strong>Buefy</strong> by
<a href="https://twitter.com/walter_tommasi">Walter
Tommasi</a> </p> <p>
The source code is licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>.
The website content is licensed
<a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC
BY NC SA 4.0</a>.
</p>
创建布局组件
为了创建布局组件,我们将使用所有创建的组件,并添加一个将容纳页面内容的插槽:
在src
文件夹中创建一个名为layouts
的新文件夹,并创建一个名为Main.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入footer-section
组件和top-menu
组件:
import FooterSection from '../components/Footer.vue'; import TopMenu from '../components/top-menu.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为Mainlayout
,并定义components
属性为导入的组件:
export default {
name: 'Mainlayout',
components: {
TopMenu,
FooterSection,
}, };
- 最后,在单文件组件的
<template>
部分,创建一个带有子top-menu
组件、一个slot
占位符和footer-section
组件的div
HTML 元素:
<template>
<div>
<top-menu />
<slot/>
<footer-section />
</div> </template>
使用 Buefy 创建用户注册表单
现在我们要创建用户注册表单并制作最终页面。在这一步中,我们将把所有其他步骤的输出合并到一个页面中:
- 打开
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分,导入main-layout
组件和hero-section
组件:
import Mainlayout from './layouts/main.vue'; import HeroSection from './components/heroSection.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为App
,然后定义components
属性为导入的组件。在 JavaScript 对象中添加data
属性,包括name
、username
、password
、email
、phone
、cellphone
、address
、zipcode
和country
属性:
export default {
name: 'App',
components: {
HeroSection,
Mainlayout,
},
data: () => ({
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
}), };
- 在单文件的
<template>
部分,添加导入的main-layout
组件,并将hero-section
作为子组件添加:
<template>
<main-layout>
<hero-section/>
</main-layout>
</template>
- 在
hero-section
组件之后,创建一个兄弟section
HTML 元素,带有section
类,并添加一个带有container
类的子div
HTML 元素。在这个div
HTML 元素中,创建一个带有title is-3
类的h1
HTML 元素和一个兄弟hr
HTML 元素:
<section class="section">
<div class="container">
<h1 class="title is-3">Personal Information</h1>
<hr/>
</div>
</section>
- 然后,创建一个带有
Name
作为label
的b-field
组件,作为hr
HTML 元素的兄弟,并添加一个带有v-model
指令绑定到name
的子b-input
。对于email
字段,做同样的操作,将label
更改为Email
,并将v-model
指令绑定到email
。在 email 的b-input
中,添加一个定义为email
的type
属性:
<b-field label="Name">
<b-input
v-model="name"
/> </b-field> <b-field
label="Email" >
<b-input
v-model="email"
type="email"
/> </b-field>
- 创建一个带有
grouped
属性的b-field
组件作为b-field
组件的兄弟。然后,作为子组件,创建以下内容:
一个带有expanded
属性和label
定义为Phone
的b-field
组件。添加一个带有v-model
指令绑定到phone
和type
为tel
的子b-input
组件。
一个带有expanded
属性和label
定义为Cellphone
的b-field
组件。添加一个带有v-model
指令绑定到cellphone
和type
为tel
的子b-input
组件:
<b-field grouped> <b-field
expanded
label="Phone"
>
<b-input
v-model="phone"
type="tel"
/>
</b-field>
<b-field
expanded
label="Cellphone"
>
<b-input
v-model="cellphone"
type="tel"
/>
</b-field> </b-field>
- 然后,创建一个带有
title is-3
类的h1
HTML 元素作为b-field
组件的兄弟,并添加一个hr
HTML 元素作为兄弟。创建一个带有label
定义为Address
的b-field
组件,并添加一个带有v-model
指令绑定到address
的b-input
组件:
<h1 class="title is-3">Address</h1> <hr/> <b-field
label="Address" >
<b-input
v-model="address"
/> </b-field>
- 创建一个
b-field
组件作为b-field
组件的兄弟,带有grouped
属性。然后,作为子组件,创建以下内容:
一个带有expanded
属性和label
定义为Zipcode
的子b-field
组件。添加一个带有v-model
指令绑定到zipcode
和type
属性定义为tel
的b-input
组件。
一个带有expanded
属性和label
定义为Country
的子b-field
组件。添加一个带有v-model
指令绑定到country
和type
属性定义为tel
的b-input
组件:
<b-field grouped>
<b-field
expanded
label="Zipcode"
>
<b-input
v-model="zipcode"
type="tel"
/>
</b-field>
<b-field
expanded
label="Country"
>
<b-input
v-model="country"
/>
</b-field> </b-field>
- 然后,创建一个
h1
HTML 元素作为b-field
组件的同级元素,使用title is-3
类,并添加一个hr
HTML 元素作为同级元素。创建一个带有grouped
属性的b-field
组件。创建一个子b-field
组件,带有expanded
属性和label
定义为username
,并添加一个带有v-model
指令绑定到username
的b-input
组件。对于Password
输入,做同样的事情,将label
更改为Password
,在b-input
组件中定义v-model
指令绑定到password
,并添加type
属性为password
:
<h1 class="title is-3">user Information</h1> <hr/> <b-field grouped>
<b-field
expanded
label="username"
>
<b-input
v-model="username"
/>
</b-field>
<b-field
expanded
label="Password"
>
<b-input
v-model="password"
type="password"
/>
</b-field> </b-field>
- 最后,创建一个
b-field
组件作为b-field
组件的同级元素,定义position
属性为is-right
和grouped
属性。然后,创建两个带有control
类的div
HTML 元素。在第一个div
HTML 元素中,添加一个button
HTML 元素作为子元素,使用button is danger is-light
类,而在第二个div
HTML 元素中,创建一个带有button is-success
类的子button
HTML 元素:
<b-field
position="is-right"
grouped
>
<div class="control">
<button class="button is-danger is-light">Cancel</button>
</div>
<div class="control">
<button class="button is-success">Submit</button>
</div>
</b-field>
它是如何工作的...
首先,我们创建一个 Vue-CLI 项目,进行基本配置,并添加额外的 CSS 预处理器node-sass
。然后,我们能够使用 Vue-CLI 和 Buefy 插件将 Buefy 框架添加到我们的项目中。使用 Buefy 框架,我们创建了一个布局页面组件,带有页眉菜单组件和页脚组件。
对于页面,我们使用 Bulma CSS 容器结构来定义我们的页面,并将用户注册表单放在默认的网格大小上。然后,我们添加了 hero 部分组件,用于页面标识,最后,我们创建了用户注册表单和输入。
这是最终项目正在运行的屏幕截图:
另请参阅
在bulma.io/
找到有关 Bulma 的更多信息。
在buefy.org/
找到有关 Buefy 的更多信息。
使用 Vuetify 创建页面、布局和用户表单
Vuetify 是 Vue 最知名的三个 UI 框架之一。基于 Google 的 Material Design,这个框架最初是由 John Leider 设计的,现在作为 Vue 生态系统中的首选 UI 框架,用于应用程序的初始开发。
在这个食谱中,我们将学习如何使用 Vuetify 创建一个布局组件包装器、一个页面和一个用户注册表单。
准备工作
此处的食谱先决条件如下:
Node.js 12+
一个 Vue-CLI 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何操作...
我们将把这个教程分为四个主要部分。前两部分专门用于创建项目和安装框架,后两部分专门用于创建用户注册页面和创建所需组件。
创建 Vue-CLI 项目
要在 Vue-CLI 项目中使用 Vuetify,我们需要创建一个自定义的 Vue-CLI 项目,并预定义配置,以便充分利用框架和提供的样式选项:
- 我们需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> vue create vue-vuetify
- 首先,Vue-CLI 会要求你选择一个预设;使用空格键选择
手动选择特性
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
- 现在 Vue-CLI 会要求选择特性,你需要选择
CSS 预处理器
作为默认特性之外的附加特性:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 在这里,Vue-CLI 会问你想使用哪种
CSS 预处理器
;选择Sass/SCSS(使用 node-sass)
:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) ❯ **Sass/SCSS (with node-sass)** Less **Stylus**
- 继续这个过程,选择一个代码检查工具和格式化工具。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 选择代码检查工具的附加特性(这里是
提交时进行代码检查和修复
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择你想要放置代码检查工具和格式化工具配置文件的位置(这里是
在专用配置文件中
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
- 最后,Vue-CLI 会问你是否要保存设置以供将来的项目使用;你会选择
N
。之后,Vue-CLI 会为你创建一个文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
将 Vuetify 添加到 Vue-CLI 项目中
要在 Vue 项目中使用 Vuetify,我们将使用 Vue-CLI 插件安装框架:
- 在你创建 Vue-CLI 项目的文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),执行以下命令:
> vue add vuetify
- Vue-CLI 会问你是否要选择安装预设。选择默认预设。然后,Vue-CLI 将完成在项目上安装框架:
? Choose a preset: (Use arrow keys)
❯ Default (recommended)
Prototype (rapid development)
Configure (advanced)
- 安装完成后,Vuetify 将配置项目内的文件以加载框架。现在你可以开始使用它了。
使用 Vuetify 创建布局
使用 Vuetify 作为 UI 框架,我们使用 Material Design 指南作为基础,因为通过使用 Material Design,我们可以遵循设计指南来创建我们的设计结构,这将意味着更具吸引力的结构。您可以在material.io/design/introduction#goals
找到 Material Design 指南。
创建顶部栏组件
在这部分,我们将创建一个top-bar
组件,该组件将用于我们页面的布局中:
在src/components
文件夹中,创建一个名为TopBar.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个具有name
属性定义为TopBar
的default
JavaScript 对象:
<script> export default {
name: 'TopBar', }; </script>
- 在
<template>
部分内,创建一个带有app
,dark
和dense
属性定义为true
,以及color
属性定义为primary
的v-app-bar
组件:
<v-app-bar
color="primary" app
dark dense ></v-app-bar>
- 在组件内部,创建一个带有
click
事件的事件监听器的v-app-bar-nav-icon
组件,在按钮被点击时发送一个名为'open-drawer'
的事件:
<v-app-bar-nav-icon
@click="$emit('open-drawer')" />
- 最后,作为
v-app-bar-nav-icon
组件的兄弟,添加一个v-toolbar-title
组件,其中包含页面或应用程序的标题:
<v-toolbar-title>Vue.JS 3 Cookbook - Packt</v-toolbar-title>
创建抽屉菜单组件
在 Material Design 应用程序中,我们有一个弹出在页面上方的抽屉菜单。当用户点击我们刚刚在TopBar
组件中创建的按钮时,这个菜单将被打开:
在src/components
文件夹中,创建一个名为DrawerMenu.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个具有三个属性的default
JavaScript 对象:
name
属性,定义为DrawerMenu
。
props
属性,定义为一个 JavaScript 对象,具有一个名为value
的属性。这个属性将是另一个 JavaScript 对象,具有type
,required
和default
属性。type
属性定义为Boolean
,required
属性定义为true
,default
属性定义为false
。
data
属性,作为返回 JavaScript 对象的单例函数。该对象将具有一个menu
属性,我们将其定义为将要使用的菜单项数组。数组将包含具有name
、link
和icon
属性的 Javascript 对象。第一个菜单项的name
定义为Home
,link
定义为*#*
,icon
定义为mdi-home
。第二个菜单项的name
定义为Contact
,link
定义为#
,icon
定义为mdi-email
。最后,第三个菜单项的name
定义为Vuetify
,link
定义为#
,icon
定义为mdi-vuetify
。
<script>
export default {
name: 'DrawerMenu',
props: {
value: {
type: Boolean,
required: true,
default: false,
},
},
data: () => ({
menu: [
{
name: 'Home',
link: '#',
icon: 'mdi-home',
},
{
name: 'Contact',
link: '#',
icon: 'mdi-email',
},
{
name: 'Vuetify',
link: '#',
icon: 'mdi-vuetify',
},
],
}),
};
</script>
- 在
<template>
部分,创建一个v-navigation-drawer
组件,其中value
属性作为绑定到value
props 的变量属性,app
属性定义为true
,并在click
事件上添加事件监听器,发送一个'input'
事件:
<v-navigation-drawer
:value="value"
app
@input="$emit('input', $event)" ></v-navigation-drawer>
- 创建一个带有
dense
属性定义为true
的v-list
组件。作为子元素,创建一个v-list-item
组件并定义以下内容:
v-for
指令遍历menu
项。
key
属性与菜单项的index
。
link
属性定义为true
。
在v-list-item
内部,创建一个带有VIcon
子元素的v-list-item-action
,内部文本为item.icon
。
在v-list-item-action
的同级位置创建一个v-list-item-content
组件,其中v-list-item-title
作为子元素,内部文本为item.name
:
<v-list dense>
<v-list-item
v-for="(item, index) in menu"
:key="index"
link>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{ item.name }}</v-list-item-
title>
</v-list-item-content>
</v-list-item>
</v-list>
创建布局组件
要创建布局组件,我们将使用所有创建的组件,并添加一个插槽来容纳页面内容:
在src/components
文件夹中,创建一个名为Layout.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入top-bar
组件和drawer-menu
组件:
import TopBar from './TopBar.vue'; import DrawerMenu from './DrawerMenu.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为Layout
,然后创建components
属性并导入组件。最后,将data
属性添加为返回 JavaScript 对象的单例函数,其中drawer
属性的值定义为false
:
export default {
name: 'Layout',
components: {
DrawerMenu,
TopBar,
},
data: () => ({
drawer: false,
}), };
- 在
<template>
部分内,创建一个v-app
组件。作为第一个子元素,添加top-bar
组件,并在open-drawer
事件监听器上添加事件监听器,将drawer
数据属性更改为drawer
属性的否定。然后,作为top-bar
的同级,创建一个drawer-menu
组件,其v-model
指令绑定到drawer
。最后,创建一个v-content
组件,其中包含一个子<slot>
元素:
<template>
<v-app>
<top-bar
@open-drawer="drawer = !drawer"
/>
<drawer-menu
v-model="drawer"
/>
<v-content>
<slot/>
</v-content>
</v-app> </template>
使用 Vuetify 创建用户注册表单
现在,布局组件准备好了,我们将创建用户注册表单。因为 Vuetify 在表单中内置了验证,我们将使用它来验证表单中的一些字段。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在src
文件夹中,打开App.vue
文件并清空其内容。
导入layout
组件:
import Layout from './components/Layout.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为App
,然后定义导入组件的components
属性。将computed
和methods
属性定义为空的 JavaScript 对象。然后创建一个data
属性,定义为返回 JavaScript 对象的单例函数。在data
属性中,创建以下内容:
一个valid
属性,其值定义为false
;
name
,username
,password
,email
,phone
,cellphone
,address
,zipcode
和country
属性定义为空字符串:
export default {
name: 'App', components: {
Layout,
}, data: () => ({
valid: true,
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
}),
computed: {}, methods: {}, };
- 在
computed
属性中,创建一个名为nameRules
的属性;这个属性是一个返回数组的函数,其中包含一个匿名函数,该函数接收一个值并返回该值的验证或错误文本。对passwordRules
和emailRules
属性也做同样的操作。在emailRules
属性中,向返回的数组中添加另一个匿名函数,该函数通过正则表达式检查值是否为有效的电子邮件,如果值不是有效的电子邮件,则返回错误消息:
computed: {
nameRules() {
return [
(v) => !!v || 'Name is required',
];
},
passwordRules() {
return [
(v) => !!v || 'Password is required',
];
},
emailRules() {
return [
(v) => !!v || 'E-mail is required',
(v) => /.+@.+\..+/.test(v) || 'E-mail must be valid',
];
}, },
- 最后,在
methods
属性内,创建一个名为register
的新属性,它是一个调用$refs.form.validate
函数的函数。还创建另一个名为cancel
的属性,它是另一个调用$refs.form.reset
函数的函数:
methods: {
register() {
this.$refs.form.validate();
},
cancel() {
this.$refs.form.reset();
}, },
单文件组件部分
现在是创建单文件组件的<template>
部分的时候。
在src
文件夹中,打开App.vue
文件。
在<template>
部分,创建一个layout
组件元素,并添加一个带有fluid
属性定义为true
的v-container
组件作为子元素。
<layout>
<v-container
fluid
>
</v-container>
</layout>
- 在
v-container
组件内部,创建一个子 HTML h1
元素作为页面标题,以及一个同级的v-subheader
组件作为页面描述。
<h1>user Registration</h1> <v-subheader>Main user registration form</v-subheader>
- 之后,创建一个带有
ref
属性定义为form
和lazy-validation
属性定义为true
的v-form
组件。然后,该组件的v-model
指令绑定到valid
变量。创建一个子v-container
组件,其中fluid
属性定义为true
。
<v-form
ref="form"
v-model="valid"
lazy-validation >
<v-container
fluid
> </v-container>
</v-form>
- 在
v-container
组件内部,创建一个v-row
组件,然后添加一个v-col
组件作为子元素,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。
<v-row>
<v-col
cols="12"
>
<v-card
outlined
flat class="mx-auto"
>
</v-card>
</v-col>
</v-row>
- 作为
v-card
组件的子元素,创建一个带有卡片标题的v-card-title
组件,然后作为同级元素创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件。
<v-card-title>
Personal Information
</v-card-title> <v-divider/> <v-container
fluid >
<v-row>
</v-row>
</v-container>
- 在
v-row
组件内部,创建一个子v-col
元素,其中cols
属性定义为12
。然后在v-col
组件内部,创建一个v-text-field
,其中v-model
指令绑定到name
变量,rules
变量属性定义为nameRules
计算属性,label
属性定义为Name
,最后,required
属性定义为true
。
<v-col
cols="12" >
<v-text-field
v-model="name"
:rules="nameRules"
label="Name"
required
/> </v-col>
- 作为
v-col
组件的同级元素,创建另一个v-col
组件,其中cols
属性定义为12
。然后,将v-text-field
组件作为子元素添加,其中v-model
指令绑定到email
变量,rules
变量属性定义为emailRules
计算属性,type
属性为email
,label
属性为E-mail
,最后,required
属性定义为true
。
<v-col
cols="12" >
<v-text-field
v-model="email"
:rules="emailRules"
type="email"
label="E-mail"
required
/> </v-col>
- 创建一个作为
v-col
组件同级的v-col
组件,并将cols
属性定义为6
。然后,作为子组件添加v-text-field
组件,其中v-model
指令绑定到phone
变量,label
属性定义为Phone
。对于Cellphone
输入,必须更改v-model
指令绑定到cellphone
变量和label
为Cellphone
。
<v-col
cols="6" >
<v-text-field
v-model="phone"
label="Phone"
/> </v-col> <v-col
cols="6" >
<v-text-field
v-model="cellphone"
label="Cellphone"
/> </v-col>
- 现在我们将创建
地址
卡,作为v-row
组件中v-col
的同级元素。创建一个v-col
组件,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。作为v-card
组件的子元素,创建一个v-card-title
组件作为卡片标题;然后,作为同级元素,创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件:
<v-col
cols="12" >
<v-card
outlined
flat class="mx-auto"
>
<v-card-title>
Address
</v-card-title>
<v-divider/>
<v-container
fluid
>
<v-row>
</v-row>
</v-container>
</v-card>
</v-col>
- 在
v-container
组件中的v-row
组件内,创建一个v-col
组件,其中cols
属性定义为12
。然后,添加一个v-text-field
作为子组件,其中v-model
指令绑定到address
变量,label
属性定义为Address
:
<v-col
cols="12" >
<v-text-field
v-model="address"
label="Address"
/> </v-col>
- 作为同级元素,创建一个
v-col
组件,其中cols
属性定义为6
。添加一个v-text-field
组件作为子元素。将v-text-field
组件的v-model
指令绑定到zipcode
变量,并将label
属性定义为Zipcode
:
<v-col
cols="6" >
<v-text-field
v-model="zipcode"
label="Zipcode"
/> </v-col>
- 然后,创建一个
v-col
组件,其中cols
属性定义为6
。作为子元素添加一个v-text-field
组件,其中v-model
指令绑定到country
变量,label
属性定义为Country
:
<v-col
cols="6" >
<v-text-field
v-model="country"
label="Country"
/> </v-col>
- 现在我们将创建
用户信息
卡作为v-row
组件中v-col
的同级元素。创建一个v-col
组件,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。作为v-card
组件的子元素,创建一个v-card-title
组件作为卡片标题;然后,作为同级元素,创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件:
<v-col
cols="12" >
<v-card
outlined
flat class="mx-auto"
>
<v-card-title>
user Information
</v-card-title>
<v-divider/>
<v-container
fluid
>
<v-row>
</v-row>
</v-container>
</v-card>
</v-col>
- 在
v-container
组件中的v-row
组件内,创建一个v-col
组件,其中cols
属性定义为6
。然后,添加一个v-text-field
作为子组件,其中v-model
指令绑定到username
变量,label
属性定义为username
:
<v-col
cols="6" >
<v-text-field
v-model="username"
label="username"
/> </v-col>
- 作为兄弟创建一个
v-col
组件,其中cols
属性定义为6
,并添加一个v-text-field
组件作为子级,其中v-model
指令绑定到password
变量,rules
变量属性定义为passwordRules
计算属性,label
属性定义为Password
:
<v-col
cols="6" >
<v-text-field
v-model="password"
:rules="passwordRules"
label="Password"
type="password"
required
/> </v-col>
- 现在我们将创建操作按钮。作为顶部
v-row
组件上的v-col
的兄弟,创建一个v-col
组件,其中cols
属性定义为12
,class
属性定义为text-right
。在v-col
组件内部,创建一个v-btn
组件,其中color
属性定义为error
,class
属性为mr-4
,并将click
事件侦听器附加到cancel
方法。最后,创建一个v-btn
组件作为兄弟,其中disabled
变量属性为valid
变量的否定,color
属性为success
,class
属性为mr-4
,并将click
事件侦听器附加到register
方法:
<v-col
cols="12"
class="text-right" >
<v-btn
color="error"
class="mr-4"
@click="cancel"
>
Cancel
</v-btn>
<v-btn
:disabled="!valid"
color="success"
class="mr-4"
@click="register"
>
Register
</v-btn> </v-col>
它是如何工作的...
在这个示例中,我们学习了如何使用 Vuetify 和 Vue-CLI 创建用户注册页面。要创建此页面,我们首先需要使用 Vue-CLI 工具创建项目,然后将 Vuetify 插件添加到其中,以便可以使用该框架。
然后,我们创建了top-bar
组件,其中包含应用程序标题和菜单按钮切换。为了使用菜单,我们创建了drawer-menu
组件来容纳导航项。最后,我们创建了layout
组件来将top-bar
和drawer-menu
组件组合在一起,并添加了一个<slot>
组件来放置页面内容。
对于用户注册表单页面,我们创建了三个包含输入表单的卡片,这些表单与组件上的变量绑定。表单中的一些输入与一组验证规则相关联,用于检查必填字段和电子邮件验证。
最后,在将数据发送到服务器之前,将检查用户注册表单是否有效。
这是最终项目正在运行的屏幕截图:
另请参阅
您可以在vuetifyjs.com/en/
找到有关 Vuetify 的更多信息。
您可以在material.io/
找到有关 Material Design 的更多信息。
使用 Ant-Design 创建页面、布局和用户表单
Ant-Design 框架是由阿里巴巴集团创建的,具体由 AliPay 和 Ant Financial 背后的技术团队创建。它是一个生态系统设计,主要被亚洲科技巨头使用,并且在 React 和 Vue 社区中占据重要地位。专注于后台 UI,框架的主要核心是其解决自定义数据输入和数据表格的解决方案。
在这里,我们将学习如何使用 Ant-Design 和 Vue 创建一个用户注册表单,方法是创建一个顶部栏组件,一个抽屉菜单,一个布局包装器,以及一个带有表单的用户注册页面。
准备工作
这个教程的先决条件如下:
Node.js 12+
一个 Vue-CLI 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
在这个教程中,我们将使用 Ant-Design 框架创建一个用户注册表单。为此,我们将创建一个布局包装器和所需的包装器组件,最后,我们将创建包含用户注册表单的页面。
创建 Vue-CLI 项目
我们需要创建一个 Vue-CLI 项目,以便安装 Ant-Design 插件并开始开发用户注册表单:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create antd-vue
- 首先,Vue-CLI 会要求您选择一个预设;使用空格键选择
手动选择功能
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
- 现在 Vue-CLI 将要求选择功能,您需要选择
CSS 预处理器
作为默认功能的附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 在这里,Vue-CLI 将询问您要使用哪种
CSS 预处理器
;选择Less
:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) ❯ Less **Stylus**
- 通过选择一个 linter 和格式化程序来继续这个过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能(这里,
保存时进行 lint
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择您想要放置的 linter 和格式化程序配置文件的位置(这里,
在专用配置文件中
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ **In dedicated config files****In package.json**
- 最后,CLI 会询问您是否要保存未来项目的设置;您应该选择
N
。之后,Vue-CLI 将为您创建一个文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
将 Ant-Design 添加到 Vue-CLI 项目
将 Ant-Design 框架添加到 Vue-CLI 项目中,我们需要使用 Vue-CLI 插件将框架安装为项目依赖项,并使其在应用程序的全局范围内可用:
- 在您创建 Vue-CLI 项目的文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue add ant-design
- Vue-CLI 将询问您如何导入 Ant-Design 组件;我们将选择
Fully import
选项:
? How do you want to import Ant-Design-Vue?
❯ Fully import
Import on demand
- Vue-CLI 将询问您是否要覆盖 Ant-Design 的
LESS
变量;我们将选择N
:
? Do you wish to overwrite Ant-Design-Vue's LESS variables? (y/N)
- 最后,Vue-CLI 将询问项目中 Ant-Design 将使用的主要语言;我们将选择
en_US
:
? Choose the locale you want to load
❯ en_US
zh_CN
zh_TW
en_GB
es_ES
ar_EG
bg_BG
(Move up and down to reveal more choices)
使用 Ant-Design 创建布局
为了能够创建用户注册表单,我们将创建一个包装页面内容和表单的基本布局。在这里,我们将创建top-bar
组件,drawer-menu
组件和layout
包装器。
创建顶部栏组件
在layout
包装器中,我们将有一个top-bar
组件,用于保存用户当前位置的面包屑。现在我们将创建top-bar
组件并使其可用于布局:
在src/components
文件夹中,创建一个名为TopBar.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个带有name
属性定义为TopBar
的default
JavaScript 对象:
<script> export default {
name: 'TopBar', }; </script>
- 在单文件组件的
<style>
部分,我们将使<style>
部分为scoped
,并创建一个名为header-bread
的类。现在,background-color
将被定义为#f0f2f5
,带有一个名为bread-menu
的类,边距定义为16px 0
:
<style scoped>
.header-bread {
background-color: #f0f2f5;
} .bread-menu {
margin: 16px 0;
} </style>
- 在单文件组件的
<template>
部分,我们将创建一个a-layout-component
组件,class
属性定义为header-bread
。还要添加一个a-breadcrumb
组件作为子元素,class
属性为bread-menu
:
<template>
<a-layout-header class="header-bread">
<a-breadcrumb class="bread-menu"> </a-breadcrumb>
</a-layout-header> </template>
- 作为
a-breadcrumb
组件的子组件,创建两个a-breadcrumb-item
组件,并为每个添加用户位置的方向。在我们的情况下,第一个将是user
,第二个将是Registration Form
:
<a-breadcrumb-item>user</a-breadcrumb-item> <a-breadcrumb-item>Registration Form</a-breadcrumb-item>
创建抽屉菜单
在布局设计中,我们将有一个抽屉菜单组件作为用户的导航菜单。在这里,我们将创建Drawer
组件:
在src/components
文件夹中,创建一个名为Drawer.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个带有两个属性的default
JavaScript 对象。name
属性定义为Drawer
,data
属性定义为返回 JavaScript 对象的singleton
函数。在data
属性中,创建以下内容:
一个drawer
属性,定义为false
。
一个menu
属性,我们将其定义为将要使用的菜单项数组。菜单数组将包含三个 JavaScript 对象,具有name
和icon
属性。这个数组将包括:
一个 JavaScript 对象,属性name
定义为Home
,icon
定义为home
一个 JavaScript 对象,属性name
定义为Ant Design Vue
,icon
定义为ant-design
一个 JavaScript 对象,属性name
定义为Contact
,icon
定义为mail
:
<script> export default {
name: 'Drawer',
data: () => ({
drawer: false,
menu: [
{ name: 'Home',
icon: 'home',
},
{
name: 'Ant Design Vue',
icon: 'ant-design',
},
{
name: 'Contact',
icon: 'mail',
},
],
}), }; </script>
- 在单文件组件的
<template>
部分,创建一个a-layout-sider
组件,v-model
指令绑定到drawer
变量,collapsible
属性定义为true
。作为子组件,创建一个a-menu
组件,default-selected-keys
变量属性定义为['1']
,theme
属性定义为dark
,mode
属性定义为inline
:
<template>
<a-layout-sider
v-model="drawer"
collapsible
>
<a-menu
:default-selected-keys="['1']"
theme="dark"
mode="inline"
> </a-menu>
</a-layout-sider> </template>
- 最后,在
a-menu
组件内部,创建一个a-menu-item
组件,使用v-for
指令迭代menu
变量,并创建item
和index
临时变量。然后,将key
变量属性定义为index
。创建一个子AIcon
组件,type
变量属性定义为item.icon
,并创建一个兄弟span
HTML 元素,内容为item.name
:
<a-menu-item
v-for="(item,index) in menu"
:key="index" >
<a-icon
:type="item.icon"
/>
<span>{{ item.name }}</span> </a-menu-item>
创建布局组件
在这里,我们将创建layout
组件。这个组件将把top-bar
组件和Drawer
菜单组件连接起来,作为用户注册页面的包装器:
在src/components
文件夹中,创建一个名为Layout.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入top-bar
组件和drawer-menu
组件:
import TopBar from './TopBar.vue'; import Drawer from './Drawer.vue';
- 然后,我们将导出一个
default
的 JavaScript 对象,带有一个name
属性,定义为layout
。然后定义components
属性,包括导入的组件。
export default {
name: 'layout',
components: {
Drawer,
TopBar,
}, };
- 在单文件组件的
<template>
部分,创建一个a-layout
组件,style
属性定义为min-height: 100vh
。然后,将Drawer
组件作为子组件添加。作为drawer
组件的兄弟,创建一个a-layout
组件:
<template>
<a-layout
style="min-height: 100vh"
>
<drawer />
<a-layout>
<top-bar />
<a-layout-content style="margin: 0 16px">
<div style="padding: 24px; background: #fff;
min-height: auto;">
<slot />
</div>
</a-layout-content>
<a-layout-footer style="text-align: center">
Vue.js 3 Cookbook | Ant Design ©2020 Created by Ant UED
</a-layout-footer>
</a-layout>
</a-layout> </template>
- 向
a-layout
组件添加top-bar
组件和一个兄弟a-layout-content
组件,其中style
属性定义为margin: 0 16px
。作为该组件的子元素,创建一个div
HTML 元素,其中style
属性定义为padding: 24px; background: #fff; min-height: auto;
,并添加一个slot
占位符。最后,创建一个a-layout-footer
组件,其中style
属性定义为text-align:center;
,并添加页面的页脚文本:
<top-bar /> <a-layout-content style="margin: 0 16px">
<div style="padding: 24px; background: #fff; min-height: auto;">
<slot />
</div> </a-layout-content> <a-layout-footer style="text-align: center">
Vue.js 3 Cookbook | Ant Design ©2020 Created by Ant UED
</a-layout-footer>
使用 Ant-Design 创建用户注册表单
现在我们将创建用户注册页面和表单,该表单将放在前面步骤中创建的布局中。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在src
文件夹中,打开App.vue
文件并清空其内容。
导入layout
组件:
import Layout from './components/Layout.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为App
,定义导入组件的components
属性,并最后将data
属性定义为返回 JavaScript 对象的单例函数。在data
属性中,创建以下内容:
一个labelCol
属性,定义为 JavaScript 对象,其中span
属性和值为4
。
一个wrapperCol
属性,定义为 JavaScript 对象,其中span
属性和值为14
。
一个form
属性,定义为 JavaScript 对象,其中name
,username
,password
,email
,phone
,cellphone
,address
,zipcode
和country
属性都定义为空字符串:
export default {
name: 'App',
components: {
Layout,
},
data() {
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
form: {
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
},
};
}, };
单文件组件部分
现在是时候创建单文件组件的<template>
部分了:
在src
文件夹中,打开App.vue
文件。
在<template>
部分,创建一个layout
组件元素,并添加一个a-form-model
组件作为子元素,其中model
变量属性绑定到form
,label-col
变量属性绑定到labelCol
,wrapper-col
变量属性绑定到wrapperCol
:
<layout>
<a-form-model
:model="form"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
</a-form-model>
</layout>
- 然后,作为
layout
组件的兄弟组件,创建一个h1
HTML 元素,页面标题为用户注册
,以及一个p
HTML 元素,页面副标题为主用户注册表单
。然后,创建一个a-card
元素,其中title
属性定义为个人信息
:
<h1>
User Registration
</h1> <p>Main user registration form</p> <a-card title="Personal Information"></a-card>
- 在
a-card
组件中,创建一个a-form-model-item
组件作为子元素,其中label
属性定义为姓名
,并添加一个子a-input
组件,其中v-model
指令绑定到form.name
变量:
<a-form-model-item label="Name">
<a-input v-model="form.name" /> </a-form-model-item>
- 接下来,作为兄弟元素,创建一个
a-form-model-item
组件,其中label
属性定义为电子邮件
,并添加一个子a-input
组件,其中v-model
指令绑定到form.email
变量,type
属性定义为email
:
<a-form-model-item label="Email">
<a-input
v-model="form.email"
type="email"
/> </a-form-model-item>
- 创建一个
a-form-model-item
组件,其中label
属性定义为电话
,并添加一个子a-input
组件,其中v-model
指令绑定到form.phone
变量:
<a-form-model-item label="Phone">
<a-input v-model="form.phone" /> </a-form-model-item>
- 创建一个
a-form-model-item
组件,其中label
属性定义为手机号码
,并添加一个子a-input
组件,其中v-model
指令绑定到form.cellphone
变量:
<a-form-model-item label="Cellphone">
<a-input v-model="form.cellphone" /> </a-form-model-item>
- 作为
a-card
组件的兄弟元素,创建一个a-card
组件,其中title
属性定义为地址
,style
属性定义为margin-top: 16px;
。然后,添加一个子a-form-model-item
组件,其中label
属性定义为地址
,并添加一个子a-input
组件,其中v-model
指令绑定到form.address
变量。
<a-card title="Address" style="margin-top: 16px">
<a-form-model-item label="Address">
<a-input v-model="form.address" />
</a-form-model-item> </a-card>
- 接下来,作为
a-card
组件的兄弟元素,创建一个a-form-model-item
组件,其中label
属性定义为邮政编码
,并添加一个子a-input
组件,其中v-model
指令绑定到form.zipcode
变量:
<a-form-model-item label="Zipcode">
<a-input v-model="form.zipcode" /> </a-form-model-item>
- 创建一个
a-form-model-item
组件,其中label
属性定义为国家
,并添加一个子a-input
组件,其中v-model
指令绑定到form.country
变量:
<a-form-model-item label="Country">
<a-input v-model="form.country" /> </a-form-model-item>
- 作为
a-card
组件的兄弟元素,创建一个a-card
组件,其中title
属性定义为用户信息
,style
属性定义为margin-top: 16px;
。然后,添加一个子a-form-model-item
组件,其中label
属性定义为用户名
,并添加一个子a-input
组件,其中v-model
指令绑定到form.username
变量:
<a-card title="user Information" style="margin-top: 16px">
<a-form-model-item label="username">
<a-input v-model="form.username" />
</a-form-model-item> </a-card>
- 创建一个
a-form-model-item
组件,其中label
属性定义为密码
,并添加一个子a-input-password
组件,其中v-model
指令绑定到form.password
变量,visibility-toggle
属性定义为true
,type
属性定义为password
:
<a-form-model-item label="Password">
<a-input-password
v-model="form.password"
visibility-toggle
type="password"
/> </a-form-model-item>
- 最后,作为
a-card
组件的一个兄弟组件,创建a-form-model-item
,并将wrapper-col
变量属性定义为 JavaScript 对象{span: 14, offset: 4}
。然后,添加一个子a-button
,其中type
定义为primary
,文本为Create
,另一个a-button
,其中style
属性定义为margin-left: 10px;
,文本为Cancel
:
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary">
Create
</a-button>
<a-button style="margin-left: 10px;">
Cancel
</a-button> </a-form-model-item>
它是如何工作的...
在这个示例中,我们学习了如何使用 Ant-Design 和 Vue-CLI 创建用户注册页面。为了创建这个页面,我们首先需要使用 Vue-CLI 创建一个项目,并向其中添加 Ant-Design of Vue 插件,以便可以使用该框架。
然后,我们创建了top-bar
组件,用于保存导航面包屑。为了用户导航,我们创建了一个自定义的Drawer
组件,底部带有内联切换按钮。最后,我们创建了layout
组件,将这两个组件放在一起,并添加了一个<slot>
组件来放置页面内容。
最后,我们创建了用户注册表单页面,其中包含三个卡片,用于保存与组件上的变量绑定的输入表单。
这是最终项目正在运行的屏幕截图:
另请参阅
您可以在vue.ant.design/
找到有关 Ant-Design 和 Vue 的更多信息。
第十章:将应用程序部署到云平台
现在是时候将您的应用程序部署到全球范围内,使其对全球所有人都可用。
在这一章中,我们将学习如何在三个不同的托管平台上进行操作 - Netlify,Now 和 Firebase。在这里,我们将学习在每个平台上创建帐户的过程,设置环境,配置应用程序以进行部署,最后将其部署到网络上。
在本章中,我们将涵盖以下配方:
创建 Netlify 帐户
为在 Netlify 上部署应用程序做准备
为在 GitHub 上的 Netlify 进行自动部署做准备
创建一个 Vercel 帐户
配置 Vercel-CLI 并部署您的项目
为在 GitHub 上的 Vercel 进行自动部署做准备
创建一个 Firebase 项目
配置 Firebase-CLI 并部署您的项目
技术要求
在本章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户!您需要安装一个名为 windows-build-tools 的 NPM 包,以便能够安装以下必需的软件包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
要安装 Vue-CLI,您需要打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个 Vue 项目
要创建一个 Vue-CLI 项目,请按照以下步骤进行:
- 打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> vue create vue-project
- Vue-CLI 将要求您选择一个预设;使用空格键选择
Manually select features
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在,Vue-CLI 将要求选择功能,您需要选择
Router
,Vuex
和Linter / Formatter
作为默认功能之外的附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router ❯ Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 现在,Vue-CLI 将询问您是否要使用历史模式进行路由管理。我们将选择
y
(是):
? Use history mode for router? (Requires proper server setup for
index fallback in production) (Y**/n) y**
- 通过选择 linter 和 formatter 来继续该过程。我们将选择
ESLint + Airbnb config
:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能(这里是
Lint and fix on commit
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择您想要放置 linter 和 formatter 配置文件的位置(这里是
In dedicated config files
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
- 最后,CLI 将询问您是否要保存未来项目的设置;您将选择
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
创建 Netlify 帐户
现在是时候开始将部署过程到 Netlify 平台。在这个教程中,我们将学习如何创建我们的 Netlify 账户,以便我们可以将我们的应用程序部署到网络上。
准备就绪
本教程的先决条件如下:
一个电子邮件地址
GitHub 账户
一个 GitLab 账户
一个 BitBucket 账户
在创建 Netlify 账户的过程中,您可以使用电子邮件地址、GitHub账户、GitLab账户或BitBucket账户来实现这一点。
如何做...
在这里,我们将学习如何使用电子邮件地址创建 Netlify 账户:
转到 Netlify 网站www.netlify.com/
,并在页眉菜单中单击注册→。您将被重定向到初始注册页面。
在这个页面上,您可以选择您想要使用的注册 Netlify 的方法。在这个过程中,我们将继续使用电子邮件地址。点击电子邮件按钮,将被重定向到电子邮件注册表单。
填写表单,使用您选择的电子邮件地址和密码。密码规则为最少 8 个字符。填写完表单后,点击注册按钮。然后,您将被重定向到成功页面。
现在,您将在收件箱中收到一封验证电子邮件,您需要这封邮件才能继续使用 Netlify 平台。要继续,请打开您的电子邮件收件箱并检查 Netlify 的电子邮件。
在您的电子邮件收件箱中,打开 Netlify 的电子邮件,然后点击验证电子邮件按钮。此时,将打开一个新窗口,您将能够使用最近注册的电子邮件和密码登录。
在这里,您可以使用您在第 3 步选择的电子邮件地址和密码填写登录表单。之后,点击登录按钮,将被重定向到 Netlify 平台的主窗口。
最后,您将发现自己在 Netlify 平台的主屏幕上,有一个空白页面可以开始在平台上部署。
它是如何工作的...
在这个教程中,我们学习了如何在 Netlify 上创建我们的账户。我们看到可以使用各种 OAuth 方法和我们在教程中使用的基本电子邮件来实现这一点。
电子邮件地址创建过程涉及定义将要使用的电子邮件地址和账户密码,验证账户电子邮件。然后,您可以登录到平台。
另请参阅
在docs.netlify.com/
找到更多关于 Netlify 的信息。
为在 Netlify 中部署应用程序做准备
要开始部署过程,我们需要配置我们的项目以具有有效的 Netlify 部署模式。在这个食谱中,您将学习如何在任何基于 Vue 的应用程序上设置 Netlify 部署模式。
做好准备
这个食谱的先决条件如下:
Node.js 12+
一个 Vue 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做…
在这个食谱中,我们将学习如何准备我们的应用程序以部署到 Netlify:
- 打开您的 Vue 项目并打开
package.json
文件。检查是否已定义build
脚本,如下例所示:
"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"lint": "Vue-CLI-service lint" },
- 打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> npm run build
确保您的应用程序的build
脚本在主文件夹中创建一个dist
文件夹。
如果您的vue-router
被定义为与历史模式一起工作,您必须在public
文件夹中创建一个_redirects
文件。在这个文件中,您需要添加指示给 Netlify 路由器的指令:
# Netlify settings for single-page application
/* /index.html 200
- 将您的应用程序发布到 GitHub 存储库。不用担心构建文件夹,因为它已经在
.gitignore
文件中声明,并且不会发送到您的存储库。
它是如何工作的…
在这个食谱中,我们学习了如何检查和准备我们的应用程序进行 Netlify 部署。
为了使部署工作,我们需要确保在package.json
的脚本部分中有build
命令,并验证构建目标是dist
文件夹。
最后,我们在 public 文件夹中创建了一个_redirects
文件,以指示 Netlify 路由器理解 vue-router 的历史模式。
另请参阅
在cli.vuejs.org/guide/deployment.html#netlify
上查找有关 Netlify 部署的官方 Vue-CLI 文档的更多信息。
在docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps
上查找有关 Netlify 路由器重写的更多信息。
为在 GitHub 上自动部署到 Netlify 做准备
是时候为部署做准备了。在这个食谱中,您将学习如何设置 Netlify 部署过程,以便在 GitHub 上自动获取并部署您的应用程序。
做好准备
这个食谱的先决条件如下:
一个 Netlify 账户
一个 Vue 项目
一个 GitHub 账户
如何做…
最后,在创建 Netlify 帐户,将项目发布到 GitHub 存储库并配置所有内容之后,现在是时候准备 Netlify 平台以在每次 GitHub 推送时执行自动部署了:
转到 Netlify(www.netlify.com/
),登录并打开您的初始仪表板。在那里,您会找到一个来自 Git 的新站点按钮。您将被重定向到创建新站点页面。
现在,您可以单击 GitHub 按钮打开一个新窗口,以在 GitHub 上进行 Netlify 授权并在那里继续该过程。
使用您的 GitHub 帐户登录,然后您将被重定向到应用程序安装页面。
在此页面上,您可以选择向 Netlify 提供对所有存储库的访问权限,或仅选择的存储库,但请确保使您的应用程序的存储库可用。
在 GitHub 上完成 Netlify 的安装后,您在上一步中授予访问权限的存储库将可以在 Netlify 平台上选择。选择包含您的应用程序的存储库。
完成创建过程,您需要选择用于自动部署的分支。然后,您需要填写应用程序中使用的构建命令,在我们的情况下是npm run build
。打开包含构建文件的文件夹,在我们的情况下是dist
文件夹,并单击“部署站点”按钮。
最后,Netlify-CLI 将启动构建过程,并在构建完成且没有任何错误时发布您的应用程序。
工作原理...
Netlify 平台连接到您的 GitHub 帐户并安装为应用程序,从而可以访问选定的存储库。然后,在平台上,您可以选择要用于部署的存储库。选择存储库后,我们需要使用构建说明和构建目标文件夹配置 Netlify-CLI。最后,CLI 运行,我们的应用程序就可以在网络上运行了。
另请参阅
在docs.netlify.com/configure-builds/file-based-configuration/
上查找有关高级 Netlify 部署的更多信息。
创建 Vercel 帐户
Vercel 是一个著名的平台,用于在网络上部署您的应用程序。使用 Vercel,您可以自动化与 GitHub、GitLab 和 BitBucket 的部署过程。在本教程中,我们将学习如何在 Vercel 平台上创建我们的帐户。
准备就绪
这个教程的先决条件只是以下选项之一:
一个 GitHub 账户
一个 GitLab 账户
一个 BitBucket 账户
如何做...
让我们开始在 Vercel 平台上的旅程。在这里,我们将学习如何在平台上创建我们的账户以开始我们的项目部署:
打开 Vercel 网站(vercel.com/
),并点击顶部栏的注册按钮。您将被重定向到注册页面。
在这里,您可以选择这些存储库管理器中的一个 - GitHub、GitLab 或 BitBucket。我们将继续点击GitHub按钮。选择注册方法后,您将被重定向到授权页面。
在此页面上,您正在授权 Vercel 平台访问您账户上的信息。点击授权按钮后,您将被重定向回 Vercel 仪表板。
最后,您已经创建了 Vercel 账户,并准备好使用。
它是如何工作的...
在这个教程中,我们进入了 Vercel 平台,并使用存储库管理器注册了它。我们能够创建我们的账户,并且现在可以通过存储库集成或 CLI 工具在平台上开始部署过程。
另请参阅
您可以在vercel.com/
找到有关 Vercel 的更多信息。
配置 Vercel-CLI 并部署您的项目
您已经创建了一个 Vercel 账户。现在是时候在您的项目上配置 Vercel-CLI,以便它可以在 Vercel 平台和 Web 上使用。
准备工作
这个教程的先决条件如下:
一个 Vercel 账户
一个 Vue 项目
Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
vercel
要安装vercel
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm i -g vercel
如何做...
在这个教程中,我们将学习如何通过 Vercel-CLI 将我们的项目链接到 Vercel 平台,然后使用它部署平台:
- 打开您的 Vue 项目,然后打开
package.json
文件。检查您是否定义了build
脚本,如下例所示:
"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"lint": "Vue-CLI-service lint" },
确保您的应用构建脚本在主文件夹中创建一个dist
文件夹。
在您的project
文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vercel
这将提示您登录 Vercel 平台:
> No existing credentials found. Please log in:
Enter your email:
输入与您用于登录 Vercel 的仓库管理器相关联的电子邮件地址。您将收到一封带有“验证”按钮的电子邮件;点击它以验证您的电子邮件地址:
一旦您的电子邮件地址得到验证,您就可以使用> vercel
命令在您的终端中部署应用程序。
要将应用程序部署到网络上,我们需要在project
文件夹中执行> vercel
命令,并且在部署之前它会询问一些关于项目设置的问题。第一个问题将涉及项目路径:
? Set up and deploy "~/Versionamento/Vue.js-3.0-Cookbook/chapter-
14/14.5"? [Y/n] y
- 现在它将要求您定义将部署项目的范围。当您在同一用户名下定义了多个帐户访问选项时使用。在大多数情况下,它只会有一个,您可以按Enter:
? Set up and deploy "~/Versionamento/Vue.js-3.0-Cookbook/chapter-
14/14.5"? y
? Which scope do you want to deploy to? ❯ **Heitor Ramon Ribeiro**
- 然后,它将要求链接到 Vercel 上的现有项目。在我们的情况下,这是一个全新的项目,所以我们将选择
n
:
? Link to existing project? [Y/n] n
- 您将被要求定义项目的名称(只允许小写字母数字字符和连字符):
? What's your project's name? vuejscookbook-12-5
- 您现在需要定义项目源代码的位置。此位置是
package.json
文件所在的位置;在我们的情况下,这将是./
文件夹,或者主项目文件夹:
? In which directory is your code located? ./
- Vercel-CLI 将检测到项目是一个 Vue-CLI 项目,并将自动定义所有用于部署应用程序的命令和目录设置。在我们的情况下,我们将选择
n
。
Auto-detected Project Settings (Vue.js): - Build Command: `npm run build` or `Vue-CLI-service build` - Output Directory: dist - Development Command: Vue-CLI-service serve --port $PORT ? Want to override the settings? [y/N] n
- 一切设置完成后,CLI 将部署应用程序的第一个预览,并将发送一个链接以访问应用程序的预览。要将应用程序部署为生产就绪状态,您需要在 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)中执行以下命令:
> vercel --prod
它是如何工作的...
在这个教程中,我们学习了如何将 Vercel-CLI 连接到与仓库管理器相关联的电子邮件地址的在线平台,并设置项目部署。
在这个过程中,我们学习了如何通过定义构建命令、分发文件夹和开发命令来配置 CLI 的高级选项。
最后,我们能够在将项目部署到生产环境之前获得项目的预览 URL。
另请参阅
您可以在vercel.com/docs/cli?query=CLI#getting-started
找到有关 Vercel-CLI 的更多信息。
您可以在vercel.com/docs/configuration?query=now.json#introduction/configuration-reference
找到有关 Vercel 高级配置的更多信息。
准备在 GitHub 上使用 Vercel 进行自动部署
在上一个教程中,我们学习了如何使用 Vercel-CLI 将我们的应用程序部署到网络上,使用您的本地终端,但是可以将存储库管理器与 Vercel 平台集成,并通过任何推送或打开拉取请求自动部署。这就是我们将在这个教程中做的事情。
准备工作
本教程的先决条件如下:
Vercel 帐户
存储库管理器上的 Vue 项目
如何做...
在这个教程中,我们将学习如何将 Vercel 平台与存储库管理器集成,并进行自动部署:
打开您的 Vercel 仪表板(vercel.com/dashboard
)并点击导入项目按钮。
在导入项目页面上,点击继续按钮,位于来自 Git 存储库卡内。
现在,Vercel 网站将询问持有您要导入的项目的存储库的用户是否是您的个人帐户。如果是,请点击是。如果不是,Vercel 将在开始该过程之前将该项目分叉到您的个人帐户。
然后,Vercel 将询问您要将项目绑定到哪个帐户。在我们的情况下,这将是我们的个人帐户。选择它,然后点击继续按钮。
您将被重定向到 GitHub 网页,以授予 Vercel 对您的存储库的访问权限。您可以授予对所有存储库的访问权限,或者只是您想要部署的存储库。在我们的情况下,我们将授予对我们帐户上所有存储库的访问权限。
在您的 GitHub 帐户上安装 Vercel 应用程序后,您将被发送回 Vercel 网页。在这里,您可以定义您正在创建的项目的设置,包括项目名称、您正在使用的预设、构建说明和环境变量。Vercel 将自动检测到我们的项目是一个 Vue-CLI 项目,并为我们配置构建和部署设置。然后,点击部署按钮继续。
Vercel 将启动第一个部署过程。完成后,Vercel 将为您提供应用程序的链接,以及一个打开仪表板的链接。
它是如何工作的...
Vercel 平台连接到您的 GitHub 帐户并安装为应用程序,从而可以访问选定的存储库。然后,在平台上,您可以选择要用于部署的存储库。
选择存储库后,您需要使用构建说明和构建目标文件夹配置 Vercel-CLI。
最后,CLI 运行,我们的应用程序在 Web 上运行起来了。
参见
在zeit.co/docs/v2/git-integrations
找到有关 Vercel 与 Git 存储库集成的更多信息。
创建 Firebase 项目
Firebase 是由 Google 创建的一体化解决方案,旨在为开发人员提供专用的分析、通知、机器学习和云解决方案工具。他们提供的云解决方案之一是托管平台。
有了托管平台,我们可以在 Google 云服务器上托管我们的单页应用程序,并通过全球内容传送网络使其对所有人可用。
准备工作
此处的先决条件如下:
一个 Google 帐户
一个 Vue 项目
如何做...
在这个教程中,我们将学习如何创建我们的 Firebase 项目,以便我们可以将我们的应用程序部署到 Firebase 托管:
打开 Firebase 主页(firebase.google.com/
)并单击页眉菜单中的“登录”链接。如果您已经登录到您的 Google 帐户,请单击“转到控制台”链接。
在控制台页面上,单击“创建项目”按钮以创建新的 Firebase 项目。
Firebase 将要求输入项目名称(您只能使用字母数字字符和空格)。
然后,Firebase 会询问您是否要在此项目中启用 Google Analytics。在我们的情况下,我们将禁用此选项。
最后,您将被重定向到项目概览仪表板。
它是如何工作的...
在这个教程中,我们创建了我们的第一个 Firebase 项目。为此,我们首先登录到我们的 Google 帐户并转到 Firebase 控制台。在 Firebase 控制台上,我们创建了一个新项目,并在设置向导步骤中禁用了 Google Analytics 选项,因为我们在这个教程中不会使用附加的分析。最后,当我们完成设置向导时,我们的项目已经准备就绪。
参见
在firebase.google.com
找到有关 Google Firebase 的更多信息。
配置 Firebase-CLI 并部署您的项目
要将我们的应用程序部署到 Firebase Hosting,我们需要使用 Firebase-CLI。CLI 将帮助打包文件并将它们发送到 Google Cloud 服务器的过程。
在本配方中,我们将学习如何配置 Firebase-CLI 以使用本地终端将应用程序部署到网络。
准备工作
本配方的先决条件如下:
一个 Google 帐户
一个 Vue 项目
一个 Firebase 项目
Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
firebase-tools
安装firebase-tools
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g firebase-tools
如何做...
在本配方中,我们将学习如何在我们的项目中设置 Firebase-CLI,并如何使用上一个配方中创建的项目对其进行初始化:
- 在项目的根文件夹中打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> firebase login
Firebase-CLI 将打开一个浏览器窗口,以便您登录到您的 Google 帐户,并允许 Firebase-CLI 访问您的 Google 帐户的部分。(如果浏览器没有自动打开,Firebase-CLI 上会出现一个链接,复制链接,然后粘贴到浏览器中继续。)
在项目的根文件夹中打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> firebase init
- 现在我们正在使用我们的项目初始化 CLI 的配置过程。对于 CLI 的第一个问题,我们将只使用Hosting功能,因此我们需要只选择
Hosting
:
**? Which Firebase CLI features do you want to set up
for this folder?**
**Press space to select feature, then Enter to confirm
your choices.**
Database: Deploy Firebase Realtime Database Rules
Firestore: Deploy rules and create indexes for Firestore
Functions: Configure and deploy Cloud Functions ❯ Hosting: Configure and deploy Firebase Hosting sites
Storage: Deploy Cloud Storage security rules Emulators: Set up local emulators for Firebase features
- 然后,CLI 将询问我们要使用哪个 Firebase 项目。在我们的情况下,我们在上一个配方中早些时候创建了项目,因此我们将选择
使用现有项目
:
? Use an existing project ❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
- 现在将显示您帐户上可用项目的列表。选择要与此应用程序一起部署的项目:
? **Select a default Firebase project for this directory: (Use arrow**
**keys)** ❯ vue-3-cookbook-firebase-18921 (Vue 3 Cookbook Firebase)
- CLI 将询问应用程序的公共目录,或者在我们的情况下,因为它是单页面应用程序,我们需要使用构建目标文件夹。键入目标文件夹的名称,在我们的情况下是
dist
:
? **What do you want to use as your project public directory?** **dist**
- 最后,过程中的最后一步是选择是否要将配置用作单页面应用程序。键入
y
以启用所有 URL 的重写为index.html
,以便我们可以使用vue-router
的历史模式:
? Configure as a single-page app (rewrite all urls to /index.html)?
(y/N) y
- 打开项目根目录下的
package.json
文件,并添加一个新的脚本来自动化构建和部署过程:
"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"deploy": "npm run build && firebase deploy",
"lint": "Vue-CLI-service lint" },
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并在项目的根目录下执行以下命令:
> npm run deploy
现在您的项目已经部署并可以在网络上访问,CLI 将为您提供访问链接:
工作原理...
在这个教程中,我们学习了如何配置 Firebase CLI 并部署我们的应用程序。
首先,我们安装了 Firebase-CLI 并登录到 Google 身份验证平台。然后,我们能够在项目文件夹中初始化 CLI。
在这个过程中,我们选择了在上一个教程中创建的项目,并将构建文件夹指向 Vue-CLI 项目中的正确文件夹。
然后,我们配置了要使用单页面应用程序路由结构,并向package.json
添加了部署脚本。最后,我们能够部署我们的应用程序并使其对所有人可用。
另请参阅
在firebase.google.com/docs/hosting
上找到有关 Firebase Hosting 的更多信息。
第十一章:指令,插件,SSR 等
现在您已经进入专业联赛!您是一个高级的 Vue 开发者。让我们来玩一些有趣的东西,并查看一些为您量身定制的优秀配方!以下是一些精选的优化解决方案,可以提高您的 Vue 应用程序的质量,并使您的生活更轻松。
在本章中,我们将涵盖以下配方:
自动加载vue-router
路由
自动加载vuex
模块
创建自定义指令
创建 Vue 插件
使用 Quasar 在 Vue 中创建 SSR,SPA,PWA,Cordova 和 Electron 应用程序
创建更智能的 Vue 观察者和计算属性
使用 Python Flask
创建Nuxt.js
SSR
Vue 应用程序的注意事项和禁忌
技术要求
在本章中,我们将使用 Node.js,Vue-CLI
,Cordova
,Electron
,Quasar
,Nuxt.js
和 Python。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的npm
包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装Vue-CLI
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
要安装**Cordova
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g cordova
如果您在 macOS 上运行,并且想要运行 iOS 模拟器,您需要在终端(macOS)中执行以下命令:
> npm install -g ios-sim ios-deploy
要安装Electron
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g electron
要安装**Quasar
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g @quasar/cli
要安装**Nuxt.js
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g create-nuxt-app
自动加载 Vue 路由
为了创建可维护的代码,我们可以使用自动导入具有相同结构的文件的策略。就像在vue-router
中的路由一样,当应用程序变得更大时,我们会发现大量的文件被手动导入和处理。在这个配方中,我们将学习一个技巧,使用 webpack 的require.context
函数来自动为我们注入文件。
此函数将读取文件内容,并将路由添加到一个数组中,默认情况下将其导出到我们的文件中。您可以通过添加更多受控的路由导入甚至基于环境的路由规则来改进此配方。
准备就绪
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用之前创建的项目:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create router-import
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用空格键选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个是手动
模式,您可以在其中选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及我们想要在项目中使用的功能。这些功能包括一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试工具,代码检查工具等。选择Babel
、Router
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个代码检查工具和格式化工具来继续这个过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置了代码检查规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、代码检查工具和处理器之后,我们需要选择设置和配置的存储位置。最佳存储位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
- 现在您可以选择是否要将此选择设置为将来项目的预设,这样您就不需要重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并自动为我们安装包。
如果您想在安装完成后在vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
或者您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令来运行内置的npm
命令:
npm run serve
- 本地运行开发服务器
npm run build
- 用于构建和压缩应用程序以进行部署
npm run lint
- 对代码执行 lint
如何做...
按照这些说明,在项目中创建路由文件的自动导入,它将处理特定文件夹内的路由文件:
- 创建并放置在
routes
文件夹内的路由文件后,我们需要确保每个路由文件中都有一个默认的export
对象。在src/router
文件夹内的index.js
文件中,删除文件中存在的默认routes
数组:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default new VueRouter({});
- 现在创建一个空的
routes
数组,它将由从文件夹中导入的路由填充,并开始导入。这样,requireRoutes
将成为一个对象,其键是文件名,值是文件的 ID:
import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const routes = []; const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*\.js$/is, ); const router = new VueRouter({
routes, }); export default router;
- 为了将这些文件推送到
routes
数组中,我们需要添加以下代码,并在router
文件夹内创建一个名为routes
的文件夹:
import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const routes = []; const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*\.js$/is, ); requireRoutes.keys().forEach((fileName) => {
routes.push({
...requireRoutes(fileName).default,
}); }); const router = new VueRouter({
routes, }); export default router;
现在,只要在routes
文件夹内创建一个新的.js
文件,你的路由就会自动加载到应用程序中。
工作原理...
require.context
是 webpack 内置的函数,允许您传入要搜索的目录、一个指示是否应该检查子目录的标志,以及一个匹配文件的正则表达式。
当构建过程开始时,webpack 将搜索所有require.context
函数,并对其进行预执行,因此导入所需的文件将在最终构建中存在。
我们向函数传递了三个参数:第一个是它将开始搜索的文件夹,第二个询问搜索是否会进入下降的文件夹,最后,第三个是用于文件名匹配的正则表达式。
在这个配方中,我们将./routes
定义为文件夹,作为函数的第一个参数自动加载路由。作为函数的第二个参数,我们定义false
,不搜索子目录。最后,作为第三个参数,我们定义/^(?!.*test).*\.js$/is
作为正则表达式,用于搜索.js
文件并忽略文件名中包含.test
的文件。
还有更多...
通过这个配方,可以通过使用子目录进行路由模块和环境进行路由控制,将应用程序提升到下一个级别。
通过这些增量,函数可以被提取到另一个文件中,但在router.js
中,它仍然需要被导入到main.js
文件中。或者,您可以获取import
函数,并将routes
数组传递给router.js
。
另请参阅
在 webpack 文档中阅读有关 webpack 依赖管理和require.context
的更多信息:webpack.js.org/guides/dependency-management/.
自动加载 Vuex 模块
有时,当我们在一个大项目上工作时,我们需要管理许多导入的Vuex
模块和存储。为了处理这些模块,我们总是需要通过创建一个包含所有导入文件的文件来导入它们,然后将其导出到 Vuex 存储创建中。
在这个食谱中,我们将学习一个使用 webpack 的require.context
函数的函数,以自动加载并将这些文件注入到 Vuex 存储创建中。
准备就绪
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用之前创建的项目:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-import
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个手动
模式,您可以在其中选择更多模式、插件、检查器和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及我们将在项目中需要哪些功能。这些功能包括一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试器,检查器等。选择Babel
,Vuex
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router ❯Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个检查器和格式化程序来继续此过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置好检查规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、检查器和处理器之后,我们需要选择设置和配置的存储位置。将它们存储在专用文件中是最好的地方,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
- 现在您可以选择是否要将此选择作为将来项目的预设,这样您就不需要重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并为我们自动安装包。
如果你想在安装完成后在vue-ui
上检查项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue ui
或者你可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令来运行内置的npm
命令:
npm run serve
– 本地运行开发服务器
npm run build
– 为部署构建和压缩应用程序
npm run lint
– 执行代码的 lint
如何做...
按照以下说明,在项目中创建vuex
模块的自动导入,以处理特定文件夹内的路由文件:
- 在
store
文件夹中创建并放置路由文件后,我们需要确保每个store
文件都有一个默认的export
对象。在src/store
文件夹中的index.js
文件中,我们需要提取stores
或modules
的数组:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({});
- 在
src/store
文件夹中创建另一个名为loader.js
的文件(它将是我们的module
加载器)。重要的是要记住,当使用这个方法时,你将使用vuex
的命名空间,因为所有的stores
都需要作为一个模块使用,并且需要导出为一个单一的 JavaScript 对象。每个文件名将被用作命名空间的引用,并且将被解析为驼峰式文本风格:
const toCamel = (s) => s.replace(/([-_][a-z])/ig, (c) => c.toUpperCase()
.replace(/[-_]/g, '')); const requireModule = require.context('./modules/', false,
/^(?!.*test).*\.js$/is); const modules = {}; requireModule.keys().forEach((fileName) => {
const moduleName = toCamel(fileName.replace(/(\.\/|\.js)/g, '')); modules[moduleName] = {
namespaced: true,
...requireModule(fileName).default,
}; }); export default modules;
- 由于我们将默认导入
modules
文件夹中的每个文件,一个好的做法是为每个模块创建一个文件。例如,当你创建一个名为user
的模块时,你需要创建一个名为user.js
的文件,它导入所有的stores
操作、mutations、getters 和 state。这些可以放在一个与模块同名的文件夹中。modules
文件夹的结构将类似于这样:
modules
├── user.js
├── user
│ └── types.js
│ └── state.js
│ └── actions.js
│ └── mutations.js
│ └── getters.js
└───────
src/store/modules
文件夹中的user.js
文件将如下所示:
import state from './user/state'; import actions from './user/actions'; import mutations from './user/mutations'; import getters from './user/getters'; export default {
state,
actions,
mutations,
getters, };
- 在
src/store
文件夹中的index.js
文件中,我们需要添加自动加载的导入模块:
import Vue from 'vue'; import Vuex from 'vuex'; import modules from './loader'; Vue.use(Vuex); export default new Vuex.Store({
modules, });
现在,只要在src/store/modules
文件夹中创建一个新的.js
文件,你的vuex
模块就会自动加载到你的应用程序中。
它是如何工作的...
require.context
是 webpack 的内置函数,它接收一个目录来执行搜索,一个布尔标志,指示是否在此搜索中包括子目录,以及用于文件名模式匹配的正则表达式(作为参数)。
当构建过程开始时,webpack 将搜索所有require.context
函数,并预先执行它们,以便导入所需的文件在最终构建中存在。
在我们的情况下,我们传递了./modules
作为文件夹,false
表示不搜索子目录,/^(?!.*test).*\.js$/is
作为正则表达式来搜索.js
文件并忽略文件名中包含.test
的文件。
然后,该函数将搜索文件,并通过for
循环将结果添加到vuex
模块的数组中。
另请参阅
在 webpack 文档中阅读有关 webpack 依赖管理和require.context
的更多信息webpack.js.org/guides/dependency-management/.
创建自定义指令
谈到 Vue 等视觉框架,我们总是想到组件、渲染和视觉元素,而忘记了除了组件本身之外还有很多东西。
这些指令使组件与模板引擎一起工作,它们是数据和视觉结果之间的绑定代理。还有 Vue 核心中的内置指令,如v-if
,v-else
和v-for
。
在这个食谱中,我们将学习如何制作我们的指令。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用之前食谱中创建的项目:
- 我们需要在 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)中执行以下命令:
> vue create vue-directive
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。
有两种方法可以启动一个新项目。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个手动
模式,您可以在其中选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及项目中想要的功能。这些功能包括一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试工具,代码检查工具等。选择Babel
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel TypeScript Progressive Web App (PWA) SupportRouter
Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个代码检查工具和格式化工具来继续这个过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置了 linting 规则之后,我们需要定义它们何时应用于您的代码。它们可以在“保存时”应用,也可以在“提交时”修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、linters 和处理器之后,我们需要选择设置和配置存储的位置。存储它们的最佳位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
- 现在您可以选择是否要将此选择作为将来项目的预设,这样您就不需要重新选择所有内容。
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并自动为我们安装软件包。
如果您想在安装完成后在vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
或者,您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的 npm 命令:
npm run serve
- 本地运行开发服务器
npm run build
- 为部署构建和缩小应用程序
npm run lint
- 对代码执行 lint
操作步骤如下...
按照以下说明创建一个用于掩码输入字段的指令:
在src/directives
文件夹中创建名为formMaskInputDirective.js
的文件,并在同一文件夹中创建名为tokens.js
的文件。
在tokens.js
文件中,我们将添加我们的掩码基本令牌。这些令牌将用于识别我们的输入将接受的值类型:
export default {
"#": { pattern: /[\x2A\d]/ },
0: { pattern: /\d/ },
9: { pattern: /\d/ },
X: { pattern: /[0-9a-zA-Z]/ },
S: { pattern: /[a-zA-Z]/ },
A: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleUpperCase() },
a: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleLowerCase() },
"!": { escape: true }
};
- 我们从
token.js
导入令牌并创建我们的函数:
import tokens from './tokens';
function maskerValue() {
// Code will be developed in this recipe
}
function eventDispatcher() {
// Code will be developed in this recipe
}
function maskDirective() {
// Code will be developed in this recipe
}
export default maskDirective;
- 在
maskDirective
函数中,我们需要检查调用者传递的指令上的绑定值,并检查它是否是有效的绑定。为此,我们首先检查binding
参数上是否存在value
属性,然后将其添加到tokens
中导入的config
变量中:
function maskDirective(el, binding) {
let config;
if (!binding.value) return false;
if (typeof config === 'string') {
config = {
mask: binding.value,
tokens,
};
} else {
throw new Error('Invalid input entered');
}
- 现在我们需要检查元素并验证它是否是
input
HTML 元素。为此,我们将检查指令传递的元素是否具有input
的tagName
,如果没有,我们将尝试在传递的元素中找到input
HTML 元素:
let element = el;
if (element.tagName.toLocaleUpperCase() !== 'INPUT') {
const els = element.getElementsByTagName('input');
if (els.length !== 1) {
throw new Error(`v-input-mask directive requires 1 input,
found ${els.length}`);
} else {
[element] = els;
}
}
- 现在我们需要为元素添加事件侦听器。侦听器将调用两个外部函数,一个用于分派事件,另一个用于将掩码值返回到输入中:
element.oninput = (evt) => {
if (!evt.isTrusted) return;
let position = element.selectionEnd;
const digit = element.value[position - 1];
element.value = maskerValue(element.value, config.mask,
config.tokens);
while (
position < element.value.length
&& element.value.charAt(position - 1) !== digit
) {
position += 1;
}
if (element === document.activeElement) {
element.setSelectionRange(position, position);
setTimeout(() => {
element.setSelectionRange(position, position);
}, 0);
}
element.dispatchEvent(eventDispatcher('input'));
};
const newDisplay = maskerValue(element.value, config.mask,
config.tokens);
if (newDisplay !== element.value) {
element.value = newDisplay;
element.dispatchEvent(eventDispatcher('input'));
}
return true;
}
// end of maskDirective function
- 让我们创建
eventDispatcher
函数;这个函数将发出事件,将被v-on
指令监听到:
function eventDispatcher(name) {
const evt = document.createEvent('Event');
evt.initEvent(name, true, true);
return evt;
}
- 现在复杂的部分:将掩码输入值返回到输入框。为此,我们需要创建
maskerValue
函数。该函数接收值、掩码和令牌作为参数。该函数检查当前值与掩码是否匹配,以查看掩码是否完整或值是否是有效令牌。如果一切正常,它将把值传递给输入框:
function maskerValue(v, m, tkn) {
const value = v || '';
const mask = m || '';
let maskIndex = 0;
let valueIndex = 0;
let output = '';
while (maskIndex < mask.length && valueIndex < value.length) {
let maskCharacter = mask[maskIndex];
const masker = tkn[maskCharacter];
const valueCharacter = value[valueIndex];
if (masker && !masker.escape) {
if (masker.pattern.test(valueCharacter)) {
output += masker.transform ?
masker.transform(valueCharacter) : valueCharacter;
maskIndex += 1;
}
valueIndex += 1;
} else {
if (masker && masker.escape) {
maskIndex += 1;
maskCharacter = mask[maskIndex];
}
output += maskCharacter;
if (valueCharacter === maskCharacter) valueIndex += 1;
maskIndex += 1;
}
}
let outputRest = '';
while (maskIndex < mask.length) {
const maskCharacter = mask[maskIndex];
if (tkn[maskCharacter]) {
outputRest = '';
break;
}
outputRest += maskCharacter;
maskIndex += 1;
}
return output + outputRest;
}
//end of maskerValue function
- 准备好后,我们需要在
main.js
文件中导入掩码指令,并将指令添加到 Vue 中,给指令命名为'input-mask'
:
import Vue from 'vue';
import App from './App.vue';
import InputMaskDirective from './directives/formMaskInputDirective';
Vue.config.productionTip = false;
Vue.directive('input-mask', InputMaskDirective);
new Vue({
render: (h) => h(App),
}).$mount('#app');
- 要在我们的应用程序中使用该指令,我们需要在单文件组件
<template>
部分的input
HTML 元素上调用指令,将token
模板'###-###-###'
作为参数传递给v-input-mask
指令,如下所示:
<template>
<div id="app">
<input
type="text"
v-input-mask="'###-###-###'"
v-model="inputMask"
/>
</div>
</template>
<script>
export default {
name: 'app',
data: () => ({
inputMask: '',
}),
};
</script>
工作原理...
Vue 指令有五个可能的钩子。我们只使用了一个bind
。它直接绑定到元素和组件。它有三个参数:element
,binding
和vnode
。
当我们在main.js
文件中将指令添加到 Vue 中时,它将在整个应用程序中可用,因此该指令已经在App.vue
中,可以被输入框使用。
在调用v-input-mask
的同时,我们将第一个参数element
传递给指令,第二个参数binding
是属性的值。
我们的指令通过检查输入框上的每个新字符值来工作。执行正则表达式测试并验证字符,以查看它是否是指令实例化时给定的令牌列表中的有效字符。然后,如果通过测试,它将返回字符,如果是无效字符,则不返回任何内容。
创建一个 Vue 插件
有时需要对应用程序进行新的添加,并且需要共享这个添加。最好的共享方式是使用插件。在 Vue 中,插件是通过扩展初始化应用程序的 Vue 全局原型来添加新功能,如指令、混合、过滤器、原型注入或全新功能。
现在我们将学习如何制作我们的插件,以及如何使用它与整个 Vue 进行交互(而不会干扰原型并破坏它)。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用以前的配方创建的项目:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vue-plugin
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,spacebar选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,以及手动
模式,您可以在其中选择更多模式、插件、linter 和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及我们想要在项目中的功能。这些功能是一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试器,linter 等。选择Babel
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router
Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个 linter 和 formatter 来继续此过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置 linting 规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、linter 和处理器之后,我们需要选择设置和配置的存储位置。存储它们的最佳位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
- 现在,您可以选择是否要将此选择设置为将来项目的预设,这样您就不需要重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
Vue-CLI 将创建项目,并自动为我们安装包。
如果您想在安装完成后在vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
或者,您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的 npm 命令:
npm run serve
- 本地运行开发服务器
npm run build
- 为部署构建和缩小应用程序
npm run lint
- 执行代码上的 lint
如何做...
编写 Vue 插件很简单,无需更多了解 Vue 本身。插件的基本概念是一个需要具有install
函数的对象,当通过Vue.use()
方法调用时将执行该函数。install
函数将接收两个参数:Vue 和将用于实例化插件的选项。
按照以下说明编写一个插件,向 Vue 全局原型添加两个新函数$localStorage
和$sessionStorage
:
在我们的项目中,我们需要在src/plugin
文件夹中创建一个名为storageManipulator.js
的文件。
在这个文件中,我们将创建插件安装对象-我们将添加默认的插件选项和函数的基本原型:
/* eslint no-param-reassign: 0 */
const defaultOption = {
useSaveFunction: true,
useRetrieveFunction: true,
onSave: value => JSON.stringify(value),
onRetrieve: value => JSON.parse(value),
};
export default {
install(Vue, option) {
const baseOptions = {
...defaultOption,
...option,
};
Vue.prototype.$localStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
Vue.prototype.$sessionStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
},
};
- 现在我们需要创建
generateStorageObject
函数。这个函数将接收两个参数:第一个是窗口存储对象,第二个是插件选项。通过这样,将可以生成将用于注入到 Vue 中的原型的对象:
const generateStorageObject = (windowStorage, options) => ({
set(key, value) {
windowStorage.setItem(
key,
options.useSaveFunction
? options.onSave(value)
: value,
);
},
get(key) {
const item = windowStorage.getItem(key);
return options.useRetrieveFunction ? options.onRetrieve(item) :
item;
},
remove(key) {
windowStorage.removeItem(key);
},
clear() {
windowStorage.clear();
},
});
- 需要将插件导入到
main.js
中,然后使用Vue.use
函数在我们的 Vue 应用程序中安装插件:
import Vue from 'vue';
import App from './App.vue';
import StorageManipulatorPlugin from './plugin/storageManipulator';
Vue.config.productionTip = false;
Vue.use(StorageManipulatorPlugin);
new Vue({
render: h => h(App),
}).$mount('#app');
现在你可以在 Vue 应用程序的任何地方使用插件,调用this.$localStorage
方法或this.$sessionStorage
。
工作原理...
Vue 插件通过将所有指令要使用的代码添加到 Vue 应用程序层(如 mixin)来工作。
当我们使用Vue.use()
导入我们的插件时,我们告诉 Vue 在导入文件的对象上调用install()
函数并执行它。Vue 将自动将当前 Vue 作为第一个参数传递,并将选项(如果声明了)作为第二个参数传递。
在我们的插件中,当调用install()
函数时,我们首先创建baseOptions
,将默认选项与传递的参数合并,然后将两个新属性注入到 Vue 原型中。这些属性现在因为传递的Vue
参数是应用程序中使用的Vue 全局
,所以在任何地方都可用。
我们的generateStorageObject
是浏览器的 Storage API 的纯抽象。我们将其用作插件内原型的生成器。
另见
您可以在vuejs.org/v2/guide/plugins.html
找到有关 Vue 插件的更多信息。
您可以在github.com/vuejs/awesome-vue
找到精选的 Vue 插件列表。
使用 Quasar 在 Vue 中创建 SSR、SPA、PWA、Cordova 和 Electron 应用程序
Quasar 是一个基于 Vue 和 Material Design 的框架,利用“一次编写,随处使用”的优势。
CLI 可以将相同的代码库部署到不同的版本,如单页应用程序(SPA)、服务器端渲染(SSR)、渐进式 Web 应用程序(PWA)、移动应用程序(Cordova)和桌面应用程序(Electron)。
这减轻了开发人员的一些问题,比如为开发配置 webpack、Cordova 和 Electron 与HMR(热模块重新加载),或者在 SPA 项目中添加 SSR 配置。该框架帮助开发人员尽快开始生产。
在这个教程中,我们将学习如何使用 Quasar 和 CLI 来创建一个基本项目,以及如何使用 CLI 为 SPA、PWA、SSR、移动应用和桌面应用添加开发目标。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@quasar/cli
我们需要使用 Quasar CLI 创建一个新的 Quasar 项目,或者使用之前创建的项目。
要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar create quasar-project
现在,当被询问时,我们需要选择手动选择功能:
Quasar-CLI
将要求您输入项目名称。定义您的项目名称。在我们的情况下,我们选择了quasar_project
:
> Project name: quasar_project
- 然后
Quasar-CLI
将要求输入项目产品名称。这将被移动应用程序用来定义它们的标题名称。在我们的情况下,我们使用了提供的默认名称:
> Project product name (must start with letter if building mobile
apps) (Quasar App)
- 现在
Quasar-CLI
将要求输入项目描述。这在页面被分享时用于搜索引擎的元标记。在我们的情况下,我们使用了提供的默认描述:
> Project description: (A Quasar Framework app)
- 然后
Quasar-CLI
将要求输入项目作者。用package.json
的有效名称填写(例如,Heitor Ribeiro<[email protected]>
):
> Author: <You>
- 现在是选择 CSS 预处理器的时候了。在我们的情况下,我们将选择
Sass with indented syntax
:
Pick your favorite CSS preprocessor: (can be changed later) (Use arrow keys) ❯ Sass with indented syntax (recommended)
Sass with SCSS syntax (recommended)
Stylus
None (the others will still be available)
- 然后
Quasar-CLI
将询问组件和指令的导入策略。我们将使用默认的auto-import
策略:
Pick a Quasar components & directives import strategy: (can be
changed later) (Use arrow keys ) ❯ * Auto-import in-use Quasar components & directives - also
treeshakes Quasar; minimum bundle size
* Import everything from Quasar - not treeshaking Quasar;
biggest bundle size
- 现在我们需要为项目选择额外的功能。我们将选择
EsLint
:
Check the features needed for your project: EsLint
- 之后,
Quasar-CLI
将要求选择 ESLint 的预设。选择Airbnb
预设:
Pick an ESLint preset: Airbnb
- 最后,
Quasar-CLI
将要求选择要用于安装项目依赖项的应用程序。在我们的情况下,我们使用了yarn
,因为我们已经安装了它(但您可以选择您喜欢的):
Continue to install project dependencies after the project has been
created? (recommended) (Use arrow keys) ❯ Yes, use Yarn (recommended)
Yes, use npm
No, I will handle that myself
现在在您的 IDE 或代码编辑器中打开创建的文件夹。
如何做...
在使用 Quasar 创建应用程序时,您总是需要选择一个口味来开始,但主要代码将是 SPA。因此,其他口味将根据其需求具有其特殊的功能和特色,但您可以根据构建环境个性化并使您的构建执行一些代码。
开发 SPA(单页应用程序)
开始开发 SPA 是一个开箱即用的解决方案;不需要添加任何新的配置。
所以让我们开始向我们的应用程序添加一个新页面。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> quasar new page About
Quasar-CLI
将自动为我们创建 Vue 页面。我们需要在路由文件中添加对页面的引用,然后该页面将在应用程序中可用:
- 为了做到这一点,我们需要打开
src/router
文件夹中的routes.js
文件,并添加About
页面:
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', name: 'home', component: () =>
import('pages/Index.vue') },
{ path: 'about', name: 'about', component: () =>
import('pages/About.vue') },
],
},
{
path: '*',
component: () => import('pages/Error404.vue'),
}
];
export default routes;
- 然后在
src/pages
文件夹中打开About.vue
文件。您会发现该文件是一个单文件组件,其中有一个空的QPage
组件,因此我们需要在<template>
部分中添加基本标题和页面指示:
<template>
<q-page
padding
class="flex flex-start"
>
<h1 class="full-width">About</h1>
<h2>This is an About Us Page</h2>
</q-page>
</template>
<script>
export default {
name: 'PageAbout',
};
</script>
- 现在,在
src/layouts
文件夹中的MainLayout.vue
文件中,对于q-drawer
组件,我们需要添加到Home
和About
页面的链接:
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn flat dense round
@click="leftDrawerOpen = !leftDrawerOpen"
aria-label="Menu">
<q-icon name="menu" />
</q-btn>
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<q-drawer v-model="leftDrawerOpen"
bordered content-class="bg-grey-2">
<q-list>
<q-item-label header>Menu</q-item-label>
<q-item clickable tag="a" :to="{name: 'home'}">
<q-item-section avatar>
<q-icon name="home" />
</q-item-section>
<q-item-section>
<q-item-label>Home</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" :to="{name: 'about'}">
<q-item-section avatar>
<q-icon name="school" />
</q-item-section>
<q-item-section>
<q-item-label>About</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
export default {
name: "MyLayout",
data() {
return {
leftDrawerOpen: this.$q.platform.is.desktop
};
}
};
</script>
我们已经完成了一个简单的 SPA 在 Quasar 框架内运行的示例。
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev
- 启动开发模式
quasar build
- 构建 SPA
开发 PWA(渐进式 Web 应用程序)
开发 PWA,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> quasar mode add pwa
Quasar-CLI
将创建一个名为src-pwa
的文件夹,其中将包含我们的service-workers
文件,与我们的主要代码分开。
为了清理新添加的文件,并将其格式化为 Airbnb 格式,我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> eslint --fix --ext .js ./src-pwa
我们添加到 SPA 的代码仍将被用作我们的基础,以便我们可以添加新页面、组件和其他功能,这些功能也将用于 PWA。
那么,您是否想知道为什么service-worker
不在主src
文件夹中?这是因为这些文件专门用于 PWA,并且在除此之外的任何其他情况下都不需要。在不同的构建类型中也会发生相同的情况,例如 Electron,Cordova 和 SSR。
在 PWA 上配置 quasar.conf
对于 PWA 开发,您可以在root
文件夹中的quasar.conf.js
文件上设置一些特殊标志:
pwa: {
// workboxPluginMode: 'InjectManifest',
// workboxOptions: {},
manifest: {
// ...
},
// variables used to inject specific PWA
// meta tags (below are default values)
metaVariables: {
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'default',
appleTouchIcon120: 'statics/icons/apple-icon-120x120.png',
appleTouchIcon180: 'statics/icons/apple-icon-180x180.png',
appleTouchIcon152: 'statics/icons/apple-icon-152x152.png',
appleTouchIcon167: 'statics/icons/apple-icon-167x167.png',
appleSafariPinnedTab: 'statics/icons/safari-pinned-tab.svg',
msapplicationTileImage: 'statics/icons/ms-icon-144x144.png',
msapplicationTileColor: '#000000'
}
}
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev -m pwa
- 以 PWA 模式启动开发模式
quasar build -m pwa
- 将代码构建为 PWA
开发 SSR(服务器端渲染)
要开发 SSR,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar mode add ssr
Quasar-CLI
将创建一个名为src-ssr
的文件夹,其中包含我们的extension
和server
启动文件,与我们的主要代码分开。
extension
文件不会被babel
转译,并在 Node.js 上下文中运行,因此与 Express 或Nuxt.js
应用程序相同。您可以使用服务器插件,如database
,fileread
和filewrites
。
server
启动文件将是我们src-ssr
文件夹中的index.js
文件。与扩展名一样,它不会被babel
转译,并在 Node.js 上下文中运行。对于 HTTP 服务器,它使用 Express,并且如果您配置quasar.conf.js
以向客户端传递 PWA,则可以同时拥有 SSR 和 PWA。
在 SSR 上配置 quasar.conf
对于 SSR 开发,您可以在root
文件夹中的quasar.conf.js
文件上配置一些特殊标志:
ssr: {
pwa: true/false, // should a PWA take over (default: false), or just
// a SPA?
},
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev -m ssr
- 以 SSR 模式启动开发模式
quasar build -m ssr
- 将代码构建为 SSR
quasar serve
- 运行 HTTP 服务器(可用于生产)
开发移动应用程序(Cordova)
要开发 SSR,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar mode add cordova
现在Quasar-CLI
将询问您一些配置问题:
Cordova 应用程序 ID 是什么? (org.cordova.quasar.app)
Cordova 是否可以匿名报告使用统计数据以随时间改进工具? (Y/N) N
Quasar-CLI
将创建一个名为src-cordova
的文件夹,其中将包含一个 Cordova 项目。
Cordova 项目的文件夹结构如下:
src-cordova/
├── config.xml
├── packages.json
├── cordova-flag.d.ts
├── hooks/
├── www/
├── platforms/
├── plugins/
作为 Quasar 内部的一个独立项目,要添加 Cordova 插件,您需要在src-cordova
文件夹内调用plugman
或cordova plugin add
命令。
在 Cordova 上配置 quasar.conf
对于 Cordova 开发,您可以在root
文件夹中的quasar.conf.js
文件上设置一些特殊标志:
cordova: { iosStatusBarPadding: true/false, // add the dynamic top padding on
// iOS mobile devices backButtonExit: true/false // Quasar handles app exit on mobile phone
// back button },
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令来运行Quasar-CLI
命令:
如果您的桌面上尚未配置 Cordova 环境,您可以在此处找到有关如何设置的更多信息:quasar.dev/quasar-cli/developing-cordova-apps/preparation#Android-setup
。
quasar dev -m cordova -T android
– 以 Android 设备模拟器的形式启动开发模式
quasar build -m cordova -T android
– 作为 Android 构建代码
quasar dev -m cordova -T ios
– 以 iOS 设备模拟器的形式启动开发模式(仅限 macOS)
quasar build -m cordova -T ios
– 以 iOS 设备模拟器的形式启动构建模式(仅限 macOS)
开发桌面应用程序(Electron)
要开发 SSR,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar mode add electron
Quasar-CLI
将创建一个名为src-electron
的文件夹,其中将包含一个 Electron 项目。
Electron 项目的文件夹结构如下:
src-electron/
├── icons/
├── main-process/
├── electron-flag.d.ts
在icons
文件夹中,您将找到electron-packager
在构建项目时将使用的图标。在main-process
文件夹中将是您的主要 Electron 文件,分成两个文件:一个仅在开发时调用,另一个在开发和生产时调用。
在 Electron 上配置 quasar.conf
对于 Electron 开发,您可以在根文件夹的quasar.conf.js
文件上设置一些特殊标志:
electron: {
// optional; webpack config Object for
// the Main Process ONLY (/src-electron/main-process/)
extendWebpack (cfg) {
// directly change props of cfg;
// no need to return anything
},
// optional; EQUIVALENT to extendWebpack() but uses webpack-chain;
// for the Main Process ONLY (/src-electron/main-process/)
chainWebpack (chain) {
// chain is a webpack-chain instance
// of the Webpack configuration
},
bundler: 'packager', // or 'builder'
// electron-packager options
packager: {
//...
},
// electron-builder options
builder: {
//...
}
},
packager
键使用electron-packager
模块的 API 选项,builder
键使用electron-builder
模块的 API 选项。
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev -m electron
– 以 Electron 模式启动开发模式
quasar build -m electron
– 以 Electron 模式构建代码
它是如何工作的...
这一切都是可能的,因为 Quasar 框架在 CLI 上封装了构建、解析和捆绑。您不需要担心使用 Electron、Cordova 甚至 Babel 的 webpack 和配置。
一个简单的 CLI 命令可以为您生成全新的页面、布局、组件、存储、路由,甚至是一个新的构建。由于 CLI 只是 Vue、webpack、Babel 和其他工具的包装器,您不必只使用 Quasar 的可视组件。如果您不想使用它们,可以选择不导入它们,并利用 CLI 构建应用程序的功能。
另请参阅
您可以在quasar.dev/introduction-to-quasar
的文档中了解更多关于 Quasar 框架的信息
在quasar.dev/quasar-cli/developing-spa/introduction
上阅读有关 Quasar 的 SPA 开发的更多信息
在quasar.dev/quasar-cli/developing-pwa/introduction
上阅读有关 Quasar 的 PWA 开发的更多信息
在quasar.dev/quasar-cli/developing-ssr/introduction
上阅读有关 Quasar 的 SSR 开发的更多信息
在quasar.dev/quasar-cli/developing-cordova-apps/introduction
上阅读有关 Quasar 的移动应用开发的更多信息
在cordova.apache.org
上阅读有关 Cordova 项目的更多信息
在quasar.dev/quasar-cli/developing-electron-apps/introduction
上阅读有关 Quasar 的桌面应用程序开发的更多信息
在electronjs.org/
上阅读有关 Electron 项目的更多信息
在github.com/electron/electron-packager
上阅读有关electron-packager
的更多信息
在electron.github.io/electron-packager/master/interfaces/electronpackager.options.html
找到electron-packager
选项 API。
在www.electron.build/
了解更多关于electron-build
的信息。
在www.electron.build/configuration/configuration
找到electron-build
选项 API。
创建更智能的 Vue 观察者和计算属性
在 Vue 中,使用观察者和计算属性总是一个很好的解决方案,可以检查和缓存您的数据,但有时这些数据需要一些特殊处理,或者需要与预期不同的方式进行操作。有一些方法可以赋予这些 Vue API 新的生命,帮助您的开发和生产力。
如何做...
我们将把这个配方分为两类:一个是观察者,另一个是计算属性。有些方法通常一起使用,比如non-cached
计算和deep-watched
值。
观察者
选择这三个观察者配方是为了提高生产力和最终代码质量。使用这些方法可以减少代码重复,提高代码重用。
使用方法名
所有观察者都可以接收方法名而不是函数,从而避免编写重复的代码。这将帮助您避免重写相同的代码,或者检查值并将其传递给函数:
<script>
export default {
watch: {
myField: 'myFunction',
},
data: () => ({
myField: '',
}),
methods: {
myFunction() {
console.log('Watcher using method name.');
},
},
};
</script>
立即调用和深度监听
通过传递一个属性立即执行您的观察者,并通过调用deep
属性使其无论值的变化深度如何都执行:
<script>
export default {
watch: {
myDeepField: {
handler(newVal, oldVal) {
console.log('Using Immediate Call, and Deep Watch');
console.log('New Value', newVal);
console.log('Old Value', oldVal);
},
deep: true,
immediate: true,
},
},
data: () => ({
myDeepField: '',
}),
};
</script>
多个处理程序
您可以使您的观察者同时执行多个处理程序,而无需将观察处理程序设置为绑定到唯一的函数:
<script>
export default {
watch: {
myMultiField: [
'myFunction',
{
handler(newVal, oldVal) {
console.log('Using Immediate Call, and Deep Watch');
console.log('New Value', newVal);
console.log('Old Value', oldVal);
},
immediate: true,
},
],
},
data: () => ({
myMultiField: '',
}),
methods: {
myFunction() {
console.log('Watcher Using Method Name');
},
},
};
</script>
计算属性
有时计算属性只是作为简单的基于缓存的值使用,但它们有更大的潜力。以下是两种显示如何提取此功能的方法。
无缓存值
通过将cache
属性设置为false
,您可以使计算属性始终更新值,而不是缓存值:
<script>
export default {
computed: {
field: {
get() {
return Date.now();
},
cache: false,
},
},
};
</script>
获取器和设置器
您可以向计算属性添加一个 setter 函数,并使其成为一个完全完整的数据属性,但不绑定到数据。
不建议这样做,但是可能,在某些情况下,你可能需要这样做。一个例子是当你需要以毫秒保存日期,但需要以 ISO 格式显示它。使用这种方法,你可以让dateIso
属性get
和set
值:
<script>
export default {
data: () => ({
dateMs: '',
}),
computed: {
dateIso: {
get() {
return new Date(this.dateMs).toISOString();
},
set(v) {
this.dateMs = new Date(v).getTime();
},
},
},
};
</script>
另请参阅
您可以在vuejs.org/v2/api/#watch
找到有关 Vue watch
API 的更多信息。
您可以在vuejs.org/v2/api/#computed
找到有关 Vue computed
API 的更多信息。
使用 Python Flask 作为 API 创建 Nuxt.js SSR
Nuxt.js
是一个服务器端渲染框架,可以在服务器端渲染所有内容并将其加载。通过这个过程,页面获得了 SEO 的力量和在渲染之前快速的 API 获取。
正确使用它,您可以实现一个功能强大的 SPA 或 PWA,以前是不可能的。
在后端,Python 是一种解释性动态语言,速度快,稳定。具有活跃的用户群和快速的学习曲线,非常适合服务器 API。
将两者结合起来,可以尽快部署一个强大的应用程序。
准备工作
这个配方的先决条件如下:
Node.js 12+
Python
所需的 Node.js 全局对象如下:
create-nuxt-app
要安装create-nuxt-app
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g create-nuxt-app
对于这个配方的后端,我们将使用Python。这个配方所需的 Python 全局对象如下:
flask
flask-restful
flask-cors
要安装flask
,flask-restful
和flask-cors
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> **pip** install **flask**
> **pip install flask-restful**
> **pip install flask-cors**
如何做...
我们需要将我们的配方分成两部分。第一部分是后端部分(或者如果你喜欢的话是 API),将使用 Python 和 Flask 完成。第二部分将是前端部分,它将在 SSR 模式下运行Nuxt.js
。
创建您的 Flask API
我们的 API 服务器将基于 Python Flask 框架。我们需要创建一个服务器文件夹来存储我们的服务器文件并开始服务器的开发。
您将需要安装以下 Python 软件包。要这样做,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
- 要安装 Flask 框架,请使用以下命令:
> **pip** install flask
- 要安装 Flask RESTful 扩展,使用以下命令:
> **pip install flask-restful**
- 要安装 Flask CORS 扩展,使用以下命令:
> **pip install flask-cors**
初始化应用程序
为了创建我们的简单 RESTful API,我们将创建一个单一的文件,并使用 SQLite3 作为数据库:
- 创建一个名为
server
的文件夹,并在其中创建一个名为app.py
的文件:
import sqlite3 as sql
from flask import Flask
from flask_restful import Resource, Api, reqparse
from flask_cors import CORS
app = Flask(__name__)
api = Api(app)
CORS(app)
parser = reqparse.RequestParser()
conn = sql.connect('tasks.db')
conn.execute('CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY
KEY AUTOINCREMENT, task TEXT)')
conn.close()
- 然后,我们将创建我们的
ToDo
类,并在类的构造函数中连接到数据库并选择所有的tasks
:
class ToDo(Resource):
def get(self):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('SELECT * from tasks')
tasks = cur.fetchall()
con.close()
return {
'tasks': tasks
}
- 要实现 RESTful POST 方法,创建一个函数来接收
task
作为参数,并将带有添加的task
、添加的status
的对象添加到任务列表中,然后将列表返回给用户:
def post(self):
parser.add_argument('task', type=str)
args = parser.parse_args()
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('INSERT INTO tasks(task) values ("
{}")'.format(args['task']))
con.commit()
con.close()
return {
'status': True,
'task': '{} added.'.format(args['task'])
}
- 接下来,我们将创建 RESTful PUT 方法,通过创建一个函数来接收
task
和id
作为函数的参数。然后,这个函数将使用当前的id
更新task
,并将更新后的task
和更新的status
返回给用户:
def put(self, id):
parser.add_argument('task', type=str)
args = parser.parse_args()
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('UPDATE tasks set task = "{}" WHERE id =
{}'.format(args['task'], id))
con.commit()
con.close()
return {
'id': id,
'status': True,
'task': 'The task {} was updated.'.format(id)
}
- 然后,创建一个 RESTful DELETE 方法,通过创建一个函数来接收将被移除的
task
的ID
,然后将返回被移除的ID
、status
和task
给用户:
def delete(self, id):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('DELETE FROM tasks WHERE id = {}'.format(id))
con.commit()
con.close()
return {
'id': id,
'status': True,
'task': 'The task {} was deleted.'.format(id)
}
- 最后,我们将在
'/'
路由上将ToDo
类作为 API 的资源,并初始化应用程序:
api.add_resource(ToDo, '/', '/<int:id>')
if __name__ == '__main__':
app.run(debug=True)
启动服务器
要启动服务器,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> python server/app.py
您的服务器将在http://localhost:5000
上运行并监听。
创建您的 Nuxt.js 服务器
要渲染您的应用程序,您需要创建您的Nuxt.js
应用程序。使用Nuxt.js
的create-nuxt-app
CLI,我们将创建它并为其选择一些选项。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> create-nuxt-app client
然后,您将被问及安装过程的一些问题。我们将使用以下内容:
- 当您使用
Nuxt-CLI
开始创建项目时,它将首先要求项目名称。在我们的情况下,我们将选择client
作为名称:
**Project Name:** **client**
- 然后,您需要选择将在项目中使用的编程语言。我们将选择
JavaScript
:
> Programming language: (Use arrow keys)
❯ JavaScript
TypeScript
- 接下来,
Nuxt-CLI
将要求选择将用于安装依赖项的软件包管理器。在我们的情况下,我们选择Yarn
,但您可以选择您喜欢的软件包管理器:
> Package manager: (Use arrow keys)
❯ Yarn
npm
- 现在,
Nuxt-CLI
将要求选择在项目中使用的 UI 框架。从可用列表中选择Bulma
:
**> UI Framework:** **Bulma**
- 然后,
Nuxt-CLI
将询问您是否要为项目选择额外的模块。我们将从当前模块列表中选择 Axios
:
**> Nuxt.JS modules:** **Axios**
Nuxt-CLI
将询问我们想要在项目上使用的 linting 工具;我们将选择 None
:
**> Choose Linting tools:** **None**
- 然后,
Nuxt-CLI
将询问我们想要在项目上实现的测试框架;我们将选择 None
:
**> Choose Test Framework: None**
- 接下来,
Nuxt-CLI
将询问项目将使用的渲染模式;我们将选择 Universal (SSR)
:
**> Choose Rendering Mode: Universal (SSR)**
Nuxt-CLI
将询问将在构建结构上使用的部署目标;我们将选择 Server (Node.js hosting)
:
> Deployment target: Server (Node.js hosting)
- 最后,
Nuxt-CLI
将询问我们想要使用的开发工具配置;我们将选择 jsconfig.json
:
> Development tools: jsconfig.json
在 CLI 完成安装过程后,我们可以在编辑器或 IDE 中打开 client
文件夹。
将 Bulma 添加到全局 CSS
要将 Bulma 添加到应用程序中,我们需要在 nuxt
配置文件中声明它,方法如下:
在 client
文件夹中打开 nuxt.config.js
。
然后,更新 CSS 属性并添加 Bulma 导入,以使其在应用程序的全局范围内可用:
export default {
/* We need to change only the css property for now, */
/* the rest we will maitain the same */
/*
** Global CSS
*/
css: ['bulma/css/bulma.css'],
}
配置 axios 插件
要开始创建我们的 API 调用,我们需要在应用程序中添加 axios
插件:
- 为此,我们需要打开根文件夹中的
nuxt.config.js
文件,并添加 axios
属性:
export default {
/* We need to change only the axios property for now, */
/* the rest we will maitain the same */
axios: {},
}
- 在
axios
属性上,添加以下配置属性:
HOST
并将其定义为 '127.0.0.1'
PORT
并将其定义为 '5000'
https
并将其定义为 false
debug
并将其定义为 true
:
axios: {
HOST: '127.0.0.1',
PORT: '5000',
https: false,
debug: true, // Only on development
},
运行 Nuxt.js 服务器
现在您已经设置好了一切,想要运行服务器并开始查看发生了什么。Nuxt.js
自带一些预先编程的 npm
脚本。您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行其中之一:
npm run dev
- 以开发模式运行服务器
npm run build
- 使用 webpack 构建文件并对生产环境进行 CSS 和 JS 的最小化
npm run generate
- 为每个路由生成静态 HTML 页面
npm start
- 在运行构建命令后,启动生产服务器
创建 TodoList 组件
对于 TodoList 应用程序,我们将需要一个组件来获取任务并删除任务。
单文件组件的 <script>
部分
在这里,我们将创建单文件组件的 <script>
部分:
在client/components
文件夹中,创建一个名为TodoList.vue
的文件并打开它。
然后,我们将导出一个default
的 JavaScript 对象,其中name
属性定义为TodoList
,然后将beforeMount
生命周期钩子定义为一个异步函数。将computed
和methods
属性定义为一个空的 JavaScript 对象。然后,创建一个data
属性,定义为返回一个 JavaScript 对象的单例函数。在data
属性中,创建一个taskList
属性,定义为一个空数组:
export default {
name: 'TodoList',
data: () => ({
taskList: [],
}),
computed: {},
async beforeMount() {},
methods: {},
};
- 在
computed
属性中,创建一个名为taskObject
的新属性。这个computed
属性将返回Object.fromEntries(new Map(this.taskList))
的结果:
taskObject() {
return Object.fromEntries(new Map(this.taskList));
},
- 在
methods
属性中,创建一个名为getTask
的新方法 - 它将是一个异步函数。这个方法将从服务器获取任务,然后将使用响应来定义taskList
属性:
async getTasks() {
try {
const { tasks } = await
this.$axios.$get('http://localhost:5000');
this.taskList = tasks;
} catch (err) {
console.error(err);
}
},
- 然后,创建一个
deleteTask
方法。这个方法将是一个异步函数,并将接收一个id
作为参数。使用这个参数,它将执行一个 API 执行来删除任务,然后执行getTask
方法:
async deleteTask(i) {
try {
const { status } = await
this.$axios.$delete(`http://localhost:5000/${i}`);
if (status) {
await this.getTasks();
}
} catch (err) {
console.error(err);
}
},
- 最后,在
beforeMount
生命周期钩子中,我们将执行getTask
方法:
async beforeMount() {
await this.getTasks();
},
单文件组件部分
现在是创建单文件组件的<template>
部分的时候了:
在client/components
文件夹中,打开TodoList.vue
文件。
在<template>
部分,创建一个div
HTML 元素,并添加值为box
的class
属性:
<div class="box"></div>
- 作为
div.box
HTML 元素的子元素,创建一个div
HTML 元素,class
属性定义为content
,具有一个子元素定义为ol
HTML 元素,属性type
定义为1
:
<div class="content">
<ol type="1"></ol>
</div>
- 作为
ol
HTML 元素的子元素,创建一个li
HTML 元素,其中v-for
指令定义为(task, i) in taskObject
,key
属性定义为一个变量i
:
<li
v-for="(task, i) in taskObject"
:key="i">
</li>
- 最后,作为
ol
HTML 元素的子元素,将{{ task }}
添加为内部文本,并作为文本的兄弟元素,创建一个button
HTML 元素,class
属性定义为delete is-small
,@click
事件监听器定义为deleteTask
方法,传递i
变量作为参数:
{{ task }}
<button
class="delete is-small"
@click="deleteTask(i)"
/>
创建 Todo 表单组件
将任务发送到服务器,我们需要一个表单。这意味着我们需要创建一个表单组件来处理这个任务。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在client/components
文件夹中,创建一个名为TodoForm.vue
的文件并打开它。
然后,我们将导出一个default
JavaScript 对象,其中name
属性定义为TodoForm
,然后将methods
属性定义为空的 JavaScript 对象。然后,创建一个data
属性,定义为返回 JavaScript 对象的单例函数。在data
属性中,创建一个task
属性作为空数组:
export default {
name: 'TodoForm',
data: () => ({
task: '',
}),
methods: {},
};
- 在
methods
属性中,创建一个名为save
的方法,这将是一个异步函数。这个方法将task
发送到 API,如果 API 接收到Ok 状态
,它将发出一个带有task
的'new-task'
事件,并清除task
属性:
async save() {
try {
const { status } = await
this.$axios.$post('http://localhost:5000/', {
task: this.task,
});
if (status) {
this.$emit('new-task', this.task);
this.task = '';
}
} catch (err) {
console.error(err);
}
},
单文件组件部分
现在是时候创建单文件组件的<template>
部分了:
在client/components
文件夹中,打开名为TodoForm.vue
的文件。
在<template>
部分中,创建一个div
HTML 元素,并添加class
属性,值为box
:
<div class="box"></div>
- 在
div.box
HTML 元素内部,创建一个div
HTML 元素,class
属性定义为field has-addons
:
<div class="field has-addons"></div>
- 然后,在
div.field.has-addons
HTML 元素内部,创建一个子div
HTML 元素,class
属性定义为control is-expanded
,并添加一个子输入 HTML 元素,v-model
指令定义为task
属性。然后,将class
属性定义为input
,type
属性定义为text
,placeholder
定义为ToDo Task
。最后,在@keypress.enter
事件监听器中,定义save
方法:
<div class="control is-expanded">
<input
v-model="task"
class="input"
type="text"
placeholder="ToDo Task"
@keypress.enter="save"
>
</div>
- 最后,在
div.control.is-expanded
HTML 元素的同级位置,创建一个div
HTML 元素,class
属性定义为control
,并添加一个子a
HTML 元素,class
属性定义为button is-info
,在@click
事件监听器中,将其定义为save
方法。在a
HTML 元素的内部文本中,添加Save Task
文本:
<div class="control">
<a
class="button is-info"
@click="save"
>
Save Task
</a>
</div>
创建布局
现在我们需要创建一个新的布局来包装应用程序作为一个简单的高阶组件。在client/layouts
文件夹中,打开名为default.vue
的文件,删除文件的<style>
部分,并将<template>
部分更改为以下内容:
<template>
<nuxt />
</template>
创建页面
现在我们将创建我们应用程序的主页面,用户将能够查看他们的TodoList
并添加一个新的TodoItem
。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在client/pages
文件夹中打开index.vue
文件。
导入我们创建的todo-form
和todo-list
组件,然后我们将导出一个带有components
属性的default
JavaScript 对象,其中包含导入的组件。
<script>
import TodoForm from '../components/TodoForm.vue';
import TodoList from '../components/TodoList.vue';
export default {
components: { TodoForm, TodoList },
};
</script>
单文件组件部分
现在是创建单文件组件的<template>
部分的时候了:
在client/pages
文件夹中,打开index.vue
文件。
在<template>
部分,创建一个div
HTML 元素,作为子元素添加一个class
属性定义为hero is-primary
的section
HTML 元素。然后,作为section
HTML 元素的子元素,创建一个class
属性定义为hero-body
的div
HTML 元素。作为div.hero-body
HTML 元素的子元素,创建一个class
属性定义为container
的div
HTML 元素,并作为子元素添加一个class
定义为title
的h1
HTML 元素,其中内部文本为Todo App
。
<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<h1 class="title">
Todo App
</h1>
</div>
</div> </section>
- 作为
section.hero.is-primary
HTML 元素的兄弟元素,创建一个section
HTML 元素,其中class
属性定义为section
,style
属性定义为padding: 1rem
。作为子元素添加一个class
属性定义为container
的div
HTML 元素,其中包含一个ref
属性定义为list
的todo-list
组件。
<section
class="section"
style="padding: 1rem" >
<div class="container">
<todo-list
ref="list"
/>
</div> </section>
- 最后,作为
section.section
HTML 元素的兄弟元素,创建一个section
HTML 元素,其中class
属性定义为section
,style
属性定义为padding: 1rem
。作为子元素添加一个class
属性定义为container
的div
HTML 元素,其中包含一个todo-form
组件,其中@new-task
事件监听器定义为$refs.list.getTasks()
。
<section
class="section"
style="padding: 1rem" >
<div class="container">
<todo-form
@new-task="$refs.list.getTasks()" />
</div> </section>
工作原理...
此示例显示了通过 Python 的本地 API 服务器与通过Nuxt.js
提供的 SSR 平台之间的集成。
当您首先启动 Python 服务器时,您正在打开端口以接收来自客户端的数据作为被动客户端,只是等待某些事件发生以启动您的代码。通过相同的过程,Nuxt.js
SSR 可以在幕后执行很多操作,但完成后会变得空闲,等待用户操作。
当用户与前端交互时,应用程序可以向服务器发送一些请求,服务器将把数据返回给用户,以在屏幕上显示。
另请参阅
您可以在palletsprojects.com/p/flask/.
了解有关 Flask 和 Python 内部的 HTTP 项目的更多信息。
如果您想了解更多关于Nuxt.js
的信息,可以在nuxtjs.org/guide/.
阅读文档。
如果您想了解Nuxt.js
对 Axios 的实现以及如何配置它和使用插件,可以在axios.nuxtjs.org/options.
阅读文档。
如果您想了解在本教程中使用的 CSS 框架 Bulma,可以在bulma.io.
找到更多信息。
Vue 应用程序的 Dos 和 Don'ts
安全性始终是每个人都担心的事情,对于技术也是如此。您需要时刻保持警惕。在本节中,我们将看看如何使用一些技术和简单的解决方案来防止攻击。
检查器
在使用 ESLint 时,请确保已启用 Vue 插件,并且遵循强烈推荐的规则。这些规则将帮助您进行开发,检查一些常见的错误,这些错误可能会打开攻击的大门,比如v-html
指令。
在Vue-CLI
项目中,选择了检查器选项后,将会创建一个名为.eslintrc.js
的文件,以及项目文件。在这个文件中,一组基本规则将被预先确定。以下是一个ESLint + AirBnb
项目的一组良好实践规则的示例:
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'plugin:vue/recommended',
'plugin:vue/strongly-recommended',
'@vue/airbnb',
],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
};
现在,如果您有任何违反检查规则的代码,它将不会在开发或构建中被解析。
JavaScript
JavaScript 具有一些漏洞,可以通过遵循一些简单的清单和简单的实现来预防。这些实现可以是客户端-服务器通信或 DOM 操作,但您始终需要小心不要忘记它们。
以下是一些使用 JavaScript 的技巧:
尽可能使用经过身份验证和加密的 API。请记住,JWT 本身并不是加密的;您需要添加加密层(JWE)才能拥有完整的 JSON。
如果您想存储 API 令牌,请始终使用SessionStorage
。
在将用户输入的 HTML 发送到服务器之前,始终对其进行消毒。
在将 HTML 呈现到 DOM 之前,始终对其进行消毒。
永远要对用户输入的RegeExp
进行转义;它将被执行,以防止任何 CPU 线程攻击。
始终捕获错误,并且不要向用户显示任何堆栈跟踪,以防止任何代码操纵。
以下是在使用 JavaScript 时不要做的一些提示:
永远不要使用eval()
;它会使您的代码运行缓慢,并为恶意代码在您的代码内执行打开一扇门。
永远不要呈现来自用户的任何输入而没有经过消毒或转义的数据。
永远不要在 DOM 上呈现任何未经过消毒的 HTML。
永远不要将 API 令牌存储在LocalStorage
中。
永远不要在 JWT 对象中存储敏感数据。
Vue
在开发 Vue 应用程序时,您需要检查一些基本规则,这些规则可以帮助开发,并且不会为外部操纵您的应用程序打开任何大门。
以下是一些使用 Vue 的提示:
始终为您的 props 添加类型验证,并在可能的情况下进行验证检查。
避免全局注册组件;使用本地组件。
尽可能使用延迟加载的组件。
使用$refs
而不是直接 DOM 操作。
以下是在使用 Vue 时不要做的一些提示:
永远不要在窗口或任何全局范围上存储Vue
,$vm
,$store
或任何应用程序变量。
永远不要修改 Vue 原型;如果需要向原型添加新变量,请创建一个新的 Vue 插件。
不建议直接在组件之间建立连接,因为这将使组件绑定到父级或子级。
另请参阅
您可以在 OWASP CheatCheat 的github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md
和html5sec.org/
找到有关 XSS(跨站点脚本)的更多信息。
在eslint.vuejs.org/
找到有关eslint-vue-plugin
的更多信息。
您可以在github.com/i0natan/nodebestpractices#6-security-best-practices
了解有关 Node.js 安全最佳实践的更多信息。
在quasar.dev/security/dos-and-donts
找到有关 Vue 应用程序的 dos 和 don'ts 的更多信息。
</style>
标签:vue,秘籍,创建,Vue,Vue3,组件,我们,属性
From: https://www.cnblogs.com/apachecn/p/18195740
<template>
结构,我们将使用 Vue 对象的template
属性,我们可以将字符串或模板字符串作为值传递,这将由 Vue 脚本插值并呈现在屏幕上:使用“创建基本文件”部分的基本示例,创建一个名为template.html
的新文件并打开它。
在空的<script>
HTML 元素中,通过对象解构Vue
全局常量,创建常量defineComponent
和createApp
:
const {
defineComponent, createApp, } = Vue;
component
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,其中有三个属性:data
、methods
和template
:const component = defineComponent({
data: () => ({}),
methods: {},
template: `` });
data
属性中,将其定义为一个单例函数,返回一个 JavaScript 对象,其中有一个名为count
的属性,并且默认值为0
:data: () => ({
count: 0 }),
methods
属性中,创建一个名为addOne
的属性,这是一个函数,将通过1
增加count
的值:methods: {
addOne() {
this.count += 1;
}, },
template
属性中,在模板字符串中,创建一个带有标题的h1
HTML 元素。然后,作为兄弟元素,创建一个带有绑定到click
事件的事件监听器的button
HTML 元素,当执行时触发addOne
函数:template: `
<h1> This is a Vue 3 Root Element! </h1>
<button @click="addOne"> Pressed {{ count }} times. </button> `
createApp
函数,将component
常量作为参数传递。然后,原型链连接mount
函数,并将div
HTML 元素的id
属性("#app")
作为函数的参数:createApp(component)
.mount('#app');
<template>
结构,我们将使用 Vue 对象的template
属性,我们可以将字符串或模板字符串作为值传递,Vue 脚本将对其进行插值处理并在屏幕上呈现:使用“创建基本文件”部分的基本示例,创建一个名为render.html
的新文件并打开它。
在空的<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,从Vue
全局常量中调用defineComponent
、h
和createApp
方法:
const {
defineComponent,
h, createApp, } = Vue;
component
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,该对象有三个属性:data
、methods
和render
:const component = defineComponent({
data: () => ({}),
methods: {},
render() {}, });
data
属性中,将其定义为一个单例函数,返回一个具有名为count
且默认值为0
的 JavaScript 对象:data: () => ({
count: 0 }),
methods
属性中,创建一个名为addOne
的属性,它是一个函数,将count
的值增加1
:methods: {
addOne() {
this.count += 1;
}, },
render
属性中,执行以下步骤:创建一个名为h1
的常量,并将其定义为h
函数,将'h1'
作为第一个参数传递,将要使用的标题作为第二个参数。
创建一个名为button
的常量,它将是h
函数,将"button"
作为第一个参数传递,将一个具有onClick
属性且值为this.addOne
的 JavaScript 对象作为第二个参数传递,将button
的内容作为第三个参数。
返回一个数组,第一个值为h1
常量,第二个值为button
常量:
render() {
const h1 = h('h1', 'This is a Vue 3 Root Element!');
const button = h('button', {
onClick: this.addOne,
}, `Pressed ${this.count} times.`); return [
h1,
button,
]; },
createApp
函数,将component
常量作为参数传递,原型链连接mount
函数,并将div
HTML 元素的id
属性("#app")
作为函数的参数:createApp(component)
.mount('#app');
defineComponent
执行,并且作为参数传递的 JavaScript 对象几乎保持与 Vue 2 中的旧结构相同。在示例中,我们使用了相同的属性data
、render
、methods
和template
,这些属性都存在于 Vue 2 中。<template>
结构的示例中,我们不必创建包装元素来封装应用程序组件的内容,并且可以直接在组件上有两个根元素。render
函数示例中,发生了相同的行为,但最终示例使用了新的暴露的h
API,它不再是render
函数的参数。在按钮创建中出现了一个重大变化;我们必须在数据 JavaScript 对象内部使用onClick
属性,而不是on
属性和click
方法。这是因为 Vue 3 的 VNode 的新数据结构。input
HTML 元素。使用创建基本文件部分的基本示例,创建一个名为component.html
的新文件并打开它。
在空的<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,调用Vue
全局常量的defineComponent
和createApp
方法:
const {
defineComponent, createApp, } = Vue;
nameInput
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,具有四个属性:name
、props
、template
和inheritAttrs
。然后,我们将inheritAttrs
的值定义为false
:const nameInput = defineComponent({
name: 'NameInput',
props: {},
inheritAttrs: false,
template: `` });
props
属性中,添加一个名为modelValue
的属性,并将其定义为String
:props: {
modelValue: String, },
创建一个label
HTML 元素,并将一个input
HTML 元素作为子元素添加。
在input
HTML 元素中,将v-bind
指令定义为一个 JavaScript 对象,其中包含this.$attrs
的解构值。
将变量属性value
定义为接收到的 prop 的modelValue
。
将input
属性type
设置为"text"
。
将匿名函数添加到change
事件监听器中,该函数接收一个event
作为参数,然后发出一个名为"update:modeValue"
的事件,载荷为event.target.value
:
template: ` <label>
<input
v-bind="{ ...$attrs, }"
:value="modelValue" type="text" @change="(event) => $emit('update:modelValue',
event.target.value)"
/> </label>`
appComponent
的常量,定义为defineComponent
方法,传递一个 JavaScript 对象作为参数,其中包含两个属性,data
和template
:const component = defineComponent({
data: () => ({}),
template: ``, });
data
属性中,将其定义为一个单例函数,返回一个具有名为name
的属性的 JavaScript 对象,其默认值为''
:data: () => ({
name: '' }),
创建一个NameInput
组件,其中v-model
指令绑定到name
数据属性。
创建一个带有值"border:0; border-bottom: 2px solid red;"
的style
属性。
创建一个带有值"name-input"
的data-test
属性:
template: ` <name-input
v-model="name" style="border:0; border-bottom: 2px solid red;"
data-test="name-input" />`
app
的常量,并将其定义为createApp
函数,将component
常量作为参数传递。然后,调用app.component
函数,将要注册的组件的名称作为第一个参数传递,组件作为第二个参数传递。最后,调用app.mount
函数,将"#app"
作为参数传递:const app = createApp(component); app.component('NameInput', nameInput); app.mount('#app');
defineComponent
函数,传递一个 JavaScript 对象作为参数。这个对象保持了几乎与 Vue 2 相同的组件声明结构。在示例中,我们使用了相同的属性,data
,methods
,props
和template
,这些属性都存在于 V2 中。inheritAttrs
属性来阻止将属性自动应用于组件上的所有元素,仅将其应用于具有v-bind
指令和解构this.$attrs
对象的元素。createApp
API 创建应用程序,然后执行app.component
函数在应用程序上全局注册组件,然后渲染我们的应用程序。reactivity
API。reactivity
和watch
API 创建一个简单的 JavaScript 动画。reactivity
API 创建一个应用程序,以在屏幕上呈现动画:使用“创建基本文件”部分中的基本示例,创建一个名为reactivity.html
的新文件并打开它。
在<head>
标签中,添加一个新的<meta>
标签,属性为chartset
定义为"utf-8"
:
<meta charset="utf-8"/>
<body>
标签中,删除div#app
HTML 元素,并创建一个div
HTML 元素,id
定义为marathon
,style
属性定义为"font-size: 50px;"
:<div
id="marathon"
style="font-size: 50px;" > </div>
<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,调用Vue
全局常量中的reactivity
和watch
方法:const {
reactive,
watch, } = Vue;
mod
的常量,定义为一个函数,接收两个参数a
和b
。然后返回一个算术运算,a
模b
:const mod = (a, b) => (a % b);
maxRoadLength
的常量,其值为50
。然后,创建一个名为competitor
的常量,其值为reactivity
函数,传递一个 JavaScript 对象作为参数,其中position
属性定义为0
,speed
定义为1
:const maxRoadLength = 50; const competitor = reactive({
position: 0,
speed: 1, });
watch
函数,传递一个匿名函数作为参数。在函数内部,执行以下操作:创建一个名为street
的常量,并将其定义为一个大小为maxRoadLength
的Array
,并用*'_'*
*.*填充它。
创建一个名为marathonEl
的常量,并将其定义为 HTML DOM 节点#marathon
。
选择数组索引中competitor.position
的street
元素,并将其定义为*"![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/c8b07311-36a4-4df3-98fd-3b68200deed3.png)"*
,如果competitor.position
是偶数,或者如果数字是奇数,则定义为*"![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/562ed724-a630-4193-a9c6-4e143a9690e2.png)"*
。
将marathonEl.innertHTML
定义为*""*
和street.reverse().join('')
:
watch(() => {
const street = Array(maxRoadLength).fill('_');
const marathonEl = document.getElementById('marathon');
street[competitor.position] = (competitor.position % 2 === 1)
? '![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/c8b07311-36a4-4df3-98fd-3b68200deed3.png)'
: '![](https://gitee.com/OpenDocCN/freelearn-vue-zh/raw/master/docs/vue3-cb/img/562ed724-a630-4193-a9c6-4e143a9690e2.png)'; marathonEl.innerHTML = '';
marathonEl.innerHTML = street.reverse().join(''); });
setInterval
函数,将一个匿名函数作为参数传递。在函数内部,将competitor.position
定义为mod
函数,将competitor.position
加上competitor.speed
作为第一个参数,将maxRoadLength
作为第二个参数:setInterval(() => {
competitor.position = mod(competitor.position +competitor.speed,
maxRoadLength) }, 100);
reactive
和watch
API,我们能够创建一个具有 Vue 框架中的响应性的应用程序,但不使用 Vue 应用程序。competitor
,它的工作方式与 Vue 的data
属性相同。然后,我们创建了一个watch
函数,它的工作方式与watch
属性相同,但是作为匿名函数使用。在watch
函数中,我们为竞争者开辟了一条跑道,并创建了一个简单的动画,使用两个不同的表情符号,根据在道路上的位置进行更改,以模拟屏幕上的动画。setInterval
函数,每 100 毫秒改变竞争者在道路上的位置:使用“创建基本文件”部分的基本示例,创建一个名为component.html
的新文件并打开它。
在空的<script>
HTML 元素中,使用对象解构方法创建将要使用的函数的常量,从Vue
全局常量中调用createApp
、defineComponent
、setup
、ref
、onMounted
和onUnmounted
方法:
const {
createApp,
defineComponent,
setup,
ref,
onMounted,
onUnmounted, } = Vue;
fetchLocation
函数,在其中创建一个名为watcher
的let
变量。然后,创建一个名为geoLocation
的常量,并将其定义为navigator.geolocation
。接下来,创建一个名为gpsTime
的常量,并将其定义为ref
函数,将Date.now()
函数作为参数传递。最后,创建一个名为coordinates
的常量,并将其定义为ref
函数,将一个 JavaScript 对象作为参数传递,其中的属性accuracy
、latitude
、longitude
、altitude
、altitudeAccuracy
、heading
和speed
都定义为0
:function fetchLocation() {
let watcher;
const geoLocation = navigator.geolocation;
const gpsTime = ref(Date.now());
const coordinates = ref({
accuracy: 0,
latitude: 0,
longitude: 0,
altitude: 0,
altitudeAccuracy: 0,
heading: 0,
speed: 0,
}); }
fetchLocation
函数内部,在常量创建之后,创建一个名为setPosition
的函数,带有一个名为payload
的参数。在函数内部,将gpsTime.value
定义为payload.timestamp
参数,将coordinates.value
定义为payload.coords
参数:function setPosition(payload) {
gpsTime.value = payload.timestamp
coordinates.value = payload.coords
}
setPosition
函数之后,调用onMounted
函数,将一个匿名函数作为参数传递。在函数内部,检查浏览器是否可用geoLocation
API,并将watcher
定义为geoLocation.watchPostion
函数,将setPosition
函数作为参数传递:onMounted(() => {
if (geoLocation) watcher = geoLocation.watchPosition(setPosition); });
onMounted
函数后,创建一个onUnmounted
函数,将一个匿名函数作为参数传递。在函数内部,检查watcher
是否已定义,然后执行geoLocation.clearWatch
函数,将watcher
作为参数传递:onUnmounted(() => {
if (watcher) geoLocation.clearWatch(watcher); });
fetchLocation
函数中,返回一个 JavaScript 对象,并将coordinates
和gpsTime
常量作为属性/值传递:return {
coordinates,
gpsTime, };
appComponent
的常量,并将其定义为defineComponent
函数,将一个具有setup
和template
属性的 JavaScript 对象作为参数传递:const appComponent = defineComponent({
setup() {},
template: `` });
setup
函数中,创建一个常量,这是一个对象解构,包括fetchLocation
函数的coordinates
和gpsTime
属性:setup() {
const {
coordinates,
gpsTime,
} = fetchLocation(); }
setup
函数内部,创建另一个名为formatOptions
的常量,并将其定义为一个具有year
、month
、day
、hour
和minute
属性的 JavaScript 对象,其值均为'numeric'
。然后,将属性hour12
定义为true
:const formatOptions = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
};
formatOptions
常量之后,创建一个名为formatDate
的常量,并将其定义为一个函数,该函数接收一个名为date
的参数。然后,返回一个新的Intl.DateTimeFormat
函数,将navigator.language
作为第一个参数,将formatOption
常量作为第二个参数。然后,原型链连接format
函数,传递date
参数:const formatDate = (date) => (new
Intl.DateTimeFormat(navigator.language,
formatOptions).format(date));
setup
函数的末尾,返回一个 JavaScript 对象,其属性定义为coordinates
、gpsTime
和formatDate
常量:return {
coordinates,
gpsTime,
formatDate };
template
属性中,进行以下操作:创建一个带有文本“我的地理位置在{{ formatDate(new Date(gpsTime) }}”的h1
HTML 元素。
创建一个ul
HTML 元素,并添加三个li
HTML 元素作为子元素。
在第一个子元素中,添加文本“纬度:{{ coordinates.latitude }}”。
在第二个子元素中,添加文本“经度:{{ coordinates.longitude }}”。
在第三个子元素中,添加文本“海拔:{{ coordinates.altitude }}”:
template: `
<h1>My Geo Position at {{formatDate(new
Date(gpsTime))}}</h1>
<ul>
<li>Latitude: {{ coordinates.latitude }}</li>
<li>Longitude: {{ coordinates.longitude }}</li>
<li>Altitude: {{ coordinates.altitude }}</li>
</ul> `
createApp
函数,传递appComponent
常量作为参数。然后,原型链连接mount
函数,并将div
HTML 元素的id
属性("#app")
作为函数的参数:createApp(appComponent)
.mount('#app');
createApp
、defineComponent
、setup
、ref
、onMounted
和onUnmounted
- 作为常量,我们将使用它们来创建组件。然后,我们创建了fetchLocation
函数,它负责获取用户的地理位置数据,并将其作为响应式数据返回,当用户更改位置时可以自动更新。navigator.geolocation
API,它能够获取用户当前的 GPS 位置。利用浏览器提供的数据,我们能够用它来定义由 Vue ref
API 创建的变量。setup
函数创建了组件,因此渲染知道我们正在使用新的组合 API 作为组件创建方法。在setup
函数内部,我们导入了fetchLocation
函数的动态变量,并创建了一个方法,用于在模板上使用日期格式化。createApp
公开的 API 创建了应用程序,并挂载了 Vue 应用程序。developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation
找到有关Navigator.geolocation
的更多信息。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。创建一个 TypeScript 项目
理解 TypeScript
创建你的第一个 TypeScript 类
使用 Vue CLI 创建你的第一个项目
使用 Vue UI 向 Vue CLI 项目添加插件
将 TypeScript 添加到 Vue CLI 项目中
使用vue-class-component
创建你的第一个 TypeScript Vue 组件
使用vue-class-component
创建自定义 mixin
使用vue-class-component
创建自定义函数装饰器
将自定义钩子添加到vue-class-component
将vue-property-decorator
添加到vue-class-component
windows-build-tools
的 npm 包,以便安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
。> npm install -g @vue/cli @vue/cli-service-global
> npm install -g typescript
npm
项目。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:> npm init -y
> npm install typescript --only=dev
.ts
文件并进行编译:> tsc --init
tsconfig.json
文件。这是一个编译器设置文件。在这里,您可以定义目标,开发中可用的 JavaScript 库,目标 ECMAScript 版本,模块生成等等。tsconfig.json
文件的compilerOption
属性中添加文档对象模型(DOM)库,这样在开发时就可以访问 window 和 document 对象。index.ts
文件。让我们在index.ts
文件中创建一些简单的代码,以便在终端中记录一个数学计算:function sum(a: number, b: number): number {
return a + b;
}
const firstNumber: number = 10;
const secondNumber: number = 20;
console.log(sum(firstNumber, secondNumber));
a
和b
,它们的类型都设置为number
,并且函数预计返回一个number
。我们创建了两个变量,firstNumber
和secondNumber
,在这种情况下都设置为number
类型——分别是10
和20
,因此,将它们传递给函数是有效的。如果我们将它们设置为其他类型,比如字符串、布尔值、浮点数或数组,编译器会在变量和函数执行的静态类型检查方面抛出错误。> tsc ./index.ts
index.js
中看到最终文件。如果我们查看文件内部,最终代码将类似于这样:function sum(a, b) {
return a + b;
}
var firstNumber = 10;
var secondNumber = 20;
console.log(sum(firstNumber, secondNumber));
tsconfig.json
文件中定义的配置。tsconfig.json
的文件会在我们的文件夹中创建。这个文件协调了编译器和开发过程中的静态类型检查的所有规则。所有的开发都基于这个文件中定义的规则。每个环境都依赖于需要导入的特定规则和库。www.typescriptlang.org/docs/home.html
找到有关 TypeScript 的更多信息。www.typescriptlang.org/docs/handbook/migrating-from-javascript.html
。www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
找到一个关于 TypeScript 的 5 分钟课程。npm
项目。打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:> npm init -y
> npm install typescript --only=dev
字符串
数字
布尔
数组
元组
枚举
任意
空
对象
(")
或单引号(')
括起来,或者用反引号(
)`,通常称为模板字符串。const myText: string = 'My Simple Text';
const myTextAgain: string = "My Simple Text";
const greeting: string = `Welcome back ${myName}!`;
const myAge: number = 31;
const hexNumber: number = 0xf010d;
const binaryNumber: number = 0b1011;
const octalNumber: number = 0o744;
const isTaskDone: boolean = false;
const isGreaterThen: boolean = 10 > 5;
[]
(方括号)来表示它是一个声明类型的数组:const primeNumbers: number[] = [1, 3, 5, 7, 11];
Array<type>
声明进行通用声明。这不是最常用的方式,但根据您正在开发的代码,您可能需要使用它:const switchInstructions: Array<boolean> = [true, false, false, true];
let person: [string, number];
person = ['Heitor', 31];
console.log(`My name is ${person[0]} and I am ${person[1]} years old`);
0
的初始值开始,并以最终索引号的值结束;或者,您可以通过传递枚举值的索引来获取枚举的名称:enum ErrorLevel {
Info,
Debug,
Warning,
Error,
Critical,
}
console.log(ErrorLevel.Error); // 3
console.log(ErrorLevel[3]); // Error
enum Color {
Red = '#FF0000',
Blue = '#0000FF',
Green = '#00FF00',
}
enum Languages {
JavaScript = 1,
PHP,
Python,
Java = 10,
Ruby,
Rust,
TypeScript,
}
console.log(Color.Red) // '#FF0000'
console.log(Languages.TypeScript) // 13
let maybeIs: any = 4;
maybeIs = 'a string?';
maybeIs = true;
function logThis(str: string): void{
console.log(str);
}
interface IPerson {
name: string;
age: number;
}
const person: IPerson = {
name: 'Heitor',
age: 31,
};
function greetingUser(user: {name: string, lastName: string}) {
console.log(`Hello, ${user.name} ${user.lastName}`);
}
type Person = {
name: string,
age: number,
};
const person: Person = {
name: 'Heitor',
age: 31,
};
console.log(`My name is ${person.name}, I am ${person.age} years old`);
const sumOfValues: (a:number, b:number): number = (a: number, b: number): number => a + b;
const complexFunction: (a: number) => (b:number) => number = (a: number): (b: number) => number => (b: number): number => a + b;
function foo(a: number, b:number): number{
return a + b;
}
function greetingStudent(student: {name: string}){
console.log(`Hello ${student.name}`);
}
const newStudent = {name: 'Heitor'};
greetingStudent(newStudent);
interface IStudent {
name: string;
course?: string;
readonly university: string;
}
function greetingStudent(student: IStudent){
console.log(`Hello ${student.name}`);
if(student.course){
console.log(`Welcome to the ${student.course}` semester`);
}
}
const newStudent: IStudent = { name: 'Heitor', university: 'UDF' };
greetingStudent(newStudent);
course
,在它上面声明了一个?
。这表示这个属性可以是 null 或 undefined。这被称为可选属性。tsconfig.json
文件中设置标志:{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
@expression
的形式使用,其中表达式是一个在运行时将被调用的函数。function classSeal(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@classSeal
class Animal {
sound: string;
constructor(sound: string) {
this.sound = sound;
}
emitSound() {
return "The animal says, " + this.sound;
}
}
www.typescriptlang.org/docs/handbook/basic-types.html
找到有关 TypeScript 基本类型的更多信息。www.typescriptlang.org/docs/handbook/functions.html
找到有关 TypeScript 函数的更多信息。www.typescriptlang.org/docs/handbook/enums.html
找到有关 TypeScript 枚举的更多信息。www.typescriptlang.org/docs/handbook/advanced-types.html
找到有关 TypeScript 高级类型的更多信息。www.typescriptlang.org/docs/handbook/decorators.html
找到有关 TypeScript 装饰器的更多信息。rmolinamir.github.io/typescript-cheatsheet/#types
上查看 TypeScript 类型的速查表。npm
项目。要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:> npm init -y
> npm install typescript --only=dev
Animal
类。这个类可以有一些基本属性,比如它的name
,它是否发出sound
,它的family
,以及这种动物所吃的基本food chain
。food chain
。我们需要确保它是一个不可枚举的列表,并且每个使用它的文件最终都有相同的值。我们只需要调用一个常量变量:export enum FoodChainType {
Carnivorous = 'carnivorous',
Herbivorous = 'herbivorous',
Omnivorous = 'omnivorous',
}
interface
。我们知道我们的动物有一个name
,可以发出一个sound
,可以成为一个family
的一部分,并且属于food chain
类别。在类中使用接口,我们在类和将要暴露的内容之间建立了一个合同,有助于开发过程:interface IAnimal {
name: string;
sound?: string;
family: string;
foodChainType: FoodChainType;
}
Animal
类。每个类都可以有它的构造函数。类构造函数可以很简单,只包含一些变量作为参数,也可以更复杂,有一个对象作为参数。如果你的构造函数将有任何参数,需要一个接口或声明每个参数的类型。在这种情况下,我们的构造函数将是一个对象,只有一个参数,与Animal
相同,所以它将扩展IAnimal
接口:interface IAnimalConstructor extends IAnimal {
}
IBasicAnimal
接口。为此,我们需要添加一些我们的类将具有的公共元素,并也声明这些元素。我们需要实现函数来显示它是什么动物以及它发出什么声音。现在,我们有了一个包含所有动物属性的基本类。它具有类和构造函数的单独接口。食物链的枚举以一种易于阅读的方式声明,因此该库的 JavaScript 导入可以无问题执行:interface IBasicAnimal extends IAnimal {
whoAmI: () => void;
makeSound: () => void;
}
export class Animal implements IBasicAnimal {
public name: string;
public sound: string;
public family: string;
public foodChainType: FoodChainType;
constructor(params: IAnimalConstructor) {
this.name = params.name;
this.sound = params.sound || '';
this.family = params.family;
this.foodChainType = params.foodChainType;
}
public whoAmI(): void {
console.log(`I am a ${this.name}, my family is ${this.family}.
My diet is ${this.foodChainType}.`);
if (this.sound) {
console.log([...Array(2).fill(this.sound)].join(', '));
}
}
public makeSound(): void {
console.log(this.sound);
}
}
Animal
转换成Dog
:import {Animal, FoodChainType} from './Animal';
class Dog extends Animal {
constructor() {
super({
name: 'Dog',
sound: 'Wof!',
family: 'Canidae',
foodChainType: FoodChainType.Carnivorous,
});
}n
}
www.typescriptlang.org/docs/handbook/classes.html
找到有关 TypeScript 类的更多信息。rmolinamir.github.io/typescript-cheatsheet/#classes
上查看 TypeScript 类的速查表。热模块重载
。@vue/cli
@vue/cli-service-global
> vue create my-first-project
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
有两种方法可以启动一个新项目。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有手动
模式,您可以选择更多的模式、插件、linters 和选项。我们将选择手动
。
现在,我们被问及我们将在项目中需要的功能。这些功能是一些 Vue 插件,如 Vuex 或 Router(Vue-Router)、测试工具、linters 等:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex CSS Pre-processors ❯ Linter / Formatter
Unit Testing ❯ **E2E Testing**
CSS 预处理器
并按Enter继续:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ **Linter / Formatter**
Unit Testing **E2E Testing**
Sass
、Less
和Stylus
。由您选择哪种最适合您:? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) **Less** ❯ Stylus
AirBnB
、Standard
和Prettier
之间进行选择,并使用基本配置。那些在ESLint
中导入的规则可以随时进行自定义,没有任何问题,并且有一个完美的规则适合您的需求。您知道什么对您最好:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) **Lint on save** ❯ Lint and fix on commit
package.json
文件中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files** In package.json
? Save this as a preset for future projects? (y/N) n
npm run serve
—用于在本地运行开发服务器
npm run build
—用于构建和缩小应用程序以进行部署
npm run lint
—对代码执行 lint
npm
脚本都被命名为任务,在这些任务中,您可以获得实时统计数据,例如资产、模块和依赖项的大小;错误或警告的数量;以及更深入的网络数据,以微调您的应用程序。> vue ui
cli.vuejs.org/guide/
找到有关 Vue CLI 项目的更多信息。cli.vuejs.org/dev-guide/plugin-dev.html
找到有关 Vue CLI 插件开发的更多信息。@vue/cli
@vue/cli-service-global
> vue ui
main.js
文件已更改,并且vuex(store)
和vue-router(router)
插件现在已导入并注入到 Vue 实例中:npm
或yarn
配合使用,自动安装项目中的软件包,然后在可能的情况下注入 Vue 实例所需的条件。@vue/cli
@vue/cli-service-global
> vue ui
@vue/cli-plugin-typescript
:**使用类样式组件语法?**使用 TypeScript 的vue-class-component
插件。
**与 TypeScript 一起使用 Babel(现代模式所需,自动检测的 polyfill,转译 JSX)?**激活 Babel 以在 TypeScript 编译器之外转译 TypeScript。
**使用 ESLint?**将 ESLint 用作.ts
和.tsx
文件的检查器。
**将所有.js 文件转换为.ts 文件?**在安装过程中自动将所有.js
文件转换为.ts
文件。
**允许编译.js 文件?**激活tsconfig.json
标志以接受编译器中的.js
文件。
选择您的选项后,点击完成安装。
现在,您的项目是一个 TypeScript Vue 项目,所有文件都已配置好,准备好进行编码:
github.com/typescript-eslint/typescript-eslint
找到有关 TypeScript ESLint 的更多信息github.com/vuejs/vue-class-component
找到有关vue-class-component
的更多信息。this
关键字有着密切的关系,因此开发 TypeScript 组件会有点混乱。vue-class-component
插件使用 ECMAScript 装饰器提案将静态类型的值直接传递给 Vue 组件,并使编译器更容易理解发生了什么。@vue/cli
@vue/cli-service-global
vue-class-component
创建你的第一个 Vue 组件:在src/components
文件夹内创建一个名为Counter.vue
的新文件。
现在,让我们开始制作 Vue 组件的脚本部分。我们将创建一个包含数字数据的类,两个方法——一个用于增加,另一个用于减少——最后,一个计算属性来格式化最终数据:
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class Counter extends Vue {
valueNumber: number = 0;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
increase() {
this.valueNumber += 1;
}
decrease() {
this.valueNumber -= 1;
}
}
</script>
<template>
<div>
<fieldset>
<legend>{{ formattedNumber }}</legend>
<button @click="increase">Increase</button>
<button @click="decrease">Decrease</button>
</fieldset>
</div>
</template>
App.vue
文件中,我们需要导入刚刚创建的组件:<template>
<div id="app">
<Counter />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Counter from './components/Counter.vue';
@Component({
components: {
Counter,
},
})
export default class App extends Vue {
}
</script>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
npm run serve
命令时,你将看到你的组件在屏幕上运行和执行:vue-class-component
插件利用装饰器的新提案来向 TypeScript 类注入和传递一些属性。github.com/vuejs/vue-class-component
找到更多关于vue-class-component
的信息。mixin
是一种在其他 Vue 对象中重用相同代码的方式,就像将mixin
的所有属性混合到组件中一样。mixin
属性,然后是组件值,因此组件始终是最后且有效的值。此合并以深度模式进行,并且已在框架内声明了特定的方式,但可以通过特殊配置进行更改。@vue/cli
@vue/cli-service-global
vue-class-component
创建自定义 mixin:我们需要在src/components
文件夹中创建一个名为CounterByTen.vue
的新组件。
现在,让我们开始制作 Vue 组件的脚本部分。我们将创建一个类,其中将有一个类型为数字的变量和默认值为0
;两种方法,一种是增加10
,另一种是减少10
;最后,一个计算属性来格式化最终数据:
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class CounterByTen extends Vue {
valueNumber: number = 0;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
increase() {
this.valueNumber += 10;
}
decrease() {
this.valueNumber -= 10;
}
}
</script>
<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase By Ten</button>
<button @click="decrease">Decrease By Ten</button>
</fieldset>
</div>
</template>
App.vue
文件中,我们需要导入刚刚创建的组件:<template>
<div id="app">
<Counter />
<hr />
<CounterByTen />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Counter from './components/Counter.vue';
import CounterByTen from './components/CounterByTen.vue';
@Component({
components: {
Counter,
CounterByTen,
},
})
export default class App extends Vue {
}
</script>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
在src/mixins
文件夹中创建一个名为defaultNumber.ts
的文件。
为了编写我们的 mixin,我们将从vue-class-component
插件中导入Component
和Vue
修饰符,作为 mixin 的基础。我们需要采用类似的代码并将其放入 mixin 中:
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class DefaultNumber extends Vue {
valueNumber: number = 0;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
}
src/components
文件夹中的Counter.vue
组件并导入它。为此,我们需要从vue-class-component
中导入一个特殊的导出,称为mixins
,并将其与我们想要扩展的 mixin 扩展。这将删除Vue
和Component
装饰器,因为它们已经在 mixin 上声明了:<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase By Ten</button>
<button @click="decrease">Decrease By Ten</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component, { mixins } from 'vue-class-component';
import DefaultNumber from '../mixins/defaultNumber';
@Component
export default class CounterByTen extends mixins(DefaultNumber) {
increase() {
this.valueNumber += 10;
}
decrease() {
this.valueNumber -= 10;
}
}
</script>
npm run serve
命令或在命令提示符/PowerShell(Windows)上运行时,您将看到您的组件在屏幕上运行和执行:vue-class-component
时,需要在 mixins 上声明Vue
和Component
装饰器,因为将使用 mixin 的类已经具有此扩展,因为它扩展了此 mixin。vue-class-component
mixins 的更多信息,请访问github.com/vuejs/vue-class-component#using-mixins
。v3.vuejs.org/guide/mixins.html
@vue/cli
@vue/cli-service-global
vue-class-component
创建自定义函数装饰器:在src/decorators
文件夹中创建一个名为componentMount.js
的文件。
我们需要从vue-class-component
中导入createDecorator
函数,以便在基于vue-class-component
的组件上使用它,并开始编写我们的装饰器:
import { createDecorator } from 'vue-class-component';
import componentMountLogger from './componentLogger';
export default createDecorator((options) => {
options.mixins = [...options.mixins, componentMountLogger];
});
createDecorator
函数就像 Vue vm *(View-Model)*的扩展,因此它不会有 ECMAScript 装饰器的属性,但会作为 Vue 装饰器的功能。componentLogger.js
文件。这个函数将获取在“装饰”组件中设置的所有数据值,并对其添加一个监视器。每当它改变时,这个监视器将记录新值和旧值。这个函数只有在调试数据设置为true
时才会执行:export default {
mounted() {
if (this.debug) {
const componentName = this.name || '';
console.log(`The ${componentName} was mounted
successfully.`);
const dataKeys = Object.keys(this.$data);
if (dataKeys.length) {
console.log('The base data are:');
console.table(dataKeys);
dataKeys.forEach((key) => {
this.$watch(key, (newValue, oldValue) => {
console.log(`The new value for ${key} is:
${newValue}`);
console.log(`The old value for ${key} is:
${oldValue}`);
}, {
deep: true,
});
});
}
}
},
};
src/components
文件夹中的Counter.vue
组件文件中,并向其添加调试器数据:<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase</button>
<button@click="decrease">Decrease</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
import componentMount from '../decorators/componentMount';
@Component
@componentMount
export default class Counter extends Vue {
valueNumber: number = 0;
debug: boolean = true;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
increase() {
this.valueNumber += 1;
}
decrease() {
this.valueNumber -= 1;
}
}
</script>
createDecorator
函数是一个工厂函数,它扩展了 Vue vm(View Model),产生了 Vue 组件的扩展,比如一个 Vue mixin。Vue mixin 是 Vue 组件的一个属性,可以用来在组件之间共享和重用代码。true
时才会附加。这个调试器将记录当前数据,并为数据的更改设置监视器,每次数据更改时都会在控制台上显示日志。no-param-reassign
规则是必需的,因为装饰器使用选项作为传递值的引用。github.com/vuejs/vue-class-component#create-custom-decorators
找到有关使用vue-class-component
创建自定义装饰器的更多信息。www.typescriptlang.org/docs/handbook/decorators.html
找到有关 ECMAScript 装饰器的更多信息。vue-router
与导航守卫,例如beforeRouterEnter
和beforeRouterLeave
函数钩子。@vue/cli
@vue/cli-service-global
vue-class-component
为您的 Vue 项目添加自定义钩子:vue-router
添加到项目中。这可以在创建 Vue CLI 项目时完成,也可以在创建项目后的 Vue UI 界面中完成。vue-router
。请注意,选择History选项将在部署时需要特殊的服务器配置。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),执行npm run serve
命令,您将看到vue-router
正在工作,并且有两个工作路由器:home
和about
。
让我们开始创建并命名我们的钩子以注册到主应用程序。为此,我们需要在src/classComponentsHooks
文件夹中创建一个vue-router.js
文件:
import Component from 'vue-class-component';
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
]);
main.ts
文件中,因为它需要在应用程序最终构建之前被调用:import './classComponentsHooks/vue-router';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app');
现在,我们已经在vue-class-component
中注册了这些钩子,并且它们可以在 TypeScript 组件中使用。
我们需要在src/views
文件夹中创建一个名为Secure.vue
的新路由位置。安全页面将有一个输入密码,vuejs
。当用户输入此密码时,路由守卫将授予权限,用户可以看到页面。如果密码错误,用户将被带回到主页。当他们离开页面时,警报将向用户显示一条消息:
<template>
<div class="secure">
<h1>This is an secure page</h1>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Route, RawLocation } from 'vue-router';
type RouteNext = (to?: RawLocation | false | ((vm: Vue) => any) |
void) => void;
@Component
export default class Home extends Vue {
beforeRouteEnter(to: Route, from: Route, next: RouteNext) {
const securePassword = 'vuejs';
const userPassword = prompt('What is the password?');
if (userPassword === securePassword) {
next();
} else if (!userPassword) {
next('/');
}
}
beforeRouteLeave(to: Route, from: Route, next: RouteNext) {
alert('Bye!');
next();
}
}
</script>
router.ts
文件中,以便在 Vue 应用程序中调用它:import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: () => import('./views/About.vue'),
},
{
path: '/secure',
name: 'secure',
component: () => import('./views/Secure.vue'),
},
],
});
App.vue
文件中,这样我们就会得到一个集成了钩子的组件:<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/secure">Secure</router-link>
</div>
<router-view/>
</div>
</template>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
#nav
padding 30px
a
font-weight bold
color #2c3e50
&.router-link-exact-active
color #42b983
</style>
main.ts
文件的第一行导入自定义钩子。vue-class-component
已经将所有这些自定义导入转换为组件装饰器的基本方法。vue-router
导航守卫的钩子。这些钩子在每次路由进入或离开时都会被调用。我们没有使用的前两个参数to
和from
是携带有关未来路由和过去路由的信息的参数。next
函数总是必需的,因为它执行路由更改。如果在函数中没有传递参数,路由将继续使用被调用的路由,但如果想要即时更改路由,可以传递参数来改变用户将要前往的位置。router.vuejs.org/guide/advanced/navigation-guards.html
中了解更多关于 vue-router 导航守卫的信息。github.com/vuejs/vue-class-component#adding-custom-hooks
中了解更多关于 vue-class-component 钩子的信息。vue-class-component
中以 TypeScript 装饰器的形式缺失。因此,社区制作了一个名为vue-property-decorator
的库,这个库得到了 Vue 核心团队的全力支持。props
、watch
、model
、inject
等。@vue/cli
@vue/cli-service-global
vue-property-decorator
添加到 Vue基于类的组件
中:vue-property-decorator
添加到我们的项目中。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:> npm install -S vue-property-decorator
import {
Vue,
Component,
Prop,
} from 'vue-property-decorator';
@Component
export default class DefaultNumber extends Vue {
valueNumber: number = 0;
@Prop(Number) readonly value: number | undefined;
get formattedNumber() {
return `Your total number is: ${this.valueNumber}`;
}
}
src/mixins
文件夹内创建一个名为numberWatcher.ts
的新文件:import {
Watch,
Mixins,
} from 'vue-property-decorator';
import DefaultNumber from './defaultNumber';
export default class NumberWatchers extends Mixins(DefaultNumber) {
@Watch('valueNumber')
onValueNumberChanged(val: number) {
this.$emit('input', val);
}
@Watch('value', { immediate: true })
onValueChanged(val: number) {
this.valueNumber = val;
}
}
v-model
指令的工作原理类似于糖语法,它是 Vue $emit
函数和 Vue props
函数的组合。当值发生变化时,组件需要使用'input'
名称进行$emit
,并且组件需要在props
函数中有一个value
键,这将是从父组件传递到子组件的值。Counter.vue
组件,将导入的 mixin 从defaultNumber.ts
文件更改为numberWatcher.ts
:<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase</button>
<button @click="decrease">Decrease</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component, { mixins } from 'vue-class-component';
import NumberWatcher from '../mixins/numberWatcher';
@Component
export default class Counter extends mixins(NumberWatcher) {
increase() {
this.valueNumber += 1;
}
decrease() {
this.valueNumber -= 1;
}
}
</script>
CounterByTen.vue
组件,并添加新创建的 mixin:<template>
<div>
<fieldset>
<legend>{{ this.formattedNumber }}</legend>
<button @click="increase">Increase By Ten</button>
<button @click="decrease">Decrease By Ten</button>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component, { mixins } from 'vue-class-component';
import NumberWatcher from '../mixins/numberWatcher';
@Component
export default class CounterByTen extends mixins(NumberWatcher) {
increase() {
this.valueNumber += 10;
}
decrease() {
this.valueNumber -= 10;
}
}
</script>
App.vue
组件。这一次,我们将在组件中存储一个变量,该变量将传递给两个子组件,当组件发出更新事件时,此变量将自动更改,也会更新其他组件:<template>
<div id="app">
<Counter
v-model="amount"
/>
<hr />
<CounterByTen
v-model="amount"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Counter from './components/Counter.vue';
import CounterByTen from './components/CounterByTen.vue';
@Component({
components: {
Counter,
CounterByTen,
},
})
export default class App extends Vue {
amount: number = 0;
}
</script>
<style lang="stylus">
#app
font-family 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
vue-class-components
中注入装饰器,vue-property-decorator
帮助 TypeScript 编译器检查 Vue 代码的类型和静态分析。@Watch
和@Prop
装饰器。vue-property-decorator
相同,但用于vuex
插件,名为vuex-class
。vue-property-decorator
相同的过程。它在组件中创建一个 inject 装饰器。这些装饰器帮助 TypeScript 编译器在开发过程中检查类型。github.com/ktsn/vuex-class/
找到有关这个库的更多信息。github.com/kaorun343/vue-property-decorator
找到有关vue-property-decorator
的更多信息。vue-devtools
深入了解 Vue 组件并查看我们的数据和应用程序发生了什么。创建“hello world”组件
创建具有双向数据绑定的输入表单
向元素添加事件侦听器
从输入中删除 v-model
创建动态待办事项列表
创建计算属性并探索其工作原理
使用自定义过滤器显示更清晰的数据和文本
使用 Vuelidate 添加表单验证
为列表创建过滤器和排序器
创建条件过滤以对列表数据进行排序
添加自定义样式和过渡
使用vue-devtools
调试您的应用程序
windows-build-tools
的npm
包,以便安装以下所需的软件包。为此,请以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
。> npm install -g @vue/cli @vue/cli-service-global
@vue/cli
@vue/cli-service-global
> vue create my-component
default
**选项:? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
让我们在src/components
文件夹中创建一个名为CurrentTime.vue
的新文件。
在这个文件中,我们将从组件的<template>
部分开始。它将是一个显示当前日期格式化的阴影框卡:
<template>
<div class='cardBox'>
<div class='container'>
<h2>Today is:</h2>
<h3>{{ getCurrentDate }}</h3>
</div>
</div>
</template>
<script>
部分。我们将从name
属性开始。这将在使用vue-devtools
调试我们的应用程序时使用,也有助于集成开发环境(IDE)。对于getCurrentDate
计算属性,我们将创建一个computed
属性,它将返回当前日期,由Intl
浏览器函数格式化:<script>
export default {
name: 'CurrentTime',
computed: {
getCurrentDate() {
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(
browserLocale,
{
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
});
return intlDateTime.format(new Date());
}
}
};
</script>
src
文件夹中创建一个style.css
文件,然后将cardBox
样式添加到其中:.cardBox {
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.2);
transition: 0.3s linear;
max-width: 33%;
border-radius: 3px;
margin: 20px;
}
.cardBox:hover {
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.2);
}
.cardBox>.container {
padding: 4px 18px;
}
[class*='col-'] {
display: inline-block;
}
@media only screen and (max-width: 600px) {
[class*='col-'] {
width: 100%;
}
.cardBox {
margin: 20px 0;
}
}
@media only screen and (min-width: 600px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
@media only screen and (min-width: 768px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
@media only screen and (min-width: 992px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
@media only screen and (min-width: 1200px) {
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
App.vue
文件中,我们需要导入我们的组件才能看到它:<template>
<div id='app'>
<current-time />
</div>
</template>
<script>
import CurrentTime from './components/CurrentTime.vue';
export default {
name: 'app',
components: {
CurrentTime
}
}
</script>
main.js
文件中,我们需要导入style.css
文件以包含在 Vue 应用程序中:import Vue from 'vue';
import App from './App.vue';
import './style.css';
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
> npm run serve
components
属性中声明它。Intl.DateTimeFormat
函数,这是一个本机函数,可用于将日期格式化和解析为声明的位置。为了获得本地格式,我们使用了navigator
全局变量。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。v3.vuejs.org/guide/single-file-component.html
找到有关 Vue 组件的更多信息@vue/cli
@vue/cli-service-global
让我们在src/components
文件夹中创建一个名为TaskInput.vue
的新文件。
在这个文件中,我们将创建一个组件,其中将包含一个文本输入和一个显示文本。这个文本将基于文本输入中键入的内容。在组件的<template>
部分,我们需要创建一个 HTML 输入和一个mustache
变量,用于接收和呈现数据:
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is: {{ task }}</strong>
<input
type='text'
v-model='task'
class='taskInput' />
</div>
</div>
</template>
<script>
部分,我们将命名它并将任务添加到data
属性中。由于数据始终需要返回一个Object
,我们将使用箭头函数直接返回一个Object
:<script>
export default {
name: 'TaskInput',
data: () => ({
task: '',
}),
};
</script>
<style>
部分,我们需要添加scoped
属性,以便样式仅绑定到组件,不会与其他层叠样式表(CSS)规则混合:<style scoped>
.tasker{
margin: 20px;
}
.tasker .taskInput {
font-size: 14px;
margin: 0 10px;
border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.75);
}
.tasker button {
border: 1px solid rgba(0, 0, 0, 0.75);
border-radius: 3px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
}
</style>
App.vue
文件中:<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' />
</div>
</template>
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'app',
components: {
CurrentTime,
TaskInput,
}
}
</script>
> npm run serve
input
元素并为其添加v-model
时,您正在传递一个内置于 Vue 中的指令,该指令检查输入类型并为我们提供输入的糖语法。这处理了变量值和 DOM 的更新。input-form
,那么 JavaScript 代码就可以执行一个函数。v3.vuejs.org/guide/forms.html
找到有关表单输入绑定的更多信息@vue/cli
@vue/cli-service-global
创建一个新组件或打开TaskInput.vue
文件。
在<template>
部分,我们将添加一个按钮元素,并使用v-on
指令为按钮点击事件添加事件监听器。我们将从组件中删除{{ task }}
变量,因为从现在开始它将被发出并不再显示在组件上:
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is:</strong>
<input
type='text'
v-model='task'
class='taskInput' />
<button
v-on:click='addTask'>
Add Task
</button>
</div>
</div>
</template>
<script>
部分,我们需要添加一个处理点击事件的方法。该方法将被命名为addTask
。该方法将触发一个名为add-task
的事件,并将任务发送到数据中。之后,组件上的任务将被重置:<script>
export default {
name: 'TaskInput',
data: () => ({
task: '',
}),
methods: {
addTask(){
this.$emit('add-task', this.task);
this.task = '';
},
}
};
</script>
App.vue
文件中,我们需要在组件上添加一个事件监听器绑定。此侦听器将附加到add-task
事件。我们将使用v-on
指令的缩写版本@
。当它被触发时,事件将调用addNewTask
方法,该方法将发送一个警报,说明已添加了一个新任务:<template>
<div id='app'>
<current-time class='col-4' />
<task-input
class='col-6'
@add-task='addNewTask'
/>
</div>
</template>
addNewTask
方法。这将接收任务作为参数,并向用户显示一个警报,显示任务已添加:<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'app',
components: {
CurrentTime,
TaskInput,
},
methods:{
addNewTask(task){
alert(`New task added: ${task}`);
},
},
}
</script>
> npm run serve
v-on
事件处理指令由 Vue 读取。当我们将v-on:click
指令附加到按钮时,我们向按钮添加了一个监听器,以便在用户单击按钮时执行一个函数。v-on
指令监听它。v3.vuejs.org/guide/events.html
找到有关事件处理的更多信息v-model
的魔术背后有很多代码,使我们的魔术糖语法发生?如果我告诉您,兔子洞可以深入到足以控制输入的事件和值发生的一切?v-model
指令的糖语法,并将其转换为其背后的基本语法。@vue/cli
@vue/cli-service-global
v-model
指令的语法糖:打开TaskInput.vue
文件。
在组件的<template>
块中,找到v-model
指令。我们将删除v-model
指令。然后,我们需要向输入添加一个新的绑定,称为v-bind:value
或缩写版本:value
,以及一个事件侦听器到 HTMLinput
元素。我们需要向input
事件添加一个事件侦听器,使用v-on:input
指令或缩写版本@input
。输入绑定将接收任务值作为参数,事件侦听器将接收一个值赋值,其中它将使任务变量等于事件值的值。
<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is:</strong>
<input
type='text'
:value='task'
@input='task = $event.target.value'
class='taskInput'
/>
<button v-on:click='addTask'>
Add Task
</button>
</div>
</div>
</template>
> npm run serve
v-model
指令可以自动为您声明绑定和元素的事件侦听器,但副作用是您无法完全控制可以实现的内容。$event
变量用于传递事件。在这种情况下,就像在普通 JavaScript 中一样,要捕获输入的值,我们需要使用event.target.value
值。v3.vuejs.org/guide/events.html
找到有关事件处理的更多信息@vue/cli
@vue/cli-service-global
App.vue
文件中,我们将创建我们的任务数组。每当TaskInput.vue
组件发出消息时,这个任务将被填充。我们将向这个数组添加一个带有任务和创建任务的当前日期的对象。目前,任务完成时的日期将是未定义的。为了做到这一点,在组件的<script>
部分,我们需要创建一个接收任务并将该任务与当前日期添加到taskList
数组中的方法:<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput,
},
data: () => ({
taskList: [],
}),
methods:{
addNewTask(task){
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined,
})
},
},
}
</script>
<template>
部分呈现这个列表。我们将使用 Vue 的v-for
指令来迭代任务列表。当我们将这个指令与数组一起使用时,它会给我们访问两个属性——项目本身和项目的索引。我们将使用项目来呈现它,使用索引来创建元素的键以进行呈现。我们需要添加一个复选框,当选中时,调用一个函数来改变任务的状态和任务完成时的显示:<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' @add-task='addNewTask' />
<div class='col-12'>
<div class='cardBox'>
<div class='container'>
<h2>My Tasks</h2>
<ul class='taskList'>
<li
v-for='(taskItem, index) in taskList'
:key='`${index}_${Math.random()}`'
>
<input type='checkbox'
:checked='!!taskItem.finishedAt'
@input='changeStatus(index)'
/>
{{ taskItem.task }}
<span v-if='taskItem.finishedAt'>
{{ taskItem.finishedAt }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
Math.random()
函数添加到索引中以生成一个唯一的键,因为当元素的数量减少时,数组的前几个元素的索引始终是相同的数字。App.vue
的methods
属性上创建changeStatus
函数。这个函数将接收任务的索引作为参数,然后去改变任务数组中的finishedAt
属性,这是我们标记任务完成的标志:changeStatus(taskIndex){
const task = this.taskList[taskIndex];
if(task.finishedAt){
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
<style>
部分,我们将使其具有作用域并添加自定义类:<style scoped>
.taskList li{
text-align: left;
}
</style>
> npm run serve
finishedAt
属性设置为undefined
。v3.vuejs.org/guide/list.html#mapping-an-array-to-elements-with-v-for
找到有关列表渲染的更多信息v3.vuejs.org/guide/conditional.html#v-if
找到有关条件渲染的更多信息developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
找到有关Math.random
的更多信息。@vue/cli
@vue/cli-service-global
App.vue
文件的<script>
部分,我们将在data
和method
之间添加一个名为computed
的新属性。这是computed
属性将被放置的地方。我们将创建一个名为displayList
的新计算属性,它将用于在模板上呈现最终列表:<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: []
}),
computed: {
displayList(){
return this.taskList;
},
},
methods: {
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskIndex){
const task = this.taskList[taskIndex];
if(task.finishedAt){
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
displayList
属性目前只返回变量的缓存值,而不是直接的变量本身。<template>
部分,我们需要更改列表的获取位置:<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' @add-task='addNewTask' />
<div class='col-12'>
<div class='cardBox'>
<div class='container'>
<h2>My Tasks</h2>
<ul class='taskList'>
<li
v-for='(taskItem, index) in displayList'
:key='`${index}_${Math.random()}`'
>
<input type='checkbox'
:checked='!!taskItem.finishedAt'
@input='changeStatus(index)'
/>
{{ taskItem.task }}
<span v-if='taskItem.finishedAt'>
{{ taskItem.finishedAt }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
> npm run serve
computed
属性将值传递到模板时,该值现在被缓存。这意味着只有在值更新时才会触发渲染过程。同时,我们确保模板不使用变量进行渲染,因此它不能在模板上更改,因为它是变量的缓存副本。computed
属性会缓存结果并不会更新最终结果。v3.vuejs.org/guide/computed.html
找到有关计算属性的更多信息。DateTime
格式。我们如何解决这个问题?在 Vue 中渲染数据时,可以使用我们称之为过滤器的东西。@vue/cli
@vue/cli-service-global
App.vue
文件的<script>
部分,在方法中,创建一个formatDate
函数。此函数将接收value
作为参数并进入过滤器管道。我们可以检查value
是否是一个数字,因为我们知道我们的时间是基于 Unix 时间戳格式的。如果它是一个数字,我们将根据当前浏览器位置进行格式化,并返回该格式化的值。如果传递的值不是一个数字,我们只需返回传递的值。<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput';
export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: []
}),
computed: {
displayList() {
return this.taskList;
}
},
methods: {
formatDate(value) {
if (!value) return '';
if (typeof value !== 'number') return value;
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(
browserLocale,
{
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
});
return intlDateTime.format(new Date(value));
},
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskIndex) {
const task = this.taskList[taskIndex];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
<template>
部分,我们需要将变量传递给过滤器方法。为此,我们需要找到taskItem.finishedAt
属性,并将其作为formatDate
方法的参数。我们将添加一些文本来表示任务在日期开始时已完成:<template>
<div id='app'>
<current-time class='col-4' />
<task-input class='col-6' @add-task='addNewTask' />
<div class='col-12'>
<div class='cardBox'>
<div class='container'>
<h2>My Tasks</h2>
<ul class='taskList'>
<li
v-for='(taskItem, index) in displayList'
:key='`${index}_${Math.random()}`'
>
<input type='checkbox'
:checked='!!taskItem.finishedAt'
@input='changeStatus(index)'
/>
{{ taskItem.task }}
<span v-if='taskItem.finishedAt'> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
> npm run serve
<template>
部分显示或在 Vue 属性中使用的方法。formatDate
方法时,我们知道它是一个有效的 Unix 时间戳,因此可以调用新的Date
类构造函数,将value
作为参数传递,因为 Unix 时间戳是一个有效的日期构造函数。Intl.DateTimeFormat
函数,这是一个原生函数,可用于格式化和解析日期到声明的位置。为了获得本地格式,我们使用navigator
全局变量。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。@vue/cli
@vue/cli-service-global
> npm install vuelidate --save
src
文件夹中的main.js
文件中导入并添加它:import Vue from 'vue';
import App from './App.vue';
import Vuelidate from 'vuelidate';
import './style.css';
Vue.config.productionTip = false
Vue.use(Vuelidate);
new Vue({
render: h => h(App),
}).$mount('#app')
TaskInput.vue
文件中,我们将向 Vue 对象添加一个新属性。这个属性由安装的新插件解释。在对象的末尾,我们将添加validations
属性,并在该属性内添加模型的名称。模型是插件将检查验证的数据或计算属性的直接名称:<script>
export default {
name: 'TaskInput',
data: () => ({
task: ''
}),
methods: {
addTask() {
this.$emit('add-task', this.task);
this.task = '';
}
},
validations: {
task: {}
}
};
</script>
required
和minLength
。导入后,我们将这些规则添加到模型中:<script>
import { required, minLength } from 'vuelidate/lib/validators';
export default {
name: 'TaskInput',
data: () => ({
task: ''
}),
methods: {
addTask() {
this.$emit('add-task', this.task);
this.task = '';
}
},
validations: {
task: {
required,
minLength: minLength(5),
}
}
};
</script>
$touch
内置函数告诉插件该字段已被用户触摸,并进行验证。如果有任何字段与用户有任何交互,插件将相应地设置标志。如果没有错误,我们将发出事件,并使用$reset
函数重置验证。为此,我们将更改addTask
方法:addTask() {
this.$v.task.$touch();
if (this.$v.task.$error) return false;
this.$emit('add-task', this.task);
this.task = '';
this.$v.task.$reset();
return true;
}
$error
属性上:<template>
<div class='cardBox'>
<div class='container tasker'>
<strong>My task is:</strong>
<input
type='text'
:value='task'
@input='task = $event.target.value'
class='taskInput'
:class="$v.task.$error ? 'fieldError' : ''"
/>
<button v-on:click='addTask'>Add Task</button>
</div>
</div>
</template>
src
文件夹中的style.css
文件中创建一个fieldError
类:.fieldError {
border: 2px solid red !important;
color: red;
border-radius: 3px;
}
> **npm run serve**
$v
属性,并在 Vue 对象中检查一个名为validations
的新对象属性。当定义了此属性并具有一些规则时,插件会在每次更新时检查模型的规则。vuelidate.netlify.com/
找到有关 Vuelidate 的更多信息。v3.vuejs.org/guide/class-and-style.html
找到有关类和样式绑定的更多信息@vue/cli
@vue/cli-service-global
App.vue
文件的<script>
部分,我们将添加新的计算属性;这些将用于排序和过滤。我们将添加三个新的计算属性,baseList
,filteredList
和sortedList
。baseList
属性将是我们的第一个操作。我们将通过Array.map
向任务列表添加一个id
属性。由于 JavaScript 数组从零开始,我们将在数组的索引上添加1
。filteredList
属性将过滤baseList
属性,并返回未完成的任务,sortedList
属性将对filteredList
属性进行排序,以便最后添加的id
属性将首先显示给用户:<script>
import CurrentTime from "./components/CurrentTime.vue";
import TaskInput from "./components/TaskInput";
export default {
name: "TodoApp",
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: [],
}),
computed: {
baseList() {
return [...this.taskList]
.map((t, index) => ({
...t,
id: index + 1
}));
},
filteredList() {
return [...this.baseList]
.filter(t => !t.finishedAt);
},
sortedList() {
return [...this.filteredList]
.sort((a, b) => b.id - a.id);
},
displayList() {
return this.sortedList;
}
},
methods: {
formatDate(value) {
if (!value) return "";
if (typeof value !== "number") return value;
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(browserLocale, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric"
});
return intlDateTime.format(new Date(value));
},
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskIndex) {
const task = this.taskList[taskIndex];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
<template>
部分,我们将Task ID
添加为指示器,并更改changeStatus
方法发送参数的方式。因为现在索引是可变的,我们不能将其用作变量;它只是数组上的临时索引。我们需要使用任务id
:<template>
<div id="app">
<current-time class="col-4" />
<task-input class="col-6" @add-task="addNewTask" />
<div class="col-12">
<div class="cardBox">
<div class="container">
<h2>My Tasks</h2>
<ul class="taskList">
<li
v-for="(taskItem, index) in displayList"
:key="`${index}_${Math.random()}`"
>
<input type="checkbox"
:checked="!!taskItem.finishedAt"
@input="changeStatus(taskItem.id)"
/>
#{{ taskItem.id }} - {{ taskItem.task }}
<span v-if="taskItem.finishedAt"> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
changeStatus
方法中,我们也需要更新我们的函数。由于索引现在从1
开始,我们需要将数组的索引减一,以获取更新前元素的真实索引:changeStatus(taskId) {
const task = this.taskList[taskId - 1];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
> npm run serve
computed
属性一起作为列表的缓存工作,并确保对元素的操作没有副作用:在baseList
属性中,我们创建了一个新数组,其中包含相同的任务,但为任务添加了一个新的id
属性。
在filteredList
属性中,我们取出了baseList
属性,并且只返回了未完成的任务。
在sortedList
属性上,我们按照它们的 ID,按降序对filteredList
属性上的任务进行排序。
displayList
属性将返回被操作的数据的结果。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
找到有关Array.prototype.map
的更多信息。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
找到有关Array.prototype.filter
的更多信息。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
找到有关Array.prototype.sort
的更多信息。@vue/cli
@vue/cli-service-global
App.vue
文件的<script>
部分,我们将更新computed
属性,filteredList
,sortedList
和displayList
。我们需要向我们的项目添加三个新变量,hideDone
,reverse
和sortById
。所有三个变量都将是布尔变量,并且默认值为false
。filteredList
属性将检查hideDone
变量是否为true
。如果是,它将具有相同的行为,但如果不是,它将显示整个列表而不进行任何过滤。sortedList
属性将检查sortById
变量是否为true
。如果是,它将具有相同的行为,但如果不是,它将按任务完成日期对列表进行排序。displayList
属性将检查reverse
变量是否为true
。如果是,它将颠倒显示的列表,但如果不是,它将具有相同的行为:<script>
import CurrentTime from "./components/CurrentTime.vue";
import TaskInput from "./components/TaskInput";
export default {
name: "TodoApp",
components: {
CurrentTime,
TaskInput
},
data: () => ({
taskList: [],
hideDone: false,
reverse: false,
sortById: false,
}),
computed: {
baseList() {
return [...this.taskList]
.map((t, index) => ({
...t,
id: index + 1
}));
},
filteredList() {
return this.hideDone
? [...this.baseList]
.filter(t => !t.finishedAt)
: [...this.baseList];
},
sortedList() {
return [...this.filteredList]
.sort((a, b) => (
this.sortById
? b.id - a.id
: (a.finishedAt || 0) - (b.finishedAt || 0)
));
},
displayList() {
const taskList = [...this.sortedList];
return this.reverse
? taskList.reverse()
: taskList;
}
},
methods: {
formatDate(value) {
if (!value) return "";
if (typeof value !== "number") return value;
const browserLocale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
const intlDateTime = new Intl.DateTimeFormat(browserLocale, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric"
});
return intlDateTime.format(new Date(value));
},
addNewTask(task) {
this.taskList.push({
task,
createdAt: Date.now(),
finishedAt: undefined
});
},
changeStatus(taskId) {
const task = this.taskList[taskId - 1];
if (task.finishedAt) {
task.finishedAt = undefined;
} else {
task.finishedAt = Date.now();
}
}
}
};
</script>
<template>
部分,我们需要为这些变量添加控制器。我们将创建三个复选框,直接通过v-model
指令与变量链接:<template>
<div id="app">
<current-time class="col-4" />
<task-input class="col-6" @add-task="addNewTask" />
<div class="col-12">
<div class="cardBox">
<div class="container">
<h2>My Tasks</h2>
<hr />
<div class="col-4">
<input
v-model="hideDone"
type="checkbox"
id="hideDone"
name="hideDone"
/>
<label for="hideDone">
Hide Done Tasks
</label>
</div>
<div class="col-4">
<input
v-model="reverse"
type="checkbox"
id="reverse"
name="reverse"
/>
<label for="reverse">
Reverse Order
</label>
</div>
<div class="col-4">
<input
v-model="sortById"
type="checkbox"
id="sortById"
name="sortById"
/>
<label for="sortById">
Sort By Id
</label>
</div>
<ul class="taskList">
<li
v-for="(taskItem, index) in displayList"
:key="`${index}_${Math.random()}`"
>
<input type="checkbox"
:checked="!!taskItem.finishedAt"
@input="changeStatus(taskItem.id)"
/>
#{{ taskItem.id }} - {{ taskItem.task }}
<span v-if="taskItem.finishedAt"> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
> npm run serve
computed
属性一起作为列表的缓存工作,并确保在元素操作中没有任何副作用。通过条件处理,可以通过变量更改过滤和排序规则,并且显示会实时更新:在filteredList
属性处,我们取出了baseList
属性,并返回了未完成的任务。当hideDone
变量为false
时,我们返回整个列表而不进行任何过滤。
在sortedList
属性处,我们对filteredList
属性上的任务进行了排序。当sortById
变量为true
时,列表按 ID 降序排序;当为false
时,按任务完成时间升序排序。
在displayList
属性处,当reverse
变量为true
时,最终列表被颠倒。
displayList
属性返回了被操作的数据的结果。computed
属性由用户屏幕上的复选框控制,因此用户可以完全控制他们可以看到什么以及如何看到它。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
找到有关Array.prototype.map
的更多信息。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
找到有关Array.prototype.filter
的更多信息。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
找到有关Array.prototype.sort
的更多信息。@vue/cli
@vue/cli-service-global
App.vue
文件中,我们将为已完成的任务的列表项添加一个条件类:<template>
<div id="app">
<current-time class="col-4" />
<task-input class="col-6" @add-task="addNewTask" />
<div class="col-12">
<div class="cardBox">
<div class="container">
<h2>My Tasks</h2>
<hr />
<div class="col-4">
<input
v-model="hideDone"
type="checkbox"
id="hideDone"
name="hideDone"
/>
<label for="hideDone">
Hide Done Tasks
</label>
</div>
<div class="col-4">
<input
v-model="reverse"
type="checkbox"
id="reverse"
name="reverse"
/>
<label for="reverse">
Reverse Order
</label>
</div>
<div class="col-4">
<input
v-model="sortById"
type="checkbox"
id="sortById"
name="sortById"
/>
<label for="sortById">
Sort By Id
</label>
</div>
<ul class="taskList">
<li
v-for="(taskItem, index) in displayList"
:key="`${index}_${Math.random()}`"
:class="!!taskItem.finishedAt ? 'taskDone' : ''"
>
<input type="checkbox"
:checked="!!taskItem.finishedAt"
@input="changeStatus(taskItem.id)"
/>
#{{ taskItem.id }} - {{ taskItem.task }}
<span v-if="taskItem.finishedAt"> |
Done at:
{{ formatDate(taskItem.finishedAt) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<style>
部分,我们将为taskDone
的 CSS 类创建 CSS 样式表类。我们需要让列表项之间有一个分隔符;然后,我们将使列表具有条纹样式;当它们被标记为完成时,背景将发生变化。要在行之间添加分隔符和条纹列表或斑马样式,我们需要添加一个 CSS 样式表规则,适用于我们列表的每个even nth-child
:<style scoped>
.taskList li {
list-style: none;
text-align: left;
padding: 5px 10px;
border-bottom: 1px solid rgba(0,0,0,0.15);
}
.taskList li:last-child {
border-bottom: 0px;
}
.taskList li:nth-child(even){
background-color: rgba(0,0,0,0.05);
}
</style>
<style>
部分的末尾添加 CSS 动画关键帧,指示背景颜色变化,并将此动画应用于.taskDone
CSS 类,以在任务完成时添加背景效果<style scoped>
.taskList li {
list-style: none;
text-align: left;
padding: 5px 10px;
border-bottom: 1px solid rgba(0,0,0,0.15);
}
.taskList li:last-child {
border-bottom: 0px;
}
.taskList li:nth-child(even){
background-color: rgba(0,0,0,0.05);
}
@keyframes colorChange {
from{
background-color: inherit;
}
to{
background-color: rgba(0, 160, 24, 0.577);
}
}
.taskList li.taskDone{
animation: colorChange 1s ease;
background-color: rgba(0, 160, 24, 0.577);
}
</style>
> npm run serve
displayList
属性都会更新并触发组件的重新渲染。taskDone
CSS 类附加了一个在渲染时执行的动画,显示绿色背景。developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations
找到有关 CSS 动画的更多信息。v3.vuejs.org/guide/class-and-style.html
找到有关类和样式绑定的更多信息vue-devtools
对于每个 Vue 开发人员都是必不可少的。这个工具向我们展示了 Vue 组件、路由、事件和 vuex 的深度。vue-devtools
扩展程序,可以调试我们的应用程序,在更改代码之前尝试新数据,执行函数而无需直接在代码中调用它们,等等。@vue/cli
@vue/cli-service-global
vue-devtools
扩展程序:Chrome 扩展程序-bit.ly/chrome-vue-devtools
Firefox 扩展程序-bit.ly/firefox-vue-devtools
vue-devtools
进行开发。vue-devtools
以及如何正确调试 Vue 应用程序:vue-devtools
,首先需要在浏览器中安装它,所以请查看本教程的“准备就绪”部分,获取 Chrome 或 Firefox 的扩展链接。在 Vue 开发应用程序中,进入浏览器开发者检查器模式。一个名为 Vue 的新标签页必须出现:vuelidate
,vue-router
或vuex
)注入的额外数据。您可以编辑数据以实时查看应用程序中的更改:vue-devtools
的刷新按钮。有时,当hot-module-reload
发生或者应用程序组件树中发生一些复杂事件时,扩展程序可能会失去对发生情况的跟踪。这个按钮强制扩展程序重新加载并再次读取 Vue 应用程序状态。github.com/vuejs/vue-devtools
找到有关vue-devtools
的更多信息。创建一个可视化模板组件
使用插槽和命名插槽将数据放入您的组件中
将数据传递给您的组件并验证数据
创建功能性组件
访问您的子组件数据
创建一个动态注入的组件
创建一个依赖注入组件
创建一个组件mixin
延迟加载您的组件
windows-build-tools
的 NPM 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:npm install -g windows-build-tools
> npm install -g @vue/cli @vue/cli-service-global
div
HTML 元素的作用域 CSS,或者它可以是一个更复杂的组件,可以实时计算元素在屏幕上的位置。@vue/cli
@vue/cli-service-global
> vue create visual-component
default
选项:? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
让我们在src/components
文件夹中创建一个名为MaterialCardBox.vue
的新文件。
在这个文件中,我们将从组件的模板开始。我们需要为卡片创建一个框。通过使用 Material Design 指南,这个框将有阴影和圆角:
<template>
<div class="cardBox elevation_2">
<div class="section">
This is a Material Card Box
</div>
</div>
</template>
<script>
部分中,我们将只添加我们的基本名称:<script>
export default {
name: 'MaterialCardBox',
};
</script>
style
文件夹中创建一个名为elevation.css
的文件。在那里,我们将创建从0
到24
的高程,以遵循 Material Design 指南上的所有高程:.elevation_0 {
border: 1px solid rgba(0, 0, 0, 0.12);
}
.elevation_1 {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2),
0 1px 1px rgba(0, 0, 0, 0.14),
0 2px 1px -1px rgba(0, 0, 0, 0.12);
}
.elevation_2 {
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2),
0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.elevation_3 {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2),
0 3px 4px rgba(0, 0, 0, 0.14),
0 3px 3px -2px rgba(0, 0, 0, 0.12);
}
.elevation_4 {
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2),
0 4px 5px rgba(0, 0, 0, 0.14),
0 1px 10px rgba(0, 0, 0, 0.12);
}
.elevation_5 {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 5px 8px rgba(0, 0, 0, 0.14),
0 1px 14px rgba(0, 0, 0, 0.12);
}
.elevation_6 {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 6px 10px rgba(0, 0, 0, 0.14),
0 1px 18px rgba(0, 0, 0, 0.12);
}
.elevation_7 {
box-shadow: 0 4px 5px -2px rgba(0, 0, 0, 0.2),
0 7px 10px 1px rgba(0, 0, 0, 0.14),
0 2px 16px 1px rgba(0, 0, 0, 0.12);
}
.elevation_8 {
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.elevation_9 {
box-shadow: 0 5px 6px -3px rgba(0, 0, 0, 0.2),
0 9px 12px 1px rgba(0, 0, 0, 0.14),
0 3px 16px 2px rgba(0, 0, 0, 0.12);
}
.elevation_10 {
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, 0.2),
0 10px 14px 1px rgba(0, 0, 0, 0.14),
0 4px 18px 3px rgba(0, 0, 0, 0.12);
}
.elevation_11 {
box-shadow: 0 6px 7px -4px rgba(0, 0, 0, 0.2),
0 11px 15px 1px rgba(0, 0, 0, 0.14),
0 4px 20px 3px rgba(0, 0, 0, 0.12);
}
.elevation_12 {
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 12px 17px 2px rgba(0, 0, 0, 0.14),
0 5px 22px 4px rgba(0, 0, 0, 0.12);
}
.elevation_13 {
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
}
.elevation_14 {
box-shadow: 0 7px 9px -4px rgba(0, 0, 0, 0.2),
0 14px 21px 2px rgba(0, 0, 0, 0.14),
0 5px 26px 4px rgba(0, 0, 0, 0.12);
}
.elevation_15 {
box-shadow: 0 8px 9px -5px rgba(0, 0, 0, 0.2),
0 15px 22px 2px rgba(0, 0, 0, 0.14),
0 6px 28px 5px rgba(0, 0, 0, 0.12);
}
.elevation_16 {
box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2),
0 16px 24px 2px rgba(0, 0, 0, 0.14),
0 6px 30px 5px rgba(0, 0, 0, 0.12);
}
.elevation_17 {
box-shadow: 0 8px 11px -5px rgba(0, 0, 0, 0.2),
0 17px 26px 2px rgba(0, 0, 0, 0.14),
0 6px 32px 5px rgba(0, 0, 0, 0.12);
}
.elevation_18 {
box-shadow: 0 9px 11px -5px rgba(0, 0, 0, 0.2),
0 18px 28px 2px rgba(0, 0, 0, 0.14),
0 7px 34px 6px rgba(0, 0, 0, 0.12);
}
.elevation_19 {
box-shadow: 0 9px 12px -6px rgba(0, 0, 0, 0.2),
0 19px 29px 2px rgba(0, 0, 0, 0.14),
0 7px 36px 6px rgba(0, 0, 0, 0.12);
}
.elevation_20 {
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, 0.2),
0 20px 31px 3px rgba(0, 0, 0, 0.14),
0 8px 38px 7px rgba(0, 0, 0, 0.12);
}
.elevation_21 {
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, 0.2),
0 21px 33px 3px rgba(0, 0, 0, 0.14),
0 8px 40px 7px rgba(0, 0, 0, 0.12);
}
.elevation_22 {
box-shadow: 0 10px 14px -6px rgba(0, 0, 0, 0.2),
0 22px 35px 3px rgba(0, 0, 0, 0.14),
0 8px 42px 7px rgba(0, 0, 0, 0.12);
}
.elevation_23 {
box-shadow: 0 11px 14px -7px rgba(0, 0, 0, 0.2),
0 23px 36px 3px rgba(0, 0, 0, 0.14),
0 9px 44px 8px rgba(0, 0, 0, 0.12);
}
.elevation_24 {
box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2),
0 24px 38px 3px rgba(0, 0, 0, 0.14),
0 9px 46px 8px rgba(0, 0, 0, 0.12);
}
<style>
部分中设置样式,我们需要在<style>
标签内设置scoped
属性,以确保视觉样式不会干扰应用程序中的任何其他组件。我们将使这张卡遵循 Material Design 指南。我们需要导入Roboto
字体系列并将其应用于将包装在此组件内的所有元素:<style scoped>
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap');
@import '../style/elevation.css';
*{
font-family: 'Roboto', sans-serif;
}
.cardBox{
width: 100%;
max-width: 300px;
background-color: #fff;
position: relative;
display: inline-block;
border-radius: 0.25rem;
}
.cardBox > .section {
padding: 1rem;
position: relative;
}
</style>
> npm run serve
vue-loader.vuejs.org/guide/scoped-css.html#child-component-root-elements
找到有关作用域 CSS 的更多信息。material.io/components/cards/
找到有关 Material Design 卡片的更多信息。fonts.google.com/specimen/Roboto
上查看 Roboto 字体系列。@vue/cli
@vue/cli-service-global
让我们打开组件文件夹中的名为MaterialCardBox.vue
的文件。
在组件的<template>
部分,我们需要在卡片上添加四个主要部分。这些部分基于 Material Design 卡片解剖学,分别是header
、media
、main section
和action
区域。我们将使用默认插槽来放置main section
,其余部分都将是命名作用域。对于一些命名插槽,我们将添加一个备用配置,如果用户没有在插槽上选择任何设置,将显示该配置:
<template>
<div class="cardBox elevation_2">
<div class="header">
<slot
v-if="$slots.header"
name="header"
/>
<div v-else>
<h1 class="cardHeader cardText">
Card Header
</h1>
<h2 class="cardSubHeader cardText">
Card Sub Header
</h2>
</div>
</div>
<div class="media">
<slot
v-if="$slots.media"
name="media"
/>
<img
v-else
src="https://via.placeholder.com/350x250"
>
</div>
<div
v-if="$slots.default"
class="section cardText"
:class="{
noBottomPadding: $slots.action,
halfPaddingTop: $slots.media,
}"
>
<slot />
</div>
<div
v-if="$slots.action"
class="action"
>
<slot name="action" />
</div>
</div>
</template>
style
文件夹中,创建一个名为cardStyles.css
的新文件,在那里我们将添加卡片文本和标题的规则:h1, h2, h3, h4, h5, h6{
margin: 0;
}
.cardText{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-decoration: inherit;
text-transform: inherit;
font-size: 0.875rem;
line-height: 1.375rem;
letter-spacing: 0.0071428571em;
}
h1.cardHeader{
font-size: 1.25rem;
line-height: 2rem;
font-weight: 500;
letter-spacing: .0125em;
}
h2.cardSubHeader{
font-size: .875rem;
line-height: 1.25rem;
font-weight: 400;
letter-spacing: .0178571429em;
opacity: .6;
}
<style>
部分,我们需要创建一些 CSS 样式表来遵循我们的设计指南的规则:<style scoped>
@import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap");
@import "../style/elevation.css";
@import "../style/cardStyles.css";
* {
font-family: "Roboto", sans-serif;
}
.cardBox {
width: 100%;
max-width: 300px;
border-radius: 0.25rem;
background-color: #fff;
position: relative;
display: inline-block;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2), 0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.cardBox > .header {
padding: 1rem;
position: relative;
display: block;
}
.cardBox > .media {
overflow: hidden;
position: relative;
display: block;
max-width: 100%;
}
.cardBox > .section {
padding: 1rem;
position: relative;
margin-bottom: 1.5rem;
display: block;
}
.cardBox > .action {
padding: 0.5rem;
position: relative;
display: block;
}
.cardBox > .action > *:not(:first-child) {
margin-left: 0.4rem;
}
.noBottomPadding {
padding-bottom: 0 !important;
}
.halfPaddingTop {
padding-top: 0.5rem !important;
}
</style>
src
文件夹中的App.vue
文件中,我们需要向这些插槽添加元素。这些元素将被添加到每个命名插槽和默认插槽中。我们将更改文件的<template>
部分中的组件。要添加命名插槽,我们需要使用一个名为v-slot:
的指令,然后是我们想要使用的插槽的名称:<template>
<div id="app">
<MaterialCardBox>
<template v-slot:header>
<strong>Card Title</strong><br>
<span>Card Sub-Title</span>
</template>
<template v-slot:media>
<img src="https://via.placeholder.com/350x150">
</template>
<p>Main Section</p>
<template v-slot:action>
<button>Action Button</button>
<button>Action Button</button>
</template>
</MaterialCardBox>
</div>
</template>
<slot />
部分中。> npm run serve
.vue
)的<template>
部分中向该组件放置任何信息,您需要添加v-slot:
指令,以便 Vue 能够知道在何处放置传递下来的信息。vuejs.org/v2/guide/components-slots.html
找到有关 Vue 插槽的更多信息。material.io/components/cards/#anatomy
找到有关 Material Design 卡片解剖的更多信息。@vue/cli
@vue/cli-service-global
让我们在src/components
文件夹中打开名为MaterialCardBox.vue
的文件。
在组件的<script>
部分,我们创建一个名为props
的新属性。该属性接收组件数据,该数据可以用于视觉操作、代码内的变量或需要执行的函数。在此属性中,我们需要声明属性的名称、类型、是否必需以及验证函数。此函数将在运行时执行,以验证传递的属性是否有效:
<script>
export default {
name: 'MaterialCardBox',
inheritAttrs: false,
props: {
header: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
subHeader: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
mainText: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
showMedia: {
type: Boolean,
required: false,
default: false,
validator: v => typeof v === 'boolean',
},
imgSrc: {
type: String,
required: false,
default: '',
validator: v => typeof v === 'string',
},
showActions: {
type: Boolean,
required: false,
default: false,
validator: v => typeof v === 'boolean',
},
elevation: {
type: Number,
required: false,
default: 2,
validator: v => typeof v === 'number',
},
},
computed: {},
};
</script>
<script>
部分的computed
属性中,我们需要创建一组用于呈现卡片的视觉操作规则。这些规则将是showMediaContent
、showActionsButtons
、showHeader
和cardElevation
。每个规则将检查接收到的props
和$slots
对象,以查看是否需要呈现相关的卡片部分: computed: {
showMediaContent() {
return (this.$slots.media || this.imgSrc) && this.showMedia;
},
showActionsButtons() {
return this.showActions && this.$slots.action;
},
showHeader() {
return this.$slots.header || (this.header || this.subHeader);
},
showMainContent() {
return this.$slots.default || this.mainText;
},
cardElevation() {
return `elevation_${parseInt(this.elevation, 10)}`;
},
},
<template>
部分。它们将影响我们卡片的外观和行为。例如,如果没有定义头部插槽,并且定义了头部属性,我们将显示备用头部。该头部是通过props
传递下来的数据:<template>
<div
class="cardBox"
:class="cardElevation"
>
<div
v-if="showHeader"
class="header"
>
<slot
v-if="$slots.header"
name="header"
/>
<div v-else>
<h1 class="cardHeader cardText">
{{ header }}
</h1>
<h2 class="cardSubHeader cardText">
{{ subHeader }}
</h2>
</div>
</div>
<div
v-if="showMediaContent"
class="media"
>
<slot
v-if="$slots.media"
name="media"
/>
<img
v-else
:src="imgSrc"
>
</div>
<div
v-if="showMainContent"
class="section cardText"
:class="{
noBottomPadding: $slots.action,
halfPaddingTop: $slots.media,
}"
>
<slot v-if="$slots.default" />
<p
v-else
class="cardText"
>
{{ mainText }}
</p>
</div>
<div
v-if="showActionsButtons"
class="action"
>
<slot
v-if="$slots.action"
name="action"
/>
</div>
</div>
</template>
> npm run serve
vue-template-compiler
将获取这些属性并将它们转换为 JavaScript 对象。vuejs.org/v2/guide/components-props.html
找到有关 props
的更多信息。vue-loader.vuejs.org/guide/
找到有关 vue-template-compiler
的更多信息。@vue/cli
@vue/cli-service-global
在src/components
文件夹中创建一个名为MaterialButton.vue
的新文件。
在这个组件中,我们需要验证我们将接收的 prop 是否是有效的颜色。为此,在项目中安装is-color
模块。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save is-color
<script>
部分,我们需要创建功能组件将接收的props
对象。由于功能组件只是一个没有状态的渲染函数,<script>
部分被简化为props
、injections
和slots
。将有四个props
对象:backgroundColor
、textColor
、isRound
和isFlat
。在安装组件时,这些不是必需的,因为我们在props
中定义了默认值:<script>
import isColor from 'is-color';
export default {
name: 'MaterialButton',
props: {
backgroundColor: {
type: String,
required: false,
default: '#fff',
validator: v => typeof v === 'string' && isColor(v),
},
textColor: {
type: String,
required: false,
default: '#000',
validator: v => typeof v === 'string' && isColor(v),
},
isRound: {
type: Boolean,
required: false,
default: false,
},
isFlat: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
部分,我们首先需要向<template>
标签添加functional
属性,以指示vue-template-compiler
这个组件是一个功能组件。我们需要创建一个按钮 HTML 元素,带有基本的class
属性按钮和一个基于props
对象接收的动态class
属性。与普通组件不同,我们需要指定props
属性以使用功能组件。对于按钮的样式,我们需要创建一个基于props
的动态style
属性。为了直接将所有事件监听器传递给父组件,我们可以调用v-on
指令并传递listeners
属性。这将绑定所有事件监听器,而无需声明每一个。在按钮内部,我们将添加一个用于视觉增强的div
HTML 元素,并添加<slot>
,文本将放置在其中:<template functional>
<button
tabindex="0"
class="button"
:class="{
round: props.isRound,
isFlat: props.isFlat,
}"
:style="{
background: props.backgroundColor,
color: props.textColor
}"
v-on="listeners"
>
<div
tabindex="-1"
class="button_focus_helper"
/>
<slot/>
</button>
</template>
<style>
部分,我们需要为这个按钮创建所有的 CSS 样式表规则。我们需要向<style>
添加scoped
属性,以便所有的 CSS 样式表规则不会影响我们应用程序中的任何其他元素:<style scoped>
.button {
user-select: none;
position: relative;
outline: 0;
border: 0;
border-radius: 0.25rem;
vertical-align: middle;
cursor: pointer;
padding: 4px 16px;
font-size: 14px;
line-height: 1.718em;
text-decoration: none;
color: inherit;
background: transparent;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
min-height: 2.572em;
font-weight: 500;
text-transform: uppercase;
}
.button:not(.isFlat){
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2),
0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}
.button:not(.isFlat):focus:before,
.button:not(.isFlat):active:before,
.button:not(.isFlat):hover:before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: inherit;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
}
.button:not(.isFlat):focus:before,
.button:not(.isFlat):active:before,
.button:not(.isFlat):hover:before {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 5px 8px rgba(0, 0, 0, 0.14),
0 1px 14px rgba(0, 0, 0, 0.12);
}
.button_focus_helper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
border-radius: inherit;
outline: 0;
opacity: 0;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5, 1),
opacity 0.4s cubic-bezier(0.25, 0.8, 0.5, 1);
}
.button_focus_helper:after, .button_focus_helper:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
border-radius: inherit;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5, 1),
opacity 0.6s cubic-bezier(0.25, 0.8, 0.5, 1);
}
.button_focus_helper:before {
background: #000;
}
.button_focus_helper:after {
background: #fff;
}
.button:focus .button_focus_helper:before,
.button:hover .button_focus_helper:before {
opacity: .1;
}
.button:focus .button_focus_helper:after,
.button:hover .button_focus_helper:after {
opacity: .6;
}
.button:focus .button_focus_helper,
.button:hover .button_focus_helper {
opacity: 0.2;
}
.round {
border-radius: 50%;
}
</style>
> npm run serve
render()
函数在 Vue 中引入;后来,它们被添加到了vue-template-compiler
中,用于 Vue 单文件应用程序。createElement
和context
。正如我们在单文件中看到的,我们只能访问元素,因为它们不在 JavaScript 对象的this
属性中。这是因为当上下文传递给渲染函数时,就没有this
属性。vuejs.org/v2/guide/render-function.html#Functional-Components
找到有关功能组件的更多信息。www.npmjs.com/package/is-color
找到有关is-color
模块的更多信息。@vue/cli
@vue/cli-service-global
StarRatingInput
、StarRatingDisplay
和StarRating
——最后一部分将涵盖数据和函数访问的父子直接操作。在src/components
文件夹中创建一个名为StarRatingInput.vue
的新文件。
在组件的<script>
部分,在props
属性中创建一个maxRating
属性,它是一个数字,非必需,并且默认值为5
。在data
属性中,我们需要创建我们的rating
属性,其默认值为0
。在methods
属性中,我们需要创建三个方法:updateRating
、emitFinalVoting
和getStarName
。updateRating
方法将保存评分到数据中,emitFinalVoting
将调用updateRating
并通过final-vote
事件将评分传递给父组件,getStarName
将接收一个值并返回星级的图标名称。
<script>
export default {
name: 'StarRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
data: () => ({
rating: 0,
}),
methods: {
updateRating(value) {
this.rating = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rating);
},
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
<template>
部分,我们需要创建一个<slot>
组件来放置星级评分之前的文本。我们将根据通过props
属性接收到的maxRating
值创建一个动态星级列表。创建的每个星级都将在mouseenter
、focus
和click
事件上附加一个监听器。当触发mouseenter
和focus
时,将调用updateRating
方法,而click
将调用emitFinalVote
方法。<template>
<div class="starRating">
<span class="rateThis">
<slot />
</span>
<ul>
<li
v-for="rate in maxRating"
:key="rate"
@mouseenter="updateRating(rate)"
@click="emitFinalVote(rate)"
@focus="updateRating(rate)"
>
<i class="material-icons">
{{ getStarName(rate) }}
</i>
</li>
</ul>
</div>
</template>
styles
文件夹中创建一个名为materialIcons.css
的新样式文件,并添加font-family
的 CSS 样式规则。@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-
Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons' !important;
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
main.js
文件,并将创建的样式表导入其中。css-loader
将处理 JavaScript 文件中导入的.css
文件的处理。这将有助于开发,因为您不需要在其他地方重新导入文件。import Vue from 'vue';
import App from './App.vue';
import './style/materialIcons.css';
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
src/style
文件夹中创建一个名为starRating.css
的通用样式文件。在那里,我们将添加StarRatingDisplay
和StarRatingInput
组件之间共享的通用样式。.starRating {
user-select: none;
display: flex;
flex-direction: row;
}
.starRating * {
line-height: 0.9rem;
}
.starRating .material-icons {
font-size: .9rem !important;
color: orange;
}
ul {
display: inline-block;
padding: 0;
margin: 0;
}
ul > li {
list-style: none;
float: left;
}
<style>
部分,我们需要创建所有的 CSS 样式表规则。然后,在位于src/components
文件夹中的StarRatingInput.vue
组件文件上,我们需要向<style>
添加scoped
属性,以便所有的 CSS 样式表规则不会影响应用程序中的任何其他元素。在这里,我们将导入我们创建的通用样式,并为输入添加新样式:<style scoped>
@import '../style/starRating.css';
.starRating {
justify-content: space-between;
}
.starRating * {
line-height: 1.7rem;
}
.starRating .material-icons {
font-size: 1.6rem !important;
}
.rateThis {
display: inline-block;
color: rgba(0, 0, 0, .65);
font-size: 1rem;
}
</style>
> npm run serve
StarRatingDisplay
组件:在src/components
文件夹中创建一个名为StarRatingDisplay.vue
的新组件。
在组件的<script>
部分,在props
属性中,我们需要创建三个新属性:maxRating
,rating
和votes
。它们三个都将是数字,非必需的,并且有默认值。在methods
属性中,我们需要创建一个名为getStarName
的新方法,它将接收一个值并返回星星的图标名称:
<script>
export default {
name: 'StarRatingDisplay',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
<template>
中,我们需要根据通过props
属性接收到的maxRating
值创建一个动态星星列表。在列表之后,我们需要显示我们收到的投票数,如果我们收到任何投票,我们也会显示它们:<template>
<div class="starRating">
<ul>
<li
v-for="rate in maxRating"
:key="rate"
>
<i class="material-icons">
{{ getStarName(rate) }}
</i>
</li>
</ul>
<span class="rating">
{{ rating }}
</span>
<span
v-if="votes"
class="votes"
>
({{ votes }})
</span>
</div>
</template>
<style>
部分,我们需要创建所有的 CSS 样式表规则。我们需要向<style>
添加scoped
属性,以便所有的 CSS 样式表规则不会影响应用程序中的任何其他元素。在这里,我们将导入我们创建的通用样式,并为显示添加新样式:<style scoped>
@import '../style/starRating.css';
.rating, .votes {
display: inline-block;
color: rgba(0,0,0, .65);
font-size: .75rem;
margin-left: .4rem;
}
</style>
> npm run serve
StarRating
组件:在src/components
文件夹中创建一个名为StarRating.vue
的新文件。
在组件的<script>
部分,我们需要导入StarRatingDisplay
和StarRatingInput
组件。在props
属性中,我们需要创建三个新属性:maxRating
,rating
和votes
。它们三个都将是数字,非必需的,并且有一个默认值。在data
属性中,我们需要创建我们的rating
属性,其默认值为0
,并且一个名为voted
的属性,其默认值为false
。在methods
属性中,我们需要添加一个名为vote
的新方法,它将接收rank
作为参数。它将把rating
定义为接收到的值,并将voted
组件的内部变量定义为true
:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
<template>
部分,我们将放置两个组件,显示评分的输入:<template>
<div>
<StarRatingInput
v-if="!voted"
:max-rating="maxRating"
@final-vote="vote"
>
Rate this Place
</StarRatingInput>
<StarRatingDisplay
v-else
:max-rating="maxRating"
:rating="rating || rank"
:votes="votes"
/>
</div>
</template>
在App.vue
文件中,在组件的<template>
部分,删除MaterialCardBox
组件的main-text
属性,并将其放置为组件的默认插槽。
在放置的文本之前,我们将添加StarRating
组件。我们将为其添加一个ref
属性。此属性将指示 Vue 将此组件直接链接到组件的this
对象中的一个特殊属性。在操作按钮中,我们将为点击事件添加监听器——一个用于resetVote
,另一个用于forceVote
。
<template>
<div id="app">
<MaterialCardBox
header="Material Card Header"
sub-header="Card Sub Header"
show-media
show-actions
img-src="https://picsum.photos/300/200"
>
<p>
<StarRating
ref="starRating"
/>
</p>
<p>
The path of the righteous man is beset on all sides by the
iniquities of the selfish and the tyranny of evil men.
</p>
<template v-slot:action>
<MaterialButton
background-color="#027be3"
text-color="#fff"
@click="resetVote"
>
Reset
</MaterialButton>
<MaterialButton
background-color="#26a69a"
text-color="#fff"
is-flat
@click="forceVote"
>
Rate 5 Stars
</MaterialButton>
</template>
</MaterialCardBox>
</div>
</template>
<script>
部分,我们将创建一个methods
属性,并添加两个新方法:resetVote
和forceVote
。这些方法将访问StarRating
组件并重置数据或将数据设置为 5 星投票:<script>
import MaterialCardBox from './components/MaterialCardBox.vue';
import MaterialButton from './components/MaterialButton.vue';
import StarRating from './components/StarRating.vue';
export default {
name: 'App',
components: {
StarRating,
MaterialButton,
MaterialCardBox,
},
methods: {
resetVote() {
this.$refs.starRating.rank = 0;
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.rank = 5;
this.$refs.starRating.voted = true;
},
},
};
</script>
ref
属性添加到组件时,Vue 会将对所引用元素的链接添加到 JavaScript 的this
属性对象内的$refs
属性中。从那里,您可以完全访问组件。this
对象上调用$parent
来访问父组件。事件可以通过调用$root
属性来访问 Vue 应用程序的根元素。vuejs.org/v2/guide/components-edge-cases.html#Accessing-the-Parent-Component-Instance
找到有关父子通信的更多信息。v-if
、v-else-if
和v-else
指令的情况下即时更改组件。@vue/cli
@vue/cli-service-global
打开StarRating.vue
组件。
在组件的<script>
部分,我们需要创建一个带有名为starComponent
的新计算值的computed
属性。此值将检查用户是否已投票。如果他们没有,它将返回StarRatingInput
组件;否则,它将返回StarRatingDisplay
组件:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
<template>
部分,我们将删除现有组件,并用一个名为<component>
的特殊组件替换它们。这个特殊组件有一个命名属性,您可以指向任何返回有效 Vue 组件的地方。在我们的例子中,我们将指向计算属性starComponent
。我们将把从这两个组件中定义的所有绑定 props 放在这个新组件中,包括放在<slot>
中的文本:<template>
<component
:is="starComponent"
:max-rating="maxRating"
:rating="rating || rank"
:votes="votes"
@final-vote="vote"
>
Rate this Place
</component>
</template>
<component>
组件,我们声明了根据计算属性设置的规则应该呈现什么组件。v-bind
指令与需要定义的 props 和规则,但也可以直接在组件上定义,因为它将作为 prop 传递下去。vuejs.org/v2/guide/components.html#Dynamic-Components
找到有关动态组件的更多信息。@vue/cli
@vue/cli-service-global
打开StarRating.vue
组件。
在组件的<script>
部分,添加一个名为provide
的新属性。在我们的情况下,我们将只添加一个键值来检查组件是否是特定组件的子级。在属性中创建一个对象,其中包含starRating
键和true
值:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
provide: {
starRating: true,
},
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
打开StarRatingDisplay.vue
文件。
在组件的<script>
部分,我们将添加一个名为inject
的新属性。此属性将接收一个名为starRating
的键的对象,值将是一个具有default()
函数的对象。如果此组件不是StarRating
组件的子级,则此函数将记录错误:
<script>
export default {
name: 'StarRatingDisplay',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingDisplay need to be a child of
StarRating');
},
},
},
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
打开StarRatingInput.vue
文件。
在组件的<script>
部分,我们将添加一个名为inject
的新属性。此属性将接收一个名为starRating
的键的对象,值将是一个具有default()
函数的对象。如果此组件不是StarRating
组件的子级,则此函数将记录错误:
<script>
export default {
name: 'StarRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingInput need to be a child of
StarRating');
},
},
},
data: () => ({
rating: 0,
}),
methods: {
updateRating(value) {
this.rating = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rating);
},
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
StarRatingDisplay
和StarRatingInput
组件中的starRating
的注入属性,如果父组件未提供此值,则将在控制台上记录错误。vuejs.org/v2/guide/components-edge-cases.html#Dependency-Injection
找到有关组件依赖注入的更多信息。mixin
,这是 Vue 中的一个特殊代码导入,它将外部代码部分连接到当前组件。@vue/cli
@vue/cli-service-global
mixin
:打开StarRating.vue
组件。
在<script>
部分,我们需要将props
属性提取到一个名为starRatingDisplay.js
的新文件中,我们需要在mixins
文件夹中创建这个新文件。这个新文件将是我们的第一个mixin
,并且看起来像这样:
export default {
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
};
StarRating.vue
组件,我们需要导入这个新创建的文件,并将其添加到一个名为mixin
的新属性中:<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
import StarRatingDisplayMixin from '../mixins/starRatingDisplay';
export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
mixins: [StarRatingDisplayMixin],
provide: {
starRating: true,
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
现在,我们将打开StarRatingDisplay.vue
文件。
在<script>
部分,我们将inject
属性提取到一个名为starRatingChild.js
的新文件中,该文件将被创建在mixins
文件夹中。这将是我们inject
属性的mixin
:
export default {
inject: {
starRating: {
default() {
console.error('StarRatingDisplay need to be a child of
StarRating');
},
},
},
};
StarRatingDisplay.vue
文件中,在<script>
部分,我们将提取methods
属性到一个名为starRatingName.js
的新文件中,该文件将被创建在mixins
文件夹中。这将是我们getStarName
方法的mixin
:export default {
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
StarRatingDisplay.vue
文件,我们需要导入这些新创建的文件,并将它们添加到一个名为mixin
的新属性中:<script>
import StarRatingDisplayMixin from '../mixins/starRatingDisplay';
import StarRatingNameMixin from '../mixins/starRatingName';
import StarRatingChildMixin from '../mixins/starRatingChild';
export default {
name: 'StarRatingDisplay',
mixins: [
StarRatingDisplayMixin,
StarRatingNameMixin,
StarRatingChildMixin,
],
};
</script>
打开StarRatingInput.vue
文件。
在<script>
部分,我们移除inject
属性,并将props
属性提取到一个名为starRatingBase.js
的新文件中,该文件将被创建在mixins
文件夹中。这将是我们props
属性的mixin
:
export default {
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
},
};
StarRatingInput.vue
文件,我们需要将rating
数据属性重命名为rank
,并且在getStarName
方法中,我们需要添加一个新的常量,该常量将接收rating
属性或rank
数据。最后,我们需要导入starRatingChild
mixin
和starRatingBase
mixin
:<script>
import StarRatingBaseMixin from '../mixins/starRatingBase';
import StarRatingChildMixin from '../mixins/starRatingChild';
export default {
name: 'StarRatingInput',
mixins: [
StarRatingBaseMixin,
StarRatingChildMixin,
],
data: () => ({
rank: 0,
}),
methods: {
updateRating(value) {
this.rank = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rank);
},
getStarName(rate) {
const rating = (this.rating || this.rank);
if (rate <= rating) {
return 'star';
}
if (Math.fround((rate - rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
mixins
的工作原理就像对象合并一样,但确保不要用导入的属性替换组件中已经存在的属性。mixins
属性的顺序也很重要,因为它们将被检查并作为for
循环导入,所以最后一个mixin
不会改变任何祖先的属性。vuejs.org/v2/guide/mixins.html
找到有关 mixins 的更多信息。webpack
和 Vue 天生就是一对。当使用webpack
作为 Vue 项目的打包工具时,可以使组件在需要时或异步加载。这通常被称为惰性加载。@vue/cli
@vue/cli-service-global
打开App.vue
文件。
在组件的<script>
部分,我们将在脚本顶部获取导入并将它们转换为每个组件的惰性加载函数:
<script>
export default {
name: 'App',
components: {
StarRating: () => import('./components/StarRating.vue'),
MaterialButton: () => import('./components/MaterialButton.vue'),
MaterialCardBox: () =>
import('./components/MaterialCardBox.vue'),
},
methods: {
resetVote() {
this.$refs.starRating.rank = 0;
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.rank = 5;
this.$refs.starRating.voted = true;
},
},
};
</script>
import()
函数的函数时,webpack
知道这个导入函数将进行代码拆分,并且它将使组件成为捆绑包中的一个新文件。import()
函数是由 TC39 提出的一个模块加载语法的建议。这个函数的基本功能是异步加载任何声明为模块的文件,避免了在第一次加载时放置所有文件的需要。vuejs.org/v2/guide/components-dynamic-async.html#Async-Components
找到有关异步组件的更多信息。github.com/tc39/proposal-dynamic-import
找到有关 TC39 动态导入的更多信息。axios
来构建自己的 API 数据操作。创建一个 Fetch API 的 HTTP 客户端包装器
创建一个随机猫图片或 GIF 组件
使用MirageJS
创建本地虚拟 JSON API 服务器
使用axios
作为新的 HTTP 客户端
创建不同的axios
实例
为axios
创建请求和响应拦截器
使用axios
和Vuesax
创建 CRUD 接口
windows-build-tools
的 NPM 包,以便能够安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
> npm install -g @vue/cli @vue/cli-service-global
XMLHttpRequest
的子代。它有一个改进的 API 和一个基于Promises
的新而强大的功能集。Request
和Response
的通用定义,使其可以在浏览器中的任何地方使用。浏览器的 Fetch API 也可以在window
或service worker
中执行。对于这个 API 的使用没有限制。@vue/cli
@vue/cli-service-global
> vue create http-project
default
选项:? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
在src/http
文件夹中创建一个名为baseFetch.js
的新文件。
我们将创建一个异步函数,它将作为参数接收url
,method
和options
的三个变量。这将是一个柯里化函数,第二个函数将接收type
作为参数:
export default async (url, method, options = {}) => {
let httpRequest;
if (method.toUpperCase() === 'GET') {
httpRequest = await fetch(url, {
cache: 'reload',
...options,
});
} else {
httpRequest = fetch(url, {
method: method.toUpperCase(),
cache: 'reload',
...options,
});
}
return (type) => {
switch (type.toLocaleLowerCase()) {
case 'json':
return httpRequest.json();
case 'blob':
return httpRequest.blob();
case 'text':
return httpRequest.text();
case 'formdata':
return httpRequest.formData();
default:
return httpRequest.arrayBuffer();
}
}; };
在src/http
文件夹中创建一个名为fetchApi.js
的新文件。
我们需要从我们在第一步创建的文件中导入baseHttp
:
import baseHttp from './baseFetch';
getHttp
函数:创建一个名为getHttp
的常量。
定义一个常量作为一个异步函数,接收三个参数,url
,type
和options
。type
参数将默认值为'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
,'get'
作为第二个参数,options
作为第三个参数,并立即执行带有我们收到的type
参数的函数:
export const getHttp = async (url, type = 'json', options) => (await
baseHttp(url, 'get', options))(type);
postHttp
函数:创建一个名为postHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
参数和'post'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
参数执行返回的函数。body
通常是 JSON 或 JavaScript 对象。如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const postHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'post',
{
body,
...options,
}))(type);
putHttp
函数:创建一个名为putHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'put'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
参数执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const putHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'put',
{
body,
...options,
}))(type);
patchHttp
函数:创建一个名为patchHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'patch'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const patchHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'patch',
{
body,
...options,
}))(type);
updateHttp
函数:创建一个名为updateHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。type
参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'update'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const updateHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'update',
{
body,
...options,
}))(type);
deleteHttp
函数:创建一个名为deleteHttp
的常量。
将一个异步函数分配给该常量,该函数接收四个参数,url
、body
、type
和options
。类型参数将具有默认值'json'
。
在这个函数返回中,我们将执行baseHttp
函数,传递我们收到的url
和'delete'
作为第二个参数。在第三个参数中,我们将传递一个带有body
变量和我们收到的解构options
参数的对象。由于baseHttp
的柯里化属性,我们将使用收到的type
执行返回的函数。body
通常是 JSON 或 JavaScript 对象,但如果这个请求将是文件上传,body
需要是一个FormData
对象:
export const deleteHttp = async (
url,
body,
type = 'json',
options,
) => (await baseHttp(url,
'delete',
{
body,
...options,
}))(type);
window
元素上呈现的Fetch
API 创建了一个包装器。这个包装器由一个柯里化和闭包函数组成,第一个函数接收 Fetch API 的 URL 数据、方法和选项,而结果函数是 Fetch API 的响应转换器。fetch
请求。在那里,我们需要检查它是否是GET方法,所以我们只需要用url
参数执行它并省略其他参数。函数的第二部分负责将fetch
响应转换。它将在type
参数之间切换,并根据正确的参数执行检索函数。getHttp('https://jsonplaceholder.typicode.com/todos/1', 'json').then((response) => { console.log(response)); }
body
,但所有其他动词都能够在请求中传递body
。developer.mozilla.org/en-US/docs/Web/API/Fetch_API
找到有关 Fetch API 的更多信息。developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
找到有关 FormData 的更多信息。developer.mozilla.org/en-US/docs/Web/API/Body/body
找到有关 Fetch 响应主体的更多信息。 developer.mozilla.org/en-US/docs/Web/API/Headers
找到有关标头的更多信息。developer.mozilla.org/
en-US/docs/Web/API/Request找到有关请求的更多信息。@vue/cli
@vue/cli-service-global
> vue create http-project
default
选项:? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
<script>
、<template>
和<style>
。<script>
部分<script>
部分:在src/components
文件夹中创建一个名为RandomCat.vue
的新文件并打开它。
从我们在'将 Fetch API 包装为 HTTP 客户端创建包装器'教程中制作的fetchApi
包装器中导入getHttp
函数:
import { getHttp } from '../http/fetchApi';
component
属性中异步导入MaterialButton
和MaterialCardBox
组件:components: {
MaterialButton: () => import('./MaterialButton.vue'),
MaterialCardBox: () => import('./MaterialCardBox.vue'), },
data
属性中,我们需要创建一个名为kittyImage
的新数据值,默认为空字符串:data: () => ({
kittyImage: '', }),
methods
属性中,我们需要创建getImage
方法,它将以Blob
的形式获取图片,并将其作为URL.createObjectURL
返回。我们还需要创建newCatImage
方法,它将获取一张新的猫的静态图片,以及newCatGif
方法,它将获取一个新的猫的 GIF:methods: {
async getImage(url) {
return URL.createObjectURL(await getHttp(url, 'blob'));
},
async newCatImage() {
this.kittyImage = await this.getImage('https://cataas.com/cat');
},
async newCatGif() {
this.kittyImage = await
this.getImage('https://cataas.com/cat/gif');
},
},
beforeMount
生命周期钩子中,我们需要将其设置为异步,并执行newCatImage
方法:async beforeMount() {
await this.newCatImage();
},
<template>
部分<template>
部分:MaterialCardBox
组件,激活media
和action
部分,并为media
和action
创建<template>
命名插槽:<MaterialCardBox
header="Cat as a Service"
sub-header="Random Cat Image"
show-media
show-actions >
<template
v-slot:media> </template>
<template v-slot:action> </template> </MaterialCardBox>
<template>
中名为media
的插槽中,我们需要添加一个<img>
元素,它将接收一个 URIBlob
,当kittyImage
变量中有任何数据时,它将显示出来,否则将显示一个加载图标:<img
v-if="kittyImage"
alt="Meow!"
:src="kittyImage"
style="width: 300px;" >
<p v-else style="text-align: center">
<i class="material-icons">
cached
</i>
</p>
<template>
中名为action
的插槽中,我们将创建两个按钮,一个用于获取猫的图片,另一个用于获取猫的 GIF,两者都将在@click
指令上有一个事件监听器,调用一个函数来获取相应的图片:<MaterialButton
background-color="#4ba3c7"
text-color="#fff"
@click="newCatImage" >
<i class="material-icons">
pets
</i> Cat Image
</MaterialButton> <MaterialButton
background-color="#005b9f"
text-color="#fff"
@click="newCatGif" >
<i class="material-icons">
pets
</i> Cat GIF
</MaterialButton>
<style>
部分<style>
部分中,我们需要设置body font-size
以便基于rem
和em
进行 CSS 样式计算:<style>
body {
font-size: 14px;
} </style>
在src
文件夹中的App.vue
文件中打开。
在components
属性中,异步导入RandomCat.vue
组件:
<script> export default { name: 'App',
components: {
RandomCat: () => import('./components/RandomCat'),
}, }; </script>
<template>
部分中,声明导入的组件:<template>
<div id="app">
<random-cat />
</div> </template>
> npm run serve
getHttp
包装器,组件能够获取 URL 并将其作为Blob
类型检索出来。有了这个响应,我们可以使用URL.createObjectUrl
导航方法,并将Blob
作为参数传递,以获取一个有效的图像 URL,该 URL 可以用作src
属性。developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
找到有关URL.createObjectUrl
的更多信息。developer.mozilla.org/en-US/docs/Web/API/Body/blob
找到有关Blob
响应类型的更多信息。@vue/cli
@vue/cli-service-global
> vue create visual-component
default
选项:? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
fetchApi
包装器的getHttp
函数。MirageJS
模拟服务器:MirageJS
服务器安装到您的软件包中。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> npm install --save miragejs
MirageJS
的任何更改,因为目前还没有当前的 LTS 版本。MirageJS
数据库,用于存储临时数据。按照以下步骤创建它:在src/server
文件夹中创建一个名为db.js
的新文件,用于初始加载的数据。
我们需要为这个文件创建一个 JavaScript 对象作为默认导出,其中包含我们希望服务器具有的初始数据:
export default {
users: [
{ name: 'Heitor Ramon Ribeiro',
email: '[email protected]',
age: 31,
country: 'Brazil',
active: true,
},
], };
MirageJS
服务器模拟的HTTP GET方法。按照以下步骤创建它:对于GET方法,我们需要在src/server
文件夹中创建一个名为get.js
的新文件。
对于这个配方,我们将创建一个通用的getFrom
函数,该函数接收一个键作为参数并返回一个函数。这个返回的函数返回一个直接指向本地数据库的指定键:
export const getFrom = key => ({ db }) => db[key]; export default {
getFrom, };
MirageJS
服务器模拟。按照以下步骤创建它:对于POST方法,我们需要在src/server
文件夹中创建一个名为post.js
的新文件。
对于这个配方,我们将创建一个通用的postFrom
函数,该函数接收一个键作为参数并返回一个函数。这个返回的函数将解析 HTTP 请求体的data
属性,并返回服务器模式的内部函数,将数据插入数据库。使用key
参数,模式知道我们正在处理哪个表:
export const postFrom = key => (schema, request) => {
const { data } = typeof request.requestBody === 'string'
? JSON.parse(request.requestBody)
: request.requestBody; return schema.db[key].insert(data); }; export default {
postFrom, };
MirageJS
服务器模拟的HTTP PATCH方法。按照以下步骤创建它:对于PATCH方法,我们需要在src/server
文件夹中创建一个名为patch.js
的新文件。
对于这个配方,我们将制作一个通用的patchFrom
函数,该函数接收一个键作为参数并返回一个函数。返回的函数将解析 HTTP 请求体的data
属性,并返回一个服务器模式的内部函数,该函数更新具有与数据一起传递的id
属性的特定对象。使用key
参数,模式知道我们正在处理哪个表:
export const patchFrom = key => (schema, request) => {
const { data } = typeof request.requestBody === 'string'
? JSON.parse(request.requestBody)
: request.requestBody; return schema.db[key].update(data.id, data); }; export default {
patchFrom, };
MirageJS
服务器模拟的HTTP DELETE方法。按照以下步骤创建它:对于DELETE方法,我们需要在src/server
文件夹中创建一个名为delete.js
的新文件。
对于这个配方,我们将制作一个通用的patchFrom
函数,该函数接收一个键作为参数并返回一个函数。返回的函数将解析 HTTP 请求体的data
属性,并返回一个服务器模式的内部函数,该函数删除具有通过路由REST参数传递给服务器的id
属性的特定对象。使用key
参数,模式知道我们正在处理哪个表:
export const deleteFrom = key => (schema, request) =>
schema.db[key].remove(request.params.id); export default {
deleteFrom, };
MirageJS
服务器和可用的路由。按照以下步骤创建服务器:在src/server
文件夹中创建一个名为server.js
的新文件。
接下来,我们需要导入Server
类,baseData
和路由方法:
import { Server } from 'miragejs'; import baseData from './db'; import { getFrom } from './get'; import { postFrom } from './post'; import { patchFrom } from './patch'; import { deleteFrom } from './delete';
server
,并将此变量设置为Server
类的新执行:window.server = new Server({});
Server
类的构造选项中,添加一个名为seeds
的新属性。此属性是一个接收服务器(srv
)作为参数并执行srv.db.loadData
函数传递baseDate
作为参数的函数:seeds(srv) {
srv.db.loadData({ ...baseData }); },
routes
的新属性中,它将创建模拟服务器路由。这个属性是一个函数,在函数体内,我们需要设置模拟服务器的namespace
和服务器响应的毫秒延迟。将有四个路由。对于创建路由,我们将创建一个名为/users
的新路由,监听POST方法。对于读取路由,我们将创建一个名为/users
的新路由,监听GET方法。对于更新路由,我们将创建一个名为/users/:id
的新路由,监听PATCH方法,最后,对于删除路由,我们将创建一个名为/users
的新路由,监听DELETE方法:routes() {
this.namespace = 'api'; this.timing = 750; this.get('/users', getFrom('users')); this.post('/users', postFrom('users')); this.patch('/users/:id', patchFrom('users')); this.delete('/users/:id', deleteFrom('users'));
},
MirageJS
服务器添加到 Vue 应用程序中。按照以下步骤使服务器对您的 Vue 应用程序可用:在src
文件夹中打开main.js
文件。
我们需要将服务器声明为第一个导入声明,这样它就可以在应用程序的初始加载时可用:
import './server/server'; import Vue from 'vue'; import App from './App.vue'; Vue.config.productionTip = false; new Vue({
render: h => h(App), }).$mount('#app');
<script>
部分<script>
部分。按照以下步骤创建它:在src
文件夹中打开App.vue
文件。
从我们在'将 Fetch API 包装为 HTTP 客户端的创建包装器'中制作的fetchHttp
包装器中导入getHttp
,postHttp
,patchHttp
和deleteHTTP
方法:
import {
getHttp,
postHttp,
patchHttp,
deleteHttp, } from './http/fetchApi';
data
属性中,我们需要创建三个新属性来使用,response
,userData
和userId
:data: () => ({
response: undefined,
userData: '',
userId: undefined,
}),
methods
属性中,我们需要创建四个新方法,getAllUsers
,createUser
,updateUser
和deleteUser
:methods: {
async getAllUsers() {
},
async createUser() {
},
async updateUser() {
},
async deleteUser() {
}, },
getAllUsers
方法中,我们将设置响应数据属性为api/users
路由的getHttp
函数的结果:async getAllUsers() {
this.response = await getHttp(`${window.location.href}api/users`); },
createUser
方法中,我们将接收一个data
参数,这将是一个对象,我们将把它传递给api/users
路由上的postHttp
,然后执行getAllUsers
方法:async createUser(data) {
await postHttp(`${window.location.href}api/users`, { data });
await this.getAllUsers(); },
updateUser
方法,我们将接收一个data
参数,这将是一个对象,我们将其传递给patchHttp
,在api/users/:id
路由上使用对象上的id
属性作为路由上的:id
。之后,我们将执行getAllUsers
方法:async updateUser(data) {
await patchHttp(`${window.location.href}api/users/${data.id}`,
{ data });
await this.getAllUsers(); },
deleteUser
方法中,我们接收用户id
作为参数,该参数是一个数字,然后我们将其传递给deleteHttp
,在api/users/:id
路由上使用 ID 作为:id
。之后,我们执行getAllUsers
方法:async deleteUser(id) {
await deleteHttp(`${window.location.href}api/users/${id}`, {}, 'text');
await this.getAllUsers(); },
在这部分,我们将创建单文件组件的<template>
部分。按照以下步骤创建它:
- 在模板的顶部,我们需要添加
response
属性,包裹在一个<pre>
HTML 元素中:
<h3>Response</h3> <pre>{{ response }}</pre>
- 对于用户的创建和更新,我们需要创建一个带有
v-model
指令绑定到userData
属性的textarea
HTML 输入:
<hr/> <h1> Create / Update User </h1> <label for="userData">
User JSON:
<textarea
id="userData"
v-model="userData"
rows="10"
cols="40"
style="display: block;"
></textarea> </label>
- 要发送这些数据,我们需要创建两个按钮,两者都在单击事件上绑定了事件侦听器,使用
@click
指令分别指向createUser
和updateUser
,并在执行时传递userData
:
<button
style="margin: 20px;"
@click="createUser(JSON.parse(userData))" >
Create User
</button> <button
style="margin: 20px;"
@click="updateUser(JSON.parse(userData))" >
Update User
</button>
- 要执行DELETE方法,我们需要创建一个类型为
number
的输入 HTML 元素,并将v-model
指令绑定到userId
属性:
<h1> Delete User </h1> <label for="userData">
User Id:
<input type="number" step="1" v-model="userId"> </label>
- 最后,要执行此操作,我们需要创建一个按钮,该按钮将在单击事件上绑定一个事件侦听器,使用
@click
指令,将其指向deleteUser
方法,并在执行时传递userId
属性:
<button
style="margin: 20px;"
@click="deleteUser(userId)" >
Delete User
</button>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
MirageJS
的工作原理类似于拦截应用程序上发生的每个 HTTP 请求的拦截器。服务器拦截浏览器上的所有**XHR (XMLHttpRequest)**执行,并检查路由,以查看它是否与服务器创建的任何一个路由匹配。如果匹配,服务器将根据相应的路由执行函数。
作为具有基本 CRUD 功能的简单 REST 服务器,服务器具有类似模式的数据库结构,有助于创建用于存储数据的虚拟数据库。
另请参阅
您可以在github.com/miragejs/miragejs
找到有关 MirageJS 的更多信息。
使用 axios 作为新的 HTTP 客户端
当您需要一个用于 HTTP 请求的库时,毫无疑问axios
是您应该选择的。这个库被超过 150 万个开源项目和无数个闭源项目使用,是 HTTP 库之王。
它构建成适用于大多数浏览器,并提供了最完整的选项集之一-您可以自定义请求中的一切。
在这个食谱中,我们将学习如何将我们的 Fetch API 包装器更改为axios
并开始围绕它工作。
准备就绪
这个食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用在'使用 MirageJS 创建您的虚拟 JSON API 服务器'食谱中制作的 Vue 项目,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,Enter键继续,Spacebar键选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
从 Fetch API 更改为 Axios
在接下来的步骤中,我们将为 HTTP 包装器中使用的 Fetch API 更改为axios
库。按照以下步骤正确更改它:
- 在您的包中安装
axios
。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save axios
此食谱中使用的版本是 0.19.0。注意axios
的更改,因为该库尚无 LTS 版本。
打开
src/http
文件夹中的baseFetch.js
文件。简化该方法,使其接收三个参数,
url
、method
和options
,并返回一个axios
方法,使用传递给实例构造函数的方法调用 HTTP 请求:
import axios from 'axios';
export default async (url, method, options = {}) => axios({
method: method.toUpperCase(),
url,
...options,
});
更改 GET 方法函数
在这部分中,我们正在更改HTTP GET方法。按照以下说明更改getHttp
函数:
打开
src/http
文件夹中的fetchApi.js
文件。在
getHttp
函数中,我们将添加一个新的参数 param,并删除柯里化函数:
export const getHttp = async (
url,
params,
options, ) => baseHttp(url,
'get',
{
...options,
params,
});
更改 POST 方法函数
在这部分中,我们正在更改HTTP POST方法。按照以下说明更改postHttp
函数:
打开
http
文件夹中的fetchApi.js
文件。在
postHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const postHttp = async (
url,
data,
options, ) => baseHttp(url,
'post',
{
data,
...options,
});
更改 PUT 方法函数
在这部分,我们正在更改HTTP PUT方法。按照以下说明更改putHttp
函数:
打开
http
文件夹内的fetchApi.js
文件。在
putHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const putHttp = async (
url,
data,
options, ) => baseHttp(url,
'put',
{
data,
...options,
});
更改 PATCH 方法函数
在这部分,我们正在更改HTTP PATCH方法。按照以下说明更改patchHttp
函数:
打开
http
文件夹内的fetchApi.js
文件。在
patchHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const patchHttp = async (
url,
data,
options, ) => baseHttp(url,
'patch',
{
data,
...options,
});
更改 UPDATE 方法函数
在这部分,我们正在更改HTTP UPDATE方法。按照以下说明更改updateHttp
函数:
打开
http
文件夹内的fetchApi.js
文件。在
updateHttp
函数中,我们将添加一个新的参数 param,并删除柯里化函数:
export const updateHttp = async (
url,
data,
options, ) => baseHttp(url,
'update',
{
data,
...options,
});
更改 DELETE 方法函数
在这部分,我们正在更改HTTP DELETE方法。按照以下说明更改deleteHttp
函数:
打开
http
文件夹内的fetchApi.js
文件。在
deleteHttp
函数中,我们将把body
参数改为data
,并删除柯里化函数:
export const deleteHttp = async (
url,
data,
options, ) => baseHttp(url,
'delete',
{
data,
...options,
});
更改组件
在这部分,我们将改变组件与新函数的工作方式。按照以下说明正确更改它:
打开
src
文件夹内的App.vue
文件。在
getAllUsers
方法中,我们需要更改响应的定义方式,因为axios
给我们提供了一个完全不同的响应对象,而不是 Fetch API:
async getAllUsers() {
const { data } = await getHttp(`${window.location.href}api/users`);
this.response = data;
},
- 在
deleteUser
方法中,我们可以直接将 URL 作为参数传递:
async deleteUser(id) {
await deleteHttp(`${window.location.href}api/users/${id}`);
await this.getAllUsers();
},
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
它是如何工作的...
当我们为 Fetch API 创建包装器时,我们使用了一种将 API 抽象成另一个接口的技术,这使得从 Fetch API 更改到axios
库成为可能。通过这样做,我们能够改进方法并简化函数的调用和处理方式。例如,GET 方法现在可以接收一个名为params的新参数,这些参数是 URL 查询参数的对象,将自动注入到 URL 中。
我们还必须更改响应的解释方式,因为axios
比 Fetch API 具有更健壮和完整的响应对象,后者只返回获取的响应本身。
另请参阅
您可以在github.com/axios/axios
找到有关axios
的更多信息。
创建不同的 axios 实例
使用axios
时,您可以运行多个实例,而它们互不干扰。例如,您可以有一个指向版本 1 的用户 API 的实例,另一个指向版本 2 的支付 API,两者共享相同的命名空间。
在这里,我们将学习如何创建各种axios
实例,因此您可以在不受问题或干扰的情况下使用尽可能多的 API 命名空间。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
操作步骤...
要启动我们的组件,我们可以使用在“使用 axios 作为新的 HTTP 客户端”食谱中使用的 Vue CLI 创建的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的实例,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,Enter键继续,Spacebar键选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
更改 HTTP 函数
创建多个axios
实例时,调用axios
库的过程会发生变化。因此,我们需要更改 HTTP 包装器实例化axios
库的方式。
在接下来的部分中,我们将改变 HTTP 包装器与创建新的axios
实例的工作方式,并使其可用于应用程序。
更改 HTTP Fetch 包装器
在接下来的步骤中,我们将创建一个新的自定义axios
实例,该实例将用于 HTTP 包装器。按照以下说明将新实例添加到应用程序中:
在
src/http
文件夹中打开baseFetch.js
文件。我们需要创建一个名为
createAxios
的新工厂函数,以便每次执行时生成一个新的axios
实例:
export function createAxios(options = {}) {
return axios.create({
...options,
}); }
- 现在,我们需要创建
localApi
常量,其值将是createAxios
工厂的执行结果:
const localApi = createAxios();
- 对于
JSONPlaceHolder
,我们将创建一个名为jsonPlaceholderApi
的常量,该常量将被导出,其值将是createAxios
工厂的执行。我们还将传递一个对象作为参数,其中包含定义的baseURL
属性:
export const jsonPlaceholderApi = createAxios({
baseURL: 'https://jsonplaceholder.typicode.com/', });
- 在
export default
函数中,我们需要从axios
更改为localApi
:
export default async (url, method, options = {}) => localApi({
method: method.toUpperCase(),
url,
...options, });
更改 HTTP 方法函数
在这部分,我们将改变 HTTP 方法如何与新的axios
实例一起工作。按照说明正确执行:
在
src/http
文件夹中打开fetchApi.js
文件。我们将从
baseFetch
导入jsonPlaceholderApi
函数作为额外导入的值:
import baseHttp, { jsonPlaceholderApi } from './baseFetch';
- 我们需要创建一个名为
getTodos
的新常量,该常量将被导出。此常量将是一个函数,将接收userId
作为参数,并返回axios
的 GET 函数,其中userId
参数将在名为params
的属性的配置对象中接收:
export const getTodos = async userId => jsonPlaceholderApi.get('todos',
{
params: {
userId,
},
});
更改 MirageJS 服务器
在这部分,我们将更改MirageJS
服务器如何与新创建的axios
实例一起工作。按照说明正确执行:
在
src/server
文件夹中打开server.js
文件。在构造函数对象的
routes
属性上,我们需要添加一个passthrough
声明,这将指示 MirageJS 不会拦截对该 URL 的所有调用:
import { Server } from 'miragejs'; import baseData from './db'; import { getFrom } from './get'; import { postFrom } from './post'; import { patchFrom } from './patch'; import { deleteFrom } from './delete'; window.server = new Server({
seeds(srv) {
srv.db.loadData({ ...baseData });
}, routes() {
this.passthrough();
this.passthrough('https://jsonplaceholder.typicode.com/**'); this.namespace = 'api'; this.timing = 750; this.get('/users', getFrom('users')); this.post('/users', postFrom('users')); this.patch('/users/:id', patchFrom('users')); this.delete('/users/:id', deleteFrom('users'));
}, });
更改组件
在包装函数、MirageJS
服务器方法和 HTTP 方法更改后,我们需要将组件更改为已实现的新库。
在接下来的部分,我们将更改组件以匹配已实现的新库。
单文件组件<script>
部分
在这部分,我们将更改单文件组件的<script>
部分。按照以下步骤执行:
在
src
文件夹中打开App.vue
文件。我们需要按以下方式导入新的
getTodos
函数:
import {
getHttp,
postHttp,
patchHttp,
deleteHttp,
getTodos, } from './http/fetchApi';
- 在
Vue
对象的data
属性中,我们需要创建一个名为userTodo
的新属性,其默认值为一个空数组:
data: () => ({
response: undefined,
userData: '',
userId: undefined,
userTodo: [], }),
- 在
methods
属性中,我们需要创建一个名为getUserTodo
的新方法,该方法接收userId
参数。此方法将获取用户的待办事项列表,并将响应属性分配给userTodo
属性:
async getUserTodo(userId) {
this.userTodo = await getTodos(userId); },
单文件组件<template>
部分
在这部分,我们将更改单文件组件的<template>
部分。按照以下步骤执行:
在
src
文件夹中打开App.vue
文件。在模板底部,我们需要创建一个新的
input
HTML 元素,使用v-model
指令绑定到userId
属性:
<h1> Get User ToDos </h1> <label for="userData">
User Id:
<input type="number" step="1" v-model="userId"> </label>
- 要获取项目列表,我们需要创建一个按钮,该按钮绑定了点击事件的事件监听器,使用
@click
指令,目标是getUserTodo
,并在执行中传递userId
:
<button
style="margin: 20px;"
@click="getUserTodo(userId)" >
Fetch Data
</button>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的地方:
它是如何工作的...
当我们创建axios
的新实例时,会创建一个新对象,并定义新的配置、标头、拦截器和操纵器。这是因为axios
声明create
函数与new Class
相同。它是相同的接口但不同的对象。
利用这个可能性,我们能够创建两个连接驱动程序,一个用于本地 API,另一个用于JSONPlaceHolder
API,它有一个不同的baseURL
。
由于 MirageJS 服务器集成,所有 HTTP 请求都被 MirageJS 拦截,因此我们需要在路由构造函数中添加一个指令,指示 MirageJS 不会拦截的路由。
另请参阅
您可以在jsonplaceholder.typicode.com/
找到有关 JSONPlaceHolder API 的更多信息。
您可以在github.com/axios/axios#creating-an-instance
找到有关axios
实例的更多信息。
您可以在github.com/miragejs/miragejs
找到有关 MirageJS 的更多信息。
为 axios 创建请求和响应拦截器
在我们的应用程序中使用axios
作为主要的 HTTP 操作器,允许我们使用请求和响应拦截器。这些用于在将数据发送到服务器之前或在接收数据时操纵数据,然后将其发送回 JavaScript 代码之前对其进行操纵。
拦截器最常用的方式是在 JWT 令牌验证和刷新接收特定错误或 API 错误操纵的请求时使用。
在这个示例中,我们将学习如何创建一个请求拦截器来检查POST、PATCH和DELETE方法以及一个响应错误操纵器。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用我们在'创建不同的 axios 实例'食谱中制作的 Vue CLI 项目,也可以启动一个新的项目。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
创建拦截器
在接下来的步骤中,我们将创建一个axios
拦截器,它将作为中间件工作。按照说明正确执行:
- 安装
Sweet Alert
包。要做到这一点,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save sweetalert2
在
src/http
文件夹中创建一个名为interceptors.js
的新文件并打开它。然后,我们导入 Sweet Alert 包:
import Swal from 'sweetalert2';
- 我们需要创建一个包含将被拦截的POST方法的数组的常量:
const postMethods = ['post', 'patch'];
- 我们需要创建一个名为
requestInterceptor
的函数并导出它。这个函数将接收一个参数config
,它是一个axios
配置对象。我们需要检查请求方法是否包含在我们之前创建的数组中,以及数据主体的data
属性是否有一个id
属性。如果任何检查未通过,我们将抛出一个Error
,否则,我们将返回config
:
export function requestInterceptor(config) {
if (
postMethods.includes(config.method.toLocaleLowerCase()) &&
Object.prototype.hasOwnProperty.call('id', config.data.data) &&
!config.data.data.id)
{
throw new Error('You need to pass an ID for this request');
} return config; }
- 对于响应拦截器,我们需要创建一个名为
responseInterceptor
的新函数,它返回响应,因为我们在这个拦截器中不会改变任何东西:
export function responseInterceptor(response) {
return response; }
- 为了捕获错误,我们需要创建一个
errorInterceptor
函数,它将被导出。这个函数接收一个error
作为参数,并显示一个sweetalert2
警报错误消息,并返回一个带有error
的Promise.reject
:
export function errorInterceptor(error) {
Swal.fire({
type: 'error',
title: 'Error!',
text: error.message,
}); return Promise.reject(error); }
将拦截器添加到 HTTP 方法函数中
在接下来的步骤中,我们将向 HTTP 方法函数添加axios
拦截器。按照以下步骤正确执行:
在
src/http
文件夹中打开baseFetch.js
文件。我们需要导入刚刚创建的三个拦截器:
import {
errorInterceptor,
requestInterceptor,
responseInterceptor, } from './interceptors';
- 在创建
localApi
实例之后,我们声明了请求和响应拦截器的使用:
localApi.interceptors
.request.use(requestInterceptor, errorInterceptor); localApi.interceptors
.response.use(responseInterceptor, errorInterceptor);
- 在创建
jsonPlaceholderApi
实例之后,我们声明了请求和响应拦截器的使用:
jsonPlaceholderApi.interceptors
.request.use(requestInterceptor, errorInterceptor); jsonPlaceholderApi.interceptors
.response.use(responseInterceptor, errorInterceptor);
工作原理...
axios
执行的每个请求都会通过拦截器集中的任何一个。响应也是一样。如果在拦截器上抛出任何错误,它将自动传递给错误处理程序,因此请求根本不会被执行,或者响应将作为错误发送到 JavaScript 代码。
我们检查了每个POST,PATCH和DELETE方法所做的每个请求,以查看在请求体数据中是否有id
属性。如果没有,我们向用户抛出错误,告诉他们需要为请求传递一个 ID。
另请参阅
您可以在sweetalert2.github.io
找到有关 Sweet Alert 2 的更多信息。
您可以在**github.com/axios/axios#interceptors
**找到有关axios
请求拦截器的更多信息。
使用 Axios 和 Vuesax 创建 CRUD 界面
在处理数据时,我们总是需要做一些事情:CRUD 过程。无论您正在开发什么类型的应用程序,都需要 CRUD 界面以便在服务器上输入和操作任何数据,管理面板,应用程序的后端,甚至客户端。
在这里,我们将学习如何使用Vuesax
框架和axios
进行 HTTP 请求来创建一个简单的 CRUD 界面。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,请使用我们在“为 axios 创建请求和响应拦截器”配方中使用的 Vue CLI 的 Vue 项目,或者启动一个新项目。
要启动一个新项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。选择default
选项:
? Please pick a preset: **(Use arrow keys)** ❯ default (babel, eslint)
Manually select features
将 Vuesax 添加到应用程序
在接下来的步骤中,我们将介绍如何将Vuesax
UI 库添加到您的 Vue 应用程序中。按照这些说明正确执行:
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save vuesax material-icons
在
src
文件夹中创建一个名为style.css
的文件并打开它。导入
vuesax
,material-icon
和Open Sans
字体样式表:
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,
800i&display=swap'); @import url('~vuesax/dist/vuesax.css'); @import url('~material-icons/iconfont/material-icons.css'); * {
font-family: 'Open Sans', sans-serif; }
打开
src
文件夹中的main.js
文件。导入
style.css
文件和Vuesax
。之后,您需要通知 Vue 使用Vuesax
:
import './server/server'; import Vue from 'vue'; import App from './App.vue'; import Vuesax from 'vuesax'; import './style.css'; Vue.use(Vuesax); Vue.config.productionTip = false; new Vue({
render: h => h(App), }).$mount('#app');
创建组件路由
我们将在五个部分中继续这个过程:List
,Create
,Read
,Update
和Delete
。我们的应用将是一个动态组件应用程序,因此我们将创建五个组件,每个部分一个。这些组件将类似于我们的页面。
首先,我们需要将App.vue
更改为我们的主路由管理器,并创建一个用于更改组件的混合器。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在
src
文件夹中打开App.vue
。导入将在此处创建的每个组件:
import List from './components/list'; import Create from './components/create'; import View from './components/view'; import Update from './components/update';
- 在
data
属性中,创建两个新值:componentIs
默认值为'list'
,userId
默认值为0
:
data: () => ({
componentIs: 'list',
userId: 0, }),
- 我们需要向 Vue 对象添加一个名为
provide
的新属性。该属性将是一个函数,因此提供给组件的值可以是响应式的:
provide () {
const base = {}; Object.defineProperty(base, 'userId', {
enumerable: true,
get: () => Number(this.userId),
}); return base; },
- 在
computed
属性中,我们需要创建一个名为component
的新属性。这将是一个 switch case,根据componentIs
属性返回我们的组件:
computed: {
component() {
switch (this.componentIs) {
case 'list':
return List;
case 'create':
return Create;
case 'view':
return View;
case 'edit':
return Update;
default:
return undefined;
}
} },
- 最后,在方法中,我们需要创建一个
changeComponent
方法,将当前组件更新为新组件:
methods: {
changeComponent(payload) {
this.componentIs = payload.component;
this.userId = Number(payload.userId);
}, },
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 在
div#app
HTML 元素中,我们需要添加一个vs-row
组件:
<div id="app">
<vs-row></vs-row> </div>
- 在
vs-row
组件中,我们需要添加一个vs-col
组件,具有以下属性:vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12"> </vs-col>
- 最后,在
vs-col
组件内部,我们将添加一个动态组件,该组件具有一个is
属性,指向计算属性component
,并将事件侦听器指向将执行changeComponent
方法的"change-component"
事件:
<component
:is="component"
@change-component="changeComponent" />
创建路由混合器
在这部分,我们将创建组件混合器,以便在其他组件中重复使用。按照以下说明正确创建组件:
在
src/mixin
文件夹中创建一个名为changeComponent.js
的新文件并打开它。这个混合器将有一个名为
changeComponent
的方法,它将发出一个名为'change-component'
的事件,其中包含需要呈现的新组件的名称和userId
:
export default {
methods: {
changeComponent(component, userId = 0) {
this.$emit('change-component', { component, userId });
},
}
}
创建列表组件
列表组件将是索引组件。它将列出应用程序中的用户,并具有其他 CRUD 操作的所有链接。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
在
src/components
文件夹中创建一个名为list.vue
的新文件并打开它。从
fetchApi
导入getHttp
和deleteHttp
,以及changeComponent
混合器:
import {
getHttp,
deleteHttp,
} from '../http/fetchApi';
import changeComponent from '../mixin/changeComponent';
- 在组件的
mixins
属性中,我们需要添加导入的changeComponent
混合器:
mixins: [changeComponent],
- 在组件的
data
属性中,我们添加了一个名为userList
的新属性,其默认为空数组:
data: () => ({
userList: [], }),
- 对于方法,我们创建了
getAllUsers
和deleteUsers
方法。在getAllUsers
方法中,我们获取用户列表,并将userList
的值设置为getHttp
函数执行的响应。deleteUser
方法将执行deleteHttp
函数,然后执行getAllUsers
方法:
methods: {
async getAllUsers() {
const { data } = await getHttp(`${window.location.href}api/users`);
this.userList = data;
},
async deleteUser(id) {
await deleteHttp(`${window.location.href}api/users/${id}`);
await this.getAllUsers();
}, }
- 最后,我们将
beforeMount
生命周期钩子异步化,调用getAllUsers
方法:
async beforeMount() {
await this.getAllUsers(); },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 创建一个带有
style
属性定义为margin: 20px
的vs-card
组件:
<vs-card style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内部,为header
创建一个动态的<template>
,其中包含一个<h3>
标签和您的标题:
<template slot="header">
<h3>
Users
</h3> </template>
- 之后,创建一个
vs-row
组件,其中包含一个vs-col
组件,具有以下属性:vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内部,我们需要创建一个vs-table
组件。该组件将具有指向userList
变量的data
属性,并将search
,stripe
和pagination
属性定义为 true。max-items
属性将被定义为10
,style
属性将具有width: 100%; padding: 20px;
的值:
<vs-table
:data="userList"
search
stripe pagination max-items="10"
style="width: 100%; padding: 20px;" ></vs-table>
- 对于表头,我们需要创建一个名为
thead
的动态 <template>
,并为每一列创建一个带有 sort-key
属性定义为相应对象键属性和显示为您想要的名称的 vs-th
组件:
<template slot="thead">
<vs-th sort-key="id">
#
</vs-th>
<vs-th sort-key="name">
Name
</vs-th>
<vs-th sort-key="email">
Email
</vs-th>
<vs-th sort-key="country">
Country
</vs-th>
<vs-th sort-key="phone">
Phone
</vs-th>
<vs-th sort-key="Birthday">
Birthday
</vs-th>
<vs-th>
Actions
</vs-th> </template>
- 对于表格主体,我们需要创建一个动态的
<template>
,其中定义了一个 slot-scope
属性作为 data
属性。在这个 <template>
中,我们需要创建一个 vs-tr
组件,它将迭代数据属性,并为表格头部设置的每一列创建一个 vs-td
组件。每个 vs-td
组件都有一个设置为相应列数据对象属性的数据属性,并且内容将是相同的数据渲染。最后一列是操作列,将有三个按钮,一个用于读取,另一个用于更新,最后一个用于删除。读取按钮将在 "click"
事件上有一个指向 changeComponent
的事件监听器,更新按钮也是如此。删除按钮的 "click"
事件监听器将指向 deleteUser
方法:
<template slot-scope="{data}">
<vs-tr :key="index" v-for="(tr, index) in data">
<vs-td :data="data[index].id">
{{data[index].id}}
</vs-td>
<vs-td :data="data[index].name">
{{data[index].name}}
</vs-td>
<vs-td :data="data[index].email">
<a :href="`mailto:${data[index].email}`">
{{data[index].email}}
</a>
</vs-td>
<vs-td :data="data[index].country">
{{data[index].country}}
</vs-td>
<vs-td :data="data[index].phone">
{{data[index].phone}}
</vs-td>
<vs-td :data="data[index].birthday">
{{data[index].birthday}}
</vs-td>
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeComponent('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeComponent('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/>
</vs-td>
</vs-tr> </template>
- 最后,在卡片页脚中,我们需要创建一个名为
footer
的动态 <template>
。在这个 <template>
中,我们将添加一个带有 vs-justify
属性定义为 flex-start
的 vs-row
组件,并插入一个带有 color
属性定义为 primary
、type
属性定义为 filled
、icon
属性定义为 fiber_new
和 size
属性定义为 small
的 vs-button
。@click
事件监听器将以参数 'create'
和 0
目标 changeComponent
方法:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="fiber_new"
size="small"
@click="changeComponent('create', 0)"
>
Create User
</vs-button>
</vs-row> </template>
单文件组件 <style> 部分
在这部分,我们将创建单文件组件的 <style>
部分。按照以下说明正确创建组件:
- 为
vs-button
组件类创建一个边距声明:
<style scoped>
.vs-button {
margin-left: 5px;
} </style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染和运行的地方:
创建一个通用的用户表单组件
在接下来的部分,我们将创建一个通用的用户表单组件,它将被其他组件使用。这个组件被认为是通用的,因为它是一个可以被任何人使用的组件。
单文件组件 <script> 部分
在这部分,我们将创建单文件组件的 <script>
部分。按照以下说明正确创建组件:
在src/components
文件夹中创建一个名为userForm.vue
的新文件并打开它。
在 Vue 的props
属性中,创建两个名为value
和disabled
的新属性,都是对象,并具有type
、required
和default
三个属性。对于value
属性,type
将是Object
,required
将是false
,default
将是返回一个对象的工厂。对于disabled
属性,type
将是Boolean
,required
将是false
,default
也将是false
:
props: {
value: {
type: Object,
required: false,
default: () => {
},
},
disabled: {
type: Boolean,
required: false,
default: false,
} },
- 在
data
属性中,我们需要添加一个名为tmpForm
的新值,其默认值为一个空对象:
data: () => ({
tmpForm: {}, }),
- 在 Vue 的
watch
属性中,我们需要为tmpForm
和value
创建处理程序。对于tmpForm
watcher,我们将添加一个handler
函数,它将在每次更改时发出一个'input'
事件,带有新的value
,并将deep
属性添加为true
。最后,在value
watcher 中,我们将添加一个handler
函数,它将将tmpForm
的值设置为新的value
。我们还需要将deep
和immediate
属性定义为true
:
watch: {
tmpForm: {
handler(value) {
this.$emit('input', value);
},
deep: true,
},
value: {
handler(value) {
this.tmpForm = value;
},
deep: true,
immediate: true,
} },
在使用 watchers 时,声明deep
属性使 watcher 检查数组或对象的深层更改,而immediate
属性在组件创建时立即执行 watcher。
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 对于输入包装器,我们需要创建一个
vs-row
组件。在vs-row
组件内部,我们将为我们的用户表单创建每个输入:
<vs-row></vs-row>
- 对于名称输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.name
,disabled
属性绑定到disabled
props,label
定义为'Name'
,placeholder
定义为'User Name'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.name"
:disabled="disabled"
label="Name"
placeholder="User Name"
class="inputMargin full-width"
/> </vs-col>
- 对于电子邮件输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.email
,disabled
属性绑定到disabled
属性,label
定义为'Email'
,placeholder
定义为'User Email'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.email"
:disabled="disabled"
label="Email"
placeholder="User Email"
class="inputMargin full-width"
/> </vs-col>
- 对于国家输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.country
,disabled
属性绑定到disabled
属性,label
定义为'Country'
,placeholder
定义为'User Country'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.country"
:disabled="disabled"
label="Country"
placeholder="User Country"
class="inputMargin full-width"
/> </vs-col>
- 对于电话输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.phone
,disabled
属性绑定到disabled
属性,label
定义为'Phone'
,placeholder
定义为'User Phone'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.phone"
:disabled="disabled"
label="Phone"
placeholder="User Phone"
class="inputMargin full-width"
/> </vs-col>
- 对于生日输入,我们需要创建一个
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.birthday
,disabled
属性绑定到disabled
属性,label
定义为'Birthday'
,placeholder
定义为'User Birthday'
,class
定义为'inputMargin full-width'
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.birthday"
:disabled="disabled"
label="Birthday"
placeholder="User Birthday"
class="inputMargin full-width"
/> </vs-col>
单文件组件<style>
部分
在这部分,我们将创建单文件组件的<style>
部分。按照以下说明正确创建组件:
创建一个名为inputMargin
的新的作用域类,其中margin
属性定义为15px
:
<style>
.inputMargin {
margin: 15px;
} </style>
创建创建用户组件
要开始我们的用户操作过程,我们需要创建一个初始的基本用户表单,以便在View
、Create
和Update
组件之间共享。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
在src/components
文件夹中创建一个名为create.vue
的新文件并打开它。
从fetchApi
中导入UserForm
组件、changeComponent
混合和postHttp
:
import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent'; import { postHttp } from '../http/fetchApi';
- 在
data
属性中,我们将添加一个userData
对象,其中name
、email
、birthday
、country
和phone
属性都定义为空字符串:
data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
mixins
属性中,我们需要添加changeComponent
:
mixins: [changeComponent],
- 在 Vue 的
components
属性中,添加UserForm
组件:
components: {
UserForm, },
- 在
methods
属性中,我们需要创建createUser
方法,该方法将使用userData
属性上的数据,并在服务器上创建一个新用户,然后将用户重定向到用户列表:
methods: {
async createUser() {
await postHttp(`${window.location.href}api/users`, {
data: {
...this.userData,
}
});
this.changeComponent('list', 0);
}, },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 创建一个
vs-card
组件,其中style
属性定义为margin: 20px
:
<vs-card
style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内部,为header
创建一个动态的<template>
插槽,其中包含一个<h3>
标签和您的标题:
<template slot="header">
<h3>
Create User
</h3> </template>
- 之后,创建一个
vs-row
组件,其中包含一个vs-col
组件,其属性为vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内部,我们将添加user-form
组件,并将v-model
指令绑定到userData
上:
<user-form
v-model="userData" />
- 最后,在卡片页脚中,我们需要为
footer
创建一个动态的<template>
插槽。在这个<template>
中,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个将用于创建用户,其属性为color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
。@click
事件监听器将针对createUser
方法,第二个vs-button
组件将用于取消此操作并返回到用户列表。它的属性为color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
,style
定义为margin-left: 5px
,@click
事件监听器将目标设置为changeComponent
方法,参数为'list'
和0
:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="save"
size="small"
@click="createUser"
>
Create User
</vs-button>
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Cancel
</vs-button>
</vs-row> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的地方:
视图组件
在接下来的部分中,我们将创建可视化组件。此组件仅用于查看用户信息。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/components
文件夹中创建名为view.vue
的文件并打开它。
导入UserForm
组件,changeComponent
mixin 和fetchApi
中的getHttp
:
import {
getHttp, } from '../http/fetchApi'; import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent';
- 在
data
属性中,我们将添加一个userData
对象,其中name
,email
,birthday
,country
和phone
属性都定义为空字符串:
data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
mixins
属性中,我们需要添加changeComponent
mixin:
mixins: [changeComponent],
- 在 Vue 的
inject
属性中,我们需要声明'userId'
属性:
inject: ['userId'],
- 在 Vue 的
components
属性中,添加UserForm
组件:
components: {
UserForm, },
- 对于方法,我们将创建
getUserById
方法。此方法将通过当前 ID 获取用户数据,并将userData
值设置为getHttp
函数执行的响应:
methods: {
async getUserById() {
const { data } = await getHttp(`${window.location.href}api/users/${this.userId}`);
this.userData = data;
}, }
- 在
beforeMount
生命周期钩子中,我们将使其异步,调用getUserById
方法:
async beforeMount() {
await this.getUserById(); },
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 创建一个带有
style
属性定义为margin: 20px
的vs-card
组件:
<vs-card
style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内,为header
创建一个动态的<template>
,并添加一个<h3>
标签和您的标题:
<template slot="header">
<h3>
View User
</h3> </template>
- 之后,创建一个带有
vs-col
组件的vs-row
组件,其中vs-type
属性定义为flex
,vs-justify
属性定义为left
,vs-align
属性定义为left
,vs-w
属性定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内,我们将添加UserForm
组件,并将v-model
指令绑定到userData
,并将disabled
属性设置为true
:
<user-form
v-model="userData"
disabled />
- 最后,在卡片页脚中,我们需要为
footer
创建一个动态的<template>
。在这个<template>
中,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个是用于取消此操作并返回到用户列表的。它将具有color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
的属性,以及@click
事件监听器目标为changeComponent
方法,参数为'list'
和0
。第二个vs-button
组件将用于编辑用户,并具有color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
,style
定义为margin-left: 5px
,以及@click
事件监听器目标为changeComponent
方法,参数为'list'
和注入的userId
:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="arrow_back"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Back
</vs-button>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
style="margin-left: 5px"
@click="changeComponent('edit', userId)"
>
Edit User
</vs-button>
</vs-row> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
您的组件已呈现并运行:
更新用户组件
我们刚刚查看了用户数据,现在我们想要更新它。我们需要制作一个新的组件,几乎与查看组件相同,但具有更新用户的方法并启用表单。
单文件组件<script>
部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/components
文件夹中创建名为update.vue
的文件并打开它。
从fetchApi
中导入UserForm
组件、changeComponent
mixin 以及getHttp
和patchHttp
函数:
import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent'; import {
getHttp,
patchHttp, } from '../http/fetchApi';
- 在
data
属性中,我们将添加一个userData
对象,其中包含name
、email
、birthday
、country
和phone
属性,全部定义为空字符串:
data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
mixins
属性中,我们需要添加changeComponent
mixin:
mixins: [changeComponent],
- 在 Vue 的
inject
属性中,我们需要声明'userId'
属性:
inject: ['userId'],
- 在 Vue 的
components
属性中,添加UserForm
组件:
components: {
UserForm, },
- 对于方法,我们将创建两个:
getUserById
和updateUser
。getUserById
方法将通过当前 ID 获取用户数据,并将userData
值设置为getHttp
函数执行的响应,而updateUser
将通过patchHttp
函数将当前userDate
发送到服务器,并重定向回用户列表:
methods: {
async getUserById() {
const { data } = await
getHttp(`${window.location.href}api/users/${this.userId}`);
this.userData = data;
},
async updateUser() {
await patchHttp
(`${window.location.href}api/users/${this.userData.id}`, {
data: {
...this.userData,
}
});
this.changeComponent('list', 0);
}, },
- 在
beforeMount
生命周期钩子上,我们将使其异步化,调用getUserById
方法:
async beforeMount() { await this.getUserById(); },
单文件组件<template>
部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 创建一个带有
style
属性定义为margin: 20px
的vs-card
组件:
<vs-card
style="margin: 20px;"
>
</vs-card>
- 在
vs-card
组件内部,为header
创建一个动态的<template>
命名插槽,其中包含一个<h3>
标签和您的标题:
<template slot="header">
<h3>
Update User
</h3> </template>
- 之后,创建一个带有
vs-col
组件的vs-row
组件,其中vs-type
属性定义为flex
,vs-justify
属性定义为left
,vs-align
属性定义为left
,vs-w
属性定义为12
:
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
- 在
vs-col
组件内部,我们将添加UserForm
组件,其中v-model
指令绑定到userData
,并将disabled
属性设置为true
:
<user-form
v-model="userData"
disabled />
- 最后,在卡片页脚中,我们需要为
footer
创建一个动态的<template>
命名插槽。在<template>
内,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个将用于创建用户,并具有color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
的属性,并且@click
事件监听器指向updateUser
方法。第二个vs-button
组件将用于取消此操作并返回到用户列表。它将具有color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
,style
定义为margin-left: 5px
的属性,并且@click
事件监听器指向changeComponent
方法,参数为'list'
和0
:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="save"
size="small"
@click="updateUser"
>
Update User
</vs-button>
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Cancel
</vs-button>
</vs-row> </template>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染并运行的情况:
它是如何工作的...
我们创建的 CRUD 接口就像一个路由应用程序,有三个路由,即索引或列表、查看和编辑路由。每个路由都有自己的屏幕和组件,具有分离的逻辑功能。
我们创建了一个抽象的UserForm
组件,该组件用于View
和Update
组件。这个抽象组件可以在许多其他组件中使用,因为它不需要任何基本逻辑来工作;它就像一个由几个输入组成的输入框。
使用 Vue 的 provide/inject API,我们能够以可观察的方式将userId
传递给每个组件,这意味着当变量更新时,组件会接收到更新后的变量。这是无法通过普通的 Vue API 实现的,因此我们必须使用Object.defineProperty
并使用provide
属性作为返回最终对象的工厂函数。
另请参阅
您可以在lusaxweb.github.io/vuesax/
找到有关Vuesax
的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
找到有关Object.defineProperty
的更多信息。
您可以在vuejs.org/v2/guide/components-edge-cases.html
找到有关 Vue provide/inject API 的更多信息。
第六章:使用 vue-router 管理路由
您的应用程序的主要部分之一是路由管理。在这里,可以将无限的组件组合在一个地方。
路由能够协调组件的渲染,并根据 URL 指示应用程序应该在哪里。有许多方法可以增加vue-router
的定制化。您可以添加路由守卫来检查特定路由是否可由访问级别导航,或在进入路由之前获取数据以管理应用程序中的错误。
在本章中,您将学习如何创建应用程序路由、动态路由、别名和信任路由,以及嵌套路由视图。我们还将看看如何管理错误,创建路由守卫,并延迟加载您的页面。
在本章中,我们将涵盖以下教程:
创建一个简单的路由
创建一个程序化导航
创建一个动态路由路径
创建一个路由别名
创建一个路由重定向
创建一个嵌套路由视图
创建一个 404 错误页面
创建一个身份验证中间件
异步延迟加载您的页面
技术要求
在本章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的 npm 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Vue-CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个简单的路由
在您的应用程序中,您可以创建无限组合的路由,可以导向任意数量的页面和组件。
vue-router
是这个组合的维护者。我们需要使用它来设置如何创建路径并为我们的访问者建立路由的指令。
在这个教程中,我们将学习如何创建一个初始路由,该路由将引导到不同的组件。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做…
创建一个 Vue-CLI 项目,按照以下步骤进行:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create initial-routes
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。
有两种方法可以启动新项目。默认方法是基本的 Babel 和 ESLint 项目,没有任何插件或配置,还有手动
模式,您可以选择更多模式、插件、代码检查器和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint**)** ❯ **Manually select features**
- 现在我们被问及项目中想要的功能。这些功能包括一些 Vue 插件,如 Vuex 或 Vue Router(Vue-Router)、测试器、代码检查器等。选择
Babel
、Router
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router
Vuex CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 现在 Vue-CLI 会询问您是否要在路由管理中使用历史模式。我们会选择
Y
(是):
? Use history mode for router? (Requires proper server setup for
index fallback in production) (Y**/n) y**
- 继续此过程,选择一个代码检查器和格式化程序。在我们的情况下,我们将选择
ESLint + Airbnb config
:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 设置完代码检查规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、代码检查器和处理器之后,我们需要选择设置和配置存储的位置。最佳存储位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
- 现在您可以选择是否要将此选择作为将来项目的预设,这样您就不需要再次重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
我们的步骤将分为五个部分:
创建NavigationBar
组件
创建联系页面
创建关于页面
更改应用程序的主要组件
创建路由
让我们开始吧。
创建 NavigationBar 组件
现在我们将创建将在我们的应用程序中使用的NavigationBar
组件。
单文件组件<script>部分
在这一部分,我们将创建单文件组件的<script>部分。按照这些说明正确创建组件:
在src/components
文件夹中创建一个navigationBar.vue
文件并打开它。
创建组件的默认export
对象,具有 Vue 属性name
:
<script> export default {
name: 'NavigationBar', }; </script>
单文件组件部分
在这一部分,我们将创建单文件组件的部分。按照这些说明正确创建组件:
- 创建一个带有
id
属性定义为"nav"
的div
HTML 元素,并在其中创建三个RouterLink
组件。这些组件将指向Home
、About
和Contact
路由。在RouterLink
组件中,我们将添加一个to
属性,分别定义为每个组件的路由,并将文本内容定义为菜单的名称:
<div id="nav">
<router-link to="/">
Home
</router-link> |
<router-link to="/about">
About
</router-link> |
<router-link to="/contact">
Contact
</router-link> </div>
创建联系页面
我们需要确保当用户输入/contact
URL 时,联系页面会被渲染。为此,我们需要创建一个单文件组件,用作联系页面。
单文件组件 <script> 部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/views
文件夹中,创建一个名为contact.vue
的新文件并打开它。
创建组件的默认export
对象,其中包含 Vue 属性name
:
<script> export default {
name: 'ContactPage', }; </script>
单文件组件 部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
创建一个div
HTML 元素,其中class
属性定义为"contact"
。
在<h1>
HTML 元素内部,添加一个显示当前页面的文本内容:
<template>
<div class="contact">
<h1>This is a contact page</h1>
</div> </template>
创建关于页面
我们需要确保当用户输入/about
URL 时,关于页面会被渲染。在接下来的小节中,我们将为关于页面创建单文件组件。
单文件组件 <script> 部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/views
文件夹中,创建一个名为About.vue
的新文件并打开它。
创建组件的默认导出对象,其中包含 Vue 属性name
:
<script> export default {
name: 'AboutPage', }; </script>
单文件组件 部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
创建一个div
HTML 元素,其中class
属性定义为"about"
。
在其中,放置一个带有显示当前页面文本内容的<h1>
元素:
<template>
<div class="about">
<h1>This is an about page</h1>
</div> </template>
更改应用程序的主要组件
创建页面和导航栏后,我们需要更改应用程序的主要组件,以便能够渲染路由并在顶部拥有导航栏。
单文件组件 <script> 部分
在这部分中,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
导入NavigationBar
组件:
import NavigationBar from './components/navigationBar.vue';
- 在 Vue 的
components
属性中,声明导入的NavigationBar
:
export default {
components: { NavigationBar }, };
单文件组件<template>
部分
在这部分中,我们将创建单文件组件的<template>
部分。在div
HTML 元素内,添加NavigationBar
组件和RouterView
组件:
<template>
<div id="app">
<navigation-bar />
<router-view/>
</div> </template>
创建路由
现在我们需要在应用程序中使路由可用。为此,首先需要声明路由和路由将呈现的组件。按照以下步骤正确创建 Vue 应用程序路由:
在src/router
文件夹中,打开index.js
文件。
导入Contact
组件页面:
import Vue from 'vue'; import VueRouter from 'vue-router'; import Home from '../views/Home.vue'; import Contact from '../views/contact.vue';
- 在
routes
数组中,我们需要创建一个新的route
对象。该对象将具有path
属性定义为'/contact'
,name
定义为'contact'
,并且component
指向导入的Contact
组件:
{
path: '/contact',
name: 'contact',
component: Contact, },
要运行服务器并查看组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的地方:
工作原理...
当将vue-router
添加到 Vue 作为插件时,它开始监视window.location.pathname
和其他 URL 属性的更改,以检查当前 URL 在浏览器上的权重与路由配置中的 URL 列表的匹配情况。
在这种情况下,我们使用直接 URL 和非动态 URL。因此,vue-router
插件只需要检查 URL 路径的直接匹配,而不需要将可能的匹配与正则表达式验证器进行比较。
匹配 URL 后,router-view
组件充当动态组件,并呈现我们在vue-router
配置中定义的组件。
另请参阅
您可以在router.vuejs.org/.
找到有关vue-router
的更多信息。
您可以在cli.vuejs.org/.
找到有关 Vue CLI 的更多信息。
创建程序化导航
使用vue-router
时,还可以通过函数执行来更改应用程序的当前路由,而无需特殊的vue-router
组件来创建链接。
使用程序化导航,您可以确保所有路由重定向可以在代码的任何位置执行。使用此方法可以使用特殊的路由方向,例如传递参数和使用路由名称进行导航。
在这个食谱中,我们将学习如何执行程序化导航函数,以及它提供的额外可能性。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们可以使用在“创建简单路由”中创建的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create route-project
选择手动功能并将Router
作为所需功能添加,如“如何做...”部分和“创建简单路由”食谱中所示。
我们的食谱将分为两部分:
更改应用程序的主要组件
更改联系视图
让我们开始吧。
更改应用程序的主要组件
我们将从App.vue
文件开始。我们将添加一个在超时后执行的程序化导航函数,该函数将添加到组件生命周期钩子中。
单文件组件<script>
部分
在这部分中,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
添加一个mounted
属性:
mounted() {}
- 在
mounted
属性中,我们需要添加一个setTimeout
函数,该函数将执行$router.push
函数。当执行时,此函数将接收一个 JavaScript 对象作为参数,其中包含两个属性,name
和params
:
mounted() {
setTimeout(() => {
this.$router.push({
name: 'contact',
params: {
name: 'Heitor Ribeiro',
age: 31,
}, }); }, 5000); },
更改联系视图
在联系视图上,我们需要添加一个事件侦听器,该侦听器将抓取路由更改并执行操作。
单文件组件<script>
部分
在这部分中,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
在src/views
文件夹中打开contact.vue
。
添加一个新的mounted
属性:
mounted() {}
- 在此属性中,我们将添加一个验证,检查
$route.params
对象上是否有任何参数,并显示具有该$route.params
的alert
:
mounted() {
if (Object.keys(this.$route.params).length) {
alert(`Hey! I've got some parameter!
${JSON.stringify(this.$route.params)}`);
} },
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现和运行的方式:
它是如何工作的...
当执行$router.push
函数时,告诉vue-router
改变应用程序所在的位置,在这个过程中,您将向新的路由器传递一些参数,这些参数将替换当前路由。在这些参数中,有一个名为params
的属性,它将一组参数发送到新的路由器。
当进入这个新的路由器时,我们将从路由器内部调用的所有参数都将在$route.params
对象中可用;在那里,我们可以在我们的视图或组件中使用它。
还有更多...
在程序化导航中,可以通过$router.push
函数导航到路由器,并将它们添加到浏览器历史记录中,但也可以使用其他函数。
$router.replace
函数将替换用户导航历史记录为新的历史记录,使其无法返回到上一页。
$router.go
用于以步骤方式移动用户导航历史记录。要前进,您需要传递正数,要后退,您需要传递负数。
参见
您可以在router.vuejs.org/guide/essentials/navigation.html
找到有关vue-router
程序化导航的更多信息。
创建动态路由器路径
向您的应用程序添加路由是必不可少的,但有时您需要的不仅仅是简单的路由。在这个食谱中,我们将看看动态路由是如何发挥作用的。通过动态路由,您可以定义可以通过 URL 设置的自定义变量,并且您的应用程序可以从这些变量开始定义。
在这个食谱中,我们将学习如何在 CRUD 列表上使用动态路由器路径。
准备就绪
这个食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在第五章中完成的 Vue 项目和 Vue-CLI,通过 HTTP 请求从 Web 获取数据中的'使用 axios 和 Vuesax 创建 CRUD 界面'食谱。在接下来的步骤中,我们将通过 Vue UI 仪表板向项目添加vue-router
:
- 首先,您需要打开
vue ui
。要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
在那里,您需要通过定位项目文件夹来导入项目。导入vue ui
后,您将被重定向到仪表板。
通过转到插件管理页面并单击“添加 vue-router”按钮,将vue-router
添加到插件中。然后,单击“继续”按钮。
Vue-CLI 将自动为我们在项目上安装和配置 vue-router。现在我们需要为列表,视图和编辑页面创建每个视图。
要开始视图开发,我们将首先进入用户列表路由。在每个路由中,我们将解构我们之前制作的旧组件,并将其重新创建为视图。
我们的步骤将分为八个部分:
更改应用程序的主要组件
更改路由 mixin
Axios 实例配置
用户列表视图
用户创建视图
用户信息视图
用户更新视图
创建动态路由
让我们开始吧。
更改应用程序的主要组件
添加 vue-router 插件后,App.vue
将发生变化。我们需要撤销安装vue-router
所做的更改。这是因为当vue-ui
添加vue-router
插件时,它会更改App.vue
,添加我们不需要的示例代码。
单文件组件部分
在这部分中,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
在src
文件夹中打开App.vue
。
删除所有内容,只留下div#app
HTML 元素和router-view
组件:
<template>
<div id="app">
<router-view/>
</div> </template>
更改路由 mixin
在上一个步骤中,我们使用了一个changeComponent
mixin。现在我们要使用路由,我们需要将此 mixin 更改为changeRoute
mixin 并更改其行为。在接下来的步骤中,我们将更改 mixin 的工作方式,以便能够更改路由而不是组件:
在src/mixin
文件夹中,将changeComponent.js
重命名为changeRoute.js
并打开它。
我们将删除changeComponent
方法并创建一个名为changeRoute
的新方法。这个新方法将接收两个参数,name
和id
。name
参数是路由名称,在vue-router
配置中设置,id
将是我们将在路由更改中传递的用户 id 参数。此方法将执行$router.push
,将这些参数作为参数传递:
export default {
methods: {
async changeRoute(name, id = 0) {
await this.$router.push({
name,
params: {
id,
},
});
},
} }
Axios 实例配置
要在 MirageJS 服务器中获取数据,我们需要在 axios 实例中定义一些选项。现在,在以下步骤中,我们将配置 axios 实例以与新的路由系统一起工作:
在src/http
文件夹中,打开baseFetch.js
文件。
在axios
的localApi
实例的创建者中,我们需要添加一个options
对象,传递baseURL
属性。这个baseURL
将是当前浏览器导航的 URL:
const localApi = createAxios({
baseURL: `${document.location.protocol}//${document.location.host}`, });
用户列表视图
为了创建我们的视图,我们将从list.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
将list.vue
文件从components
移动到views
文件夹,并将其重命名为List.vue
。
删除旧的changeComponent
mixin 导入,并导入新的changeRoute
mixin:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
getAllUsers
和deleteUser
方法中,我们需要从getHttp
和deleteHttp
函数参数中删除${window.location.href}
:
methods: {
async getAllUsers() {
const { data } = await getHttp(`api/users`);
this.userList = data;
},
async deleteUser(id) {
await deleteHttp(`api/users/${id}`);
await this.getAllUsers();
}, }
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包装VsCard
组件及其子内容。VsCol
组件将vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在操作按钮上,我们将把
changeComponent
函数改为changeRoute
:
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeRoute('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeRoute('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/> </vs-td>
- 在
VsCard
的页脚处,我们需要将操作按钮的changeComponent
方法改为changeRoute
方法:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="fiber_new"
size="small"
@click="changeRoute('create')"
>
Create User
</vs-button>
</vs-row> </template>
用户创建视图
为了创建我们的视图,我们将从create.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照以下说明正确创建组件:
将create.vue
文件从components
移动到views
文件夹,并将其重命名为Create.vue
。
删除旧的changeComponent
mixin 导入,并导入新的changeRoute
mixin:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
getUserById
方法中,我们需要从postHttp
函数的 URL 中移除${window.location.href}
,并将changeComponent
函数更改为changeRoute
:
async createUser() {
await postHttp(`/api/users`, {
data: {
...this.userData,
}
});
this.changeRoute('list'); },
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将定义vs-type
属性为'flex'
,vs-justify
属性为'left'
,vs-align
属性为'left'
,vs-w
属性为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在
VsCard
的页脚上,我们需要将Cancel
按钮的changeComponent
函数更改为changeRoute
:
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Cancel
</vs-button>
用户信息视图
为了创建我们的视图,我们将从view.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
将view.vue
文件从src/components
移动到src/views
文件夹,并将其重命名为View.vue
。
移除旧的changeComponent
混入导入,并导入新的changeRoute
:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
component
对象中创建一个新的computed
属性,属性为userId
,它将返回$route.params.id
:
computed: {
userId() {
return this.$route.params.id;
}, },
- 在
getUserById
方法中,我们需要从getHttp
函数的 URL 中移除${window.location.href}
:
methods: {
async getUserById() {
const { data } = await getHttp(`api/users/${this.userId}`);
this.userData = data;
}, }
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将定义vs-type
属性为'flex'
,vs-justify
属性为'left'
,vs-align
属性为'left'
,vs-w
属性为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在
VsCard
的页脚上,我们需要将返回按钮的changeComponent
函数更改为changeRoute
:
<vs-button
color="primary"
type="filled"
icon="arrow_back"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Back
</vs-button>
用户更新视图
为了创建我们的视图,我们将从update.vue
组件中提取代码,并将其重塑为页面视图。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
将update.vue
文件从src/components
移动到src/views
文件夹,并将其重命名为Edit.vue
。
移除旧的changeComponent
混入导入,并导入新的changeRoute
混入:
import changeRouteMixin from '@/mixin/changeRoute';
- 在 Vue 的
mixins
属性中,我们需要用changeRoute
替换changeComponent
:
mixins: [changeRouteMixin],
- 在
component
对象中创建一个新的computed
属性,具有userId
属性,它将返回$route.params.id
:
computed: {
userId() {
return this.$route.params.id;
}, },
- 在
getUserById
和updateUser
方法中,我们需要移除
从getHttp
和patchHttp
函数的 URL 中删除${window.location.href}
,并将changeComponent
函数改为changeRoute
:
methods: {
async getUserById() {
const { data } = await getHttp(`api/users/${this.userId}`);
this.userData = data;
},
async updateUser() {
await patchHttp(`api/users/${this.userData.id}`, {
data: {
...this.userData,
}
});
this.changeRoute('list');
}, },
单文件组件的部分
在这部分,我们将创建单文件组件的<template>
部分。按照以下说明正确创建组件:
- 我们需要用
VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为12
:
<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
- 在
VsCard
的页脚上,我们需要把Cancel
按钮的changeComponent
函数改为changeRoute
:
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Cancel
</vs-button>
创建动态路由
现在,我们已经创建了页面视图,我们需要创建路由并使其接受参数,将它们转换为动态路由。在接下来的步骤中,我们将创建应用程序的动态路由:
打开src/router
文件夹中的index.js
。
首先,我们需要导入四个新页面 - List
,View
,Edit
,Create
和Update
:
import List from '@/views/List.vue'; import View from '@/views/View.vue'; import Edit from '@/views/Edit.vue'; import Create from '@/views/Create.vue';
在routes
数组上,我们将为每个导入的页面添加一个新的路由对象。在这个对象中,将有三个属性:name
,path
和component
。
对于list
路由,我们将把name
定义为'list'
,path
定义为'/'
,并把component
定义为导入的List
组件:
{
path: '/',
name: 'list',
component: List, },
- 在
view
路由上,我们将把name
定义为'view'
,path
定义为'/view/:id'
,并把component
定义为导入的View
组件:
{
path: '/view/:id',
name: 'view',
component: View, },
- 在
edit
路由上,我们将把name
定义为'edit'
,path
定义为'/edit/:id'
,并把component
定义为导入的Edit
组件:
{
path: '/edit/:id',
name: 'edit',
component: Edit, },
- 最后,在
create
路由上,我们将把name
定义为'create'
,path
定义为'/create'
,并把component
定义为导入的Create
组件:
{
path: '/create',
name: 'create',
component: Create, },
- 当创建
VueRouter
时,我们将添加mode
选项属性,并将其设置为'history'
:
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes });
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件渲染和运行的方式:
- 列表视图路由 -
/
将是您的用户列表页面,包含应用程序中所有用户的列表以及查看、编辑和删除用户的按钮,以及创建新用户的按钮:
- 用户视图路由 -
/view/:id
将是您的用户查看页面,您可以在此页面查看用户信息,例如用户的姓名、电子邮件、国家、生日和电话号码:
- 用户编辑路由 -
/update/:id
将是您的用户编辑页面,您可以在此页面编辑用户信息,更改用户的姓名、电子邮件、国家、生日和电话号码:
- 创建用户路由别名 -
/update/:id
将是您的用户创建页面,您可以在系统上创建新用户:
它是如何工作的...
当创建vue-router
并将路由传递进行匹配时,路由分析会根据每个路由的权重定义的正则表达式来寻找最佳匹配路由。
当定义路由并在其路径中有一个变量时,您需要在变量参数之前添加:
。此参数通过$route.params
属性传递给组件。
另请参阅
您可以在router.vuejs.org/guide/essentials/dynamic-matching.html
找到有关动态路由匹配的更多信息。
创建路由别名
每个应用程序都是一个活生生的有机体-它不断发展、突变和变化。有时,这些进化可以通过路由更改的形式来实现,以获得更好的命名或废弃的服务。在vue-router
中,可以使所有这些更改对用户不可见,因此当他们使用旧链接时,仍然可以访问应用程序。
在这个教程中,我们将学习如何为我们的应用程序创建路由别名并使用它。
准备工作
此教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到这一点...
要启动我们的组件,我们将使用在“创建动态路由路径”配方中完成的 Vue 项目,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动功能,并按照“如何做…”部分的指示,将router
添加为必需功能。
现在,在以下步骤中,我们将创建路由别名:
在src/router
文件夹中打开index.js
。
在list
对象中,我们将把path
属性从'/'
改为'/user'
,并为alias
属性设置为'/'
:
{
path: '/user',
name: 'list',
alias: '/',
component: List, },
- 在
view
对象中,我们将把path
属性从'/view/:id'
改为'/user/:id'
,并将alias
属性设置为'/view/:id'
:
{
path: '/user/:id',
name: 'view',
alias: '/view/:id',
component: View, },
- 在
edit
对象中,我们将把path
属性从'/edit/:id'
改为'/user/edit/:id'
,并将alias
属性设置为'/edit/:id'
。
{
path: '/user/edit/:id',
name: 'edit',
alias: '/edit/:id',
component: Edit, },
- 最后,在
create
对象中,我们将把path
属性从'/create'
改为'/user/create'
,并将alias
属性设置为'/create'
:
{
path: '/user/create',
name: 'create',
alias: '/create',
component: Create, },
工作原理…
当用户进入您的应用程序时,vue-router
将尝试将路径与用户尝试访问的路径匹配。如果路由对象中有一个名为alias
的属性,则vue-router
将使用此属性在幕后维护旧路由,并使用别名路由。如果找到别名,则渲染该别名的组件,并且路由器保持为别名,不向用户显示更改,使其透明。
在我们的场景中,我们对应用程序进行了转换,现在处理所有在/user
命名空间上调用的用户,但仍保持旧的 URL 结构,以便如果旧访问者尝试访问网站,他们将能够正常使用应用程序。
另请参阅
您可以在router.vuejs.org/guide/essentials/redirect-and-alias.html#alias
找到有关vue-router
别名的更多信息。
创建路由重定向
路由重定向几乎与路由别名相同,但主要区别在于用户确实被重定向到新的 URL。使用此过程,您可以管理如何加载新路由。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在'创建路由别名'配方中完成的 Vue-CLI 中的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动功能并将Router
作为必需的功能添加到'如何做...'步骤中的'创建一个简单路由'配方中。
现在,在这些步骤中,我们将创建路由重定向规则:
打开src/router
文件夹中的index.js
。
在routes
数组的末尾插入一个新的路由对象。这个对象将有两个属性,path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/create-new-user'
,在redirect
中,用户将被重定向到的路径,在这种情况下是'/user/create'
。
{
path: '/create-new-user',
redirect: '/user/create', },
- 创建一个新对象,这个对象将有两个属性,
path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/users'
,在redirect
中,我们将创建一个具有名为name
的属性的对象,并将值设置为'list'
。
{
path: '/users',
redirect: {
name: 'list',
}, },
- 创建一个新对象。这个对象将有两个属性,
path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/my-user/:id?'
,在redirect
中,我们将创建一个函数,该函数将接收一个参数to
,这是当前路由的对象。我们需要检查路由中是否存在用户 ID,以便将用户重定向到编辑页面。否则,我们将把他们重定向到用户列表。
{
path: '/my-user/:id?',
redirect(to) {
if (to.params.id) {
return '/user/:id';
}
return '/user';
}, },
- 最后,在最后,我们将创建一个具有两个属性,
path
和redirect
的路由对象。在path
属性中,我们需要定义用户将输入的路径,'/*'
,在redirect
中,我们需要将redirect
属性定义为'/'
。
{
path: '*',
redirect: '/', },
请记住,具有'*'
的最后一个路由将始终是在用户尝试输入的 URL 中没有匹配时呈现的路由。
它是如何工作的...
当我们将redirect
定义为一个新的路由时,它的工作方式类似于别名,但是redirect
属性可以接收三种类型的参数:一个字符串,当重定向到路由本身时,对象,当使用其他参数重定向时,例如路由的名称,最后但并非最不重要的是函数类型,redirect
可以处理并返回前两个对象中的一个,以便用户可以被重定向。
另请参阅
您可以在router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect
找到有关vue-router
重定向的更多信息。
创建嵌套路由视图
在vue-router
中,嵌套路由就像是路由的命名空间,您可以在同一个路由内拥有多个级别的路由,使用基本视图作为主视图,并在其中呈现嵌套路由。
在多模块应用程序中,这用于处理像 CRUD 这样的路由,其中您将拥有一个基本路由,而子路由将是 CRUD 视图。
在这个配方中,您将学习如何创建嵌套路由。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何操作...
要启动我们的组件,我们将使用在“创建动态路由路径”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动功能,并在“如何操作...”部分中将Router
添加为必需功能,如“创建简单路由”配方中所示。
我们的配方将分为两部分:
在布局上创建router-view
更改路由文件
让我们开始吧。
在布局上创建router-view
在使用带有子路由的vue-router
时,我们需要创建主视图,它将具有一个名为RouterView
的特殊组件。此组件将在您呈现的布局或页面内呈现当前路由。
现在,在接下来的步骤中,我们将为页面创建布局:
在src/views
文件夹中,我们需要创建一个名为user
的新文件夹,并将Create
、Edit
、List
、和View
页面移动到这个新文件夹中。
在user
文件夹中创建一个名为Index.vue
的新文件并打开它。
在单文件组件<template>
部分中,添加一个router-view
组件:
<template>
<router-view/>
</template>
<script>
export default {
name: 'User',
}
</script>
更改路由文件
我们将创建一个新文件来管理用户的特定路由,这将帮助我们维护代码并使其更清晰。
用户路由
在接下来的步骤中,我们将为用户创建路由:
在src/router
文件夹中创建一个名为user.js
的新文件。
导入Index
、List
、View
、Edit
和Create
视图:
import Index from '@/views/user/Index.vue'; import List from '@/views/user/List.vue'; import View from '@/views/user/View.vue'; import Edit from '@/views/user/Edit.vue'; import Create from '@/views/user/Create.vue';
- 创建一个数组,并将其设置为文件的默认导出。在这个数组中,添加一个
route
对象,具有四个属性-path
,name
,component
和children
。将path
属性设置为'/user'
,将name
属性定义为'user'
,将component
定义为导入的Index
组件,最后,将children
属性定义为空数组:
export default [
{ path: '/user',
name: 'user',
component: Index,
children: [],
}, ]
- 在
children
属性中,添加一个新的路由对象,具有三个属性-path
,name
和component
。将path
定义为''
,name
定义为'list'
,最后,将component
属性定义为导入的List
组件:
{
path: '',
name: 'list',
component: List, },
- 为视图路由创建一个路由对象,并使用与上一个
route
对象相同的结构。将path
属性定义为':id'
,将name
定义为'view'
,将component
定义为导入的View
组件:
{
path: ':id',
name: 'view',
component: View, },
- 为
edit
路由创建一个路由对象,并使用与上一个route
对象相同的结构。将path
属性定义为'edit/:id'
,将name
定义为'edit'
,将component
定义为导入的Edit
组件:
{
path: 'edit/:id',
name: 'edit',
component: Edit, },
- 为
create
路由创建一个路由对象,使用与上一个route
对象相同的结构。将path
属性定义为'create'
,将name
定义为'create'
,将component
定义为导入的Create
组件:
{
path: 'create',
name: 'create',
component: Create, },
路由管理器
在以下步骤中,我们将创建路由管理器,该管理器将控制应用程序中的所有路由:
在src/router
文件夹中打开index.js
。
在src/router
文件夹中导入新创建的user.js
文件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user';
- 在
routes
数组中,将导入的UserRoutes
作为解构数组添加:
const routes = [
...UserRoutes,
{
path: '*',
redirect: '/user',
}, ];
工作原理...
vue-router
提供了使用子路由作为当前视图或布局的内部组件的能力。这使得可以创建具有特殊布局文件的初始路由,并通过RouterView
组件在此布局中呈现子组件。
这种技术通常用于在应用程序中定义布局并为模块设置命名空间,其中父路由可以具有一组特定顺序,这些顺序将对其每个子路由都可用。
另请参阅
您可以在router.vuejs.org/guide/essentials/nested-routes.html#nested-routes
找到有关嵌套路由的更多信息。
创建 404 错误页面
有时您的用户可能会尝试输入旧链接或输入拼写错误,无法到达正确的路由,这应该直接导致找不到错误。
在这个配方中,您将学习如何在vue-router
中处理 404 错误。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在“创建嵌套路由视图”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create http-project
选择手动功能并将Router
添加为所需功能,如“如何做...”部分在“创建简单路由”配方中所示。
我们的配方将分为两部分:
创建NotFound
视图
更改路由文件
让我们开始吧。
创建 NotFound 视图
我们需要创建一个新的视图,当应用程序上没有匹配的路由时,将显示给用户。这个页面将是一个简单的通用页面。
单文件组件部分
在这部分中,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
在src/views
文件夹中,创建一个名为NotFound.vue
的新文件并打开它。
创建一个VsRow
组件,在其中创建四个VsCol
组件。所有这些组件都将具有属性vs-w
定义为12
和class
定义为text-center
:
<vs-row>
<vs-col vs-w="12" class="text-center">
<!-- Icon --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Title --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Text --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Button --> </vs-col> </vs-row>
- 在第一个
VsCol
组件中,我们将添加一个VsIcon
组件,并将属性 icon 定义为sentiment_dissatisfied
,并将size
定义为large
:
<vs-icon
icon="sentiment_dissatisfied"
size="large" />
- 在第二个
VsCol
组件中,我们将为页面添加一个标题:
<h1>Oops!</h1>
- 在第三个
VsCol
组件中,我们需要创建将放置在页面上的文本:
<h3>The page you are looking for are not here anymore...</h3>
- 最后,在第四个
VsCol
组件上,我们将添加VsButton
组件。此按钮将具有属性type
定义为relief
和to
定义为'/'
:
<vs-button
type="relief"
to="/" >
Back to Home...
</vs-button>
单文件组件<style>部分
在这部分中,我们将创建单文件组件的<style>
部分。按照这些说明正确创建组件:
- 在
<style>
标签中添加scoped
标签:
<style scoped> </style>
- 创建一个名为
.text-center
的新规则,其中text-align
属性定义为center
,margin-bottom
定义为20px;
:
.text-center {
text-align: center;
margin-bottom: 20px; }
更改路由文件
创建视图后,我们需要将其添加到路由并使其对用户可用。为此,我们需要将视图路由添加到路由管理器中。
在这些步骤中,我们将更改路由管理器,以添加新的错误页面:
在src/router
文件夹中打开index.js
。
导入NotFound
组件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user'; import NotFound from '@/views/NotFound';
- 在
routes
数组中,在UserRoutes
之后,添加一个新的route
对象,具有两个属性,path
和redirect
。将path
属性定义为'/'
,将redirect
属性定义为'/user'
:
{
path: '/',
redirect: '/user' },
- 对于未找到的页面,我们需要创建一个新的路由对象,该对象需要放在
routes
数组的最后位置。这个路由对象将有两个属性,path
和component
。path
属性将被定义为'*'
,component
将被定义为导入的NotFound
视图:
{
path: '*',
component: NotFound, },
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
vue-router
尝试找到用户想要访问的 URL 的最佳匹配;如果没有匹配项,vue-router
将使用'*'
路径作为这些情况的默认值,其中*
表示用户输入的不在路由列表中的任何值。
因为在vue-router
中匹配的过程是由路由的权重决定的,所以我们需要将错误页面放在最底部,这样vue-router
在实际调用NotFound
路由之前需要通过每个可能的路由。
另请参阅
您可以在router.vuejs.org/guide/essentials/history-mode.html#caveat
找到有关处理 vue-router 历史模式中的 404 错误的更多信息。
创建和应用身份验证中间件
在vue-router
中,可以创建路由守卫-每次路由更改时运行的函数。这些守卫被用作路由管理过程中的中间件。通常将它们用作身份验证中间件或会话验证器。
在这个示例中,我们将学习如何创建身份验证中间件,向我们的路由添加元数据以使它们受限制,并创建登录页面。
准备工作
这个示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到...
要启动我们的组件,我们将使用在“创建 404 错误页面”配方中使用的 Vue-CLI 的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create http-project
选择手动特性,并在“如何做...”部分中添加Router
作为必需特性,如“创建简单路由”配方中所示。
我们的配方将分为三个部分:
创建身份验证中间件
向路由添加元数据和中间件
将中间件附加到 vue-router 并创建登录页面
让我们开始。
创建登录视图
登录视图将是用户在未经过身份验证时看到的页面。我们将构建一个简单的页面,里面有两个输入框 - 一个卡片和一个按钮。
单文件组件<script>部分
在这部分,我们将创建单文件组件的<script>
部分。按照这些说明正确创建组件:
在src/views
文件夹中,创建一个名为Login.vue
的新文件并打开它。
创建一个包含username
、password
和error
的data
属性:
data: () => ({
username: '',
password: '',
error: false, }),
- 然后创建一个名为
userSignIn
的方法的methods
属性。此方法将验证username
和password
数据是否完整。如果是,它将在sessionStorage
中创建一个名为'auth'
的新密钥,其中包含username
数据的加密字符串化 JSON。然后,将error
设置为false
并执行$router.replace
将用户重定向到用户列表'/user'
。如果任何字段未通过任何验证,该方法将将错误定义为true
并返回false
:
methods: {
userSignIn() {
if (this.username && this.password) {
window.sessionStorage.setItem('auth',
window.btoa(JSON.stringify({
username: this.username
})
) );
this.error = false;
this.$router.replace('/user');
}
this.error = true;
return false;
}, }
单文件组件部分
在这部分,我们将创建单文件组件的<template>
部分。按照这些说明正确创建组件:
- 创建一个带有
VsRow
组件的div.container
HTML 元素。VsRow
组件将具有属性vs-align
定义为"center"
和vs-justify
定义为"center"
:
<div class="container">
<vs-row
vs-align="center"
vs-justify="center"
>
</vs-row>
</div>
- 在
VsRow
组件内部,添加一个带有属性vs-lg
定义为4
,vs-sm
定义为6
和vs-xs
定义为10
的VsCol
组件。然后,在VsCol
组件内部,我们将创建一个带有style
属性定义为margin: 20px;
的VsCard
组件:
<vs-col
vs-lg="4"
vs-sm="6"
vs-xs="10" >
<vs-card
style="margin: 20px;"
>
</vs-card>
</vs-col>
- 在
VsCard
组件内部,创建一个带有名称为header
的slot
的动态<template>
,一个h3
HTML 元素和您的标题:
<template slot="header">
<h3>
User Login
</h3> </template>
- 之后,创建一个
VsRow
组件,其中属性vs-align
定义为"center"
,vs-justify
定义为"center"
,并在其中放置两个VsCol
组件,其中属性vs-w
定义为12
:
<vs-row
vs-align="center"
vs-justify="center" >
<vs-col vs-w="12">
</vs-col>
<vs-col vs-w="12">
</vs-col>
</vs-row>
- 在第一个
VsCol
组件上,我们将添加一个VsInput
组件,其中属性danger
定义为数据error
的值,danger-text
定义为错误时显示的文本,label
定义为"Username"
,placeholder
定义为"Username or e-mail"
,并且v-model
指令绑定到username
:
<vs-input
:danger="error"
danger-text="Check your username or email"
label="Username"
placeholder="Username or e-mail"
v-model="username" />
- 在第二个
VsCol
组件中,我们将添加一个VsInput
组件,其中属性danger
定义为数据error
的值,danger-text
定义为错误时显示的文本,label
定义为"Password"
,type
定义为password
,placeholder
定义为"Your password"
,并且v-model
指令绑定到password
:
<vs-input
:danger="error"
label="Password"
type="password"
danger-text="Check your password"
placeholder="Your password"
v-model="password" />
- 最后,在卡片页脚中,我们需要创建一个动态的
<template>
,其中包含名为footer
的插槽。在这个<template>
中,我们将添加一个VsRow
组件,其中vs-justify
属性定义为flex-start
,并插入一个VsButton
,其中属性color
定义为success
,type
定义为filled
,icon
定义为account_circle
,size
定义为small
,并且@click
事件监听器指向userSignIn
方法:
<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="account_circle"
size="small"
@click="userSignIn"
>
Sign-in
</vs-button>
</vs-row> </template>
单文件组件<style>部分
在这部分,我们将创建单文件组件的<style>
部分。按照以下说明正确创建组件:
- 首先,我们需要使这个部分具有作用域,这样 CSS 规则就不会影响应用程序的任何其他组件:
<style scoped></style>
- 然后,我们需要为
container
类和VsInput
组件添加规则:
<style scoped>
.container {
height: 100vh;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
} .vs-input {
margin: 5px;
} </style>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
创建中间件
所有vue-router
中间件也可以称为导航守卫,并且它们可以附加到应用程序路由更改上。这些更改有一些钩子,您可以将其应用于您的中间件。身份验证中间件在路由更改之前发生,因此我们可以处理一切并将用户发送到正确的路由。
在src/router
文件夹中,创建一个名为middleware
的新文件夹,然后创建并打开一个名为authentication.js
的新文件。
在这个文件中,我们将创建一个默认的export
函数,它将有三个函数参数 - to
,from
和next
。to
和from
参数是对象,next
参数是一个回调函数:
export default (to, from, next) => { };
- 我们需要检查我们被重定向到的路由是否具有设置为
true
的经过身份验证的meta
属性,并且我们是否有一个具有'auth'
键的sessionStorage
项。如果通过了这些验证,我们可以执行next
回调:
if (to.meta.authenticated && sessionStorage.getItem('auth')) {
return next(); }
- 然后,如果第一个验证没有通过,我们需要检查我们将用户重定向到的路由是否具有经过身份验证的
meta
属性,并检查它是否为false
值。如果验证通过,我们将执行next
回调:
if (!to.meta.authenticated) {
return next(); }
- 最后,如果我们的任何验证都没有通过,执行
next
回调,传递'/login'
作为参数:
next('/login');
将元数据和中间件添加到路由器
创建完我们的中间件后,我们需要定义哪些路由将被验证,哪些路由不会被验证。然后我们需要将中间件导入到路由器中,并在执行时定义它:
在src/router
文件夹中打开user.js
。
在每个route
对象中,添加一个名为meta
的新属性。这个属性将是一个对象,具有一个经过身份验证的key
和一个值定义为true
。我们需要对每个路由都这样做 - 即使是子路由:
import Index from '@/views/user/Index.vue'; import List from '@/views/user/List.vue'; import View from '@/views/user/View.vue'; import Edit from '@/views/user/Edit.vue'; import Create from '@/views/user/Create.vue'; export default [
{ path: '/user',
name: 'user',
component: Index,
meta: {
authenticated: true,
},
children: [
{ path: '',
name: 'list',
component: List,
meta: {
authenticated: true,
},
},
{
path: ':id',
name: 'view',
component: View,
meta: {
authenticated: true,
},
},
{
path: 'edit/:id',
name: 'edit',
component: Edit,
meta: {
authenticated: true,
},
},
{
path: 'create',
name: 'create',
component: Create,
meta: {
authenticated: true,
},
},
],
}, ]
在src/router
文件夹中打开index.js
。
导入新创建的中间件和Login
视图组件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user'; import NotFound from '@/views/NotFound'; import Login from '@/views/Login'; import AuthenticationMiddleware from './middleware/authentication';
- 为登录页面视图创建一个新的
route
对象。这个路由对象将path
设置为'/login'
,name
定义为'login'
,component
定义为Login
,并且meta
属性将具有authenticated
键,其值设置为false
:
{
path: '/login',
name: 'login',
component: Login,
meta: {
authenticated: false,
}, },
- 在错误处理路由上,我们将定义
meta
属性authenticated
为false
,因为登录视图是一个公共路由:
{
path: '*',
component: NotFound,
meta: {
authenticated: false,
}, },
- 最后,在创建了
router
构造函数之后,我们需要在beforeEach
执行中注入中间件:
router.beforeEach(AuthenticationMiddleware);
它是如何工作的...
路由守卫作为中间件工作;它们在vue-router
进程的每个生命周期中都有一个钩子被执行。对于这个示例,我们选择了beforeEach
钩子来添加我们的中间件。
在这个钩子中,我们检查用户是否经过了身份验证,以及用户是否需要身份验证才能导航到该路由。在检查了这些变量之后,我们通过将用户发送到他们需要的路由来继续这个过程。
另请参阅
您可以在router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
找到有关 vue-router 路由守卫的更多信息。
异步加载您的页面
组件可以在需要时加载,路由也可以。使用vue-router
的惰性加载技术可以在应用程序中进行更多的代码拆分和更小的最终捆绑包。
在这个配方中,我们将学习如何转换路由以异步加载它们。
准备工作
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“创建身份验证中间件”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以启动一个新的项目。
要启动新的路由管理器,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create http-project
选择手动功能,并将Router
添加为所需的功能,如“如何做...”部分和“创建简单路由”配方中所示。
我们的配方将分为两部分:
更新路由管理器
更新用户路由
让我们开始吧。
更新路由管理器
要更新路由管理器,请按照以下说明进行操作:
在src/router
文件夹中打开index.js
文件。
在每个具有component
属性的路由中,我们将把组件的直接赋值转换为一个新函数。这将是一个返回 webpack 的import()
方法的箭头函数:
{
path: '/login',
name: 'login',
component: () => import('@/views/Login'),
meta: {
authenticated: false,
}, },
- 在每个具有
component
属性的route
对象上重复此过程。
更新用户路由
要更新用户路由,请按照以下说明进行操作:
在src/router
文件夹中打开user.js
文件。
在每个具有component
属性的路由中,我们将把组件的直接赋值转换为一个新函数。这将是一个返回 webpack 的import()
方法的箭头函数。
{
path: '/user',
name: 'user',
component: () => import('@/views/user/Index.vue'),
meta: {
authenticated: true,
},
children: [], },
- 在每个具有
component
属性的route
对象上重复此过程。
它是如何工作的...
在 ECMAScript 中,当我们使用export default
方法时,export
和import
是具有预定义值的对象。这意味着当我们import
一个新组件时,该组件已经指向该文件的default export
。
为了进行延迟加载过程,我们需要传递一个在运行时执行的函数,该函数的返回值将是 webpack 在捆绑过程中分割的代码的一部分。
当我们在vue-router
中调用这个函数时,vue-router
不直接导入组件,而是进行验证检查,确保当前组件导入是一个需要执行的函数。在函数执行后,响应被用作将显示在用户屏幕上的组件。
由于 webpack 的import()
方法是异步的,这个过程可以与其他代码执行同时进行,而不会干扰或阻塞 JavaScript 虚拟机的主线程。
另请参阅
您可以在router.vuejs.org/guide/advanced/lazy-loading.html
找到有关vue-router
延迟加载的更多信息。
您可以在webpack.js.org/guides/code-splitting/
找到有关webpack
代码拆分的更多信息。
您可以在github.com/tc39/proposal-dynamic-import
找到有关 ECMAScript 动态导入提案的更多信息。
第七章:使用 Vuex 管理应用程序状态
在兄弟组件之间传输数据可能非常容易,但想象一下让一系列组件对任何数据变化做出反应。你需要在事件总线中触发一个事件,或者通过所有父组件发送事件,直到它到达事件链的顶部,然后再一路发送到所需的组件;这个过程可能非常繁琐和痛苦。如果你正在开发一个大型应用程序,这个过程是不可持续的。
Flux 库是为了帮助这个过程而开发的,其想法是将反应性带出组件边界,因为 Vuex 能够维护数据的唯一真相来源,并且同时也是你制定业务规则的地方。
在这一章中,我们将学习如何使用 Vuex,开发我们的存储,将其应用到我们的组件,并对其进行命名空间,以便我们可以在同一个存储中拥有不同的 Vuex 模块。
在这一章中,我们将涵盖以下的配方:
创建一个简单的 Vuex 存储
创建和理解 Vuex 状态
创建和理解 Vuex 变化
创建和理解 Vuex 操作
创建和理解 Vuex 获取器
使用 Vuex 创建一个动态组件
为开发添加热模块重新加载
创建一个 Vuex 模块
技术要求
在这一章中,我们将使用Node.js和Vue-CLI。
注意,Windows 用户,你需要安装一个名为windows-build-tools
的 NPM 包,以便安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Vue-CLI,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个简单的 Vuex 存储
在应用程序中创建一个唯一的真相来源可以让你简化数据流,使数据的反应性流向另一个视角,你不再受限于父子关系。数据现在可以存储在一个地方,每个人都可以获取或请求数据。
在这个配方中,我们将学习如何安装 Vuex 库并创建我们的第一个单一存储,以及如何使用反应式操作和数据获取器来操作它。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要创建一个 Vue-CLI 项目,请按照以下步骤进行:
- 我们需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> vue create initial-vuex
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用Spacebar选择选项。
有两种方法可以启动新项目。默认方法是基本的babel
和eslint
项目,没有任何插件或配置,还有手动
模式,您可以选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys)
default (babel, eslint)
❯ Manually select features
- 现在我们被问及项目中需要的功能。这些功能包括一些 Vue 插件,如 Vuex 或 Router(
Vue-Router
),测试工具,代码检查工具等。选择Babel
,Router
,Vuex
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router ❯ Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 继续此过程,选择一个代码检查工具和格式化工具。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 设置了代码检查规则后,我们需要定义它们何时应用于您的代码。它们可以在“保存时”应用,也可以在“提交时”修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、代码检查工具和处理器之后,我们需要选择设置和配置的存储位置。最好的存储位置是专用文件,但也可以将它们存储在
package.json
文件中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ **In dedicated config files****In package.json**
- 现在您可以选择是否将此选择作为将来项目的预设,这样您就不需要再次重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
我们的步骤将分为两部分:
创建 store
使用 Vuex 创建响应式组件
让我们开始吧。
创建 store
现在您已经有了包含 Vuex 库的项目,我们需要创建我们的第一个 store。在接下来的步骤中,我们将创建 Vuex store:
打开src/store
文件夹中的index.js
。
在state
属性中,添加一个名为counter
的新键,并将值设置为0
:
state: {
counter: 0,
},
- 在
mutations
属性中,添加两个新函数,increment
和decrement
。这两个函数都将有一个state
参数,这是当前的 Vuexstate
对象。increment
函数将counter
增加1
,而decrement
函数将counter
减少1
:
mutations: {
increment: (state) => {
state.counter += 1;
},
decrement: (state) => {
state.counter -= 1;
},
},
- 最后,在
actions
属性中,添加两个新函数,increment
和decrement
。这两个函数都将有一个解构参数commit
,它是调用 Vuex mutation 的函数。在每个函数中,我们将执行commit
函数,将当前函数的名称作为字符串参数传递:
actions: {
increment({ commit }) {
commit('increment');
},
decrement({ commit }) {
commit('decrement');
},
},
使用 Vuex 创建响应式组件
现在您已经定义了您的 Vuex 存储,您需要与之交互。我们将创建一个响应式组件,它将在屏幕上显示当前状态的counter
,并显示两个按钮,一个用于增加counter
,另一个用于减少counter
。
单文件组件<script>部分
在这里,我们将编写单文件组件的<script>
部分:
从src
文件夹中打开App.vue
文件。
在文件中创建<script>
部分,使用export default
对象:
<script>
export default {}; </script>
- 在新创建的对象中,添加 Vue
computed
属性,属性名为counter
。在这个属性中,我们需要返回当前的$store.state.counter
:
computed: {
counter() {
return this.$store.state.counter;
}, },
- 最后,在 Vue
methods
属性中创建两个函数,increment
和decrement
。这两个函数都将执行一个带有函数名称作为字符串参数的$store.dispatch
:
methods: {
increment() {
this.$store.dispatch('increment');
},
decrement() {
this.$store.dispatch('decrement');
}, },
单文件组件部分
让我们编写单文件组件的<template>
部分:
在src
文件夹中打开App.vue
文件。
在<template>
部分中,删除div#app
内的所有内容。
创建一个包含计数器变量的h1
HTML 元素。
创建一个带有@click
指令的事件监听器的按钮,调用increment
函数,并将+
作为标签:
<button @click="increment">+</button>
- 创建一个带有
@click
指令的事件监听器的按钮,调用decrement
函数,并将-
作为标签:
<button @click="decrement">-</button>
要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
这是您的组件呈现并运行的方式:
它是如何工作的...
当您声明 Vuex 存储时,您需要创建三个主要属性,state
,mutations
和actions
。这些属性作为一个单一的结构,通过注入的$store
原型或导出的store
变量绑定到 Vue 应用程序。
state
是一个集中的对象,保存您的信息并使其可供mutation
、actions
或组件使用。改变state
始终需要通过mutation
执行的同步函数。
mutation
是一个同步函数,可以改变state
并且是可追踪的,因此在开发时,可以通过 Vuex 存储中执行的所有mutations
进行时间旅行。
action
是一个异步函数,可用于保存业务逻辑、API 调用、分派其他actions
和执行mutations
。这些函数是 Vuex 存储中任何更改的常见入口点。
Vuex 存储的简单表示可以在此图表中看到:
另请参阅
您可以在vuex.vuejs.org/
找到有关 Vuex 的更多信息。
创建和理解 Vuex 状态
Vuex 状态似乎很容易理解。但是,随着数据变得更加深入和嵌套,其复杂性和可维护性可能变得更加复杂。
在本配方中,我们将学习如何创建一个 Vuex 状态,该状态可以在渐进式 Web 应用程序(PWA)/**单页面应用程序(SPA)和服务器端渲染(SSR)**的情景中使用,而无需任何问题。
准备就绪
本配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在第六章中使用的 Vue-CLI 的 Vue 项目,或者我们可以开始一个新的项目。
要启动一个新项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create vuex-store
选择手动功能,根据'如何做...'部分的指示,将Router
和Vuex
添加为必需功能。
我们的配方将分为两部分:
通过vue ui
添加 Vuex
创建Vuex
状态
让我们开始吧。
通过 vue ui 添加 Vuex
当导入通过 Vue-CLI 创建的旧项目时,可以通过vue ui
界面自动添加 Vuex,而无需任何努力。我们将学习如何向旧项目添加 Vuex 库,以便继续开发该项目。
在接下来的步骤中,我们将使用vue ui
界面添加 Vuex。
- 在项目文件夹中,通过在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)上执行以下命令来打开
vue ui
:
> vue ui
- 选择你正在工作的正确项目。在右侧边栏中,点击插件菜单图标:
- 在插件页面的顶部工具栏上,点击“添加 vuex”按钮。这将触发一个弹出模态窗口,然后点击“继续”按钮完成在应用程序上安装 Vuex:
- 将 Vuex 添加到我们的应用程序将改变应用程序的结构。首先,我们会注意到
src
文件夹中有一个名为store
的新文件夹,在main.js
文件中,它被添加到了导入和在 Vue 应用程序中注入store
:
import './server/server'; import Vue from 'vue'; import App from './App.vue'; import Vuesax from 'vuesax'; import './style.css'; import router from './router' import store from './store' Vue.use(Vuesax); Vue.config.productionTip = false; new Vue({
router,
store,
render: h => h(App) }).$mount('#app');
创建 Vuex 状态
为了将数据保存在 Vuex 中,您需要有一个初始状态,在用户进入您的应用程序时加载并定义为默认状态。在这里,我们将学习如何创建 Vuex 状态并将其用作单例,以便 Vuex 可以在 SPA 和 SSR 页面中使用:
现在我们将创建一个可以在 SSR 和 SPA 中使用的 Vuex 存储:
在src/store
文件夹中,创建一个名为user
的新文件夹,在这个文件夹里创建一个名为state.js
的新文件。
创建一个新的generateState
函数。这个函数将返回一个 JavaScript 对象,有三个主要属性,data
,loading
和error
。data
属性将是一个 JavaScript 对象,其中有一个名为usersList
的属性,默认为空数组,以及一个名为userData
的属性,其中包含用户的默认对象。loading
属性将默认设置为布尔值false
,error
将有一个默认值初始化为null
:
const generateState = () => ({
data: {
usersList: [],
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
},
},
loading: false,
error: null, });
- 创建函数后,我们将在文件末尾创建一个
export default
对象,它将是一个 JavaScript 对象,并且我们将解构generateState
函数的返回值:
export default { ...generateState() };
在user
文件夹中创建一个名为index.js
的新文件并打开它。
导入新创建的state
:
import state from './state';
- 在文件末尾,创建一个
export default
文件作为 JavaScript 对象。在这个对象中,我们将添加导入的state
:
export default {
state, };
打开src/store
文件夹中的index.js
文件。
从user
文件夹中导入index.js
文件:
import Vue from 'vue'; import Vuex from 'vuex'; import UserStore from './user';
- 在创建一个新的 Vuex store 的
export default
函数中,我们将删除其中的所有属性,并将导入的UserStore
解构对象放入Vuex.Store
参数中:
export default new Vuex.Store({
...UserStore, })
工作原理...
当使用vue ui
将 Vuex 作为插件添加时,vue ui
将自动添加所需的文件,并导入所有需要的内容。这是创建 Vuex store
的初始阶段。
首先是创建一个专门管理state
的文件,我们可以使用它来分离store
中状态的开始过程以及如何初始化状态。
在这种情况下,我们使用一个函数来生成每次调用时都会生成一个全新的state
。这是一个很好的做法,因为在 SSR 环境中,服务器的state
始终是相同的,我们需要为每个新连接创建一个新的state
。
在创建state
之后,我们需要创建一个默认文件来导出将在user
文件夹中创建的 Vuex 文件。这个文件是对将在文件夹中创建的所有文件(state
,actions
,mutation
和getters
)的简单导入。导入后,我们导出一个带有所需的 Vuex 属性名称的对象,state
,actions
,mutations
和getters
。
最后,在 Vuex 的store
中,我们导入了一个文件,将所有内容聚合并解构到我们的 store 中进行初始化。
还有更多...
Vuex
state 是应用程序中的唯一数据源,它就像一个全局数据管理器,不应直接更改。这是因为我们需要防止数据的同时变异。为了避免这种情况,我们总是需要通过 mutations 来改变我们的 state,因为这些函数是同步的,并由 Vuex 控制。
另请参阅
在vuex.vuejs.org/guide/state.html
找到有关 Vuex state 的更多信息。
创建和理解 Vuex mutations
当 Vuex 发生变化时,我们需要一种以异步形式执行这种变化并跟踪它的方式,以便在第一个变化完成之前不会执行另一个变化。
在这种情况下,我们需要 mutations,这些是仅负责改变应用程序状态的函数。
在这个示例中,我们将学习如何创建 Vuex mutations 以及最佳实践。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在'创建和理解 Vuex 状态'食谱中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create vuex-store
选择手动功能,根据'如何做...'部分的指示,添加Router
和Vuex
作为必需功能。
现在我们创建一个 Vuex mutation 和基本类型的 mutation:
在src/store
文件夹内的user
文件夹中创建一个名为types.js
的新文件,并打开它。
在这个文件中,我们将创建一个export default
的 JavaScript 对象,其中包含一组键,这些键将是我们 mutations 的名称。这些键将是LOADING
、ERROR
、SET_USER_LIST
、SET_USER_DATA
、UPDATE_USER
和REMOVE_USER
:
export default {
LOADING: 'LOADING',
ERROR: 'ERROR',
SET_USER_LIST: 'SET_USER_LIST',
SET_USER_DATA: 'SET_USER_DATA',
UPDATE_USER: 'UPDATE_USER',
REMOVE_USER: 'REMOVE_USER', }
在user
文件夹中创建一个名为mutations.js
的新文件,并打开它。
导入新创建的types.js
文件:
import MT from './types';
- 创建一个名为
setLoading
的新函数,它将接收 Vuex state
作为参数,并在执行时将状态的 loading 属性定义为true
。
const setLoading = state => {
state.loading = true; };
- 创建一个名为
setError
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state
的loading
属性设置为false
,将error
属性设置为接收到的payload
参数:
const setError = (state, payload) => {
state.loading = false;
state.error = payload; };
- 创建一个名为
setUserList
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state.data
的usersList
属性定义为接收到的payload
参数,将state
的loading
属性设置为false
,将error
属性设置为null
:
const setUserList = (state, payload) => {
state.data.usersList = payload;
state.loading = false;
state.error = null; };
- 创建一个名为
setUserData
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state.data
的userData
属性定义为接收到的payload
参数,将state
的loading
属性设置为false
,将error
属性设置为null
:
const setUserData = (state, payload) => {
state.data.userData = payload;
state.loading = false;
state.error = null; };
- 创建一个名为
updateUser
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将更新state.data
的usersList
属性中的用户数据,将state
的loading
属性定义为false
,将error
属性定义为null
:
const updateUser = (state, payload) => {
const userIndex = state.data.usersList.findIndex(u => u.id ===
payload.id);
if (userIndex > -1) {
state.data.usersList[userIndex] = payload;
}
state.loading = false;
state.error = null; };
- 创建一个名为
removeUser
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将从state.data
的usersList
属性中删除用户数据,将state
的loading
属性定义为false
,将error
属性定义为null
:
const removeUser = (state, payload) => {
const userIndex = state.data.usersList.findIndex(u => u.id ===
payload);
if (userIndex > -1) {
state.data.usersList.splice(userIndex, 1);
}
state.loading = false;
state.error = null; };
- 最后,创建一个
export default
对象,其中键是我们在types.js
文件中创建的类型,并将每个键定义为我们创建的函数:
export default {
[MT.LOADING]: setLoading,
[MT.ERROR]: setError,
[MT.SET_USER_LIST]: setUserList,
[MT.SET_USER_DATA]: setUserData,
[MT.UPDATE_USER]: updateUser,
[MT.REMOVE_USER]: removeUser, }
打开user
文件夹中的index.js
文件。
导入新创建的mutations.js
文件,并将其添加到export default
JavaScript 对象中:
import state from './state';
import mutations from './mutations';
export default {
state,
mutations,
};
它是如何工作的...
每个mutation
都是一个将作为commit
调用的函数,并且在 Vuex 存储中具有标识符。这个标识符是导出的 JavaScript 对象中的mutation
键。在这个示例中,我们创建了一个文件,将所有标识符作为对象值保存,以便在我们的代码中作为常量使用。
这种模式有助于我们开发需要知道每个mutation
名称的 Vuex actions
。
在导出mutation
JavaScript 对象时,我们使用常量作为键,相应的函数作为其值,这样 Vuex 存储在调用时可以执行正确的函数。
另请参阅
在vuex.vuejs.org/guide/mutations.html
找到有关 Vuex mutations 的更多信息。
创建和理解 Vuex getters
从Vuex
中访问数据可以通过状态本身完成,这可能非常危险,或者通过 getters 完成。Getters 就像是可以预处理并传递数据而不触及或干扰 Vuex 存储状态的数据。
Getter 背后的整个理念是可以编写自定义函数,可以在需要时从状态中提取数据的单一位置,以便获得所需的数据。
在这个示例中,我们将学习如何创建一个 Vuex getter 和一个可以作为高阶函数使用的动态 getter。
准备工作
此示例的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“创建和理解 Vuex mutations”示例中使用的 Vue 项目与 Vue-CLI,或者我们可以启动一个新的项目。
要启动一个新的项目,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能,并根据“如何做...”部分中的指示添加 Router 和Vuex
作为需要的功能。
在接下来的步骤中,我们将创建 Vuex 的 getter:
在src/store/user
文件夹中创建一个名为getters.js
的新文件。
创建一个名为getUsersList
的新函数,并返回state.data.usersList
属性:
function getUsersList(state) {
return state.data.usersList;
}
在getter
函数中,函数将始终接收到的第一个参数是 Vuex store
的当前state
。
- 创建一个名为
getUserData
的新函数,并返回state.data.userData
属性:
function getUserData(state) {
return state.data.userData; }
- 创建一个名为
getUserById
的新函数,并返回另一个函数,该函数接收userId
作为参数。这个返回函数将返回与接收到的userId
相匹配的state.data.usersList
的搜索结果:
function getUserById(state) {
return (userId) => {
return state.data.usersList.find(u => u.id === userId);
} }
- 创建一个名为
isLoading
的新函数,并返回state.loading
属性:
function isLoading(state) {
return state.loading;
}
- 创建一个名为
hasError
的新函数,并返回state.error
属性:
function hasError(state) {
return state.error;
}
- 最后,创建一个带有所有创建的函数作为属性的
export default
JavaScript 对象:
export default {
getUsersList,
getUserData,
getUserById,
isLoading,
hasError, };
在src/store/user
文件夹中打开index.js
文件。
导入新创建的getters.js
文件,并将其添加到默认导出的 JavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';
export default {
state,
mutations,
getters,
};
它是如何工作的...
Getter 就像是从对象中获取的 GET 函数,是静态缓存函数-只有在state
发生变化时才会改变返回值。但是,如果将返回值作为高阶函数添加,就可以赋予它更多的功能,使用更复杂的算法并提供特定的数据。
在这个示例中,我们创建了两种类型的 getter:最基本的,返回简单数据,以及高阶函数,需要作为函数调用以检索所需的值。
还有更多...
使用带有业务逻辑的 getter 是收集更多状态数据的好方法。这是一个很好的模式,因为在较大的项目中,它可以帮助其他开发人员更好地理解每个 GET 函数中发生了什么以及它在幕后是如何工作的。
您始终需要记住,getter 是同步函数,并对状态变化具有反应性,因此 getter 上的数据是被记忆和缓存的,直到单一的真相源接收到提交并更改它。
参见
您可以在vuex.vuejs.org/guide/getters.html
找到有关 Vuex getters 的更多信息。
创建和理解 Vuex actions
你已经准备好了所有的状态,你的数据集,现在你需要从外部来源获取新数据或者在你的应用程序中改变这些数据。这就是操作发挥作用的地方。
操作负责在应用程序和外部世界之间的通信中编排过程。控制数据何时需要在状态上进行变异并返回给操作的调用者。
通常,操作是通过组件或视图进行调度,但有时操作可以调度另一个操作,以在应用程序中创建一系列操作。
在这个配方中,我们将学习如何在我们的应用程序中创建所需的操作,以定义用户列表,更新用户和删除用户。
准备工作
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“创建和理解 Vuex getters”配方中使用的 Vue 项目,或者我们可以启动一个新的项目。
要启动一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能,并根据“如何做...”部分和“创建一个简单的 Vuex 存储”配方中指示的要求,添加Router
和Vuex
作为必需的功能。
现在按照以下步骤创建 Vuex 操作:
在src/store/user
文件夹中创建一个名为actions.js
的新文件。
从fetchApi
包装器中导入变异类型文件(types.js
)和getHttp
,patchHttp
,postHttp
和deleteHttp
函数:
import {
getHttp,
patchHttp,
deleteHttp,
postHttp,
} from '@/http/fetchApi';
import MT from './types';
- 创建一个名为
createUser
的新的异步
函数,它接收解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,并将userData
作为第二个参数,用于创建用户。添加一个try/catch
语句,在try
上下文中。首先,我们执行commit(MT.LOADING)
,然后我们从 API 中获取用户列表,最后,执行commit(MT.SET_USER_DATA, data)
,将用户列表传递给被突变。如果我们收到异常并进入Catch
语句,我们将执行commit(MT.ERROR, error)
,将收到的错误传递给state
:
async function createUser({ commit }, userData) {
try {
commit(MT.LOADING);
await postHttp(`/api/users`, {
data: {
...userData,
}
});
commit(MT.SET_USER_DATA, userData);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
fetchUsersList
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中获取用户列表,最后执行commit(MT.SET_USER_LIST, data)
,将用户列表传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function fetchUsersList({ commit }) {
try {
commit(MT.LOADING);
const { data } = await getHttp(`api/users`);
commit(MT.SET_USER_LIST, data);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
fetchUsersData
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的将要获取的userId
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中获取用户列表,最后执行commit(MT.SET_USER_DATA, data)
,将用户列表传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function fetchUserData({ commit }, userId) {
try {
commit(MT.LOADING);
const { data } = await getHttp(`api/users/${userId}`);
commit(MT.SET_USER_DATA, data);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
updateUser
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的payload
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后将用户数据提交到 API,最后执行commit(MT.UPDATE_USER, payload)
,将新的用户数据传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function updateUser({ commit }, payload) {
try {
commit(MT.LOADING);
await patchHttp(`api/users/${payload.id}`, {
data: {
...payload,
}
});
commit(MT.UPDATE_USER, payload);
} catch (error) {
commit(MT.ERROR, error);
} }
- 创建一个名为
removeUser
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的userId
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中删除用户数据,最后执行commit(MT.REMOVE_USER, userId)
,将userId
传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。
async function removeUser({ commit }, userId) {
try {
commit(MT.LOADING);
await deleteHttp(`api/users/${userId}`);
commit(MT.REMOVE_USER, userId);
} catch (error) {
commit(MT.ERROR, error);
} }
- 最后,我们将创建一个默认导出的 JavaScript 对象,其中包含所有创建的函数作为属性:
export default {
createUser,
fetchUsersList,
fetchUserData,
updateUser,
removeUser, }
- 在
src/store/user
文件夹的index.js
中导入新创建的actions.js
文件,并将其添加到export default
JavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';
import actions from './actions';
export default {
state,
mutations,
getters,
actions,
};
它是如何工作的...
操作是所有 Vuex 生命周期更改的初始化程序。当分发时,操作可以执行一个 mutation commit,或者另一个操作 dispatch,甚至是对服务器的 API 调用。
在我们的情况下,我们将我们的 API 调用放在了 actions 中,因此当异步函数返回时,我们可以执行 commit 并将状态设置为函数的结果。
另请参阅
在vuex.vuejs.org/guide/actions.html
找到有关 Vuex 操作的更多信息。
使用 Vuex 创建动态组件
将 Vuex 与 Vue 组件结合使用,可以在多个组件之间实现响应性,而无需直接进行父子通信,并分担组件的责任。
使用这种方法允许开发人员增强应用程序的规模,无需将数据状态存储在组件本身,而是使用单一真相作为整个应用程序的存储。
在这个配方中,我们将使用最后的配方来改进一个应用程序,其中使用了父子通信,并将其作为整个应用程序中可用的单一真相。
准备就绪
这个配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要创建我们的动态组件,我们将把组件从有状态转换为无状态,并提取一些可以制作成新组件的部分。
我们将使用在“创建和理解 Vuex 操作”配方中使用的 Vue 项目与 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create vuex-store
选择手动功能,并根据“如何做...”部分中“创建简单的 Vuex 存储”配方中的指示添加Router
和Vuex
作为所需功能。
我们的配方将分为五个部分:
创建用户列表组件
编辑用户列表页面
编辑用户视图页面
编辑用户视图页面
编辑用户创建页面
让我们开始吧。
创建用户列表组件
因为 Vuex 给了我们在应用程序中拥有单一数据源的能力,我们可以为我们的应用程序创建一个新的组件,该组件将处理用户列表并触发从服务器获取用户列表的 Vuex 操作。这个组件可以是无状态的,并且可以自行执行Vuex
操作。
单文件组件<script>
部分
让我们编写单文件组件的<script>
部分:
在src/components
文件夹中创建一个名为userList.vue
的新文件。
从src/mixin
文件夹导入changeRouterMixin
:
import changeRouteMixin from '@/mixin/changeRoute';
- 创建一个
export default
的 JavaScript 对象,并添加一个名为mixin
的新 Vue 属性,其默认值为一个数组。将导入的changeRouteMixin
添加到这个数组中:
mixins: [changeRouteMixin],
- 创建一个名为
computed
的新 Vue 属性。在这个属性中,创建一个名为userList
的新值。这个属性将是一个返回 Vuex 存储器 gettergetUsersList
的函数:
computed: {
userList() {
return this.$store.getters.getUsersList;
}, },
单文件组件<template>
部分
在这里,我们将编写单文件组件的<template>
部分:
打开views
文件夹内users
文件夹中的List.vue
文件,并复制VsTable
组件的内容和组件。
打开src/components
文件夹中的userList.vue
文件。
将你从List.vue
文件中复制的内容粘贴到<template>
部分中:
<template>
<vs-table
:data="userList"
search
stripe pagination max-items="10"
style="width: 100%; padding: 20px;"
>
<template slot="thead">
<vs-th sort-key="name">
#
</vs-th>
<vs-th sort-key="name">
Name
</vs-th>
<vs-th sort-key="email">
Email
</vs-th>
<vs-th sort-key="country">
Country
</vs-th>
<vs-th sort-key="phone">
Phone
</vs-th>
<vs-th sort-key="Birthday">
Birthday
</vs-th>
<vs-th>
Actions
</vs-th>
</template>
<template slot-scope="{data}">
<vs-tr :key="index" v-for="(tr, index) in data">
<vs-td :data="data[index].id">
{{data[index].id}}
</vs-td>
<vs-td :data="data[index].name">
{{data[index].name}}
</vs-td>
<vs-td :data="data[index].email">
<a :href="`mailto:${data[index].email}`">
{{data[index].email}}
</a>
</vs-td>
<vs-td :data="data[index].country">
{{data[index].country}}
</vs-td>
<vs-td :data="data[index].phone">
{{data[index].phone}}
</vs-td>
<vs-td :data="data[index].birthday">
{{data[index].birthday}}
</vs-td>
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeRoute('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeRoute('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/>
</vs-td>
</vs-tr>
</template>
</vs-table> </template>
编辑用户列表页面
现在我们已经将用户列表提取到一个新的组件中,我们需要导入这个组件并移除旧的 VsTable,它使我们的视图混乱。
单文件组件<script>
部分
在这一步中,我们将编写单文件组件的<script>
部分:
打开views
文件夹内users
文件夹中的List.vue
文件。
从components
文件夹导入新创建的用户列表组件:
import changeRouteMixin from '@/mixin/changeRoute'; import UserTableList from '@/components/userList';
- 在
export default
的 JavaScript 对象中,添加一个名为components
的新属性。将该属性声明为 JavaScript 对象,并将导入的UserTableList
组件添加到对象中:
components: { UserTableList },
- 在
methods
属性中,在getAllUsers
函数中,我们需要更改内容以在调用时执行一个 Vuex 分发。这个方法将执行fetchUsersList
的 Vuex 操作:
async getAllUsers() {
this.$store.dispatch('fetchUsersList'); },
- 最后,在
deleteUser
函数中,我们需要更改内容以在调用时执行一个 Vuex 分发。这个方法将执行removeUser
的 Vuex 操作,并将userId
作为参数传递:
async deleteUser(id) {
this.$store.dispatch('removeUser', id);
await this.getAllUsers(); },
单文件组件<template>
部分
让我们编写单文件组件的<template>
部分:
在view
文件夹内的users
文件夹中打开List.vue
文件。
用新导入的UserTableList
替换VsTable
组件及其内容:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<user-table-list /> </vs-col>
编辑用户视图页面
现在我们可以将 Vuex 添加到用户视图页面。我们将添加 Vuex 操作和获取器来操作数据,并从页面中提取管理责任。
单文件组件的<script>
部分
现在你要创建单文件组件的<script>
部分:
从view
文件夹内的users
文件夹中打开View.vue
文件。
删除 Vue 的data
属性。
在 Vue 的computed
属性中,添加userData
,返回一个 Vuex 的 getter,getUserData
:
userData() {
return this.$store.getters.getUserData; },
- 最后,在
getUserById
方法中,将内容更改为调度一个 Vuex 操作fetchUserData
,传递计算的userId
属性作为参数:
async getUserById() {
await this.$store.dispatch('fetchUserData', this.userId); },
单文件组件的<template>
部分
是时候编写单文件组件的<template>
部分了:
在view
文件夹内的users
文件夹中打开View.vue
文件。
在 UserForm 组件中,将v-model
指令更改为:value
指令:
<user-form
:value="userData"
disabled />
当使用只读值,或者需要删除v-model
指令的语法糖时,可以将输入值声明为:value
指令,并将值更改事件声明为@input
事件监听器。
编辑用户编辑页面
我们需要编辑我们的用户。在上一个示例中,我们使用了一个有状态的页面,并在页面内执行了所有操作。我们将状态转换为临时状态,并在 Vuex 操作上执行 API 调用。
单文件组件的<script>
部分
在这里,我们将创建单文件组件的<script>
部分:
在view
文件夹内的users
文件夹中打开Edit.vue
文件。
在 Vue 的data
属性中,将数据的名称从userData
更改为tmpUserData
:
data: () => ({
tmpUserData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
- 在 Vue 的
computed
属性中,添加一个名为userData
的新属性,它将返回 Vuex 的 gettergetUserData
:
userData() {
return this.$store.getters.getUserData; }
- 添加一个名为
watch
的新 Vue 属性,并添加一个名为userData
的新属性,它将是一个 JavaScript 对象。在这个对象中,添加三个属性,handler
,immediate
和deep
。handler
属性将是一个接收名为newData
的参数的函数,它将tmpUserData
设置为这个参数。immediate
和deep
属性都是设置为true
的布尔属性:
watch: {
userData: {
handler(newData) {
this.tmpUserData = newData;
},
immediate: true,
deep: true,
} },
- 在 Vue 的
methods
属性中,我们需要更改getUserById
的内容以调度名为fetchUserData
的 Vuex 动作,并将computed
属性userId
作为参数传递:
async getUserById() {
await this.$store.dispatch('fetchUserData', this.userId); },
- 在
updateUser
方法中,更改内容以调度名为updateUser
的 Vuex 动作,并将tmpUserData
作为参数传递:
async updateUser() {
await this.$store.dispatch('updateUser', this.tmpUserData);
this.changeRoute('list'); },
单文件组件部分
在这部分,我们将编写单文件组件的<template>
部分:
在view
文件夹内的users
文件夹中打开Edit.vue
。
将UserForm
组件的v-model
指令的目标更改为tmpUserData
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12"
style="margin: 20px" >
<user-form
v-model="tmpUserData"
/> </vs-col>
编辑用户创建页面
对于用户创建页面,更改将是最小的,因为它只执行 API 调用。我们需要添加 Vuex 动作调度。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在view
文件夹内的users
文件夹中打开Create.vue
文件。
更改createUser
方法的内容以调度名为createUser
的 Vuex 动作,并将userData
作为参数传递:
async createUser() {
await this.$store.dispatch('createUser', this.userData);
this.changeRoute('list'); },
它是如何工作的...
在所有四个页面中,我们进行了更改,将业务逻辑或 API 调用从页面中移除到 Vuex 存储,并尝试使其对于数据的维护责任更少。
因此,我们可以将一段代码放入一个新组件中,该组件可以放置在应用程序的任何位置,并且将显示当前用户列表,而不受实例化它的容器的任何限制。
这种模式有助于我们开发更突出的应用程序,其中需要的组件不那么业务导向,而更专注于它们的任务。
另请参阅
您可以在vuex.vuejs.org/guide/structure.html
找到有关 Vuex 应用程序结构的更多信息。
为开发添加热模块重载
热模块重载(HMR)是一种用于加快应用程序开发的技术,您无需刷新整个页面即可获取您刚刚在编辑器上更改的新代码。HMR 将仅更改和刷新您在编辑器上更新的部分。
在所有 Vue-CLI 项目或基于 Vue 的框架(如 Quasar Framework)中,HMR 存在于应用程序的呈现中。因此,每当您更改任何文件,该文件是 Vue 组件并且正在呈现时,应用程序将在运行时将旧代码替换为新代码。
在这个教程中,我们将学习如何向 Vuex 存储添加 HMR,并能够在不需要刷新整个应用程序的情况下更改 Vuex 存储。
准备工作
此教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要启动我们的组件,我们将使用在“使用 Vuex 创建动态组件”中使用的 Vue 项目和 Vue-CLI,或者我们可以启动一个新的项目。
要启动一个新的项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能,将Router
和Vuex
添加为所需功能,如“如何做...”部分和“创建简单的 Vuex 存储”教程中所示。
在接下来的步骤中,我们将向 Vuex 添加 HMR:
打开src/store
文件夹中的index.js
文件。
将export default
转换为一个名为store
的常量,并使其可导出:
export const store = new Vuex.Store({
...UserStore, });
- 检查 webpack
hot-module-reload
插件是否处于活动状态:
if (module.hot) {}
- 创建一个名为
hmr
的新常量,其中包含user
文件夹中index.js
,getters.js
,actions.js
和mutations.js
文件的路径:
const hmr = [
'./user',
'./user/getters',
'./user/actions',
'./user/mutations', ];
- 创建一个名为
reloadCallback
的新函数。在这个函数中,创建三个常量getters
,actions
和mutations
。每个常量将指向user
文件夹中的等效文件,并调用store.hotUpdate
函数,将一个对象作为参数传递,其中包含您创建的常量的值:
const reloadCallback = () => {
const getters = require('./user/getters').default;
const actions = require('./user/actions').default;
const mutations = require('./user/mutations').default; store.hotUpdate({
getters,
actions,
mutations,
}) };
由于文件的 Babel 输出,您需要在使用 webpack require
函数动态导入的文件末尾添加.default
。
- 执行 webpack HMR 的
accept
函数,将hmr
常量作为第一个参数传递,将reloadCallback
作为第二个参数传递:
module.hot.accept(hmr, reloadCallback);
- 最后,默认导出创建的
store
:
export default store;
它是如何工作的...
Vuex 存储支持使用 webpack HMR 插件的 API 进行 HMR。
当它可用时,我们创建一个可能需要更新的文件列表,以便 webpack 可以意识到这些文件的任何更新。当这些文件中的任何一个被更新时,将执行您创建的特殊回调。这个回调是使 Vuex 能够完全更新或更改更新文件的行为的回调。
另请参阅
您可以在vuex.vuejs.org/guide/hot-reload.html
找到有关 Vuex 热重载的更多信息。
您可以在 webpack.js.org/guides/hot-module-replacement/
找到有关 webpack HMR 的更多信息。
创建一个 Vuex 模块
随着我们的应用程序的增长,在单个对象中工作可能非常危险。项目的可维护性和每次更改可能产生的风险都会变得更糟。
Vuex 有一种叫做模块的方法,可以帮助我们将存储分成不同的存储分支。这些分支或模块中的每一个都有不同的状态、变化、获取器和操作。这种模式有助于开发,并减少了向应用程序添加新功能的风险。
在这个教程中,我们将学习如何创建一个模块以及如何与之一起工作,将其分成专用分支。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要开始我们的组件,我们将使用在“使用 Vuex 创建动态组件”中使用的 Vue 项目和 Vue-CLI,或者我们可以开始一个新的项目。
要开始一个新的项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-store
选择手动功能并将 Router
和 Vuex
添加为必需功能,如“如何做...”部分和“创建简单的 Vuex 存储”教程中所示。
我们的教程将分为两个部分:
创建新的认证模块
向 Vuex 添加模块
让我们开始吧。
创建新的认证模块
首先,我们需要创建一个新的 Vuex
模块。这个示例模块将被称为 authentication
,并将存储用户的凭据数据。
在这些步骤中,我们将为 Vuex
创建 authentication
模块:
在 src/store
文件夹中创建一个名为 authentication
的新文件夹。
在这个新创建的文件夹中,创建一个名为 state.js
的新文件,并打开它。
创建一个名为 generateState
的函数,它将返回一个具有 data.username
、data.token
、data.expiresAt
、loading
和 error
属性的 JavaScript 对象:
const generateState = () => ({
data: {
username: '',
token: '',
expiresAt: null,
},
loading: false,
error: null, });
- 在文件末尾创建一个
export default
对象。这个对象将是一个 JavaScript 对象。我们将解构 generateState
函数的返回值:
export default { ...generateState() };
在 src/store
文件夹中的 authentication
文件夹中创建一个名为 index.js
的新文件,并打开它。
导入新创建的 state.js
文件:
import state from './state';
- 在文件末尾创建一个
export default
对象。这个对象将是一个 JavaScript 对象。添加一个名为namespaced
的新属性,其值设置为true
,并添加导入的state
:
export default {
namespaced: true,
state, };
将模块添加到 Vuex
现在我们已经创建了我们的模块,我们将把它们添加到 Vuex 存储中。我们可以将新模块与旧代码集成在一起。这不是问题,因为 Vuex 将把新模块处理为一个命名空间对象,具有完全独立的 Vuex 存储。
现在,在这些步骤中,我们将把创建的模块添加到 Vuex 中:
打开src/store
文件夹中的index.js
文件。
从authentication
文件夹中导入index.js
文件:
import Vue from 'vue'; import Vuex from 'vuex'; import UserStore from './user'; import Authentication from './authentication';
- 在
Vuex.Store
函数中,添加一个名为modules
的新属性,这是一个 JavaScript 对象。然后添加导入的User
和Authentication
模块:
export default new Vuex.Store({
...UserStore,
modules: { Authentication,
} })
工作原理...
模块的工作方式类似于单一的 Vuex 存储,但在同一个 Vuex 单一的数据源中。这有助于开发更大规模的应用程序,因为你可以维护和处理更复杂的结构,而无需在同一个文件中检查问题。
与此同时,可以使用模块和普通的 Vuex 存储,从传统应用程序迁移,这样你就不必从头开始重写所有内容才能使用模块结构。
在我们的情况下,我们添加了一个名为authentication
的新模块,只有一个状态存在于存储中,并继续使用旧的用户 Vuex 存储,这样将来我们可以将用户存储重构为一个新模块,并将其分离成更具体的、面向领域的架构。
另请参阅
您可以在vuex.vuejs.org/guide/modules.html
找到有关 Vuex 模块的更多信息。
第八章:使用过渡和 CSS 为您的应用程序添加动画
为了使应用程序更加动态并吸引用户的全部注意力,使用动画是至关重要的。如今,CSS 动画出现在提示、横幅、通知甚至输入字段中。
有些情况下,您需要创建特殊的动画,称为过渡,并完全控制页面上发生的事情。为此,您必须使用自定义组件,并让框架帮助您渲染应用程序。
使用 Vue,我们可以使用两个自定义组件来帮助我们在应用程序中创建动画和过渡,这些组件是Transition
和TransitionGroup
。
在这一章中,我们将学习如何创建 CSS 动画,使用 Animate.css 框架创建自定义过渡,使用Transition
组件钩子执行自定义函数,创建在组件渲染时执行的动画,为组和列表创建动画和过渡,创建可重用的自定义过渡组件,并在组件之间创建无缝过渡。
在这一章中,我们将涵盖以下内容:
创建你的第一个 CSS 动画
使用 Animate.css 创建自定义过渡类
使用自定义钩子创建交易
在页面渲染时创建动画
为列表和组创建动画
创建自定义过渡组件
在元素之间创建无缝过渡
技术要求
在这一章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户!您需要安装一个名为windows-build-tools
的 NPM 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行
> npm install -g windows-build-tools
命令。
要安装Vue-CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建基础项目
在这一章中,我们将使用此项目作为每个配方的基础。在这里,我将指导您如何创建基础项目:
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create {replace-with-recipe-name}
- Vue-CLI 将要求您选择预设;使用空格键选择
手动选择功能
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
- 现在,Vue-CLI 将询问您希望安装哪些功能。您需要选择
CSS 预处理器
作为默认功能之外的附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 继续这个过程,选择一个 linter 和 formatter。在我们的情况下,我们将选择“ESLint + Airbnb 配置”:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能。在我们的情况下,选择“保存时进行 lint”和“提交时进行 lint 和修复”:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择要放置 linter 和 formatter 配置文件的位置。在我们的情况下,我们将选择“在专用配置文件中”:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
- 最后,CLI 会询问您是否要保存未来项目的设置;选择
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
- 从创建的项目中,打开位于
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分,删除HelloWorld
组件。添加一个data
属性,并将其定义为一个返回具有名为display
的属性和默认值为true
的 JavaScript 对象的单例函数:
<script> export default {
name: 'App',
data: () => ({
display: true,
}), }; </script>
- 在单文件组件的
<template>
部分,删除HelloWorld
组件,并添加一个带有文本Toggle
的button
HTML 元素。在img
HTML 元素中,添加一个绑定到display
变量的v-if
指令。最后,在button
HTML 元素中,添加一个click
事件。在事件监听器中,将值定义为一个将display
变量设置为display
变量的否定的匿名函数:
<template>
<div id="app">
<button @click="display = !display">
Toggle
</button>
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </div> </template>
有了这些说明,我们可以为本章中的每个示例创建一个基础项目。
创建您的第一个 CSS 动画
借助 CSS,我们可以在不需要手动通过 JavaScript 编程 DOM 元素的更改的情况下为我们的应用程序添加动画。使用专门用于控制动画的特殊 CSS 属性,我们可以实现美丽的动画和过渡效果。
要使用 Vue 中可用的动画,当将动画应用于单个元素时,我们需要使用一个名为Transition
的组件,或者当处理组件列表时,需要使用一个名为TransitionGroup
的组件。
在这个示例中,我们将学习如何创建一个 CSS 动画,并将此动画应用于 Vue 应用程序中的单个元素。
准备工作
以下是此示例的先决条件:
Node.js 12+
一个名为cssanimation
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为这个示例创建一个名为cssanimation
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 打开
App.vue
文件。在单文件组件的<template>
部分中,使用Transaction
组件包装img
HTML 元素。在Transaction
组件中,添加一个name
属性,其值为"image"
:
<transition name="image">
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
- 在单文件组件的
<style>
部分中,创建一个.image-enter-active
类,其中包含一个值为bounce-in .5s
的animation
属性。然后,创建一个.image-leave-active
类,其中包含一个值为bounce-in .5s reverse
的animation
属性:
.image-enter-active {
animation: bounce-in .5s; } .image-leave-active {
animation: bounce-in .5s reverse; }
- 最后,创建一个
@keyframes bounce-in
CSS 规则。在其中,执行以下操作:
创建一个0%
规则,其中包含属性变换和值为scale(0)
。
创建一个50%
规则,其中包含属性变换和值为scale(1.5)
。
创建一个100%
规则,其中包含属性变换和值为scale(1)
:
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
} }
完成此操作后,当第一次按下切换按钮时,您的图像将放大并消失。再次按下时,动画完成后,图像将放大并保持正确的比例:
它是如何工作的...
首先,我们将 Vue 动画包装器添加到我们想要添加过渡效果的元素上,然后添加将在过渡中使用的 CSS 类的名称。
Transition
组件使用预先制作的命名空间来要求必须存在的 CSS 类。这些是-enter-active
,用于组件进入屏幕时,以及-leave-active
,用于组件离开屏幕时。
然后,在<style>
中创建 CSS 类,用于元素离开和进入屏幕的过渡,以及bounce-in
动画的keyframe
规则集,以定义其行为方式。
另请参阅
您可以在v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions
找到有关使用 Vue 类进行基于类的动画和过渡的更多信息。
您可以在developer.mozilla.org/en-US/docs/Web/CSS/@keyframes
找到有关 CSS 关键帧的更多信息。
使用 Animate.css 创建自定义过渡类
在Transition
组件中,可以定义在每个过渡步骤中使用的 CSS 类。通过使用此属性,我们可以使Transition
组件在过渡动画中使用 Animate.css。
在这个示例中,我们将学习如何在组件中使用 Animate.css 类与Transition
组件,以创建自定义过渡效果。
准备工作
以下是此示例的先决条件:
Node.js 12+
一个名为animatecss
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为此示例创建一个名为animatecss
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 在项目文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令安装 Animate.css 框架:
> npm install [email protected]
- 打开
src
文件夹中的main.js
文件并导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 打开
src
文件夹中的App.vue
文件,并将Transition
组件添加为img
HTML 元素的包装器。在Transition
组件中,添加一个名为enter-active-class
的属性,并将其定义为"animated bounceInLeft"
。然后,添加另一个名为leave-active-class
的属性,并将其定义为"animated bounceOutLeft"
:
<transition
enter-active-class="animated bounceInLeft"
leave-active-class="animated bounceOutLeft" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
完成此操作后,当第一次按下切换按钮时,您的图像将向左滑出并消失。再次按下时,它将从左侧滑入:
工作原理...
Transition
组件最多可以接收六个属性,可以为交易的每个步骤设置自定义类。这些属性是enter-class
、enter-active-class
、enter-to-class
、leave-class
、leave-active-class
和leave-to-class
。在这个示例中,我们使用了enter-active-class
和leave-active-class
;这些属性定义了元素在屏幕上可见或离开屏幕时的自定义类。
为了使用自定义动画,我们使用了 Animate.css 框架,该框架提供了预先制作并准备好供使用的自定义 CSS 动画。我们使用了bounceInLeft
和bounceOutLeft
来使元素从屏幕中滑入和滑出。
还有更多...
您可以尝试更改enter-active-class
和leave-active-class
属性的类,以查看 CSS 动画在浏览器上的行为。
您可以在 Animate.css 文档中找到可用类的完整列表animate.style/
。
另请参阅
您可以在v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions
找到有关基于类的动画和 Vue 类的过渡的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
使用自定义钩子创建交易
Transaction
组件具有每个动画生命周期的自定义事件发射器。这些可以用于附加自定义函数和方法,以在动画周期完成时执行。
我们可以使用这些方法在页面交易完成或按钮动画结束后执行数据获取,从而按特定顺序链接动画,这些动画需要根据动态数据依次执行。
在这个配方中,我们将学习如何使用Transaction
组件的自定义事件发射器来执行自定义方法。
准备就绪
以下是此配方的先决条件:
Node.js 12+
名为transactionhooks
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做到...
使用基础项目,为此配方创建一个名为transactionhooks
的新项目,并打开项目文件夹。现在,按照以下步骤:
- 在项目文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令以安装 Animate.css 框架:
> npm install [email protected]
- 在
src
文件夹中打开main.js
文件并导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 在
src
文件夹中打开App.vue
文件。在单文件组件的<script>
部分,在数据属性中,在单例函数中,添加一个名为status
的新属性,并将其值定义为"appeared"
:
data: () => ({
display: true,
status: 'appeared', }),
- 创建一个
methods
属性,并将其定义为 JavaScript 对象。在对象内部,添加两个名为onEnter
和onLeave
的属性。在onEnter
属性中,将其定义为一个函数,并在其中将数据status
变量设置为"appeared"
。在onLeave
属性中,将其定义为一个函数,并在其中将数据status
变量设置为"disappeared"
:
methods: {
onEnter() {
this.status = 'appeared';
},
onLeave() {
this.status = 'disappeared';
}, },
- 在单文件组件的
<template>
部分,添加一个Transition
组件作为img
HTML 元素的包装器。在Transition
组件中,执行以下操作:
添加一个名为enter-active-class
的属性,并将其定义为"animated rotateIn"
。
添加另一个名为leave-active-class
的属性,并将其定义为"animated rotateOut"
。
添加事件监听器after-enter
绑定并将其附加到onEnter
方法。
添加事件监听器after-leave
绑定并将其附加到onLeave
方法:
<transition
enter-active-class="animated rotateIn"
leave-active-class="animated rotateOut"
@after-enter="onEnter"
@after-leave="onLeave" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
- 创建一个
h1
HTML 元素作为Transition
组件的兄弟元素,并添加文本The image {{ status }}
:
<h1>The image {{ status }}</h1>
现在,当点击按钮时,文本将在动画完成时更改。 它将显示动画完成后出现的图像和动画完成后消失的图像:
工作原理...
Transition
组件有八个自定义钩子。 这些钩子由 CSS 动画触发,当它们被触发时,它们会发出自定义事件,可以被父组件使用。 这些自定义事件是before-enter
,enter
,after-enter
,enter-cancelled
,before-leave
,leave
,after-leave
和leave-cancelled
。
在使用after-enter
和after-leave
钩子时,当 CSS 动画完成时,屏幕上的文本会相应地更改为每个钩子的事件监听器上定义的函数。
另请参阅
您可以在v3.vuejs.org/guide/transitions-enterleave.html#javascript-hooks
找到有关转换钩子的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
在页面渲染时创建动画
在页面渲染时使用页面转换动画或自定义动画是常见的,有时需要引起应用程序用户的注意。
在 Vue 应用程序中可以创建此效果,无需刷新页面或重新渲染屏幕上的所有元素。 您可以使用Transition
组件或TransitionGroup
组件来实现这一点。
在本教程中,我们将学习如何使用Transition
组件,以便在页面渲染时触发动画。
准备工作
本文档的先决条件如下:
Node.js 12+
一个名为transactionappear
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为本教程创建一个名为transactionappear
的新项目,并打开项目文件夹。 现在,按照以下步骤:
- 在项目文件夹中,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows),并执行以下命令以安装 Animate.css 框架:
> npm install [email protected]
- 在
src
文件夹中的main.js
文件中导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 在
src
文件夹中的App.vue
文件中,为img
HTML 元素添加一个Transition
组件作为包装器。在Transition
组件中,执行以下操作:
添加一个名为appear-active-class
的属性,并将其定义为"animated jackInTheBox"
.
添加一个名为enter-active-class
的属性,并将其定义为"animated jackInTheBox"
。
添加另一个名为leave-active-class
的属性,并将其定义为"animated rollOut"
。
添加appear
属性并将其定义为true
:
<transition
appear
appear-active-class="animated jackInTheBox"
enter-active-class="animated jackInTheBox"
leave-active-class="animated rollOut" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
页面打开时,Vue 标志将像一个爆米花盒一样摇晃,并在动画完成后保持静止:
工作原理...
Transition
组件有一个特殊属性叫做appear
,当启用时,使元素在屏幕上呈现时触发动画。该属性带有三个控制动画 CSS 类的属性,分别为appear-class
、appear-to-class
和appear-active-class
。
还有四个与此属性一起执行的自定义钩子,分别为before-appear
、appear
、after-appear
和appear-cancelled
。
在我们的案例中,当组件在屏幕上呈现时,我们使组件执行 Animate.css 框架中的jackInTheBox
动画。
另请参阅
您可以在v3.vuejs.org/guide/transitions-enterleave.html#transitions-on-initial-render
找到有关初始渲染过渡的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
为列表和组创建动画
有一些动画需要在一组元素或列表中执行。这些动画需要包装在TransitionGroup
元素中才能工作。
该组件具有一些与Transition
组件中相同的属性,但要使其工作,您必须为子元素和特定于该组件的组件定义一组特殊指令。
在此示例中,我们将创建一个动态图像列表,当用户点击相应按钮时将添加图像。这将在图像出现在屏幕上时执行动画。
准备工作
以下是此示例的先决条件:
Node.js 12+
一个名为transactiongroup
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为此示例创建一个名为transactiongroup
的新项目并打开项目文件夹。现在,按照以下步骤进行:
- 在项目文件夹中,打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows),并执行以下命令来安装 Animate.css 框架:
> npm install [email protected]
- 打开
src
文件夹中的main.js
文件并导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 打开
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分中,在data
单例上返回一个名为count
且值为0
的属性:
data: () => ({
count: 0, }),
- 在单文件组件的
<template>
部分中,删除div#app
HTML 元素内的所有内容。然后,作为div#app
HTML 元素的子元素,创建一个带有tag
属性定义为"ul"
和enter-active-class
属性定义为"animated zoomIn"
的TransitionGroup
组件:
<div id="app">
<transition-group
tag="ul"
enter-active-class="animated zoomIn"
></transition-group>
</div>
- 作为
TransitionGroup
组件的子元素,创建一个带有v-for
指令的li
HTML 元素,对count
变量进行迭代,定义一个名为key
的变量属性,其值为i
,并定义一个style
属性,其值为"float: left"
。作为li
HTML 组件的子元素,创建一个带有src
属性定义为"https://picsum.photos/100"
的img
HTML 组件:
<li
v-for="i in count"
:key="i"
style="float: left" >
<img src="https://picsum.photos/100"/> </li>
- 然后,作为
TransitionGroup
组件的兄弟元素,创建一个带有style
属性定义为"clear: both"
的hr
HTML 元素:
<hr style="clear: both"/>
- 最后,作为
hr
HTML 元素的兄弟,创建一个带有click
事件的button
HTML 元素,将1
添加到当前的count
变量,并将文本设置为Increase
:
<button
@click="count = count + 1" >
Increase
</button>
现在,当用户点击相应的按钮以增加列表时,它将向列表添加一个新项目,并触发放大动画:
它是如何工作的...
TransitionGroup
元素通过tag
属性中声明的标记创建一个包装器元素。这将通过检查子元素的唯一键来管理将触发动画的自定义元素。因此,TransitionGroup
组件内的所有子元素都需要声明一个key
并且必须是唯一的。
在我们的案例中,我们使用ul
和li
HTML 元素创建了一个 HTML 列表,其中TransitionGroup
是使用ul
标签定义的,子元素是使用li
HTML 元素定义的。然后,我们对数字进行了虚拟迭代。这意味着将有一个项目列表,并根据该列表上的项目数量在屏幕上显示图像。
为了增加我们的列表,我们创建了一个button
HTML 元素,每次按下时都会将count
变量的计数增加一。
另请参阅
您可以在v3.vuejs.org/guide/transitions-list.html#reusable-transitions
找到有关转换组的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
创建自定义过渡组件
使用框架创建应用程序很好,因为您可以创建可重用的组件和可共享的代码。使用这种模式对简化应用程序的开发非常有帮助。
创建可重用的过渡组件与创建可重用组件相同,可以使用更简单的方法,因为它可以与函数渲染一起使用,而不是正常的渲染方法。
在本教程中,我们将学习如何创建一个可在我们的应用程序中使用的可重用的函数组件。
准备工作
本章的先决条件如下:
Node.js 12+
一个名为customtransition
的 Vue-CLI 基本项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基本项目,为本教程创建一个名为customtransition
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 在项目文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令来安装 Animate.css 框架:
> npm install [email protected]
- 在
src
文件夹中的main.js
文件中导入 Animate.css 框架:
import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
- 在
src/components
文件夹中创建一个名为CustomTransition.vue
的新文件,并打开它。在单文件组件的<template>
部分,添加functional
属性以启用组件的函数渲染。然后,创建一个Transition
组件,将appear
变量属性定义为props.appear
。将enter-active-class
属性定义为"animated slideInLeft"
,将leave-active-class
属性定义为"animated slideOutRight"
。最后,在Transition
组件内部,添加一个<slot>
占位符:
<template functional>
<transition
:appear="props.appear"
enter-active-class="animated slideInLeft"
leave-active-class="animated slideOutRight"
>
<slot />
</transition> </template>
- 在
src
文件夹中的App.vue
文件中打开。在单文件组件的<script>
部分,导入新创建的CustomTransition
组件。在 Vue 对象上,添加一个名为components
的新属性,将其定义为 JavaScript 对象,并添加导入的CustomTransition
组件:
<script> import CustomTransition from './components/CustomTransition.vue'; export default {
name: 'App',
components: {
CustomTransition,
}, data: () => ({
display: true,
}), }; </script>
- 最后,在单文件组件的
<template>
部分,使用CustomTransition
组件包装img
HTML 元素:
<custom-transition>
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </custom-transition>
使用这个自定义组件,可以在不需要在组件上重新声明Transition
组件和过渡 CSS 类的情况下重用过渡:
它是如何工作的...
首先,我们使用函数组件方法创建了一个自定义组件,不需要声明单文件组件的<script>
部分。
在这个自定义组件中,我们使用Transaction
组件作为基础组件。然后,我们使用注入的函数上下文prop.appear
定义了appear
属性,并为过渡添加了动画类,使其在组件呈现时从左侧滑入,销毁时从右侧滑出。
然后,在主应用程序中,我们使用这个自定义组件来包装img
HTML 元素,并使其作为Transition
组件工作。
另请参阅
您可以在v3.vuejs.org/guide/transitions-list.html#reusable-transitions
找到有关可重用过渡组件的更多信息。
您可以在animate.style/
找到有关 Animate.css 的更多信息。
在元素之间创建无缝过渡
当两个组件之间有动画和过渡时,它们需要是无缝的,这样用户在将组件放置在屏幕上时就看不到 DOM 抖动和重绘。为了实现这一点,我们可以使用Transition
组件和过渡模式属性来定义过渡的方式。
在这个示例中,我们将使用Transition
组件和过渡模式属性来创建图像之间的过渡动画。
准备工作
本章的先决条件如下:
Node.js 12+
一个名为seamlesstransition
的 Vue-CLI 基础项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
使用基础项目,为这个示例创建一个名为seamlesstransition
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:
- 在
src
文件夹中打开App.vue
文件。在单文件组件的<style>
部分,创建一个名为.rotate-enter-active,.rotate-leave-active
的属性,并将transition
CSS 样式属性定义为transform .8s ease-in-out;
。然后,创建一个名为.rotate-enter,.rotate-leave-active
的属性,并将transform
CSS 样式属性定义为rotate( -180deg );
,并将transition
定义为.8s ease-in-out;
:
.rotate-enter-active, .rotate-leave-active {
transition: transform .8s ease-in-out; } .rotate-enter, .rotate-leave-active {
transform: rotate( -180deg );
transition: transform .8s ease-in-out; }
- 在单文件组件的
<template>
部分,用Transition
组件包裹img
HTML 元素。然后,将name
属性定义为rotate
,mode
属性定义为out-in
:
<transition
name="rotate"
mode="out-in" ></transition>
- 在
Transition
组件中,对img
HTML 元素添加一个key
属性,并将其定义为up
。然后,添加另一个img
HTML 元素并添加一个v-else
指令。添加一个key
属性并将其定义为down
,添加一个src
属性并将其定义为"./assets/logo.png"
,最后添加一个style
属性并将其定义为"transform: rotate(180deg)"
:
<img
v-if="display"
key="up"
src="./assets/logo.png"> <img
v-else
key="down"
src="./assets/logo.png"
style="transform: rotate(180deg)" >
当用户切换元素时,将执行离开动画,然后在完成后,进入动画将立即开始。这样就可以实现旧元素和新元素之间的无缝过渡:
它是如何工作的...
Transition
组件有一个特殊的属性叫做mode
,在这里可以定义元素过渡动画的行为。这种行为将创建一组规则,控制动画步骤在Transition
组件内部的发生方式。在组件中可以使用"in-out"
或"out-in"
模式:
在"in-out"
行为中,新元素的过渡将首先发生,当它完成后,旧元素的过渡将开始。
在"out-in"
行为中,旧元素的过渡将首先发生,然后新元素的过渡将开始。
在我们的案例中,我们创建了一个动画,将 Vue 标志旋转到倒置状态。然后,为了处理这个无缝的变化,我们使用了"out-in"
模式,这样新元素只会在旧元素完成过渡后才会显示出来。
另请参阅
您可以在v3.vuejs.org/guide/transitions-enterleave.html
找到有关过渡模式的更多信息。
第九章:使用 UI 框架创建漂亮的应用程序
使用 UI 框架和库是提高生产力并帮助应用程序开发的好方法。您可以更多地专注于代码,而不是设计。
学习如何使用这些框架意味着您知道这些框架的行为和工作原理。这将有助于您在将来开发应用程序或框架的过程中。
在这里,您将学习在创建用户注册表单和页面所需的所有组件时,使用框架的更多用法。在本章中,我们将学习如何使用 Buefy、Vuetify 和 Ant-Design 创建布局、页面和表单。
在本章中,我们将涵盖以下示例:
使用 Buefy 创建页面、布局和用户表单
使用 Vuetify 创建页面、布局和用户表单
使用 Ant-Design 创建页面、布局和用户表单
技术要求
在这一章中,我们将使用 Node.js 和 Vue-CLI。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的npm
包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装Vue-CLI
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
使用 Buefy 创建页面、布局和用户表单
Bulma 是最早用于快速原型设计和 Web 开发的框架之一,它不需要附加 JavaScript 库。所有需要编码的特殊组件都是使用框架的开发人员的责任。
随着 JavaScript 框架的出现和围绕 Bulma 框架创建的社区,为 Vue 创建了一个包装器。这个包装器承担了 JavaScript 组件开发的所有责任,并为开发人员提供了在其应用程序中使用 Bulma 框架的完整解决方案,而无需重新发明轮子。
在这个示例中,我们将学习如何在 Vue 中使用 Buefy 框架,以及如何创建布局、页面和用户注册表单。
准备工作
本示例的先决条件如下:
Node.js 12+
一个 Vue-CLI 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
要使用 Buefy 框架创建一个 Vue-CLI 项目,我们首先需要创建一个 Vue-CLI 项目,然后将 Buefy 框架添加到项目中。我们将把这个步骤分为四个部分:创建 Vue-CLI 项目,将 Buefy 框架添加到项目中,创建布局和页面,最后创建用户注册表单。
创建 Vue-CLI 项目
在这里,我们将创建用于此示例的 Vue-CLI 项目。这个项目将具有自定义设置,以便能够与 Buefy 框架一起工作:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create bulma-vue
- Vue-CLI 会要求您选择一个预设 - 选择
手动选择功能
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在 Vue-CLI 会要求选择功能,您需要在默认功能之上选择
CSS 预处理器
作为附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter Unit Testing E2E Testing
- 在这里,Vue-CLI 会询问您想要使用哪种 CSS 预处理器;选择
Sass/SCSS(使用 node-sass)
:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) ❯ Sass/SCSS (with node-sass) Less **Stylus**
- 继续这个过程,选择一个 linter 和格式化程序。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能(这里是
在提交时进行 Lint 和修复
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择您想要放置 linter 和格式化程序配置文件的位置(这里是
在专用配置文件中
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
- 最后,Vue-CLI 会询问您是否要保存设置以供将来使用;您应该选择
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
将 Buefy 添加到 Vue-CLI 项目中
要在 Vue 项目中使用 Bulma,我们将使用 Buefy UI 库。这个库是 Bulma 框架的一个包装器,并提供了所有原始框架可用的组件以及一些额外的组件来使用:
- 在为 Vue-CLI 项目创建的文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue add buefy
- Vue-CLI 会询问您是否要选择一个样式来使用 Buefy;我们将选择
scss
:
? Add Buefy style? (Use arrow keys)
none
css
❯ scss
- 然后,Vue-CLI 会询问您是否要包括 Material Design 图标;对于这个项目,我们不会使用它们:
? Include Material Design Icons? (y/N) n
- 现在 Vue-CLI 会询问您是否要包括 Font Awesome 图标;我们将把它们添加到项目中:
? Include Font Awesome Icons? (y/N) y
使用 Buefy 创建布局和页面
要创建一个页面,我们需要创建一个布局结构和页面的基本组件,比如页眉菜单、页脚和页面的主要部分。
创建页眉菜单组件
在我们的设计中,我们将有一个页眉菜单,其中包含链接和呼吁行动按钮的基本组合:
在src/components
文件夹中创建一个名为top-menu.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为TopMenu
:
<script> export default {
name: 'TopMenu', }; </script>
- 在单文件组件的
<template>
部分,创建一个带有section
类的section
HTML 元素,并添加一个带有container
类的子div
HTML 元素:
<section class="section">
<div class="container">
</div>
</section>
- 现在,在
div.container
HTML 元素的子级中创建一个b-navbar
组件,并添加一个具有命名插槽brand
的template
占位符组件作为子级。在其中,添加一个带有href
属性定义为#
的b-navbar-item
组件,并添加一个img
HTML 元素作为子级:
<b-navbar>
<template slot="brand">
<b-navbar-item href="#">
<img src="https://raw.githubusercontent.com/buefy/buefy/dev
/static/img/buefy-logo.png"
alt="Lightweight UI components for Vue.js based on Bulma"
>
</b-navbar-item>
</template> </b-navbar>
- 在这个
template
占位符之后,创建另一个具有命名插槽start
的template
占位符。在其中,创建两个带有href
属性定义为#
的b-navbar-item
组件。作为同级组件,创建一个带有label
属性定义为Info
的b-navbar-dropdown
组件。在这个组件中,添加两个带有href
属性定义为#
的b-navbar-item
组件作为子级:
<template slot="start">
<b-navbar-item href="#">
Home
</b-navbar-item>
<b-navbar-item href="#">
Documentation
</b-navbar-item>
<b-navbar-dropdown label="Info">
<b-navbar-item href="#">
About
</b-navbar-item>
<b-navbar-item href="#">
Contact
</b-navbar-item>
</b-navbar-dropdown> </template>
- 最后,在
template
中创建另一个具有命名插槽end
的占位符。创建一个b-navbar-item
组件作为子组件,tag
属性定义为div
,并在该组件的子级中添加一个带有buttons
类的div
HTML 元素。在div
HTML 元素中,创建一个带有button is-primary
类的a
HTML 元素,以及另一个带有button is-light
类的a
HTML 元素:
<template slot="end">
<b-navbar-item tag="div">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light">
Log in
</a>
</div>
</b-navbar-item> </template>
创建英雄区组件
我们将创建一个英雄区组件。英雄组件是一个全宽的横幅,为用户提供页面上的视觉信息:
在src/components
文件夹中创建一个名为hero-section.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为HeroSection
:
<script> export default {
name: 'HeroSection', }; </script>
- 在单文件组件的
<template>
部分,创建一个带有hero is-primary
类的section
HTML 元素,然后添加一个带有hero-body
类的div
HTML 元素作为子级:
<section class="hero is-primary">
<div class="hero-body">
</div>
</section>
- 在
div.hero-body
HTML 元素内部,创建一个带有container
类的子div
HTML 元素。然后,添加一个带有title
类的h1
HTML 元素作为子级,以及一个带有subtitle
类的h2
HTML 元素:
<div class="container">
<h1 class="title">
user Registration
</h1>
<h2 class="subtitle">
Main user registration form
</h2> </div>
创建页脚组件
我们将在布局中使用的最终组件是页脚组件。这将显示为我们页面的页脚:
在src/components
文件夹中创建一个名为Footer.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为FooterSection
:
<script> export default {
name: 'FooterSection', }; </script>
- 在单文件组件的
<template>
部分,创建一个带有footer
类的footer
HTML 元素,然后添加一个带有content has-text-centered
类的div
HTML 元素:
<footer class="footer">
<div class="content has-text-centered">
</div>
</footer>
- 在
div.content
HTML 元素内,创建一个p
HTML 元素作为初始页脚行,并创建第二个p
HTML 元素作为第二行:
<p>
<strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy
Thomas</a>
| <strong>Buefy</strong> by
<a href="https://twitter.com/walter_tommasi">Walter
Tommasi</a> </p> <p>
The source code is licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>.
The website content is licensed
<a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC
BY NC SA 4.0</a>.
</p>
创建布局组件
为了创建布局组件,我们将使用所有创建的组件,并添加一个将容纳页面内容的插槽:
在src
文件夹中创建一个名为layouts
的新文件夹,并创建一个名为Main.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入footer-section
组件和top-menu
组件:
import FooterSection from '../components/Footer.vue'; import TopMenu from '../components/top-menu.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为Mainlayout
,并定义components
属性为导入的组件:
export default {
name: 'Mainlayout',
components: {
TopMenu,
FooterSection,
}, };
- 最后,在单文件组件的
<template>
部分,创建一个带有子top-menu
组件、一个slot
占位符和footer-section
组件的div
HTML 元素:
<template>
<div>
<top-menu />
<slot/>
<footer-section />
</div> </template>
使用 Buefy 创建用户注册表单
现在我们要创建用户注册表单并制作最终页面。在这一步中,我们将把所有其他步骤的输出合并到一个页面中:
- 打开
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分,导入main-layout
组件和hero-section
组件:
import Mainlayout from './layouts/main.vue'; import HeroSection from './components/heroSection.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为App
,然后定义components
属性为导入的组件。在 JavaScript 对象中添加data
属性,包括name
、username
、password
、email
、phone
、cellphone
、address
、zipcode
和country
属性:
export default {
name: 'App',
components: {
HeroSection,
Mainlayout,
},
data: () => ({
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
}), };
- 在单文件的
<template>
部分,添加导入的main-layout
组件,并将hero-section
作为子组件添加:
<template>
<main-layout>
<hero-section/>
</main-layout>
</template>
- 在
hero-section
组件之后,创建一个兄弟section
HTML 元素,带有section
类,并添加一个带有container
类的子div
HTML 元素。在这个div
HTML 元素中,创建一个带有title is-3
类的h1
HTML 元素和一个兄弟hr
HTML 元素:
<section class="section">
<div class="container">
<h1 class="title is-3">Personal Information</h1>
<hr/>
</div>
</section>
- 然后,创建一个带有
Name
作为label
的b-field
组件,作为hr
HTML 元素的兄弟,并添加一个带有v-model
指令绑定到name
的子b-input
。对于email
字段,做同样的操作,将label
更改为Email
,并将v-model
指令绑定到email
。在 email 的b-input
中,添加一个定义为email
的type
属性:
<b-field label="Name">
<b-input
v-model="name"
/> </b-field> <b-field
label="Email" >
<b-input
v-model="email"
type="email"
/> </b-field>
- 创建一个带有
grouped
属性的b-field
组件作为b-field
组件的兄弟。然后,作为子组件,创建以下内容:
一个带有expanded
属性和label
定义为Phone
的b-field
组件。添加一个带有v-model
指令绑定到phone
和type
为tel
的子b-input
组件。
一个带有expanded
属性和label
定义为Cellphone
的b-field
组件。添加一个带有v-model
指令绑定到cellphone
和type
为tel
的子b-input
组件:
<b-field grouped> <b-field
expanded
label="Phone"
>
<b-input
v-model="phone"
type="tel"
/>
</b-field>
<b-field
expanded
label="Cellphone"
>
<b-input
v-model="cellphone"
type="tel"
/>
</b-field> </b-field>
- 然后,创建一个带有
title is-3
类的h1
HTML 元素作为b-field
组件的兄弟,并添加一个hr
HTML 元素作为兄弟。创建一个带有label
定义为Address
的b-field
组件,并添加一个带有v-model
指令绑定到address
的b-input
组件:
<h1 class="title is-3">Address</h1> <hr/> <b-field
label="Address" >
<b-input
v-model="address"
/> </b-field>
- 创建一个
b-field
组件作为b-field
组件的兄弟,带有grouped
属性。然后,作为子组件,创建以下内容:
一个带有expanded
属性和label
定义为Zipcode
的子b-field
组件。添加一个带有v-model
指令绑定到zipcode
和type
属性定义为tel
的b-input
组件。
一个带有expanded
属性和label
定义为Country
的子b-field
组件。添加一个带有v-model
指令绑定到country
和type
属性定义为tel
的b-input
组件:
<b-field grouped>
<b-field
expanded
label="Zipcode"
>
<b-input
v-model="zipcode"
type="tel"
/>
</b-field>
<b-field
expanded
label="Country"
>
<b-input
v-model="country"
/>
</b-field> </b-field>
- 然后,创建一个
h1
HTML 元素作为b-field
组件的同级元素,使用title is-3
类,并添加一个hr
HTML 元素作为同级元素。创建一个带有grouped
属性的b-field
组件。创建一个子b-field
组件,带有expanded
属性和label
定义为username
,并添加一个带有v-model
指令绑定到username
的b-input
组件。对于Password
输入,做同样的事情,将label
更改为Password
,在b-input
组件中定义v-model
指令绑定到password
,并添加type
属性为password
:
<h1 class="title is-3">user Information</h1> <hr/> <b-field grouped>
<b-field
expanded
label="username"
>
<b-input
v-model="username"
/>
</b-field>
<b-field
expanded
label="Password"
>
<b-input
v-model="password"
type="password"
/>
</b-field> </b-field>
- 最后,创建一个
b-field
组件作为b-field
组件的同级元素,定义position
属性为is-right
和grouped
属性。然后,创建两个带有control
类的div
HTML 元素。在第一个div
HTML 元素中,添加一个button
HTML 元素作为子元素,使用button is danger is-light
类,而在第二个div
HTML 元素中,创建一个带有button is-success
类的子button
HTML 元素:
<b-field
position="is-right"
grouped
>
<div class="control">
<button class="button is-danger is-light">Cancel</button>
</div>
<div class="control">
<button class="button is-success">Submit</button>
</div>
</b-field>
它是如何工作的...
首先,我们创建一个 Vue-CLI 项目,进行基本配置,并添加额外的 CSS 预处理器node-sass
。然后,我们能够使用 Vue-CLI 和 Buefy 插件将 Buefy 框架添加到我们的项目中。使用 Buefy 框架,我们创建了一个布局页面组件,带有页眉菜单组件和页脚组件。
对于页面,我们使用 Bulma CSS 容器结构来定义我们的页面,并将用户注册表单放在默认的网格大小上。然后,我们添加了 hero 部分组件,用于页面标识,最后,我们创建了用户注册表单和输入。
这是最终项目正在运行的屏幕截图:
另请参阅
在bulma.io/
找到有关 Bulma 的更多信息。
在buefy.org/
找到有关 Buefy 的更多信息。
使用 Vuetify 创建页面、布局和用户表单
Vuetify 是 Vue 最知名的三个 UI 框架之一。基于 Google 的 Material Design,这个框架最初是由 John Leider 设计的,现在作为 Vue 生态系统中的首选 UI 框架,用于应用程序的初始开发。
在这个食谱中,我们将学习如何使用 Vuetify 创建一个布局组件包装器、一个页面和一个用户注册表单。
准备工作
此处的食谱先决条件如下:
Node.js 12+
一个 Vue-CLI 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何操作...
我们将把这个教程分为四个主要部分。前两部分专门用于创建项目和安装框架,后两部分专门用于创建用户注册页面和创建所需组件。
创建 Vue-CLI 项目
要在 Vue-CLI 项目中使用 Vuetify,我们需要创建一个自定义的 Vue-CLI 项目,并预定义配置,以便充分利用框架和提供的样式选项:
- 我们需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> vue create vue-vuetify
- 首先,Vue-CLI 会要求你选择一个预设;使用空格键选择
手动选择特性
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
- 现在 Vue-CLI 会要求选择特性,你需要选择
CSS 预处理器
作为默认特性之外的附加特性:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 在这里,Vue-CLI 会问你想使用哪种
CSS 预处理器
;选择Sass/SCSS(使用 node-sass)
:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) ❯ **Sass/SCSS (with node-sass)** Less **Stylus**
- 继续这个过程,选择一个代码检查工具和格式化工具。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 选择代码检查工具的附加特性(这里是
提交时进行代码检查和修复
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择你想要放置代码检查工具和格式化工具配置文件的位置(这里是
在专用配置文件中
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
- 最后,Vue-CLI 会问你是否要保存设置以供将来的项目使用;你会选择
N
。之后,Vue-CLI 会为你创建一个文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
将 Vuetify 添加到 Vue-CLI 项目中
要在 Vue 项目中使用 Vuetify,我们将使用 Vue-CLI 插件安装框架:
- 在你创建 Vue-CLI 项目的文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),执行以下命令:
> vue add vuetify
- Vue-CLI 会问你是否要选择安装预设。选择默认预设。然后,Vue-CLI 将完成在项目上安装框架:
? Choose a preset: (Use arrow keys)
❯ Default (recommended)
Prototype (rapid development)
Configure (advanced)
- 安装完成后,Vuetify 将配置项目内的文件以加载框架。现在你可以开始使用它了。
使用 Vuetify 创建布局
使用 Vuetify 作为 UI 框架,我们使用 Material Design 指南作为基础,因为通过使用 Material Design,我们可以遵循设计指南来创建我们的设计结构,这将意味着更具吸引力的结构。您可以在material.io/design/introduction#goals
找到 Material Design 指南。
创建顶部栏组件
在这部分,我们将创建一个top-bar
组件,该组件将用于我们页面的布局中:
在src/components
文件夹中,创建一个名为TopBar.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个具有name
属性定义为TopBar
的default
JavaScript 对象:
<script> export default {
name: 'TopBar', }; </script>
- 在
<template>
部分内,创建一个带有app
,dark
和dense
属性定义为true
,以及color
属性定义为primary
的v-app-bar
组件:
<v-app-bar
color="primary" app
dark dense ></v-app-bar>
- 在组件内部,创建一个带有
click
事件的事件监听器的v-app-bar-nav-icon
组件,在按钮被点击时发送一个名为'open-drawer'
的事件:
<v-app-bar-nav-icon
@click="$emit('open-drawer')" />
- 最后,作为
v-app-bar-nav-icon
组件的兄弟,添加一个v-toolbar-title
组件,其中包含页面或应用程序的标题:
<v-toolbar-title>Vue.JS 3 Cookbook - Packt</v-toolbar-title>
创建抽屉菜单组件
在 Material Design 应用程序中,我们有一个弹出在页面上方的抽屉菜单。当用户点击我们刚刚在TopBar
组件中创建的按钮时,这个菜单将被打开:
在src/components
文件夹中,创建一个名为DrawerMenu.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个具有三个属性的default
JavaScript 对象:
name
属性,定义为DrawerMenu
。
props
属性,定义为一个 JavaScript 对象,具有一个名为value
的属性。这个属性将是另一个 JavaScript 对象,具有type
,required
和default
属性。type
属性定义为Boolean
,required
属性定义为true
,default
属性定义为false
。
data
属性,作为返回 JavaScript 对象的单例函数。该对象将具有一个menu
属性,我们将其定义为将要使用的菜单项数组。数组将包含具有name
、link
和icon
属性的 Javascript 对象。第一个菜单项的name
定义为Home
,link
定义为*#*
,icon
定义为mdi-home
。第二个菜单项的name
定义为Contact
,link
定义为#
,icon
定义为mdi-email
。最后,第三个菜单项的name
定义为Vuetify
,link
定义为#
,icon
定义为mdi-vuetify
。
<script>
export default {
name: 'DrawerMenu',
props: {
value: {
type: Boolean,
required: true,
default: false,
},
},
data: () => ({
menu: [
{
name: 'Home',
link: '#',
icon: 'mdi-home',
},
{
name: 'Contact',
link: '#',
icon: 'mdi-email',
},
{
name: 'Vuetify',
link: '#',
icon: 'mdi-vuetify',
},
],
}),
};
</script>
- 在
<template>
部分,创建一个v-navigation-drawer
组件,其中value
属性作为绑定到value
props 的变量属性,app
属性定义为true
,并在click
事件上添加事件监听器,发送一个'input'
事件:
<v-navigation-drawer
:value="value"
app
@input="$emit('input', $event)" ></v-navigation-drawer>
- 创建一个带有
dense
属性定义为true
的v-list
组件。作为子元素,创建一个v-list-item
组件并定义以下内容:
v-for
指令遍历menu
项。
key
属性与菜单项的index
。
link
属性定义为true
。
在v-list-item
内部,创建一个带有VIcon
子元素的v-list-item-action
,内部文本为item.icon
。
在v-list-item-action
的同级位置创建一个v-list-item-content
组件,其中v-list-item-title
作为子元素,内部文本为item.name
:
<v-list dense>
<v-list-item
v-for="(item, index) in menu"
:key="index"
link>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{ item.name }}</v-list-item-
title>
</v-list-item-content>
</v-list-item>
</v-list>
创建布局组件
要创建布局组件,我们将使用所有创建的组件,并添加一个插槽来容纳页面内容:
在src/components
文件夹中,创建一个名为Layout.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入top-bar
组件和drawer-menu
组件:
import TopBar from './TopBar.vue'; import DrawerMenu from './DrawerMenu.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为Layout
,然后创建components
属性并导入组件。最后,将data
属性添加为返回 JavaScript 对象的单例函数,其中drawer
属性的值定义为false
:
export default {
name: 'Layout',
components: {
DrawerMenu,
TopBar,
},
data: () => ({
drawer: false,
}), };
- 在
<template>
部分内,创建一个v-app
组件。作为第一个子元素,添加top-bar
组件,并在open-drawer
事件监听器上添加事件监听器,将drawer
数据属性更改为drawer
属性的否定。然后,作为top-bar
的同级,创建一个drawer-menu
组件,其v-model
指令绑定到drawer
。最后,创建一个v-content
组件,其中包含一个子<slot>
元素:
<template>
<v-app>
<top-bar
@open-drawer="drawer = !drawer"
/>
<drawer-menu
v-model="drawer"
/>
<v-content>
<slot/>
</v-content>
</v-app> </template>
使用 Vuetify 创建用户注册表单
现在,布局组件准备好了,我们将创建用户注册表单。因为 Vuetify 在表单中内置了验证,我们将使用它来验证表单中的一些字段。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在src
文件夹中,打开App.vue
文件并清空其内容。
导入layout
组件:
import Layout from './components/Layout.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为App
,然后定义导入组件的components
属性。将computed
和methods
属性定义为空的 JavaScript 对象。然后创建一个data
属性,定义为返回 JavaScript 对象的单例函数。在data
属性中,创建以下内容:
一个valid
属性,其值定义为false
;
name
,username
,password
,email
,phone
,cellphone
,address
,zipcode
和country
属性定义为空字符串:
export default {
name: 'App', components: {
Layout,
}, data: () => ({
valid: true,
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
}),
computed: {}, methods: {}, };
- 在
computed
属性中,创建一个名为nameRules
的属性;这个属性是一个返回数组的函数,其中包含一个匿名函数,该函数接收一个值并返回该值的验证或错误文本。对passwordRules
和emailRules
属性也做同样的操作。在emailRules
属性中,向返回的数组中添加另一个匿名函数,该函数通过正则表达式检查值是否为有效的电子邮件,如果值不是有效的电子邮件,则返回错误消息:
computed: {
nameRules() {
return [
(v) => !!v || 'Name is required',
];
},
passwordRules() {
return [
(v) => !!v || 'Password is required',
];
},
emailRules() {
return [
(v) => !!v || 'E-mail is required',
(v) => /.+@.+\..+/.test(v) || 'E-mail must be valid',
];
}, },
- 最后,在
methods
属性内,创建一个名为register
的新属性,它是一个调用$refs.form.validate
函数的函数。还创建另一个名为cancel
的属性,它是另一个调用$refs.form.reset
函数的函数:
methods: {
register() {
this.$refs.form.validate();
},
cancel() {
this.$refs.form.reset();
}, },
单文件组件部分
现在是创建单文件组件的<template>
部分的时候。
在src
文件夹中,打开App.vue
文件。
在<template>
部分,创建一个layout
组件元素,并添加一个带有fluid
属性定义为true
的v-container
组件作为子元素。
<layout>
<v-container
fluid
>
</v-container>
</layout>
- 在
v-container
组件内部,创建一个子 HTML h1
元素作为页面标题,以及一个同级的v-subheader
组件作为页面描述。
<h1>user Registration</h1> <v-subheader>Main user registration form</v-subheader>
- 之后,创建一个带有
ref
属性定义为form
和lazy-validation
属性定义为true
的v-form
组件。然后,该组件的v-model
指令绑定到valid
变量。创建一个子v-container
组件,其中fluid
属性定义为true
。
<v-form
ref="form"
v-model="valid"
lazy-validation >
<v-container
fluid
> </v-container>
</v-form>
- 在
v-container
组件内部,创建一个v-row
组件,然后添加一个v-col
组件作为子元素,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。
<v-row>
<v-col
cols="12"
>
<v-card
outlined
flat class="mx-auto"
>
</v-card>
</v-col>
</v-row>
- 作为
v-card
组件的子元素,创建一个带有卡片标题的v-card-title
组件,然后作为同级元素创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件。
<v-card-title>
Personal Information
</v-card-title> <v-divider/> <v-container
fluid >
<v-row>
</v-row>
</v-container>
- 在
v-row
组件内部,创建一个子v-col
元素,其中cols
属性定义为12
。然后在v-col
组件内部,创建一个v-text-field
,其中v-model
指令绑定到name
变量,rules
变量属性定义为nameRules
计算属性,label
属性定义为Name
,最后,required
属性定义为true
。
<v-col
cols="12" >
<v-text-field
v-model="name"
:rules="nameRules"
label="Name"
required
/> </v-col>
- 作为
v-col
组件的同级元素,创建另一个v-col
组件,其中cols
属性定义为12
。然后,将v-text-field
组件作为子元素添加,其中v-model
指令绑定到email
变量,rules
变量属性定义为emailRules
计算属性,type
属性为email
,label
属性为E-mail
,最后,required
属性定义为true
。
<v-col
cols="12" >
<v-text-field
v-model="email"
:rules="emailRules"
type="email"
label="E-mail"
required
/> </v-col>
- 创建一个作为
v-col
组件同级的v-col
组件,并将cols
属性定义为6
。然后,作为子组件添加v-text-field
组件,其中v-model
指令绑定到phone
变量,label
属性定义为Phone
。对于Cellphone
输入,必须更改v-model
指令绑定到cellphone
变量和label
为Cellphone
。
<v-col
cols="6" >
<v-text-field
v-model="phone"
label="Phone"
/> </v-col> <v-col
cols="6" >
<v-text-field
v-model="cellphone"
label="Cellphone"
/> </v-col>
- 现在我们将创建
地址
卡,作为v-row
组件中v-col
的同级元素。创建一个v-col
组件,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。作为v-card
组件的子元素,创建一个v-card-title
组件作为卡片标题;然后,作为同级元素,创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件:
<v-col
cols="12" >
<v-card
outlined
flat class="mx-auto"
>
<v-card-title>
Address
</v-card-title>
<v-divider/>
<v-container
fluid
>
<v-row>
</v-row>
</v-container>
</v-card>
</v-col>
- 在
v-container
组件中的v-row
组件内,创建一个v-col
组件,其中cols
属性定义为12
。然后,添加一个v-text-field
作为子组件,其中v-model
指令绑定到address
变量,label
属性定义为Address
:
<v-col
cols="12" >
<v-text-field
v-model="address"
label="Address"
/> </v-col>
- 作为同级元素,创建一个
v-col
组件,其中cols
属性定义为6
。添加一个v-text-field
组件作为子元素。将v-text-field
组件的v-model
指令绑定到zipcode
变量,并将label
属性定义为Zipcode
:
<v-col
cols="6" >
<v-text-field
v-model="zipcode"
label="Zipcode"
/> </v-col>
- 然后,创建一个
v-col
组件,其中cols
属性定义为6
。作为子元素添加一个v-text-field
组件,其中v-model
指令绑定到country
变量,label
属性定义为Country
:
<v-col
cols="6" >
<v-text-field
v-model="country"
label="Country"
/> </v-col>
- 现在我们将创建
用户信息
卡作为v-row
组件中v-col
的同级元素。创建一个v-col
组件,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。作为v-card
组件的子元素,创建一个v-card-title
组件作为卡片标题;然后,作为同级元素,创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件:
<v-col
cols="12" >
<v-card
outlined
flat class="mx-auto"
>
<v-card-title>
user Information
</v-card-title>
<v-divider/>
<v-container
fluid
>
<v-row>
</v-row>
</v-container>
</v-card>
</v-col>
- 在
v-container
组件中的v-row
组件内,创建一个v-col
组件,其中cols
属性定义为6
。然后,添加一个v-text-field
作为子组件,其中v-model
指令绑定到username
变量,label
属性定义为username
:
<v-col
cols="6" >
<v-text-field
v-model="username"
label="username"
/> </v-col>
- 作为兄弟创建一个
v-col
组件,其中cols
属性定义为6
,并添加一个v-text-field
组件作为子级,其中v-model
指令绑定到password
变量,rules
变量属性定义为passwordRules
计算属性,label
属性定义为Password
:
<v-col
cols="6" >
<v-text-field
v-model="password"
:rules="passwordRules"
label="Password"
type="password"
required
/> </v-col>
- 现在我们将创建操作按钮。作为顶部
v-row
组件上的v-col
的兄弟,创建一个v-col
组件,其中cols
属性定义为12
,class
属性定义为text-right
。在v-col
组件内部,创建一个v-btn
组件,其中color
属性定义为error
,class
属性为mr-4
,并将click
事件侦听器附加到cancel
方法。最后,创建一个v-btn
组件作为兄弟,其中disabled
变量属性为valid
变量的否定,color
属性为success
,class
属性为mr-4
,并将click
事件侦听器附加到register
方法:
<v-col
cols="12"
class="text-right" >
<v-btn
color="error"
class="mr-4"
@click="cancel"
>
Cancel
</v-btn>
<v-btn
:disabled="!valid"
color="success"
class="mr-4"
@click="register"
>
Register
</v-btn> </v-col>
它是如何工作的...
在这个示例中,我们学习了如何使用 Vuetify 和 Vue-CLI 创建用户注册页面。要创建此页面,我们首先需要使用 Vue-CLI 工具创建项目,然后将 Vuetify 插件添加到其中,以便可以使用该框架。
然后,我们创建了top-bar
组件,其中包含应用程序标题和菜单按钮切换。为了使用菜单,我们创建了drawer-menu
组件来容纳导航项。最后,我们创建了layout
组件来将top-bar
和drawer-menu
组件组合在一起,并添加了一个<slot>
组件来放置页面内容。
对于用户注册表单页面,我们创建了三个包含输入表单的卡片,这些表单与组件上的变量绑定。表单中的一些输入与一组验证规则相关联,用于检查必填字段和电子邮件验证。
最后,在将数据发送到服务器之前,将检查用户注册表单是否有效。
这是最终项目正在运行的屏幕截图:
另请参阅
您可以在vuetifyjs.com/en/
找到有关 Vuetify 的更多信息。
您可以在material.io/
找到有关 Material Design 的更多信息。
使用 Ant-Design 创建页面、布局和用户表单
Ant-Design 框架是由阿里巴巴集团创建的,具体由 AliPay 和 Ant Financial 背后的技术团队创建。它是一个生态系统设计,主要被亚洲科技巨头使用,并且在 React 和 Vue 社区中占据重要地位。专注于后台 UI,框架的主要核心是其解决自定义数据输入和数据表格的解决方案。
在这里,我们将学习如何使用 Ant-Design 和 Vue 创建一个用户注册表单,方法是创建一个顶部栏组件,一个抽屉菜单,一个布局包装器,以及一个带有表单的用户注册页面。
准备工作
这个教程的先决条件如下:
Node.js 12+
一个 Vue-CLI 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做...
在这个教程中,我们将使用 Ant-Design 框架创建一个用户注册表单。为此,我们将创建一个布局包装器和所需的包装器组件,最后,我们将创建包含用户注册表单的页面。
创建 Vue-CLI 项目
我们需要创建一个 Vue-CLI 项目,以便安装 Ant-Design 插件并开始开发用户注册表单:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create antd-vue
- 首先,Vue-CLI 会要求您选择一个预设;使用空格键选择
手动选择功能
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
- 现在 Vue-CLI 将要求选择功能,您需要选择
CSS 预处理器
作为默认功能的附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 在这里,Vue-CLI 将询问您要使用哪种
CSS 预处理器
;选择Less
:
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) ❯ Less **Stylus**
- 通过选择一个 linter 和格式化程序来继续这个过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能(这里,
保存时进行 lint
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择您想要放置的 linter 和格式化程序配置文件的位置(这里,
在专用配置文件中
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ **In dedicated config files****In package.json**
- 最后,CLI 会询问您是否要保存未来项目的设置;您应该选择
N
。之后,Vue-CLI 将为您创建一个文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
将 Ant-Design 添加到 Vue-CLI 项目
将 Ant-Design 框架添加到 Vue-CLI 项目中,我们需要使用 Vue-CLI 插件将框架安装为项目依赖项,并使其在应用程序的全局范围内可用:
- 在您创建 Vue-CLI 项目的文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue add ant-design
- Vue-CLI 将询问您如何导入 Ant-Design 组件;我们将选择
Fully import
选项:
? How do you want to import Ant-Design-Vue?
❯ Fully import
Import on demand
- Vue-CLI 将询问您是否要覆盖 Ant-Design 的
LESS
变量;我们将选择N
:
? Do you wish to overwrite Ant-Design-Vue's LESS variables? (y/N)
- 最后,Vue-CLI 将询问项目中 Ant-Design 将使用的主要语言;我们将选择
en_US
:
? Choose the locale you want to load
❯ en_US
zh_CN
zh_TW
en_GB
es_ES
ar_EG
bg_BG
(Move up and down to reveal more choices)
使用 Ant-Design 创建布局
为了能够创建用户注册表单,我们将创建一个包装页面内容和表单的基本布局。在这里,我们将创建top-bar
组件,drawer-menu
组件和layout
包装器。
创建顶部栏组件
在layout
包装器中,我们将有一个top-bar
组件,用于保存用户当前位置的面包屑。现在我们将创建top-bar
组件并使其可用于布局:
在src/components
文件夹中,创建一个名为TopBar.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个带有name
属性定义为TopBar
的default
JavaScript 对象:
<script> export default {
name: 'TopBar', }; </script>
- 在单文件组件的
<style>
部分,我们将使<style>
部分为scoped
,并创建一个名为header-bread
的类。现在,background-color
将被定义为#f0f2f5
,带有一个名为bread-menu
的类,边距定义为16px 0
:
<style scoped>
.header-bread {
background-color: #f0f2f5;
} .bread-menu {
margin: 16px 0;
} </style>
- 在单文件组件的
<template>
部分,我们将创建一个a-layout-component
组件,class
属性定义为header-bread
。还要添加一个a-breadcrumb
组件作为子元素,class
属性为bread-menu
:
<template>
<a-layout-header class="header-bread">
<a-breadcrumb class="bread-menu"> </a-breadcrumb>
</a-layout-header> </template>
- 作为
a-breadcrumb
组件的子组件,创建两个a-breadcrumb-item
组件,并为每个添加用户位置的方向。在我们的情况下,第一个将是user
,第二个将是Registration Form
:
<a-breadcrumb-item>user</a-breadcrumb-item> <a-breadcrumb-item>Registration Form</a-breadcrumb-item>
创建抽屉菜单
在布局设计中,我们将有一个抽屉菜单组件作为用户的导航菜单。在这里,我们将创建Drawer
组件:
在src/components
文件夹中,创建一个名为Drawer.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个带有两个属性的default
JavaScript 对象。name
属性定义为Drawer
,data
属性定义为返回 JavaScript 对象的singleton
函数。在data
属性中,创建以下内容:
一个drawer
属性,定义为false
。
一个menu
属性,我们将其定义为将要使用的菜单项数组。菜单数组将包含三个 JavaScript 对象,具有name
和icon
属性。这个数组将包括:
一个 JavaScript 对象,属性name
定义为Home
,icon
定义为home
一个 JavaScript 对象,属性name
定义为Ant Design Vue
,icon
定义为ant-design
一个 JavaScript 对象,属性name
定义为Contact
,icon
定义为mail
:
<script> export default {
name: 'Drawer',
data: () => ({
drawer: false,
menu: [
{ name: 'Home',
icon: 'home',
},
{
name: 'Ant Design Vue',
icon: 'ant-design',
},
{
name: 'Contact',
icon: 'mail',
},
],
}), }; </script>
- 在单文件组件的
<template>
部分,创建一个a-layout-sider
组件,v-model
指令绑定到drawer
变量,collapsible
属性定义为true
。作为子组件,创建一个a-menu
组件,default-selected-keys
变量属性定义为['1']
,theme
属性定义为dark
,mode
属性定义为inline
:
<template>
<a-layout-sider
v-model="drawer"
collapsible
>
<a-menu
:default-selected-keys="['1']"
theme="dark"
mode="inline"
> </a-menu>
</a-layout-sider> </template>
- 最后,在
a-menu
组件内部,创建一个a-menu-item
组件,使用v-for
指令迭代menu
变量,并创建item
和index
临时变量。然后,将key
变量属性定义为index
。创建一个子AIcon
组件,type
变量属性定义为item.icon
,并创建一个兄弟span
HTML 元素,内容为item.name
:
<a-menu-item
v-for="(item,index) in menu"
:key="index" >
<a-icon
:type="item.icon"
/>
<span>{{ item.name }}</span> </a-menu-item>
创建布局组件
在这里,我们将创建layout
组件。这个组件将把top-bar
组件和Drawer
菜单组件连接起来,作为用户注册页面的包装器:
在src/components
文件夹中,创建一个名为Layout.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入top-bar
组件和drawer-menu
组件:
import TopBar from './TopBar.vue'; import Drawer from './Drawer.vue';
- 然后,我们将导出一个
default
的 JavaScript 对象,带有一个name
属性,定义为layout
。然后定义components
属性,包括导入的组件。
export default {
name: 'layout',
components: {
Drawer,
TopBar,
}, };
- 在单文件组件的
<template>
部分,创建一个a-layout
组件,style
属性定义为min-height: 100vh
。然后,将Drawer
组件作为子组件添加。作为drawer
组件的兄弟,创建一个a-layout
组件:
<template>
<a-layout
style="min-height: 100vh"
>
<drawer />
<a-layout>
<top-bar />
<a-layout-content style="margin: 0 16px">
<div style="padding: 24px; background: #fff;
min-height: auto;">
<slot />
</div>
</a-layout-content>
<a-layout-footer style="text-align: center">
Vue.js 3 Cookbook | Ant Design ©2020 Created by Ant UED
</a-layout-footer>
</a-layout>
</a-layout> </template>
- 向
a-layout
组件添加top-bar
组件和一个兄弟a-layout-content
组件,其中style
属性定义为margin: 0 16px
。作为该组件的子元素,创建一个div
HTML 元素,其中style
属性定义为padding: 24px; background: #fff; min-height: auto;
,并添加一个slot
占位符。最后,创建一个a-layout-footer
组件,其中style
属性定义为text-align:center;
,并添加页面的页脚文本:
<top-bar /> <a-layout-content style="margin: 0 16px">
<div style="padding: 24px; background: #fff; min-height: auto;">
<slot />
</div> </a-layout-content> <a-layout-footer style="text-align: center">
Vue.js 3 Cookbook | Ant Design ©2020 Created by Ant UED
</a-layout-footer>
使用 Ant-Design 创建用户注册表单
现在我们将创建用户注册页面和表单,该表单将放在前面步骤中创建的布局中。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在src
文件夹中,打开App.vue
文件并清空其内容。
导入layout
组件:
import Layout from './components/Layout.vue';
- 然后,我们将导出一个
default
JavaScript 对象,其中name
属性定义为App
,定义导入组件的components
属性,并最后将data
属性定义为返回 JavaScript 对象的单例函数。在data
属性中,创建以下内容:
一个labelCol
属性,定义为 JavaScript 对象,其中span
属性和值为4
。
一个wrapperCol
属性,定义为 JavaScript 对象,其中span
属性和值为14
。
一个form
属性,定义为 JavaScript 对象,其中name
,username
,password
,email
,phone
,cellphone
,address
,zipcode
和country
属性都定义为空字符串:
export default {
name: 'App',
components: {
Layout,
},
data() {
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
form: {
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
},
};
}, };
单文件组件部分
现在是时候创建单文件组件的<template>
部分了:
在src
文件夹中,打开App.vue
文件。
在<template>
部分,创建一个layout
组件元素,并添加一个a-form-model
组件作为子元素,其中model
变量属性绑定到form
,label-col
变量属性绑定到labelCol
,wrapper-col
变量属性绑定到wrapperCol
:
<layout>
<a-form-model
:model="form"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
</a-form-model>
</layout>
- 然后,作为
layout
组件的兄弟组件,创建一个h1
HTML 元素,页面标题为用户注册
,以及一个p
HTML 元素,页面副标题为主用户注册表单
。然后,创建一个a-card
元素,其中title
属性定义为个人信息
:
<h1>
User Registration
</h1> <p>Main user registration form</p> <a-card title="Personal Information"></a-card>
- 在
a-card
组件中,创建一个a-form-model-item
组件作为子元素,其中label
属性定义为姓名
,并添加一个子a-input
组件,其中v-model
指令绑定到form.name
变量:
<a-form-model-item label="Name">
<a-input v-model="form.name" /> </a-form-model-item>
- 接下来,作为兄弟元素,创建一个
a-form-model-item
组件,其中label
属性定义为电子邮件
,并添加一个子a-input
组件,其中v-model
指令绑定到form.email
变量,type
属性定义为email
:
<a-form-model-item label="Email">
<a-input
v-model="form.email"
type="email"
/> </a-form-model-item>
- 创建一个
a-form-model-item
组件,其中label
属性定义为电话
,并添加一个子a-input
组件,其中v-model
指令绑定到form.phone
变量:
<a-form-model-item label="Phone">
<a-input v-model="form.phone" /> </a-form-model-item>
- 创建一个
a-form-model-item
组件,其中label
属性定义为手机号码
,并添加一个子a-input
组件,其中v-model
指令绑定到form.cellphone
变量:
<a-form-model-item label="Cellphone">
<a-input v-model="form.cellphone" /> </a-form-model-item>
- 作为
a-card
组件的兄弟元素,创建一个a-card
组件,其中title
属性定义为地址
,style
属性定义为margin-top: 16px;
。然后,添加一个子a-form-model-item
组件,其中label
属性定义为地址
,并添加一个子a-input
组件,其中v-model
指令绑定到form.address
变量。
<a-card title="Address" style="margin-top: 16px">
<a-form-model-item label="Address">
<a-input v-model="form.address" />
</a-form-model-item> </a-card>
- 接下来,作为
a-card
组件的兄弟元素,创建一个a-form-model-item
组件,其中label
属性定义为邮政编码
,并添加一个子a-input
组件,其中v-model
指令绑定到form.zipcode
变量:
<a-form-model-item label="Zipcode">
<a-input v-model="form.zipcode" /> </a-form-model-item>
- 创建一个
a-form-model-item
组件,其中label
属性定义为国家
,并添加一个子a-input
组件,其中v-model
指令绑定到form.country
变量:
<a-form-model-item label="Country">
<a-input v-model="form.country" /> </a-form-model-item>
- 作为
a-card
组件的兄弟元素,创建一个a-card
组件,其中title
属性定义为用户信息
,style
属性定义为margin-top: 16px;
。然后,添加一个子a-form-model-item
组件,其中label
属性定义为用户名
,并添加一个子a-input
组件,其中v-model
指令绑定到form.username
变量:
<a-card title="user Information" style="margin-top: 16px">
<a-form-model-item label="username">
<a-input v-model="form.username" />
</a-form-model-item> </a-card>
- 创建一个
a-form-model-item
组件,其中label
属性定义为密码
,并添加一个子a-input-password
组件,其中v-model
指令绑定到form.password
变量,visibility-toggle
属性定义为true
,type
属性定义为password
:
<a-form-model-item label="Password">
<a-input-password
v-model="form.password"
visibility-toggle
type="password"
/> </a-form-model-item>
- 最后,作为
a-card
组件的一个兄弟组件,创建a-form-model-item
,并将wrapper-col
变量属性定义为 JavaScript 对象{span: 14, offset: 4}
。然后,添加一个子a-button
,其中type
定义为primary
,文本为Create
,另一个a-button
,其中style
属性定义为margin-left: 10px;
,文本为Cancel
:
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary">
Create
</a-button>
<a-button style="margin-left: 10px;">
Cancel
</a-button> </a-form-model-item>
它是如何工作的...
在这个示例中,我们学习了如何使用 Ant-Design 和 Vue-CLI 创建用户注册页面。为了创建这个页面,我们首先需要使用 Vue-CLI 创建一个项目,并向其中添加 Ant-Design of Vue 插件,以便可以使用该框架。
然后,我们创建了top-bar
组件,用于保存导航面包屑。为了用户导航,我们创建了一个自定义的Drawer
组件,底部带有内联切换按钮。最后,我们创建了layout
组件,将这两个组件放在一起,并添加了一个<slot>
组件来放置页面内容。
最后,我们创建了用户注册表单页面,其中包含三个卡片,用于保存与组件上的变量绑定的输入表单。
这是最终项目正在运行的屏幕截图:
另请参阅
您可以在vue.ant.design/
找到有关 Ant-Design 和 Vue 的更多信息。
第十章:将应用程序部署到云平台
现在是时候将您的应用程序部署到全球范围内,使其对全球所有人都可用。
在这一章中,我们将学习如何在三个不同的托管平台上进行操作 - Netlify,Now 和 Firebase。在这里,我们将学习在每个平台上创建帐户的过程,设置环境,配置应用程序以进行部署,最后将其部署到网络上。
在本章中,我们将涵盖以下配方:
创建 Netlify 帐户
为在 Netlify 上部署应用程序做准备
为在 GitHub 上的 Netlify 进行自动部署做准备
创建一个 Vercel 帐户
配置 Vercel-CLI 并部署您的项目
为在 GitHub 上的 Vercel 进行自动部署做准备
创建一个 Firebase 项目
配置 Firebase-CLI 并部署您的项目
技术要求
在本章中,我们将使用Node.js和Vue-CLI。
注意 Windows 用户!您需要安装一个名为 windows-build-tools 的 NPM 包,以便能够安装以下必需的软件包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
要安装 Vue-CLI,您需要打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
创建一个 Vue 项目
要创建一个 Vue-CLI 项目,请按照以下步骤进行:
- 打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> vue create vue-project
- Vue-CLI 将要求您选择一个预设;使用空格键选择
Manually select features
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在,Vue-CLI 将要求选择功能,您需要选择
Router
,Vuex
和Linter / Formatter
作为默认功能之外的附加功能:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router ❯ Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 现在,Vue-CLI 将询问您是否要使用历史模式进行路由管理。我们将选择
y
(是):
? Use history mode for router? (Requires proper server setup for
index fallback in production) (Y**/n) y**
- 通过选择 linter 和 formatter 来继续该过程。我们将选择
ESLint + Airbnb config
:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 选择 linter 的附加功能(这里是
Lint and fix on commit
):
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 选择您想要放置 linter 和 formatter 配置文件的位置(这里是
In dedicated config files
):
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
- 最后,CLI 将询问您是否要保存未来项目的设置;您将选择
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:
? Save this as a preset for future projects? (y/N) n
创建 Netlify 帐户
现在是时候开始将部署过程到 Netlify 平台。在这个教程中,我们将学习如何创建我们的 Netlify 账户,以便我们可以将我们的应用程序部署到网络上。
准备就绪
本教程的先决条件如下:
一个电子邮件地址
GitHub 账户
一个 GitLab 账户
一个 BitBucket 账户
在创建 Netlify 账户的过程中,您可以使用电子邮件地址、GitHub账户、GitLab账户或BitBucket账户来实现这一点。
如何做...
在这里,我们将学习如何使用电子邮件地址创建 Netlify 账户:
转到 Netlify 网站www.netlify.com/
,并在页眉菜单中单击注册→。您将被重定向到初始注册页面。
在这个页面上,您可以选择您想要使用的注册 Netlify 的方法。在这个过程中,我们将继续使用电子邮件地址。点击电子邮件按钮,将被重定向到电子邮件注册表单。
填写表单,使用您选择的电子邮件地址和密码。密码规则为最少 8 个字符。填写完表单后,点击注册按钮。然后,您将被重定向到成功页面。
现在,您将在收件箱中收到一封验证电子邮件,您需要这封邮件才能继续使用 Netlify 平台。要继续,请打开您的电子邮件收件箱并检查 Netlify 的电子邮件。
在您的电子邮件收件箱中,打开 Netlify 的电子邮件,然后点击验证电子邮件按钮。此时,将打开一个新窗口,您将能够使用最近注册的电子邮件和密码登录。
在这里,您可以使用您在第 3 步选择的电子邮件地址和密码填写登录表单。之后,点击登录按钮,将被重定向到 Netlify 平台的主窗口。
最后,您将发现自己在 Netlify 平台的主屏幕上,有一个空白页面可以开始在平台上部署。
它是如何工作的...
在这个教程中,我们学习了如何在 Netlify 上创建我们的账户。我们看到可以使用各种 OAuth 方法和我们在教程中使用的基本电子邮件来实现这一点。
电子邮件地址创建过程涉及定义将要使用的电子邮件地址和账户密码,验证账户电子邮件。然后,您可以登录到平台。
另请参阅
在docs.netlify.com/
找到更多关于 Netlify 的信息。
为在 Netlify 中部署应用程序做准备
要开始部署过程,我们需要配置我们的项目以具有有效的 Netlify 部署模式。在这个食谱中,您将学习如何在任何基于 Vue 的应用程序上设置 Netlify 部署模式。
做好准备
这个食谱的先决条件如下:
Node.js 12+
一个 Vue 项目
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
如何做…
在这个食谱中,我们将学习如何准备我们的应用程序以部署到 Netlify:
- 打开您的 Vue 项目并打开
package.json
文件。检查是否已定义build
脚本,如下例所示:
"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"lint": "Vue-CLI-service lint" },
- 打开 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)并执行以下命令:
> npm run build
确保您的应用程序的build
脚本在主文件夹中创建一个dist
文件夹。
如果您的vue-router
被定义为与历史模式一起工作,您必须在public
文件夹中创建一个_redirects
文件。在这个文件中,您需要添加指示给 Netlify 路由器的指令:
# Netlify settings for single-page application
/* /index.html 200
- 将您的应用程序发布到 GitHub 存储库。不用担心构建文件夹,因为它已经在
.gitignore
文件中声明,并且不会发送到您的存储库。
它是如何工作的…
在这个食谱中,我们学习了如何检查和准备我们的应用程序进行 Netlify 部署。
为了使部署工作,我们需要确保在package.json
的脚本部分中有build
命令,并验证构建目标是dist
文件夹。
最后,我们在 public 文件夹中创建了一个_redirects
文件,以指示 Netlify 路由器理解 vue-router 的历史模式。
另请参阅
在cli.vuejs.org/guide/deployment.html#netlify
上查找有关 Netlify 部署的官方 Vue-CLI 文档的更多信息。
在docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps
上查找有关 Netlify 路由器重写的更多信息。
为在 GitHub 上自动部署到 Netlify 做准备
是时候为部署做准备了。在这个食谱中,您将学习如何设置 Netlify 部署过程,以便在 GitHub 上自动获取并部署您的应用程序。
做好准备
这个食谱的先决条件如下:
一个 Netlify 账户
一个 Vue 项目
一个 GitHub 账户
如何做…
最后,在创建 Netlify 帐户,将项目发布到 GitHub 存储库并配置所有内容之后,现在是时候准备 Netlify 平台以在每次 GitHub 推送时执行自动部署了:
转到 Netlify(www.netlify.com/
),登录并打开您的初始仪表板。在那里,您会找到一个来自 Git 的新站点按钮。您将被重定向到创建新站点页面。
现在,您可以单击 GitHub 按钮打开一个新窗口,以在 GitHub 上进行 Netlify 授权并在那里继续该过程。
使用您的 GitHub 帐户登录,然后您将被重定向到应用程序安装页面。
在此页面上,您可以选择向 Netlify 提供对所有存储库的访问权限,或仅选择的存储库,但请确保使您的应用程序的存储库可用。
在 GitHub 上完成 Netlify 的安装后,您在上一步中授予访问权限的存储库将可以在 Netlify 平台上选择。选择包含您的应用程序的存储库。
完成创建过程,您需要选择用于自动部署的分支。然后,您需要填写应用程序中使用的构建命令,在我们的情况下是npm run build
。打开包含构建文件的文件夹,在我们的情况下是dist
文件夹,并单击“部署站点”按钮。
最后,Netlify-CLI 将启动构建过程,并在构建完成且没有任何错误时发布您的应用程序。
工作原理...
Netlify 平台连接到您的 GitHub 帐户并安装为应用程序,从而可以访问选定的存储库。然后,在平台上,您可以选择要用于部署的存储库。选择存储库后,我们需要使用构建说明和构建目标文件夹配置 Netlify-CLI。最后,CLI 运行,我们的应用程序就可以在网络上运行了。
另请参阅
在docs.netlify.com/configure-builds/file-based-configuration/
上查找有关高级 Netlify 部署的更多信息。
创建 Vercel 帐户
Vercel 是一个著名的平台,用于在网络上部署您的应用程序。使用 Vercel,您可以自动化与 GitHub、GitLab 和 BitBucket 的部署过程。在本教程中,我们将学习如何在 Vercel 平台上创建我们的帐户。
准备就绪
这个教程的先决条件只是以下选项之一:
一个 GitHub 账户
一个 GitLab 账户
一个 BitBucket 账户
如何做...
让我们开始在 Vercel 平台上的旅程。在这里,我们将学习如何在平台上创建我们的账户以开始我们的项目部署:
打开 Vercel 网站(vercel.com/
),并点击顶部栏的注册按钮。您将被重定向到注册页面。
在这里,您可以选择这些存储库管理器中的一个 - GitHub、GitLab 或 BitBucket。我们将继续点击GitHub按钮。选择注册方法后,您将被重定向到授权页面。
在此页面上,您正在授权 Vercel 平台访问您账户上的信息。点击授权按钮后,您将被重定向回 Vercel 仪表板。
最后,您已经创建了 Vercel 账户,并准备好使用。
它是如何工作的...
在这个教程中,我们进入了 Vercel 平台,并使用存储库管理器注册了它。我们能够创建我们的账户,并且现在可以通过存储库集成或 CLI 工具在平台上开始部署过程。
另请参阅
您可以在vercel.com/
找到有关 Vercel 的更多信息。
配置 Vercel-CLI 并部署您的项目
您已经创建了一个 Vercel 账户。现在是时候在您的项目上配置 Vercel-CLI,以便它可以在 Vercel 平台和 Web 上使用。
准备工作
这个教程的先决条件如下:
一个 Vercel 账户
一个 Vue 项目
Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
vercel
要安装vercel
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm i -g vercel
如何做...
在这个教程中,我们将学习如何通过 Vercel-CLI 将我们的项目链接到 Vercel 平台,然后使用它部署平台:
- 打开您的 Vue 项目,然后打开
package.json
文件。检查您是否定义了build
脚本,如下例所示:
"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"lint": "Vue-CLI-service lint" },
确保您的应用构建脚本在主文件夹中创建一个dist
文件夹。
在您的project
文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vercel
这将提示您登录 Vercel 平台:
> No existing credentials found. Please log in:
Enter your email:
输入与您用于登录 Vercel 的仓库管理器相关联的电子邮件地址。您将收到一封带有“验证”按钮的电子邮件;点击它以验证您的电子邮件地址:
一旦您的电子邮件地址得到验证,您就可以使用> vercel
命令在您的终端中部署应用程序。
要将应用程序部署到网络上,我们需要在project
文件夹中执行> vercel
命令,并且在部署之前它会询问一些关于项目设置的问题。第一个问题将涉及项目路径:
? Set up and deploy "~/Versionamento/Vue.js-3.0-Cookbook/chapter-
14/14.5"? [Y/n] y
- 现在它将要求您定义将部署项目的范围。当您在同一用户名下定义了多个帐户访问选项时使用。在大多数情况下,它只会有一个,您可以按Enter:
? Set up and deploy "~/Versionamento/Vue.js-3.0-Cookbook/chapter-
14/14.5"? y
? Which scope do you want to deploy to? ❯ **Heitor Ramon Ribeiro**
- 然后,它将要求链接到 Vercel 上的现有项目。在我们的情况下,这是一个全新的项目,所以我们将选择
n
:
? Link to existing project? [Y/n] n
- 您将被要求定义项目的名称(只允许小写字母数字字符和连字符):
? What's your project's name? vuejscookbook-12-5
- 您现在需要定义项目源代码的位置。此位置是
package.json
文件所在的位置;在我们的情况下,这将是./
文件夹,或者主项目文件夹:
? In which directory is your code located? ./
- Vercel-CLI 将检测到项目是一个 Vue-CLI 项目,并将自动定义所有用于部署应用程序的命令和目录设置。在我们的情况下,我们将选择
n
。
Auto-detected Project Settings (Vue.js): - Build Command: `npm run build` or `Vue-CLI-service build` - Output Directory: dist - Development Command: Vue-CLI-service serve --port $PORT ? Want to override the settings? [y/N] n
- 一切设置完成后,CLI 将部署应用程序的第一个预览,并将发送一个链接以访问应用程序的预览。要将应用程序部署为生产就绪状态,您需要在 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)中执行以下命令:
> vercel --prod
它是如何工作的...
在这个教程中,我们学习了如何将 Vercel-CLI 连接到与仓库管理器相关联的电子邮件地址的在线平台,并设置项目部署。
在这个过程中,我们学习了如何通过定义构建命令、分发文件夹和开发命令来配置 CLI 的高级选项。
最后,我们能够在将项目部署到生产环境之前获得项目的预览 URL。
另请参阅
您可以在vercel.com/docs/cli?query=CLI#getting-started
找到有关 Vercel-CLI 的更多信息。
您可以在vercel.com/docs/configuration?query=now.json#introduction/configuration-reference
找到有关 Vercel 高级配置的更多信息。
准备在 GitHub 上使用 Vercel 进行自动部署
在上一个教程中,我们学习了如何使用 Vercel-CLI 将我们的应用程序部署到网络上,使用您的本地终端,但是可以将存储库管理器与 Vercel 平台集成,并通过任何推送或打开拉取请求自动部署。这就是我们将在这个教程中做的事情。
准备工作
本教程的先决条件如下:
Vercel 帐户
存储库管理器上的 Vue 项目
如何做...
在这个教程中,我们将学习如何将 Vercel 平台与存储库管理器集成,并进行自动部署:
打开您的 Vercel 仪表板(vercel.com/dashboard
)并点击导入项目按钮。
在导入项目页面上,点击继续按钮,位于来自 Git 存储库卡内。
现在,Vercel 网站将询问持有您要导入的项目的存储库的用户是否是您的个人帐户。如果是,请点击是。如果不是,Vercel 将在开始该过程之前将该项目分叉到您的个人帐户。
然后,Vercel 将询问您要将项目绑定到哪个帐户。在我们的情况下,这将是我们的个人帐户。选择它,然后点击继续按钮。
您将被重定向到 GitHub 网页,以授予 Vercel 对您的存储库的访问权限。您可以授予对所有存储库的访问权限,或者只是您想要部署的存储库。在我们的情况下,我们将授予对我们帐户上所有存储库的访问权限。
在您的 GitHub 帐户上安装 Vercel 应用程序后,您将被发送回 Vercel 网页。在这里,您可以定义您正在创建的项目的设置,包括项目名称、您正在使用的预设、构建说明和环境变量。Vercel 将自动检测到我们的项目是一个 Vue-CLI 项目,并为我们配置构建和部署设置。然后,点击部署按钮继续。
Vercel 将启动第一个部署过程。完成后,Vercel 将为您提供应用程序的链接,以及一个打开仪表板的链接。
它是如何工作的...
Vercel 平台连接到您的 GitHub 帐户并安装为应用程序,从而可以访问选定的存储库。然后,在平台上,您可以选择要用于部署的存储库。
选择存储库后,您需要使用构建说明和构建目标文件夹配置 Vercel-CLI。
最后,CLI 运行,我们的应用程序在 Web 上运行起来了。
参见
在zeit.co/docs/v2/git-integrations
找到有关 Vercel 与 Git 存储库集成的更多信息。
创建 Firebase 项目
Firebase 是由 Google 创建的一体化解决方案,旨在为开发人员提供专用的分析、通知、机器学习和云解决方案工具。他们提供的云解决方案之一是托管平台。
有了托管平台,我们可以在 Google 云服务器上托管我们的单页应用程序,并通过全球内容传送网络使其对所有人可用。
准备工作
此处的先决条件如下:
一个 Google 帐户
一个 Vue 项目
如何做...
在这个教程中,我们将学习如何创建我们的 Firebase 项目,以便我们可以将我们的应用程序部署到 Firebase 托管:
打开 Firebase 主页(firebase.google.com/
)并单击页眉菜单中的“登录”链接。如果您已经登录到您的 Google 帐户,请单击“转到控制台”链接。
在控制台页面上,单击“创建项目”按钮以创建新的 Firebase 项目。
Firebase 将要求输入项目名称(您只能使用字母数字字符和空格)。
然后,Firebase 会询问您是否要在此项目中启用 Google Analytics。在我们的情况下,我们将禁用此选项。
最后,您将被重定向到项目概览仪表板。
它是如何工作的...
在这个教程中,我们创建了我们的第一个 Firebase 项目。为此,我们首先登录到我们的 Google 帐户并转到 Firebase 控制台。在 Firebase 控制台上,我们创建了一个新项目,并在设置向导步骤中禁用了 Google Analytics 选项,因为我们在这个教程中不会使用附加的分析。最后,当我们完成设置向导时,我们的项目已经准备就绪。
参见
在firebase.google.com
找到有关 Google Firebase 的更多信息。
配置 Firebase-CLI 并部署您的项目
要将我们的应用程序部署到 Firebase Hosting,我们需要使用 Firebase-CLI。CLI 将帮助打包文件并将它们发送到 Google Cloud 服务器的过程。
在本配方中,我们将学习如何配置 Firebase-CLI 以使用本地终端将应用程序部署到网络。
准备工作
本配方的先决条件如下:
一个 Google 帐户
一个 Vue 项目
一个 Firebase 项目
Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
firebase-tools
安装firebase-tools
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g firebase-tools
如何做...
在本配方中,我们将学习如何在我们的项目中设置 Firebase-CLI,并如何使用上一个配方中创建的项目对其进行初始化:
- 在项目的根文件夹中打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> firebase login
Firebase-CLI 将打开一个浏览器窗口,以便您登录到您的 Google 帐户,并允许 Firebase-CLI 访问您的 Google 帐户的部分。(如果浏览器没有自动打开,Firebase-CLI 上会出现一个链接,复制链接,然后粘贴到浏览器中继续。)
在项目的根文件夹中打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> firebase init
- 现在我们正在使用我们的项目初始化 CLI 的配置过程。对于 CLI 的第一个问题,我们将只使用Hosting功能,因此我们需要只选择
Hosting
:
**? Which Firebase CLI features do you want to set up
for this folder?**
**Press space to select feature, then Enter to confirm
your choices.**
Database: Deploy Firebase Realtime Database Rules
Firestore: Deploy rules and create indexes for Firestore
Functions: Configure and deploy Cloud Functions ❯ Hosting: Configure and deploy Firebase Hosting sites
Storage: Deploy Cloud Storage security rules Emulators: Set up local emulators for Firebase features
- 然后,CLI 将询问我们要使用哪个 Firebase 项目。在我们的情况下,我们在上一个配方中早些时候创建了项目,因此我们将选择
使用现有项目
:
? Use an existing project ❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
- 现在将显示您帐户上可用项目的列表。选择要与此应用程序一起部署的项目:
? **Select a default Firebase project for this directory: (Use arrow**
**keys)** ❯ vue-3-cookbook-firebase-18921 (Vue 3 Cookbook Firebase)
- CLI 将询问应用程序的公共目录,或者在我们的情况下,因为它是单页面应用程序,我们需要使用构建目标文件夹。键入目标文件夹的名称,在我们的情况下是
dist
:
? **What do you want to use as your project public directory?** **dist**
- 最后,过程中的最后一步是选择是否要将配置用作单页面应用程序。键入
y
以启用所有 URL 的重写为index.html
,以便我们可以使用vue-router
的历史模式:
? Configure as a single-page app (rewrite all urls to /index.html)?
(y/N) y
- 打开项目根目录下的
package.json
文件,并添加一个新的脚本来自动化构建和部署过程:
"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"deploy": "npm run build && firebase deploy",
"lint": "Vue-CLI-service lint" },
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并在项目的根目录下执行以下命令:
> npm run deploy
现在您的项目已经部署并可以在网络上访问,CLI 将为您提供访问链接:
工作原理...
在这个教程中,我们学习了如何配置 Firebase CLI 并部署我们的应用程序。
首先,我们安装了 Firebase-CLI 并登录到 Google 身份验证平台。然后,我们能够在项目文件夹中初始化 CLI。
在这个过程中,我们选择了在上一个教程中创建的项目,并将构建文件夹指向 Vue-CLI 项目中的正确文件夹。
然后,我们配置了要使用单页面应用程序路由结构,并向package.json
添加了部署脚本。最后,我们能够部署我们的应用程序并使其对所有人可用。
另请参阅
在firebase.google.com/docs/hosting
上找到有关 Firebase Hosting 的更多信息。
第十一章:指令,插件,SSR 等
现在您已经进入专业联赛!您是一个高级的 Vue 开发者。让我们来玩一些有趣的东西,并查看一些为您量身定制的优秀配方!以下是一些精选的优化解决方案,可以提高您的 Vue 应用程序的质量,并使您的生活更轻松。
在本章中,我们将涵盖以下配方:
自动加载vue-router
路由
自动加载vuex
模块
创建自定义指令
创建 Vue 插件
使用 Quasar 在 Vue 中创建 SSR,SPA,PWA,Cordova 和 Electron 应用程序
创建更智能的 Vue 观察者和计算属性
使用 Python Flask
创建Nuxt.js
SSR
Vue 应用程序的注意事项和禁忌
技术要求
在本章中,我们将使用 Node.js,Vue-CLI
,Cordova
,Electron
,Quasar
,Nuxt.js
和 Python。
注意 Windows 用户:您需要安装一个名为windows-build-tools
的npm
包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装Vue-CLI
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g @vue/cli @vue/cli-service-global
要安装**Cordova
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g cordova
如果您在 macOS 上运行,并且想要运行 iOS 模拟器,您需要在终端(macOS)中执行以下命令:
> npm install -g ios-sim ios-deploy
要安装Electron
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g electron
要安装**Quasar
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g @quasar/cli
要安装**Nuxt.js
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g create-nuxt-app
自动加载 Vue 路由
为了创建可维护的代码,我们可以使用自动导入具有相同结构的文件的策略。就像在vue-router
中的路由一样,当应用程序变得更大时,我们会发现大量的文件被手动导入和处理。在这个配方中,我们将学习一个技巧,使用 webpack 的require.context
函数来自动为我们注入文件。
此函数将读取文件内容,并将路由添加到一个数组中,默认情况下将其导出到我们的文件中。您可以通过添加更多受控的路由导入甚至基于环境的路由规则来改进此配方。
准备就绪
此配方的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用之前创建的项目:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue create router-import
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用空格键选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个是手动
模式,您可以在其中选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及我们想要在项目中使用的功能。这些功能包括一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试工具,代码检查工具等。选择Babel
、Router
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个代码检查工具和格式化工具来继续这个过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置了代码检查规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、代码检查工具和处理器之后,我们需要选择设置和配置的存储位置。最佳存储位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
- 现在您可以选择是否要将此选择设置为将来项目的预设,这样您就不需要重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并自动为我们安装包。
如果您想在安装完成后在vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
或者您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令来运行内置的npm
命令:
npm run serve
- 本地运行开发服务器
npm run build
- 用于构建和压缩应用程序以进行部署
npm run lint
- 对代码执行 lint
如何做...
按照这些说明,在项目中创建路由文件的自动导入,它将处理特定文件夹内的路由文件:
- 创建并放置在
routes
文件夹内的路由文件后,我们需要确保每个路由文件中都有一个默认的export
对象。在src/router
文件夹内的index.js
文件中,删除文件中存在的默认routes
数组:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default new VueRouter({});
- 现在创建一个空的
routes
数组,它将由从文件夹中导入的路由填充,并开始导入。这样,requireRoutes
将成为一个对象,其键是文件名,值是文件的 ID:
import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const routes = []; const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*\.js$/is, ); const router = new VueRouter({
routes, }); export default router;
- 为了将这些文件推送到
routes
数组中,我们需要添加以下代码,并在router
文件夹内创建一个名为routes
的文件夹:
import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const routes = []; const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*\.js$/is, ); requireRoutes.keys().forEach((fileName) => {
routes.push({
...requireRoutes(fileName).default,
}); }); const router = new VueRouter({
routes, }); export default router;
现在,只要在routes
文件夹内创建一个新的.js
文件,你的路由就会自动加载到应用程序中。
工作原理...
require.context
是 webpack 内置的函数,允许您传入要搜索的目录、一个指示是否应该检查子目录的标志,以及一个匹配文件的正则表达式。
当构建过程开始时,webpack 将搜索所有require.context
函数,并对其进行预执行,因此导入所需的文件将在最终构建中存在。
我们向函数传递了三个参数:第一个是它将开始搜索的文件夹,第二个询问搜索是否会进入下降的文件夹,最后,第三个是用于文件名匹配的正则表达式。
在这个配方中,我们将./routes
定义为文件夹,作为函数的第一个参数自动加载路由。作为函数的第二个参数,我们定义false
,不搜索子目录。最后,作为第三个参数,我们定义/^(?!.*test).*\.js$/is
作为正则表达式,用于搜索.js
文件并忽略文件名中包含.test
的文件。
还有更多...
通过这个配方,可以通过使用子目录进行路由模块和环境进行路由控制,将应用程序提升到下一个级别。
通过这些增量,函数可以被提取到另一个文件中,但在router.js
中,它仍然需要被导入到main.js
文件中。或者,您可以获取import
函数,并将routes
数组传递给router.js
。
另请参阅
在 webpack 文档中阅读有关 webpack 依赖管理和require.context
的更多信息:webpack.js.org/guides/dependency-management/.
自动加载 Vuex 模块
有时,当我们在一个大项目上工作时,我们需要管理许多导入的Vuex
模块和存储。为了处理这些模块,我们总是需要通过创建一个包含所有导入文件的文件来导入它们,然后将其导出到 Vuex 存储创建中。
在这个食谱中,我们将学习一个使用 webpack 的require.context
函数的函数,以自动加载并将这些文件注入到 Vuex 存储创建中。
准备就绪
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用之前创建的项目:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-import
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个手动
模式,您可以在其中选择更多模式、插件、检查器和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及我们将在项目中需要哪些功能。这些功能包括一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试器,检查器等。选择Babel
,Vuex
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router ❯Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个检查器和格式化程序来继续此过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置好检查规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、检查器和处理器之后,我们需要选择设置和配置的存储位置。将它们存储在专用文件中是最好的地方,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
- 现在您可以选择是否要将此选择作为将来项目的预设,这样您就不需要重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并为我们自动安装包。
如果你想在安装完成后在vue-ui
上检查项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vue ui
或者你可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令来运行内置的npm
命令:
npm run serve
– 本地运行开发服务器
npm run build
– 为部署构建和压缩应用程序
npm run lint
– 执行代码的 lint
如何做...
按照以下说明,在项目中创建vuex
模块的自动导入,以处理特定文件夹内的路由文件:
- 在
store
文件夹中创建并放置路由文件后,我们需要确保每个store
文件都有一个默认的export
对象。在src/store
文件夹中的index.js
文件中,我们需要提取stores
或modules
的数组:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({});
- 在
src/store
文件夹中创建另一个名为loader.js
的文件(它将是我们的module
加载器)。重要的是要记住,当使用这个方法时,你将使用vuex
的命名空间,因为所有的stores
都需要作为一个模块使用,并且需要导出为一个单一的 JavaScript 对象。每个文件名将被用作命名空间的引用,并且将被解析为驼峰式文本风格:
const toCamel = (s) => s.replace(/([-_][a-z])/ig, (c) => c.toUpperCase()
.replace(/[-_]/g, '')); const requireModule = require.context('./modules/', false,
/^(?!.*test).*\.js$/is); const modules = {}; requireModule.keys().forEach((fileName) => {
const moduleName = toCamel(fileName.replace(/(\.\/|\.js)/g, '')); modules[moduleName] = {
namespaced: true,
...requireModule(fileName).default,
}; }); export default modules;
- 由于我们将默认导入
modules
文件夹中的每个文件,一个好的做法是为每个模块创建一个文件。例如,当你创建一个名为user
的模块时,你需要创建一个名为user.js
的文件,它导入所有的stores
操作、mutations、getters 和 state。这些可以放在一个与模块同名的文件夹中。modules
文件夹的结构将类似于这样:
modules
├── user.js
├── user
│ └── types.js
│ └── state.js
│ └── actions.js
│ └── mutations.js
│ └── getters.js
└───────
src/store/modules
文件夹中的user.js
文件将如下所示:
import state from './user/state'; import actions from './user/actions'; import mutations from './user/mutations'; import getters from './user/getters'; export default {
state,
actions,
mutations,
getters, };
- 在
src/store
文件夹中的index.js
文件中,我们需要添加自动加载的导入模块:
import Vue from 'vue'; import Vuex from 'vuex'; import modules from './loader'; Vue.use(Vuex); export default new Vuex.Store({
modules, });
现在,只要在src/store/modules
文件夹中创建一个新的.js
文件,你的vuex
模块就会自动加载到你的应用程序中。
它是如何工作的...
require.context
是 webpack 的内置函数,它接收一个目录来执行搜索,一个布尔标志,指示是否在此搜索中包括子目录,以及用于文件名模式匹配的正则表达式(作为参数)。
当构建过程开始时,webpack 将搜索所有require.context
函数,并预先执行它们,以便导入所需的文件在最终构建中存在。
在我们的情况下,我们传递了./modules
作为文件夹,false
表示不搜索子目录,/^(?!.*test).*\.js$/is
作为正则表达式来搜索.js
文件并忽略文件名中包含.test
的文件。
然后,该函数将搜索文件,并通过for
循环将结果添加到vuex
模块的数组中。
另请参阅
在 webpack 文档中阅读有关 webpack 依赖管理和require.context
的更多信息webpack.js.org/guides/dependency-management/.
创建自定义指令
谈到 Vue 等视觉框架,我们总是想到组件、渲染和视觉元素,而忘记了除了组件本身之外还有很多东西。
这些指令使组件与模板引擎一起工作,它们是数据和视觉结果之间的绑定代理。还有 Vue 核心中的内置指令,如v-if
,v-else
和v-for
。
在这个食谱中,我们将学习如何制作我们的指令。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用之前食谱中创建的项目:
- 我们需要在 Terminal(macOS 或 Linux)或 Command Prompt/PowerShell(Windows)中执行以下命令:
> vue create vue-directive
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。
有两种方法可以启动一个新项目。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个手动
模式,您可以在其中选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及项目中想要的功能。这些功能包括一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试工具,代码检查工具等。选择Babel
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel TypeScript Progressive Web App (PWA) SupportRouter
Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个代码检查工具和格式化工具来继续这个过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置了 linting 规则之后,我们需要定义它们何时应用于您的代码。它们可以在“保存时”应用,也可以在“提交时”修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、linters 和处理器之后,我们需要选择设置和配置存储的位置。存储它们的最佳位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
- 现在您可以选择是否要将此选择作为将来项目的预设,这样您就不需要重新选择所有内容。
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并自动为我们安装软件包。
如果您想在安装完成后在vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
或者,您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的 npm 命令:
npm run serve
- 本地运行开发服务器
npm run build
- 为部署构建和缩小应用程序
npm run lint
- 对代码执行 lint
操作步骤如下...
按照以下说明创建一个用于掩码输入字段的指令:
在src/directives
文件夹中创建名为formMaskInputDirective.js
的文件,并在同一文件夹中创建名为tokens.js
的文件。
在tokens.js
文件中,我们将添加我们的掩码基本令牌。这些令牌将用于识别我们的输入将接受的值类型:
export default {
"#": { pattern: /[\x2A\d]/ },
0: { pattern: /\d/ },
9: { pattern: /\d/ },
X: { pattern: /[0-9a-zA-Z]/ },
S: { pattern: /[a-zA-Z]/ },
A: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleUpperCase() },
a: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleLowerCase() },
"!": { escape: true }
};
- 我们从
token.js
导入令牌并创建我们的函数:
import tokens from './tokens';
function maskerValue() {
// Code will be developed in this recipe
}
function eventDispatcher() {
// Code will be developed in this recipe
}
function maskDirective() {
// Code will be developed in this recipe
}
export default maskDirective;
- 在
maskDirective
函数中,我们需要检查调用者传递的指令上的绑定值,并检查它是否是有效的绑定。为此,我们首先检查binding
参数上是否存在value
属性,然后将其添加到tokens
中导入的config
变量中:
function maskDirective(el, binding) {
let config;
if (!binding.value) return false;
if (typeof config === 'string') {
config = {
mask: binding.value,
tokens,
};
} else {
throw new Error('Invalid input entered');
}
- 现在我们需要检查元素并验证它是否是
input
HTML 元素。为此,我们将检查指令传递的元素是否具有input
的tagName
,如果没有,我们将尝试在传递的元素中找到input
HTML 元素:
let element = el;
if (element.tagName.toLocaleUpperCase() !== 'INPUT') {
const els = element.getElementsByTagName('input');
if (els.length !== 1) {
throw new Error(`v-input-mask directive requires 1 input,
found ${els.length}`);
} else {
[element] = els;
}
}
- 现在我们需要为元素添加事件侦听器。侦听器将调用两个外部函数,一个用于分派事件,另一个用于将掩码值返回到输入中:
element.oninput = (evt) => {
if (!evt.isTrusted) return;
let position = element.selectionEnd;
const digit = element.value[position - 1];
element.value = maskerValue(element.value, config.mask,
config.tokens);
while (
position < element.value.length
&& element.value.charAt(position - 1) !== digit
) {
position += 1;
}
if (element === document.activeElement) {
element.setSelectionRange(position, position);
setTimeout(() => {
element.setSelectionRange(position, position);
}, 0);
}
element.dispatchEvent(eventDispatcher('input'));
};
const newDisplay = maskerValue(element.value, config.mask,
config.tokens);
if (newDisplay !== element.value) {
element.value = newDisplay;
element.dispatchEvent(eventDispatcher('input'));
}
return true;
}
// end of maskDirective function
- 让我们创建
eventDispatcher
函数;这个函数将发出事件,将被v-on
指令监听到:
function eventDispatcher(name) {
const evt = document.createEvent('Event');
evt.initEvent(name, true, true);
return evt;
}
- 现在复杂的部分:将掩码输入值返回到输入框。为此,我们需要创建
maskerValue
函数。该函数接收值、掩码和令牌作为参数。该函数检查当前值与掩码是否匹配,以查看掩码是否完整或值是否是有效令牌。如果一切正常,它将把值传递给输入框:
function maskerValue(v, m, tkn) {
const value = v || '';
const mask = m || '';
let maskIndex = 0;
let valueIndex = 0;
let output = '';
while (maskIndex < mask.length && valueIndex < value.length) {
let maskCharacter = mask[maskIndex];
const masker = tkn[maskCharacter];
const valueCharacter = value[valueIndex];
if (masker && !masker.escape) {
if (masker.pattern.test(valueCharacter)) {
output += masker.transform ?
masker.transform(valueCharacter) : valueCharacter;
maskIndex += 1;
}
valueIndex += 1;
} else {
if (masker && masker.escape) {
maskIndex += 1;
maskCharacter = mask[maskIndex];
}
output += maskCharacter;
if (valueCharacter === maskCharacter) valueIndex += 1;
maskIndex += 1;
}
}
let outputRest = '';
while (maskIndex < mask.length) {
const maskCharacter = mask[maskIndex];
if (tkn[maskCharacter]) {
outputRest = '';
break;
}
outputRest += maskCharacter;
maskIndex += 1;
}
return output + outputRest;
}
//end of maskerValue function
- 准备好后,我们需要在
main.js
文件中导入掩码指令,并将指令添加到 Vue 中,给指令命名为'input-mask'
:
import Vue from 'vue';
import App from './App.vue';
import InputMaskDirective from './directives/formMaskInputDirective';
Vue.config.productionTip = false;
Vue.directive('input-mask', InputMaskDirective);
new Vue({
render: (h) => h(App),
}).$mount('#app');
- 要在我们的应用程序中使用该指令,我们需要在单文件组件
<template>
部分的input
HTML 元素上调用指令,将token
模板'###-###-###'
作为参数传递给v-input-mask
指令,如下所示:
<template>
<div id="app">
<input
type="text"
v-input-mask="'###-###-###'"
v-model="inputMask"
/>
</div>
</template>
<script>
export default {
name: 'app',
data: () => ({
inputMask: '',
}),
};
</script>
工作原理...
Vue 指令有五个可能的钩子。我们只使用了一个bind
。它直接绑定到元素和组件。它有三个参数:element
,binding
和vnode
。
当我们在main.js
文件中将指令添加到 Vue 中时,它将在整个应用程序中可用,因此该指令已经在App.vue
中,可以被输入框使用。
在调用v-input-mask
的同时,我们将第一个参数element
传递给指令,第二个参数binding
是属性的值。
我们的指令通过检查输入框上的每个新字符值来工作。执行正则表达式测试并验证字符,以查看它是否是指令实例化时给定的令牌列表中的有效字符。然后,如果通过测试,它将返回字符,如果是无效字符,则不返回任何内容。
创建一个 Vue 插件
有时需要对应用程序进行新的添加,并且需要共享这个添加。最好的共享方式是使用插件。在 Vue 中,插件是通过扩展初始化应用程序的 Vue 全局原型来添加新功能,如指令、混合、过滤器、原型注入或全新功能。
现在我们将学习如何制作我们的插件,以及如何使用它与整个 Vue 进行交互(而不会干扰原型并破坏它)。
准备工作
此食谱的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@vue/cli
@vue/cli-service-global
我们需要使用Vue-CLI
创建一个新的 Vue 项目,或者使用以前的配方创建的项目:
- 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vue-plugin
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,spacebar选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,以及手动
模式,您可以在其中选择更多模式、插件、linter 和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
- 现在我们被问及我们想要在项目中的功能。这些功能是一些 Vue 插件,如
Vuex
或Router
(vue-router
),测试器,linter 等。选择Babel
和Linter / Formatter
:
? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router
Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
- 通过选择一个 linter 和 formatter 来继续此过程。在我们的情况下,我们将选择
ESLint + Airbnb
配置:
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
- 设置 linting 规则后,我们需要定义它们何时应用于您的代码。它们可以在保存时应用,也可以在提交时修复:
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
- 在定义了所有这些插件、linter 和处理器之后,我们需要选择设置和配置的存储位置。存储它们的最佳位置是专用文件,但也可以将它们存储在
package.json
中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
- 现在,您可以选择是否要将此选择设置为将来项目的预设,这样您就不需要重新选择所有内容:
? Save this as a preset for future projects? (y/N) n
Vue-CLI 将创建项目,并自动为我们安装包。
如果您想在安装完成后在vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
或者,您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的 npm 命令:
npm run serve
- 本地运行开发服务器
npm run build
- 为部署构建和缩小应用程序
npm run lint
- 执行代码上的 lint
如何做...
编写 Vue 插件很简单,无需更多了解 Vue 本身。插件的基本概念是一个需要具有install
函数的对象,当通过Vue.use()
方法调用时将执行该函数。install
函数将接收两个参数:Vue 和将用于实例化插件的选项。
按照以下说明编写一个插件,向 Vue 全局原型添加两个新函数$localStorage
和$sessionStorage
:
在我们的项目中,我们需要在src/plugin
文件夹中创建一个名为storageManipulator.js
的文件。
在这个文件中,我们将创建插件安装对象-我们将添加默认的插件选项和函数的基本原型:
/* eslint no-param-reassign: 0 */
const defaultOption = {
useSaveFunction: true,
useRetrieveFunction: true,
onSave: value => JSON.stringify(value),
onRetrieve: value => JSON.parse(value),
};
export default {
install(Vue, option) {
const baseOptions = {
...defaultOption,
...option,
};
Vue.prototype.$localStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
Vue.prototype.$sessionStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
},
};
- 现在我们需要创建
generateStorageObject
函数。这个函数将接收两个参数:第一个是窗口存储对象,第二个是插件选项。通过这样,将可以生成将用于注入到 Vue 中的原型的对象:
const generateStorageObject = (windowStorage, options) => ({
set(key, value) {
windowStorage.setItem(
key,
options.useSaveFunction
? options.onSave(value)
: value,
);
},
get(key) {
const item = windowStorage.getItem(key);
return options.useRetrieveFunction ? options.onRetrieve(item) :
item;
},
remove(key) {
windowStorage.removeItem(key);
},
clear() {
windowStorage.clear();
},
});
- 需要将插件导入到
main.js
中,然后使用Vue.use
函数在我们的 Vue 应用程序中安装插件:
import Vue from 'vue';
import App from './App.vue';
import StorageManipulatorPlugin from './plugin/storageManipulator';
Vue.config.productionTip = false;
Vue.use(StorageManipulatorPlugin);
new Vue({
render: h => h(App),
}).$mount('#app');
现在你可以在 Vue 应用程序的任何地方使用插件,调用this.$localStorage
方法或this.$sessionStorage
。
工作原理...
Vue 插件通过将所有指令要使用的代码添加到 Vue 应用程序层(如 mixin)来工作。
当我们使用Vue.use()
导入我们的插件时,我们告诉 Vue 在导入文件的对象上调用install()
函数并执行它。Vue 将自动将当前 Vue 作为第一个参数传递,并将选项(如果声明了)作为第二个参数传递。
在我们的插件中,当调用install()
函数时,我们首先创建baseOptions
,将默认选项与传递的参数合并,然后将两个新属性注入到 Vue 原型中。这些属性现在因为传递的Vue
参数是应用程序中使用的Vue 全局
,所以在任何地方都可用。
我们的generateStorageObject
是浏览器的 Storage API 的纯抽象。我们将其用作插件内原型的生成器。
另见
您可以在vuejs.org/v2/guide/plugins.html
找到有关 Vue 插件的更多信息。
您可以在github.com/vuejs/awesome-vue
找到精选的 Vue 插件列表。
使用 Quasar 在 Vue 中创建 SSR、SPA、PWA、Cordova 和 Electron 应用程序
Quasar 是一个基于 Vue 和 Material Design 的框架,利用“一次编写,随处使用”的优势。
CLI 可以将相同的代码库部署到不同的版本,如单页应用程序(SPA)、服务器端渲染(SSR)、渐进式 Web 应用程序(PWA)、移动应用程序(Cordova)和桌面应用程序(Electron)。
这减轻了开发人员的一些问题,比如为开发配置 webpack、Cordova 和 Electron 与HMR(热模块重新加载),或者在 SPA 项目中添加 SSR 配置。该框架帮助开发人员尽快开始生产。
在这个教程中,我们将学习如何使用 Quasar 和 CLI 来创建一个基本项目,以及如何使用 CLI 为 SPA、PWA、SSR、移动应用和桌面应用添加开发目标。
准备工作
这个教程的先决条件如下:
- Node.js 12+
所需的 Node.js 全局对象如下:
@quasar/cli
我们需要使用 Quasar CLI 创建一个新的 Quasar 项目,或者使用之前创建的项目。
要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar create quasar-project
现在,当被询问时,我们需要选择手动选择功能:
Quasar-CLI
将要求您输入项目名称。定义您的项目名称。在我们的情况下,我们选择了quasar_project
:
> Project name: quasar_project
- 然后
Quasar-CLI
将要求输入项目产品名称。这将被移动应用程序用来定义它们的标题名称。在我们的情况下,我们使用了提供的默认名称:
> Project product name (must start with letter if building mobile
apps) (Quasar App)
- 现在
Quasar-CLI
将要求输入项目描述。这在页面被分享时用于搜索引擎的元标记。在我们的情况下,我们使用了提供的默认描述:
> Project description: (A Quasar Framework app)
- 然后
Quasar-CLI
将要求输入项目作者。用package.json
的有效名称填写(例如,Heitor Ribeiro<[email protected]>
):
> Author: <You>
- 现在是选择 CSS 预处理器的时候了。在我们的情况下,我们将选择
Sass with indented syntax
:
Pick your favorite CSS preprocessor: (can be changed later) (Use arrow keys) ❯ Sass with indented syntax (recommended)
Sass with SCSS syntax (recommended)
Stylus
None (the others will still be available)
- 然后
Quasar-CLI
将询问组件和指令的导入策略。我们将使用默认的auto-import
策略:
Pick a Quasar components & directives import strategy: (can be
changed later) (Use arrow keys ) ❯ * Auto-import in-use Quasar components & directives - also
treeshakes Quasar; minimum bundle size
* Import everything from Quasar - not treeshaking Quasar;
biggest bundle size
- 现在我们需要为项目选择额外的功能。我们将选择
EsLint
:
Check the features needed for your project: EsLint
- 之后,
Quasar-CLI
将要求选择 ESLint 的预设。选择Airbnb
预设:
Pick an ESLint preset: Airbnb
- 最后,
Quasar-CLI
将要求选择要用于安装项目依赖项的应用程序。在我们的情况下,我们使用了yarn
,因为我们已经安装了它(但您可以选择您喜欢的):
Continue to install project dependencies after the project has been
created? (recommended) (Use arrow keys) ❯ Yes, use Yarn (recommended)
Yes, use npm
No, I will handle that myself
现在在您的 IDE 或代码编辑器中打开创建的文件夹。
如何做...
在使用 Quasar 创建应用程序时,您总是需要选择一个口味来开始,但主要代码将是 SPA。因此,其他口味将根据其需求具有其特殊的功能和特色,但您可以根据构建环境个性化并使您的构建执行一些代码。
开发 SPA(单页应用程序)
开始开发 SPA 是一个开箱即用的解决方案;不需要添加任何新的配置。
所以让我们开始向我们的应用程序添加一个新页面。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> quasar new page About
Quasar-CLI
将自动为我们创建 Vue 页面。我们需要在路由文件中添加对页面的引用,然后该页面将在应用程序中可用:
- 为了做到这一点,我们需要打开
src/router
文件夹中的routes.js
文件,并添加About
页面:
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', name: 'home', component: () =>
import('pages/Index.vue') },
{ path: 'about', name: 'about', component: () =>
import('pages/About.vue') },
],
},
{
path: '*',
component: () => import('pages/Error404.vue'),
}
];
export default routes;
- 然后在
src/pages
文件夹中打开About.vue
文件。您会发现该文件是一个单文件组件,其中有一个空的QPage
组件,因此我们需要在<template>
部分中添加基本标题和页面指示:
<template>
<q-page
padding
class="flex flex-start"
>
<h1 class="full-width">About</h1>
<h2>This is an About Us Page</h2>
</q-page>
</template>
<script>
export default {
name: 'PageAbout',
};
</script>
- 现在,在
src/layouts
文件夹中的MainLayout.vue
文件中,对于q-drawer
组件,我们需要添加到Home
和About
页面的链接:
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn flat dense round
@click="leftDrawerOpen = !leftDrawerOpen"
aria-label="Menu">
<q-icon name="menu" />
</q-btn>
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<q-drawer v-model="leftDrawerOpen"
bordered content-class="bg-grey-2">
<q-list>
<q-item-label header>Menu</q-item-label>
<q-item clickable tag="a" :to="{name: 'home'}">
<q-item-section avatar>
<q-icon name="home" />
</q-item-section>
<q-item-section>
<q-item-label>Home</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" :to="{name: 'about'}">
<q-item-section avatar>
<q-icon name="school" />
</q-item-section>
<q-item-section>
<q-item-label>About</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
export default {
name: "MyLayout",
data() {
return {
leftDrawerOpen: this.$q.platform.is.desktop
};
}
};
</script>
我们已经完成了一个简单的 SPA 在 Quasar 框架内运行的示例。
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev
- 启动开发模式
quasar build
- 构建 SPA
开发 PWA(渐进式 Web 应用程序)
开发 PWA,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> quasar mode add pwa
Quasar-CLI
将创建一个名为src-pwa
的文件夹,其中将包含我们的service-workers
文件,与我们的主要代码分开。
为了清理新添加的文件,并将其格式化为 Airbnb 格式,我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> eslint --fix --ext .js ./src-pwa
我们添加到 SPA 的代码仍将被用作我们的基础,以便我们可以添加新页面、组件和其他功能,这些功能也将用于 PWA。
那么,您是否想知道为什么service-worker
不在主src
文件夹中?这是因为这些文件专门用于 PWA,并且在除此之外的任何其他情况下都不需要。在不同的构建类型中也会发生相同的情况,例如 Electron,Cordova 和 SSR。
在 PWA 上配置 quasar.conf
对于 PWA 开发,您可以在root
文件夹中的quasar.conf.js
文件上设置一些特殊标志:
pwa: {
// workboxPluginMode: 'InjectManifest',
// workboxOptions: {},
manifest: {
// ...
},
// variables used to inject specific PWA
// meta tags (below are default values)
metaVariables: {
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'default',
appleTouchIcon120: 'statics/icons/apple-icon-120x120.png',
appleTouchIcon180: 'statics/icons/apple-icon-180x180.png',
appleTouchIcon152: 'statics/icons/apple-icon-152x152.png',
appleTouchIcon167: 'statics/icons/apple-icon-167x167.png',
appleSafariPinnedTab: 'statics/icons/safari-pinned-tab.svg',
msapplicationTileImage: 'statics/icons/ms-icon-144x144.png',
msapplicationTileColor: '#000000'
}
}
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev -m pwa
- 以 PWA 模式启动开发模式
quasar build -m pwa
- 将代码构建为 PWA
开发 SSR(服务器端渲染)
要开发 SSR,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar mode add ssr
Quasar-CLI
将创建一个名为src-ssr
的文件夹,其中包含我们的extension
和server
启动文件,与我们的主要代码分开。
extension
文件不会被babel
转译,并在 Node.js 上下文中运行,因此与 Express 或Nuxt.js
应用程序相同。您可以使用服务器插件,如database
,fileread
和filewrites
。
server
启动文件将是我们src-ssr
文件夹中的index.js
文件。与扩展名一样,它不会被babel
转译,并在 Node.js 上下文中运行。对于 HTTP 服务器,它使用 Express,并且如果您配置quasar.conf.js
以向客户端传递 PWA,则可以同时拥有 SSR 和 PWA。
在 SSR 上配置 quasar.conf
对于 SSR 开发,您可以在root
文件夹中的quasar.conf.js
文件上配置一些特殊标志:
ssr: {
pwa: true/false, // should a PWA take over (default: false), or just
// a SPA?
},
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev -m ssr
- 以 SSR 模式启动开发模式
quasar build -m ssr
- 将代码构建为 SSR
quasar serve
- 运行 HTTP 服务器(可用于生产)
开发移动应用程序(Cordova)
要开发 SSR,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar mode add cordova
现在Quasar-CLI
将询问您一些配置问题:
Cordova 应用程序 ID 是什么? (org.cordova.quasar.app)
Cordova 是否可以匿名报告使用统计数据以随时间改进工具? (Y/N) N
Quasar-CLI
将创建一个名为src-cordova
的文件夹,其中将包含一个 Cordova 项目。
Cordova 项目的文件夹结构如下:
src-cordova/
├── config.xml
├── packages.json
├── cordova-flag.d.ts
├── hooks/
├── www/
├── platforms/
├── plugins/
作为 Quasar 内部的一个独立项目,要添加 Cordova 插件,您需要在src-cordova
文件夹内调用plugman
或cordova plugin add
命令。
在 Cordova 上配置 quasar.conf
对于 Cordova 开发,您可以在root
文件夹中的quasar.conf.js
文件上设置一些特殊标志:
cordova: { iosStatusBarPadding: true/false, // add the dynamic top padding on
// iOS mobile devices backButtonExit: true/false // Quasar handles app exit on mobile phone
// back button },
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令来运行Quasar-CLI
命令:
如果您的桌面上尚未配置 Cordova 环境,您可以在此处找到有关如何设置的更多信息:quasar.dev/quasar-cli/developing-cordova-apps/preparation#Android-setup
。
quasar dev -m cordova -T android
– 以 Android 设备模拟器的形式启动开发模式
quasar build -m cordova -T android
– 作为 Android 构建代码
quasar dev -m cordova -T ios
– 以 iOS 设备模拟器的形式启动开发模式(仅限 macOS)
quasar build -m cordova -T ios
– 以 iOS 设备模拟器的形式启动构建模式(仅限 macOS)
开发桌面应用程序(Electron)
要开发 SSR,我们首先需要告诉 Quasar 我们想要添加一个新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar mode add electron
Quasar-CLI
将创建一个名为src-electron
的文件夹,其中将包含一个 Electron 项目。
Electron 项目的文件夹结构如下:
src-electron/
├── icons/
├── main-process/
├── electron-flag.d.ts
在icons
文件夹中,您将找到electron-packager
在构建项目时将使用的图标。在main-process
文件夹中将是您的主要 Electron 文件,分成两个文件:一个仅在开发时调用,另一个在开发和生产时调用。
在 Electron 上配置 quasar.conf
对于 Electron 开发,您可以在根文件夹的quasar.conf.js
文件上设置一些特殊标志:
electron: {
// optional; webpack config Object for
// the Main Process ONLY (/src-electron/main-process/)
extendWebpack (cfg) {
// directly change props of cfg;
// no need to return anything
},
// optional; EQUIVALENT to extendWebpack() but uses webpack-chain;
// for the Main Process ONLY (/src-electron/main-process/)
chainWebpack (chain) {
// chain is a webpack-chain instance
// of the Webpack configuration
},
bundler: 'packager', // or 'builder'
// electron-packager options
packager: {
//...
},
// electron-builder options
builder: {
//...
}
},
packager
键使用electron-packager
模块的 API 选项,builder
键使用electron-builder
模块的 API 选项。
命令
您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行Quasar-CLI
命令:
quasar dev -m electron
– 以 Electron 模式启动开发模式
quasar build -m electron
– 以 Electron 模式构建代码
它是如何工作的...
这一切都是可能的,因为 Quasar 框架在 CLI 上封装了构建、解析和捆绑。您不需要担心使用 Electron、Cordova 甚至 Babel 的 webpack 和配置。
一个简单的 CLI 命令可以为您生成全新的页面、布局、组件、存储、路由,甚至是一个新的构建。由于 CLI 只是 Vue、webpack、Babel 和其他工具的包装器,您不必只使用 Quasar 的可视组件。如果您不想使用它们,可以选择不导入它们,并利用 CLI 构建应用程序的功能。
另请参阅
您可以在quasar.dev/introduction-to-quasar
的文档中了解更多关于 Quasar 框架的信息
在quasar.dev/quasar-cli/developing-spa/introduction
上阅读有关 Quasar 的 SPA 开发的更多信息
在quasar.dev/quasar-cli/developing-pwa/introduction
上阅读有关 Quasar 的 PWA 开发的更多信息
在quasar.dev/quasar-cli/developing-ssr/introduction
上阅读有关 Quasar 的 SSR 开发的更多信息
在quasar.dev/quasar-cli/developing-cordova-apps/introduction
上阅读有关 Quasar 的移动应用开发的更多信息
在cordova.apache.org
上阅读有关 Cordova 项目的更多信息
在quasar.dev/quasar-cli/developing-electron-apps/introduction
上阅读有关 Quasar 的桌面应用程序开发的更多信息
在electronjs.org/
上阅读有关 Electron 项目的更多信息
在github.com/electron/electron-packager
上阅读有关electron-packager
的更多信息
在electron.github.io/electron-packager/master/interfaces/electronpackager.options.html
找到electron-packager
选项 API。
在www.electron.build/
了解更多关于electron-build
的信息。
在www.electron.build/configuration/configuration
找到electron-build
选项 API。
创建更智能的 Vue 观察者和计算属性
在 Vue 中,使用观察者和计算属性总是一个很好的解决方案,可以检查和缓存您的数据,但有时这些数据需要一些特殊处理,或者需要与预期不同的方式进行操作。有一些方法可以赋予这些 Vue API 新的生命,帮助您的开发和生产力。
如何做...
我们将把这个配方分为两类:一个是观察者,另一个是计算属性。有些方法通常一起使用,比如non-cached
计算和deep-watched
值。
观察者
选择这三个观察者配方是为了提高生产力和最终代码质量。使用这些方法可以减少代码重复,提高代码重用。
使用方法名
所有观察者都可以接收方法名而不是函数,从而避免编写重复的代码。这将帮助您避免重写相同的代码,或者检查值并将其传递给函数:
<script>
export default {
watch: {
myField: 'myFunction',
},
data: () => ({
myField: '',
}),
methods: {
myFunction() {
console.log('Watcher using method name.');
},
},
};
</script>
立即调用和深度监听
通过传递一个属性立即执行您的观察者,并通过调用deep
属性使其无论值的变化深度如何都执行:
<script>
export default {
watch: {
myDeepField: {
handler(newVal, oldVal) {
console.log('Using Immediate Call, and Deep Watch');
console.log('New Value', newVal);
console.log('Old Value', oldVal);
},
deep: true,
immediate: true,
},
},
data: () => ({
myDeepField: '',
}),
};
</script>
多个处理程序
您可以使您的观察者同时执行多个处理程序,而无需将观察处理程序设置为绑定到唯一的函数:
<script>
export default {
watch: {
myMultiField: [
'myFunction',
{
handler(newVal, oldVal) {
console.log('Using Immediate Call, and Deep Watch');
console.log('New Value', newVal);
console.log('Old Value', oldVal);
},
immediate: true,
},
],
},
data: () => ({
myMultiField: '',
}),
methods: {
myFunction() {
console.log('Watcher Using Method Name');
},
},
};
</script>
计算属性
有时计算属性只是作为简单的基于缓存的值使用,但它们有更大的潜力。以下是两种显示如何提取此功能的方法。
无缓存值
通过将cache
属性设置为false
,您可以使计算属性始终更新值,而不是缓存值:
<script>
export default {
computed: {
field: {
get() {
return Date.now();
},
cache: false,
},
},
};
</script>
获取器和设置器
您可以向计算属性添加一个 setter 函数,并使其成为一个完全完整的数据属性,但不绑定到数据。
不建议这样做,但是可能,在某些情况下,你可能需要这样做。一个例子是当你需要以毫秒保存日期,但需要以 ISO 格式显示它。使用这种方法,你可以让dateIso
属性get
和set
值:
<script>
export default {
data: () => ({
dateMs: '',
}),
computed: {
dateIso: {
get() {
return new Date(this.dateMs).toISOString();
},
set(v) {
this.dateMs = new Date(v).getTime();
},
},
},
};
</script>
另请参阅
您可以在vuejs.org/v2/api/#watch
找到有关 Vue watch
API 的更多信息。
您可以在vuejs.org/v2/api/#computed
找到有关 Vue computed
API 的更多信息。
使用 Python Flask 作为 API 创建 Nuxt.js SSR
Nuxt.js
是一个服务器端渲染框架,可以在服务器端渲染所有内容并将其加载。通过这个过程,页面获得了 SEO 的力量和在渲染之前快速的 API 获取。
正确使用它,您可以实现一个功能强大的 SPA 或 PWA,以前是不可能的。
在后端,Python 是一种解释性动态语言,速度快,稳定。具有活跃的用户群和快速的学习曲线,非常适合服务器 API。
将两者结合起来,可以尽快部署一个强大的应用程序。
准备工作
这个配方的先决条件如下:
Node.js 12+
Python
所需的 Node.js 全局对象如下:
create-nuxt-app
要安装create-nuxt-app
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> npm install -g create-nuxt-app
对于这个配方的后端,我们将使用Python。这个配方所需的 Python 全局对象如下:
flask
flask-restful
flask-cors
要安装flask
,flask-restful
和flask-cors
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:
> **pip** install **flask**
> **pip install flask-restful**
> **pip install flask-cors**
如何做...
我们需要将我们的配方分成两部分。第一部分是后端部分(或者如果你喜欢的话是 API),将使用 Python 和 Flask 完成。第二部分将是前端部分,它将在 SSR 模式下运行Nuxt.js
。
创建您的 Flask API
我们的 API 服务器将基于 Python Flask 框架。我们需要创建一个服务器文件夹来存储我们的服务器文件并开始服务器的开发。
您将需要安装以下 Python 软件包。要这样做,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
- 要安装 Flask 框架,请使用以下命令:
> **pip** install flask
- 要安装 Flask RESTful 扩展,使用以下命令:
> **pip install flask-restful**
- 要安装 Flask CORS 扩展,使用以下命令:
> **pip install flask-cors**
初始化应用程序
为了创建我们的简单 RESTful API,我们将创建一个单一的文件,并使用 SQLite3 作为数据库:
- 创建一个名为
server
的文件夹,并在其中创建一个名为app.py
的文件:
import sqlite3 as sql
from flask import Flask
from flask_restful import Resource, Api, reqparse
from flask_cors import CORS
app = Flask(__name__)
api = Api(app)
CORS(app)
parser = reqparse.RequestParser()
conn = sql.connect('tasks.db')
conn.execute('CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY
KEY AUTOINCREMENT, task TEXT)')
conn.close()
- 然后,我们将创建我们的
ToDo
类,并在类的构造函数中连接到数据库并选择所有的tasks
:
class ToDo(Resource):
def get(self):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('SELECT * from tasks')
tasks = cur.fetchall()
con.close()
return {
'tasks': tasks
}
- 要实现 RESTful POST 方法,创建一个函数来接收
task
作为参数,并将带有添加的task
、添加的status
的对象添加到任务列表中,然后将列表返回给用户:
def post(self):
parser.add_argument('task', type=str)
args = parser.parse_args()
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('INSERT INTO tasks(task) values ("
{}")'.format(args['task']))
con.commit()
con.close()
return {
'status': True,
'task': '{} added.'.format(args['task'])
}
- 接下来,我们将创建 RESTful PUT 方法,通过创建一个函数来接收
task
和id
作为函数的参数。然后,这个函数将使用当前的id
更新task
,并将更新后的task
和更新的status
返回给用户:
def put(self, id):
parser.add_argument('task', type=str)
args = parser.parse_args()
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('UPDATE tasks set task = "{}" WHERE id =
{}'.format(args['task'], id))
con.commit()
con.close()
return {
'id': id,
'status': True,
'task': 'The task {} was updated.'.format(id)
}
- 然后,创建一个 RESTful DELETE 方法,通过创建一个函数来接收将被移除的
task
的ID
,然后将返回被移除的ID
、status
和task
给用户:
def delete(self, id):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('DELETE FROM tasks WHERE id = {}'.format(id))
con.commit()
con.close()
return {
'id': id,
'status': True,
'task': 'The task {} was deleted.'.format(id)
}
- 最后,我们将在
'/'
路由上将ToDo
类作为 API 的资源,并初始化应用程序:
api.add_resource(ToDo, '/', '/<int:id>')
if __name__ == '__main__':
app.run(debug=True)
启动服务器
要启动服务器,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> python server/app.py
您的服务器将在http://localhost:5000
上运行并监听。
创建您的 Nuxt.js 服务器
要渲染您的应用程序,您需要创建您的Nuxt.js
应用程序。使用Nuxt.js
的create-nuxt-app
CLI,我们将创建它并为其选择一些选项。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> create-nuxt-app client
然后,您将被问及安装过程的一些问题。我们将使用以下内容:
- 当您使用
Nuxt-CLI
开始创建项目时,它将首先要求项目名称。在我们的情况下,我们将选择client
作为名称:
**Project Name:** **client**
- 然后,您需要选择将在项目中使用的编程语言。我们将选择
JavaScript
:
> Programming language: (Use arrow keys)
❯ JavaScript
TypeScript
- 接下来,
Nuxt-CLI
将要求选择将用于安装依赖项的软件包管理器。在我们的情况下,我们选择Yarn
,但您可以选择您喜欢的软件包管理器:
> Package manager: (Use arrow keys)
❯ Yarn
npm
- 现在,
Nuxt-CLI
将要求选择在项目中使用的 UI 框架。从可用列表中选择Bulma
:
**> UI Framework:** **Bulma**
- 然后,
Nuxt-CLI
将询问您是否要为项目选择额外的模块。我们将从当前模块列表中选择 Axios
:
**> Nuxt.JS modules:** **Axios**
Nuxt-CLI
将询问我们想要在项目上使用的 linting 工具;我们将选择 None
:
**> Choose Linting tools:** **None**
- 然后,
Nuxt-CLI
将询问我们想要在项目上实现的测试框架;我们将选择 None
:
**> Choose Test Framework: None**
- 接下来,
Nuxt-CLI
将询问项目将使用的渲染模式;我们将选择 Universal (SSR)
:
**> Choose Rendering Mode: Universal (SSR)**
Nuxt-CLI
将询问将在构建结构上使用的部署目标;我们将选择 Server (Node.js hosting)
:
> Deployment target: Server (Node.js hosting)
- 最后,
Nuxt-CLI
将询问我们想要使用的开发工具配置;我们将选择 jsconfig.json
:
> Development tools: jsconfig.json
在 CLI 完成安装过程后,我们可以在编辑器或 IDE 中打开 client
文件夹。
将 Bulma 添加到全局 CSS
要将 Bulma 添加到应用程序中,我们需要在 nuxt
配置文件中声明它,方法如下:
在 client
文件夹中打开 nuxt.config.js
。
然后,更新 CSS 属性并添加 Bulma 导入,以使其在应用程序的全局范围内可用:
export default {
/* We need to change only the css property for now, */
/* the rest we will maitain the same */
/*
** Global CSS
*/
css: ['bulma/css/bulma.css'],
}
配置 axios 插件
要开始创建我们的 API 调用,我们需要在应用程序中添加 axios
插件:
- 为此,我们需要打开根文件夹中的
nuxt.config.js
文件,并添加 axios
属性:
export default {
/* We need to change only the axios property for now, */
/* the rest we will maitain the same */
axios: {},
}
- 在
axios
属性上,添加以下配置属性:
HOST
并将其定义为 '127.0.0.1'
PORT
并将其定义为 '5000'
https
并将其定义为 false
debug
并将其定义为 true
:
axios: {
HOST: '127.0.0.1',
PORT: '5000',
https: false,
debug: true, // Only on development
},
运行 Nuxt.js 服务器
现在您已经设置好了一切,想要运行服务器并开始查看发生了什么。Nuxt.js
自带一些预先编程的 npm
脚本。您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行其中之一:
npm run dev
- 以开发模式运行服务器
npm run build
- 使用 webpack 构建文件并对生产环境进行 CSS 和 JS 的最小化
npm run generate
- 为每个路由生成静态 HTML 页面
npm start
- 在运行构建命令后,启动生产服务器
创建 TodoList 组件
对于 TodoList 应用程序,我们将需要一个组件来获取任务并删除任务。
单文件组件的 <script>
部分
在这里,我们将创建单文件组件的 <script>
部分:
在client/components
文件夹中,创建一个名为TodoList.vue
的文件并打开它。
然后,我们将导出一个default
的 JavaScript 对象,其中name
属性定义为TodoList
,然后将beforeMount
生命周期钩子定义为一个异步函数。将computed
和methods
属性定义为一个空的 JavaScript 对象。然后,创建一个data
属性,定义为返回一个 JavaScript 对象的单例函数。在data
属性中,创建一个taskList
属性,定义为一个空数组:
export default {
name: 'TodoList',
data: () => ({
taskList: [],
}),
computed: {},
async beforeMount() {},
methods: {},
};
- 在
computed
属性中,创建一个名为taskObject
的新属性。这个computed
属性将返回Object.fromEntries(new Map(this.taskList))
的结果:
taskObject() {
return Object.fromEntries(new Map(this.taskList));
},
- 在
methods
属性中,创建一个名为getTask
的新方法 - 它将是一个异步函数。这个方法将从服务器获取任务,然后将使用响应来定义taskList
属性:
async getTasks() {
try {
const { tasks } = await
this.$axios.$get('http://localhost:5000');
this.taskList = tasks;
} catch (err) {
console.error(err);
}
},
- 然后,创建一个
deleteTask
方法。这个方法将是一个异步函数,并将接收一个id
作为参数。使用这个参数,它将执行一个 API 执行来删除任务,然后执行getTask
方法:
async deleteTask(i) {
try {
const { status } = await
this.$axios.$delete(`http://localhost:5000/${i}`);
if (status) {
await this.getTasks();
}
} catch (err) {
console.error(err);
}
},
- 最后,在
beforeMount
生命周期钩子中,我们将执行getTask
方法:
async beforeMount() {
await this.getTasks();
},
单文件组件部分
现在是创建单文件组件的<template>
部分的时候了:
在client/components
文件夹中,打开TodoList.vue
文件。
在<template>
部分,创建一个div
HTML 元素,并添加值为box
的class
属性:
<div class="box"></div>
- 作为
div.box
HTML 元素的子元素,创建一个div
HTML 元素,class
属性定义为content
,具有一个子元素定义为ol
HTML 元素,属性type
定义为1
:
<div class="content">
<ol type="1"></ol>
</div>
- 作为
ol
HTML 元素的子元素,创建一个li
HTML 元素,其中v-for
指令定义为(task, i) in taskObject
,key
属性定义为一个变量i
:
<li
v-for="(task, i) in taskObject"
:key="i">
</li>
- 最后,作为
ol
HTML 元素的子元素,将{{ task }}
添加为内部文本,并作为文本的兄弟元素,创建一个button
HTML 元素,class
属性定义为delete is-small
,@click
事件监听器定义为deleteTask
方法,传递i
变量作为参数:
{{ task }}
<button
class="delete is-small"
@click="deleteTask(i)"
/>
创建 Todo 表单组件
将任务发送到服务器,我们需要一个表单。这意味着我们需要创建一个表单组件来处理这个任务。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在client/components
文件夹中,创建一个名为TodoForm.vue
的文件并打开它。
然后,我们将导出一个default
JavaScript 对象,其中name
属性定义为TodoForm
,然后将methods
属性定义为空的 JavaScript 对象。然后,创建一个data
属性,定义为返回 JavaScript 对象的单例函数。在data
属性中,创建一个task
属性作为空数组:
export default {
name: 'TodoForm',
data: () => ({
task: '',
}),
methods: {},
};
- 在
methods
属性中,创建一个名为save
的方法,这将是一个异步函数。这个方法将task
发送到 API,如果 API 接收到Ok 状态
,它将发出一个带有task
的'new-task'
事件,并清除task
属性:
async save() {
try {
const { status } = await
this.$axios.$post('http://localhost:5000/', {
task: this.task,
});
if (status) {
this.$emit('new-task', this.task);
this.task = '';
}
} catch (err) {
console.error(err);
}
},
单文件组件部分
现在是时候创建单文件组件的<template>
部分了:
在client/components
文件夹中,打开名为TodoForm.vue
的文件。
在<template>
部分中,创建一个div
HTML 元素,并添加class
属性,值为box
:
<div class="box"></div>
- 在
div.box
HTML 元素内部,创建一个div
HTML 元素,class
属性定义为field has-addons
:
<div class="field has-addons"></div>
- 然后,在
div.field.has-addons
HTML 元素内部,创建一个子div
HTML 元素,class
属性定义为control is-expanded
,并添加一个子输入 HTML 元素,v-model
指令定义为task
属性。然后,将class
属性定义为input
,type
属性定义为text
,placeholder
定义为ToDo Task
。最后,在@keypress.enter
事件监听器中,定义save
方法:
<div class="control is-expanded">
<input
v-model="task"
class="input"
type="text"
placeholder="ToDo Task"
@keypress.enter="save"
>
</div>
- 最后,在
div.control.is-expanded
HTML 元素的同级位置,创建一个div
HTML 元素,class
属性定义为control
,并添加一个子a
HTML 元素,class
属性定义为button is-info
,在@click
事件监听器中,将其定义为save
方法。在a
HTML 元素的内部文本中,添加Save Task
文本:
<div class="control">
<a
class="button is-info"
@click="save"
>
Save Task
</a>
</div>
创建布局
现在我们需要创建一个新的布局来包装应用程序作为一个简单的高阶组件。在client/layouts
文件夹中,打开名为default.vue
的文件,删除文件的<style>
部分,并将<template>
部分更改为以下内容:
<template>
<nuxt />
</template>
创建页面
现在我们将创建我们应用程序的主页面,用户将能够查看他们的TodoList
并添加一个新的TodoItem
。
单文件组件<script>部分
在这里,我们将创建单文件组件的<script>
部分:
在client/pages
文件夹中打开index.vue
文件。
导入我们创建的todo-form
和todo-list
组件,然后我们将导出一个带有components
属性的default
JavaScript 对象,其中包含导入的组件。
<script>
import TodoForm from '../components/TodoForm.vue';
import TodoList from '../components/TodoList.vue';
export default {
components: { TodoForm, TodoList },
};
</script>
单文件组件部分
现在是创建单文件组件的<template>
部分的时候了:
在client/pages
文件夹中,打开index.vue
文件。
在<template>
部分,创建一个div
HTML 元素,作为子元素添加一个class
属性定义为hero is-primary
的section
HTML 元素。然后,作为section
HTML 元素的子元素,创建一个class
属性定义为hero-body
的div
HTML 元素。作为div.hero-body
HTML 元素的子元素,创建一个class
属性定义为container
的div
HTML 元素,并作为子元素添加一个class
定义为title
的h1
HTML 元素,其中内部文本为Todo App
。
<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<h1 class="title">
Todo App
</h1>
</div>
</div> </section>
- 作为
section.hero.is-primary
HTML 元素的兄弟元素,创建一个section
HTML 元素,其中class
属性定义为section
,style
属性定义为padding: 1rem
。作为子元素添加一个class
属性定义为container
的div
HTML 元素,其中包含一个ref
属性定义为list
的todo-list
组件。
<section
class="section"
style="padding: 1rem" >
<div class="container">
<todo-list
ref="list"
/>
</div> </section>
- 最后,作为
section.section
HTML 元素的兄弟元素,创建一个section
HTML 元素,其中class
属性定义为section
,style
属性定义为padding: 1rem
。作为子元素添加一个class
属性定义为container
的div
HTML 元素,其中包含一个todo-form
组件,其中@new-task
事件监听器定义为$refs.list.getTasks()
。
<section
class="section"
style="padding: 1rem" >
<div class="container">
<todo-form
@new-task="$refs.list.getTasks()" />
</div> </section>
工作原理...
此示例显示了通过 Python 的本地 API 服务器与通过Nuxt.js
提供的 SSR 平台之间的集成。
当您首先启动 Python 服务器时,您正在打开端口以接收来自客户端的数据作为被动客户端,只是等待某些事件发生以启动您的代码。通过相同的过程,Nuxt.js
SSR 可以在幕后执行很多操作,但完成后会变得空闲,等待用户操作。
当用户与前端交互时,应用程序可以向服务器发送一些请求,服务器将把数据返回给用户,以在屏幕上显示。
另请参阅
您可以在palletsprojects.com/p/flask/.
了解有关 Flask 和 Python 内部的 HTTP 项目的更多信息。
如果您想了解更多关于Nuxt.js
的信息,可以在nuxtjs.org/guide/.
阅读文档。
如果您想了解Nuxt.js
对 Axios 的实现以及如何配置它和使用插件,可以在axios.nuxtjs.org/options.
阅读文档。
如果您想了解在本教程中使用的 CSS 框架 Bulma,可以在bulma.io.
找到更多信息。
Vue 应用程序的 Dos 和 Don'ts
安全性始终是每个人都担心的事情,对于技术也是如此。您需要时刻保持警惕。在本节中,我们将看看如何使用一些技术和简单的解决方案来防止攻击。
检查器
在使用 ESLint 时,请确保已启用 Vue 插件,并且遵循强烈推荐的规则。这些规则将帮助您进行开发,检查一些常见的错误,这些错误可能会打开攻击的大门,比如v-html
指令。
在Vue-CLI
项目中,选择了检查器选项后,将会创建一个名为.eslintrc.js
的文件,以及项目文件。在这个文件中,一组基本规则将被预先确定。以下是一个ESLint + AirBnb
项目的一组良好实践规则的示例:
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'plugin:vue/recommended',
'plugin:vue/strongly-recommended',
'@vue/airbnb',
],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
};
现在,如果您有任何违反检查规则的代码,它将不会在开发或构建中被解析。
JavaScript
JavaScript 具有一些漏洞,可以通过遵循一些简单的清单和简单的实现来预防。这些实现可以是客户端-服务器通信或 DOM 操作,但您始终需要小心不要忘记它们。
以下是一些使用 JavaScript 的技巧:
尽可能使用经过身份验证和加密的 API。请记住,JWT 本身并不是加密的;您需要添加加密层(JWE)才能拥有完整的 JSON。
如果您想存储 API 令牌,请始终使用SessionStorage
。
在将用户输入的 HTML 发送到服务器之前,始终对其进行消毒。
在将 HTML 呈现到 DOM 之前,始终对其进行消毒。
永远要对用户输入的RegeExp
进行转义;它将被执行,以防止任何 CPU 线程攻击。
始终捕获错误,并且不要向用户显示任何堆栈跟踪,以防止任何代码操纵。
以下是在使用 JavaScript 时不要做的一些提示:
永远不要使用eval()
;它会使您的代码运行缓慢,并为恶意代码在您的代码内执行打开一扇门。
永远不要呈现来自用户的任何输入而没有经过消毒或转义的数据。
永远不要在 DOM 上呈现任何未经过消毒的 HTML。
永远不要将 API 令牌存储在LocalStorage
中。
永远不要在 JWT 对象中存储敏感数据。
Vue
在开发 Vue 应用程序时,您需要检查一些基本规则,这些规则可以帮助开发,并且不会为外部操纵您的应用程序打开任何大门。
以下是一些使用 Vue 的提示:
始终为您的 props 添加类型验证,并在可能的情况下进行验证检查。
避免全局注册组件;使用本地组件。
尽可能使用延迟加载的组件。
使用$refs
而不是直接 DOM 操作。
以下是在使用 Vue 时不要做的一些提示:
永远不要在窗口或任何全局范围上存储Vue
,$vm
,$store
或任何应用程序变量。
永远不要修改 Vue 原型;如果需要向原型添加新变量,请创建一个新的 Vue 插件。
不建议直接在组件之间建立连接,因为这将使组件绑定到父级或子级。
另请参阅
您可以在 OWASP CheatCheat 的github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md
和html5sec.org/
找到有关 XSS(跨站点脚本)的更多信息。
在eslint.vuejs.org/
找到有关eslint-vue-plugin
的更多信息。
您可以在github.com/i0natan/nodebestpractices#6-security-best-practices
了解有关 Node.js 安全最佳实践的更多信息。
在quasar.dev/security/dos-and-donts
找到有关 Vue 应用程序的 dos 和 don'ts 的更多信息。
</style>
<template>
部分。按照这些说明正确创建组件:style
属性定义为margin: 20px
的vs-card
组件:<vs-card style="margin: 20px;"
>
</vs-card>
vs-card
组件内部,为header
创建一个动态的<template>
,其中包含一个<h3>
标签和您的标题:<template slot="header">
<h3>
Users
</h3> </template>
vs-row
组件,其中包含一个vs-col
组件,具有以下属性:vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
vs-col
组件内部,我们需要创建一个vs-table
组件。该组件将具有指向userList
变量的data
属性,并将search
,stripe
和pagination
属性定义为 true。max-items
属性将被定义为10
,style
属性将具有width: 100%; padding: 20px;
的值:<vs-table
:data="userList"
search
stripe pagination max-items="10"
style="width: 100%; padding: 20px;" ></vs-table>
thead
的动态 <template>
,并为每一列创建一个带有 sort-key
属性定义为相应对象键属性和显示为您想要的名称的 vs-th
组件:<template slot="thead">
<vs-th sort-key="id">
#
</vs-th>
<vs-th sort-key="name">
Name
</vs-th>
<vs-th sort-key="email">
Email
</vs-th>
<vs-th sort-key="country">
Country
</vs-th>
<vs-th sort-key="phone">
Phone
</vs-th>
<vs-th sort-key="Birthday">
Birthday
</vs-th>
<vs-th>
Actions
</vs-th> </template>
<template>
,其中定义了一个 slot-scope
属性作为 data
属性。在这个 <template>
中,我们需要创建一个 vs-tr
组件,它将迭代数据属性,并为表格头部设置的每一列创建一个 vs-td
组件。每个 vs-td
组件都有一个设置为相应列数据对象属性的数据属性,并且内容将是相同的数据渲染。最后一列是操作列,将有三个按钮,一个用于读取,另一个用于更新,最后一个用于删除。读取按钮将在 "click"
事件上有一个指向 changeComponent
的事件监听器,更新按钮也是如此。删除按钮的 "click"
事件监听器将指向 deleteUser
方法:<template slot-scope="{data}">
<vs-tr :key="index" v-for="(tr, index) in data">
<vs-td :data="data[index].id">
{{data[index].id}}
</vs-td>
<vs-td :data="data[index].name">
{{data[index].name}}
</vs-td>
<vs-td :data="data[index].email">
<a :href="`mailto:${data[index].email}`">
{{data[index].email}}
</a>
</vs-td>
<vs-td :data="data[index].country">
{{data[index].country}}
</vs-td>
<vs-td :data="data[index].phone">
{{data[index].phone}}
</vs-td>
<vs-td :data="data[index].birthday">
{{data[index].birthday}}
</vs-td>
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeComponent('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeComponent('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/>
</vs-td>
</vs-tr> </template>
footer
的动态 <template>
。在这个 <template>
中,我们将添加一个带有 vs-justify
属性定义为 flex-start
的 vs-row
组件,并插入一个带有 color
属性定义为 primary
、type
属性定义为 filled
、icon
属性定义为 fiber_new
和 size
属性定义为 small
的 vs-button
。@click
事件监听器将以参数 'create'
和 0
目标 changeComponent
方法:<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="fiber_new"
size="small"
@click="changeComponent('create', 0)"
>
Create User
</vs-button>
</vs-row> </template>
<style>
部分。按照以下说明正确创建组件:vs-button
组件类创建一个边距声明:<style scoped>
.vs-button {
margin-left: 5px;
} </style>
> npm run serve
<script>
部分。按照以下说明正确创建组件:在src/components
文件夹中创建一个名为userForm.vue
的新文件并打开它。
在 Vue 的props
属性中,创建两个名为value
和disabled
的新属性,都是对象,并具有type
、required
和default
三个属性。对于value
属性,type
将是Object
,required
将是false
,default
将是返回一个对象的工厂。对于disabled
属性,type
将是Boolean
,required
将是false
,default
也将是false
:
props: {
value: {
type: Object,
required: false,
default: () => {
},
},
disabled: {
type: Boolean,
required: false,
default: false,
} },
data
属性中,我们需要添加一个名为tmpForm
的新值,其默认值为一个空对象:data: () => ({
tmpForm: {}, }),
watch
属性中,我们需要为tmpForm
和value
创建处理程序。对于tmpForm
watcher,我们将添加一个handler
函数,它将在每次更改时发出一个'input'
事件,带有新的value
,并将deep
属性添加为true
。最后,在value
watcher 中,我们将添加一个handler
函数,它将将tmpForm
的值设置为新的value
。我们还需要将deep
和immediate
属性定义为true
:watch: {
tmpForm: {
handler(value) {
this.$emit('input', value);
},
deep: true,
},
value: {
handler(value) {
this.tmpForm = value;
},
deep: true,
immediate: true,
} },
deep
属性使 watcher 检查数组或对象的深层更改,而immediate
属性在组件创建时立即执行 watcher。<template>
部分<template>
部分。按照这些说明正确创建组件:vs-row
组件。在vs-row
组件内部,我们将为我们的用户表单创建每个输入:<vs-row></vs-row>
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.name
,disabled
属性绑定到disabled
props,label
定义为'Name'
,placeholder
定义为'User Name'
,class
定义为'inputMargin full-width'
:<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.name"
:disabled="disabled"
label="Name"
placeholder="User Name"
class="inputMargin full-width"
/> </vs-col>
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.email
,disabled
属性绑定到disabled
属性,label
定义为'Email'
,placeholder
定义为'User Email'
,class
定义为'inputMargin full-width'
:<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.email"
:disabled="disabled"
label="Email"
placeholder="User Email"
class="inputMargin full-width"
/> </vs-col>
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.country
,disabled
属性绑定到disabled
属性,label
定义为'Country'
,placeholder
定义为'User Country'
,class
定义为'inputMargin full-width'
:<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.country"
:disabled="disabled"
label="Country"
placeholder="User Country"
class="inputMargin full-width"
/> </vs-col>
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.phone
,disabled
属性绑定到disabled
属性,label
定义为'Phone'
,placeholder
定义为'User Phone'
,class
定义为'inputMargin full-width'
:<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.phone"
:disabled="disabled"
label="Phone"
placeholder="User Phone"
class="inputMargin full-width"
/> </vs-col>
vs-col
组件,其中vs-type
属性定义为'flex'
,vs-justify
属性定义为'left'
,vs-align
属性定义为'left'
,vs-w
属性定义为'6'
。在vs-col
组件内部,我们需要创建一个vs-input
组件,其中v-model
指令绑定到tmpForm.birthday
,disabled
属性绑定到disabled
属性,label
定义为'Birthday'
,placeholder
定义为'User Birthday'
,class
定义为'inputMargin full-width'
:<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="6">
<vs-input
v-model="tmpForm.birthday"
:disabled="disabled"
label="Birthday"
placeholder="User Birthday"
class="inputMargin full-width"
/> </vs-col>
<style>
部分<style>
部分。按照以下说明正确创建组件:inputMargin
的新的作用域类,其中margin
属性定义为15px
:<style>
.inputMargin {
margin: 15px;
} </style>
View
、Create
和Update
组件之间共享。<script>
部分。按照这些说明正确创建组件:在src/components
文件夹中创建一个名为create.vue
的新文件并打开它。
从fetchApi
中导入UserForm
组件、changeComponent
混合和postHttp
:
import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent'; import { postHttp } from '../http/fetchApi';
data
属性中,我们将添加一个userData
对象,其中name
、email
、birthday
、country
和phone
属性都定义为空字符串:data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
mixins
属性中,我们需要添加changeComponent
:mixins: [changeComponent],
components
属性中,添加UserForm
组件:components: {
UserForm, },
methods
属性中,我们需要创建createUser
方法,该方法将使用userData
属性上的数据,并在服务器上创建一个新用户,然后将用户重定向到用户列表:methods: {
async createUser() {
await postHttp(`${window.location.href}api/users`, {
data: {
...this.userData,
}
});
this.changeComponent('list', 0);
}, },
<template>
部分。按照这些说明正确创建组件:vs-card
组件,其中style
属性定义为margin: 20px
:<vs-card
style="margin: 20px;"
>
</vs-card>
vs-card
组件内部,为header
创建一个动态的<template>
插槽,其中包含一个<h3>
标签和您的标题:<template slot="header">
<h3>
Create User
</h3> </template>
vs-row
组件,其中包含一个vs-col
组件,其属性为vs-type
定义为flex
,vs-justify
定义为left
,vs-align
定义为left
,vs-w
定义为12
:<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
vs-col
组件内部,我们将添加user-form
组件,并将v-model
指令绑定到userData
上:<user-form
v-model="userData" />
footer
创建一个动态的<template>
插槽。在这个<template>
中,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个将用于创建用户,其属性为color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
。@click
事件监听器将针对createUser
方法,第二个vs-button
组件将用于取消此操作并返回到用户列表。它的属性为color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
,style
定义为margin-left: 5px
,@click
事件监听器将目标设置为changeComponent
方法,参数为'list'
和0
:<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="save"
size="small"
@click="createUser"
>
Create User
</vs-button>
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Cancel
</vs-button>
</vs-row> </template>
> npm run serve
<script>
部分<script>
部分。按照以下说明正确创建组件:在src/components
文件夹中创建名为view.vue
的文件并打开它。
导入UserForm
组件,changeComponent
mixin 和fetchApi
中的getHttp
:
import {
getHttp, } from '../http/fetchApi'; import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent';
data
属性中,我们将添加一个userData
对象,其中name
,email
,birthday
,country
和phone
属性都定义为空字符串:data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
mixins
属性中,我们需要添加changeComponent
mixin:mixins: [changeComponent],
inject
属性中,我们需要声明'userId'
属性:inject: ['userId'],
components
属性中,添加UserForm
组件:components: {
UserForm, },
getUserById
方法。此方法将通过当前 ID 获取用户数据,并将userData
值设置为getHttp
函数执行的响应:methods: {
async getUserById() {
const { data } = await getHttp(`${window.location.href}api/users/${this.userId}`);
this.userData = data;
}, }
beforeMount
生命周期钩子中,我们将使其异步,调用getUserById
方法:async beforeMount() {
await this.getUserById(); },
<template>
部分<template>
部分。按照以下说明正确创建组件:style
属性定义为margin: 20px
的vs-card
组件:<vs-card
style="margin: 20px;"
>
</vs-card>
vs-card
组件内,为header
创建一个动态的<template>
,并添加一个<h3>
标签和您的标题:<template slot="header">
<h3>
View User
</h3> </template>
vs-col
组件的vs-row
组件,其中vs-type
属性定义为flex
,vs-justify
属性定义为left
,vs-align
属性定义为left
,vs-w
属性定义为12
:<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
vs-col
组件内,我们将添加UserForm
组件,并将v-model
指令绑定到userData
,并将disabled
属性设置为true
:<user-form
v-model="userData"
disabled />
footer
创建一个动态的<template>
。在这个<template>
中,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个是用于取消此操作并返回到用户列表的。它将具有color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
的属性,以及@click
事件监听器目标为changeComponent
方法,参数为'list'
和0
。第二个vs-button
组件将用于编辑用户,并具有color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
,style
定义为margin-left: 5px
,以及@click
事件监听器目标为changeComponent
方法,参数为'list'
和注入的userId
:<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="arrow_back"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Back
</vs-button>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
style="margin-left: 5px"
@click="changeComponent('edit', userId)"
>
Edit User
</vs-button>
</vs-row> </template>
> npm run serve
<script>
部分<script>
部分。按照以下说明正确创建组件:在src/components
文件夹中创建名为update.vue
的文件并打开它。
从fetchApi
中导入UserForm
组件、changeComponent
mixin 以及getHttp
和patchHttp
函数:
import UserForm from './userForm'; import changeComponent from '../mixin/changeComponent'; import {
getHttp,
patchHttp, } from '../http/fetchApi';
data
属性中,我们将添加一个userData
对象,其中包含name
、email
、birthday
、country
和phone
属性,全部定义为空字符串:data: () => ({
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
mixins
属性中,我们需要添加changeComponent
mixin:mixins: [changeComponent],
inject
属性中,我们需要声明'userId'
属性:inject: ['userId'],
components
属性中,添加UserForm
组件:components: {
UserForm, },
getUserById
和updateUser
。getUserById
方法将通过当前 ID 获取用户数据,并将userData
值设置为getHttp
函数执行的响应,而updateUser
将通过patchHttp
函数将当前userDate
发送到服务器,并重定向回用户列表:methods: {
async getUserById() {
const { data } = await
getHttp(`${window.location.href}api/users/${this.userId}`);
this.userData = data;
},
async updateUser() {
await patchHttp
(`${window.location.href}api/users/${this.userData.id}`, {
data: {
...this.userData,
}
});
this.changeComponent('list', 0);
}, },
beforeMount
生命周期钩子上,我们将使其异步化,调用getUserById
方法:async beforeMount() { await this.getUserById(); },
<template>
部分<template>
部分。按照以下说明正确创建组件:style
属性定义为margin: 20px
的vs-card
组件:<vs-card
style="margin: 20px;"
>
</vs-card>
vs-card
组件内部,为header
创建一个动态的<template>
命名插槽,其中包含一个<h3>
标签和您的标题:<template slot="header">
<h3>
Update User
</h3> </template>
vs-col
组件的vs-row
组件,其中vs-type
属性定义为flex
,vs-justify
属性定义为left
,vs-align
属性定义为left
,vs-w
属性定义为12
:<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
</vs-col>
</vs-row>
vs-col
组件内部,我们将添加UserForm
组件,其中v-model
指令绑定到userData
,并将disabled
属性设置为true
:<user-form
v-model="userData"
disabled />
footer
创建一个动态的<template>
命名插槽。在<template>
内,我们将添加一个带有vs-justify
属性定义为flex-start
的vs-row
组件,并插入两个vs-button
组件。第一个将用于创建用户,并具有color
定义为success
,type
定义为filled
,icon
定义为save
,size
定义为small
的属性,并且@click
事件监听器指向updateUser
方法。第二个vs-button
组件将用于取消此操作并返回到用户列表。它将具有color
定义为danger
,type
定义为filled
,icon
定义为cancel
,size
定义为small
,style
定义为margin-left: 5px
的属性,并且@click
事件监听器指向changeComponent
方法,参数为'list'
和0
:<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="save"
size="small"
@click="updateUser"
>
Update User
</vs-button>
<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeComponent('list', 0)"
>
Cancel
</vs-button>
</vs-row> </template>
> npm run serve
UserForm
组件,该组件用于View
和Update
组件。这个抽象组件可以在许多其他组件中使用,因为它不需要任何基本逻辑来工作;它就像一个由几个输入组成的输入框。userId
传递给每个组件,这意味着当变量更新时,组件会接收到更新后的变量。这是无法通过普通的 Vue API 实现的,因此我们必须使用Object.defineProperty
并使用provide
属性作为返回最终对象的工厂函数。lusaxweb.github.io/vuesax/
找到有关Vuesax
的更多信息。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
找到有关Object.defineProperty
的更多信息。vuejs.org/v2/guide/components-edge-cases.html
找到有关 Vue provide/inject API 的更多信息。vue-router
的定制化。您可以添加路由守卫来检查特定路由是否可由访问级别导航,或在进入路由之前获取数据以管理应用程序中的错误。创建一个简单的路由
创建一个程序化导航
创建一个动态路由路径
创建一个路由别名
创建一个路由重定向
创建一个嵌套路由视图
创建一个 404 错误页面
创建一个身份验证中间件
异步延迟加载您的页面
windows-build-tools
的 npm 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
> npm install -g @vue/cli @vue/cli-service-global
vue-router
是这个组合的维护者。我们需要使用它来设置如何创建路径并为我们的访问者建立路由的指令。@vue/cli
@vue/cli-service-global
> vue create initial-routes
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,Spacebar选择选项。
有两种方法可以启动新项目。默认方法是基本的 Babel 和 ESLint 项目,没有任何插件或配置,还有手动
模式,您可以选择更多模式、插件、代码检查器和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint**)** ❯ **Manually select features**
Babel
、Router
和Linter / Formatter
:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router
Vuex CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
Y
(是):? Use history mode for router? (Requires proper server setup for
index fallback in production) (Y**/n) y**
ESLint + Airbnb config
:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
package.json
中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
? Save this as a preset for future projects? (y/N) n
创建NavigationBar
组件
创建联系页面
创建关于页面
更改应用程序的主要组件
创建路由
NavigationBar
组件。在src/components
文件夹中创建一个navigationBar.vue
文件并打开它。
创建组件的默认export
对象,具有 Vue 属性name
:
<script> export default {
name: 'NavigationBar', }; </script>
id
属性定义为"nav"
的div
HTML 元素,并在其中创建三个RouterLink
组件。这些组件将指向Home
、About
和Contact
路由。在RouterLink
组件中,我们将添加一个to
属性,分别定义为每个组件的路由,并将文本内容定义为菜单的名称:<div id="nav">
<router-link to="/">
Home
</router-link> |
<router-link to="/about">
About
</router-link> |
<router-link to="/contact">
Contact
</router-link> </div>
/contact
URL 时,联系页面会被渲染。为此,我们需要创建一个单文件组件,用作联系页面。<script>
部分。按照以下说明正确创建组件:在src/views
文件夹中,创建一个名为contact.vue
的新文件并打开它。
创建组件的默认export
对象,其中包含 Vue 属性name
:
<script> export default {
name: 'ContactPage', }; </script>
<template>
部分。按照以下说明正确创建组件:创建一个div
HTML 元素,其中class
属性定义为"contact"
。
在<h1>
HTML 元素内部,添加一个显示当前页面的文本内容:
<template>
<div class="contact">
<h1>This is a contact page</h1>
</div> </template>
/about
URL 时,关于页面会被渲染。在接下来的小节中,我们将为关于页面创建单文件组件。<script>
部分。按照以下说明正确创建组件:在src/views
文件夹中,创建一个名为About.vue
的新文件并打开它。
创建组件的默认导出对象,其中包含 Vue 属性name
:
<script> export default {
name: 'AboutPage', }; </script>
<template>
部分。按照以下说明正确创建组件:创建一个div
HTML 元素,其中class
属性定义为"about"
。
在其中,放置一个带有显示当前页面文本内容的<h1>
元素:
<template>
<div class="about">
<h1>This is an about page</h1>
</div> </template>
<script>
部分。按照以下说明正确创建组件:在src
文件夹中打开App.vue
。
导入NavigationBar
组件:
import NavigationBar from './components/navigationBar.vue';
components
属性中,声明导入的NavigationBar
:export default {
components: { NavigationBar }, };
<template>
部分<template>
部分。在div
HTML 元素内,添加NavigationBar
组件和RouterView
组件:<template>
<div id="app">
<navigation-bar />
<router-view/>
</div> </template>
在src/router
文件夹中,打开index.js
文件。
导入Contact
组件页面:
import Vue from 'vue'; import VueRouter from 'vue-router'; import Home from '../views/Home.vue'; import Contact from '../views/contact.vue';
routes
数组中,我们需要创建一个新的route
对象。该对象将具有path
属性定义为'/contact'
,name
定义为'contact'
,并且component
指向导入的Contact
组件:{
path: '/contact',
name: 'contact',
component: Contact, },
> npm run serve
vue-router
添加到 Vue 作为插件时,它开始监视window.location.pathname
和其他 URL 属性的更改,以检查当前 URL 在浏览器上的权重与路由配置中的 URL 列表的匹配情况。vue-router
插件只需要检查 URL 路径的直接匹配,而不需要将可能的匹配与正则表达式验证器进行比较。router-view
组件充当动态组件,并呈现我们在vue-router
配置中定义的组件。router.vuejs.org/.
找到有关vue-router
的更多信息。cli.vuejs.org/.
找到有关 Vue CLI 的更多信息。vue-router
时,还可以通过函数执行来更改应用程序的当前路由,而无需特殊的vue-router
组件来创建链接。@vue/cli
@vue/cli-service-global
> vue create route-project
Router
作为所需功能添加,如“如何做...”部分和“创建简单路由”食谱中所示。更改应用程序的主要组件
更改联系视图
App.vue
文件开始。我们将添加一个在超时后执行的程序化导航函数,该函数将添加到组件生命周期钩子中。<script>
部分<script>
部分。按照以下说明正确创建组件:在src
文件夹中打开App.vue
。
添加一个mounted
属性:
mounted() {}
mounted
属性中,我们需要添加一个setTimeout
函数,该函数将执行$router.push
函数。当执行时,此函数将接收一个 JavaScript 对象作为参数,其中包含两个属性,name
和params
:mounted() {
setTimeout(() => {
this.$router.push({
name: 'contact',
params: {
name: 'Heitor Ribeiro',
age: 31,
}, }); }, 5000); },
<script>
部分<script>
部分。按照以下说明正确创建组件:在src/views
文件夹中打开contact.vue
。
添加一个新的mounted
属性:
mounted() {}
$route.params
对象上是否有任何参数,并显示具有该$route.params
的alert
:mounted() {
if (Object.keys(this.$route.params).length) {
alert(`Hey! I've got some parameter!
${JSON.stringify(this.$route.params)}`);
} },
> npm run serve
$router.push
函数时,告诉vue-router
改变应用程序所在的位置,在这个过程中,您将向新的路由器传递一些参数,这些参数将替换当前路由。在这些参数中,有一个名为params
的属性,它将一组参数发送到新的路由器。$route.params
对象中可用;在那里,我们可以在我们的视图或组件中使用它。$router.push
函数导航到路由器,并将它们添加到浏览器历史记录中,但也可以使用其他函数。$router.replace
函数将替换用户导航历史记录为新的历史记录,使其无法返回到上一页。$router.go
用于以步骤方式移动用户导航历史记录。要前进,您需要传递正数,要后退,您需要传递负数。router.vuejs.org/guide/essentials/navigation.html
找到有关vue-router
程序化导航的更多信息。@vue/cli
@vue/cli-service-global
vue-router
:vue ui
。要做到这一点,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> vue ui
在那里,您需要通过定位项目文件夹来导入项目。导入vue ui
后,您将被重定向到仪表板。
通过转到插件管理页面并单击“添加 vue-router”按钮,将vue-router
添加到插件中。然后,单击“继续”按钮。
Vue-CLI 将自动为我们在项目上安装和配置 vue-router。现在我们需要为列表,视图和编辑页面创建每个视图。
更改应用程序的主要组件
更改路由 mixin
Axios 实例配置
用户列表视图
用户创建视图
用户信息视图
用户更新视图
创建动态路由
App.vue
将发生变化。我们需要撤销安装vue-router
所做的更改。这是因为当vue-ui
添加vue-router
插件时,它会更改App.vue
,添加我们不需要的示例代码。<template>
部分。按照以下说明正确创建组件:在src
文件夹中打开App.vue
。
删除所有内容,只留下div#app
HTML 元素和router-view
组件:
<template>
<div id="app">
<router-view/>
</div> </template>
changeComponent
mixin。现在我们要使用路由,我们需要将此 mixin 更改为changeRoute
mixin 并更改其行为。在接下来的步骤中,我们将更改 mixin 的工作方式,以便能够更改路由而不是组件:在src/mixin
文件夹中,将changeComponent.js
重命名为changeRoute.js
并打开它。
我们将删除changeComponent
方法并创建一个名为changeRoute
的新方法。这个新方法将接收两个参数,name
和id
。name
参数是路由名称,在vue-router
配置中设置,id
将是我们将在路由更改中传递的用户 id 参数。此方法将执行$router.push
,将这些参数作为参数传递:
export default {
methods: {
async changeRoute(name, id = 0) {
await this.$router.push({
name,
params: {
id,
},
});
},
} }
在src/http
文件夹中,打开baseFetch.js
文件。
在axios
的localApi
实例的创建者中,我们需要添加一个options
对象,传递baseURL
属性。这个baseURL
将是当前浏览器导航的 URL:
const localApi = createAxios({
baseURL: `${document.location.protocol}//${document.location.host}`, });
list.vue
组件中提取代码,并将其重塑为页面视图。<script>
部分。按照以下说明正确创建组件:将list.vue
文件从components
移动到views
文件夹,并将其重命名为List.vue
。
删除旧的changeComponent
mixin 导入,并导入新的changeRoute
mixin:
import changeRouteMixin from '@/mixin/changeRoute';
mixins
属性中,我们需要用changeRoute
替换changeComponent
:mixins: [changeRouteMixin],
getAllUsers
和deleteUser
方法中,我们需要从getHttp
和deleteHttp
函数参数中删除${window.location.href}
:methods: {
async getAllUsers() {
const { data } = await getHttp(`api/users`);
this.userList = data;
},
async deleteUser(id) {
await deleteHttp(`api/users/${id}`);
await this.getAllUsers();
}, }
<template>
部分。按照以下说明正确创建组件:VsRow
和VsCol
组件包装VsCard
组件及其子内容。VsCol
组件将vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为12
:<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
changeComponent
函数改为changeRoute
:<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeRoute('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeRoute('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/> </vs-td>
VsCard
的页脚处,我们需要将操作按钮的changeComponent
方法改为changeRoute
方法:<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="primary"
type="filled"
icon="fiber_new"
size="small"
@click="changeRoute('create')"
>
Create User
</vs-button>
</vs-row> </template>
create.vue
组件中提取代码,并将其重塑为页面视图。<script>
部分。按照以下说明正确创建组件:将create.vue
文件从components
移动到views
文件夹,并将其重命名为Create.vue
。
删除旧的changeComponent
mixin 导入,并导入新的changeRoute
mixin:
import changeRouteMixin from '@/mixin/changeRoute';
mixins
属性中,我们需要用changeRoute
替换changeComponent
:mixins: [changeRouteMixin],
getUserById
方法中,我们需要从postHttp
函数的 URL 中移除${window.location.href}
,并将changeComponent
函数更改为changeRoute
:async createUser() {
await postHttp(`/api/users`, {
data: {
...this.userData,
}
});
this.changeRoute('list'); },
<template>
部分。按照这些说明正确创建组件:VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将定义vs-type
属性为'flex'
,vs-justify
属性为'left'
,vs-align
属性为'left'
,vs-w
属性为12
:<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
VsCard
的页脚上,我们需要将Cancel
按钮的changeComponent
函数更改为changeRoute
:<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Cancel
</vs-button>
view.vue
组件中提取代码,并将其重塑为页面视图。<script>
部分。按照这些说明正确创建组件:将view.vue
文件从src/components
移动到src/views
文件夹,并将其重命名为View.vue
。
移除旧的changeComponent
混入导入,并导入新的changeRoute
:
import changeRouteMixin from '@/mixin/changeRoute';
mixins
属性中,我们需要用changeRoute
替换changeComponent
:mixins: [changeRouteMixin],
component
对象中创建一个新的computed
属性,属性为userId
,它将返回$route.params.id
:computed: {
userId() {
return this.$route.params.id;
}, },
getUserById
方法中,我们需要从getHttp
函数的 URL 中移除${window.location.href}
:methods: {
async getUserById() {
const { data } = await getHttp(`api/users/${this.userId}`);
this.userData = data;
}, }
<template>
部分。按照这些说明正确创建组件:VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将定义vs-type
属性为'flex'
,vs-justify
属性为'left'
,vs-align
属性为'left'
,vs-w
属性为12
:<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
VsCard
的页脚上,我们需要将返回按钮的changeComponent
函数更改为changeRoute
:<vs-button
color="primary"
type="filled"
icon="arrow_back"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Back
</vs-button>
update.vue
组件中提取代码,并将其重塑为页面视图。<script>
部分。按照这些说明正确创建组件:将update.vue
文件从src/components
移动到src/views
文件夹,并将其重命名为Edit.vue
。
移除旧的changeComponent
混入导入,并导入新的changeRoute
混入:
import changeRouteMixin from '@/mixin/changeRoute';
mixins
属性中,我们需要用changeRoute
替换changeComponent
:mixins: [changeRouteMixin],
component
对象中创建一个新的computed
属性,具有userId
属性,它将返回$route.params.id
:computed: {
userId() {
return this.$route.params.id;
}, },
getUserById
和updateUser
方法中,我们需要移除getHttp
和patchHttp
函数的 URL 中删除${window.location.href}
,并将changeComponent
函数改为changeRoute
:methods: {
async getUserById() {
const { data } = await getHttp(`api/users/${this.userId}`);
this.userData = data;
},
async updateUser() {
await patchHttp(`api/users/${this.userData.id}`, {
data: {
...this.userData,
}
});
this.changeRoute('list');
}, },
<template>
部分。按照以下说明正确创建组件:VsRow
和VsCol
组件包裹VsCard
组件及其子内容。VsCol
组件将vs-type
属性定义为'flex'
,vs-justify
定义为'left'
,vs-align
定义为'left'
,vs-w
定义为12
:<template>
<vs-row>
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<vs-card... />
</vs-col>
</vs-row>
</template>
VsCard
的页脚上,我们需要把Cancel
按钮的changeComponent
函数改为changeRoute
:<vs-button
color="danger"
type="filled"
icon="cancel"
size="small"
style="margin-left: 5px"
@click="changeRoute('list')" >
Cancel
</vs-button>
打开src/router
文件夹中的index.js
。
首先,我们需要导入四个新页面 - List
,View
,Edit
,Create
和Update
:
import List from '@/views/List.vue'; import View from '@/views/View.vue'; import Edit from '@/views/Edit.vue'; import Create from '@/views/Create.vue';
在routes
数组上,我们将为每个导入的页面添加一个新的路由对象。在这个对象中,将有三个属性:name
,path
和component
。
对于list
路由,我们将把name
定义为'list'
,path
定义为'/'
,并把component
定义为导入的List
组件:
{
path: '/',
name: 'list',
component: List, },
view
路由上,我们将把name
定义为'view'
,path
定义为'/view/:id'
,并把component
定义为导入的View
组件:{
path: '/view/:id',
name: 'view',
component: View, },
edit
路由上,我们将把name
定义为'edit'
,path
定义为'/edit/:id'
,并把component
定义为导入的Edit
组件:{
path: '/edit/:id',
name: 'edit',
component: Edit, },
create
路由上,我们将把name
定义为'create'
,path
定义为'/create'
,并把component
定义为导入的Create
组件:{
path: '/create',
name: 'create',
component: Create, },
VueRouter
时,我们将添加mode
选项属性,并将其设置为'history'
:const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes });
> npm run serve
/
将是您的用户列表页面,包含应用程序中所有用户的列表以及查看、编辑和删除用户的按钮,以及创建新用户的按钮:/view/:id
将是您的用户查看页面,您可以在此页面查看用户信息,例如用户的姓名、电子邮件、国家、生日和电话号码:/update/:id
将是您的用户编辑页面,您可以在此页面编辑用户信息,更改用户的姓名、电子邮件、国家、生日和电话号码:/update/:id
将是您的用户创建页面,您可以在系统上创建新用户:vue-router
并将路由传递进行匹配时,路由分析会根据每个路由的权重定义的正则表达式来寻找最佳匹配路由。:
。此参数通过$route.params
属性传递给组件。router.vuejs.org/guide/essentials/dynamic-matching.html
找到有关动态路由匹配的更多信息。vue-router
中,可以使所有这些更改对用户不可见,因此当他们使用旧链接时,仍然可以访问应用程序。@vue/cli
@vue/cli-service-global
> vue create http-project
router
添加为必需功能。在src/router
文件夹中打开index.js
。
在list
对象中,我们将把path
属性从'/'
改为'/user'
,并为alias
属性设置为'/'
:
{
path: '/user',
name: 'list',
alias: '/',
component: List, },
view
对象中,我们将把path
属性从'/view/:id'
改为'/user/:id'
,并将alias
属性设置为'/view/:id'
:{
path: '/user/:id',
name: 'view',
alias: '/view/:id',
component: View, },
edit
对象中,我们将把path
属性从'/edit/:id'
改为'/user/edit/:id'
,并将alias
属性设置为'/edit/:id'
。{
path: '/user/edit/:id',
name: 'edit',
alias: '/edit/:id',
component: Edit, },
create
对象中,我们将把path
属性从'/create'
改为'/user/create'
,并将alias
属性设置为'/create'
:{
path: '/user/create',
name: 'create',
alias: '/create',
component: Create, },
vue-router
将尝试将路径与用户尝试访问的路径匹配。如果路由对象中有一个名为alias
的属性,则vue-router
将使用此属性在幕后维护旧路由,并使用别名路由。如果找到别名,则渲染该别名的组件,并且路由器保持为别名,不向用户显示更改,使其透明。/user
命名空间上调用的用户,但仍保持旧的 URL 结构,以便如果旧访问者尝试访问网站,他们将能够正常使用应用程序。router.vuejs.org/guide/essentials/redirect-and-alias.html#alias
找到有关vue-router
别名的更多信息。@vue/cli
@vue/cli-service-global
> vue create http-project
Router
作为必需的功能添加到'如何做...'步骤中的'创建一个简单路由'配方中。打开src/router
文件夹中的index.js
。
在routes
数组的末尾插入一个新的路由对象。这个对象将有两个属性,path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/create-new-user'
,在redirect
中,用户将被重定向到的路径,在这种情况下是'/user/create'
。
{
path: '/create-new-user',
redirect: '/user/create', },
path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/users'
,在redirect
中,我们将创建一个具有名为name
的属性的对象,并将值设置为'list'
。{
path: '/users',
redirect: {
name: 'list',
}, },
path
和redirect
。在path
属性中,我们需要定义用户将输入的路径,'/my-user/:id?'
,在redirect
中,我们将创建一个函数,该函数将接收一个参数to
,这是当前路由的对象。我们需要检查路由中是否存在用户 ID,以便将用户重定向到编辑页面。否则,我们将把他们重定向到用户列表。{
path: '/my-user/:id?',
redirect(to) {
if (to.params.id) {
return '/user/:id';
}
return '/user';
}, },
path
和redirect
的路由对象。在path
属性中,我们需要定义用户将输入的路径,'/*'
,在redirect
中,我们需要将redirect
属性定义为'/'
。{
path: '*',
redirect: '/', },
'*'
的最后一个路由将始终是在用户尝试输入的 URL 中没有匹配时呈现的路由。redirect
定义为一个新的路由时,它的工作方式类似于别名,但是redirect
属性可以接收三种类型的参数:一个字符串,当重定向到路由本身时,对象,当使用其他参数重定向时,例如路由的名称,最后但并非最不重要的是函数类型,redirect
可以处理并返回前两个对象中的一个,以便用户可以被重定向。router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect
找到有关vue-router
重定向的更多信息。vue-router
中,嵌套路由就像是路由的命名空间,您可以在同一个路由内拥有多个级别的路由,使用基本视图作为主视图,并在其中呈现嵌套路由。@vue/cli
@vue/cli-service-global
> vue create http-project
Router
添加为必需功能,如“创建简单路由”配方中所示。在布局上创建router-view
更改路由文件
router-view
vue-router
时,我们需要创建主视图,它将具有一个名为RouterView
的特殊组件。此组件将在您呈现的布局或页面内呈现当前路由。在src/views
文件夹中,我们需要创建一个名为user
的新文件夹,并将Create
、Edit
、List
、和View
页面移动到这个新文件夹中。
在user
文件夹中创建一个名为Index.vue
的新文件并打开它。
在单文件组件<template>
部分中,添加一个router-view
组件:
<template>
<router-view/>
</template>
<script>
export default {
name: 'User',
}
</script>
在src/router
文件夹中创建一个名为user.js
的新文件。
导入Index
、List
、View
、Edit
和Create
视图:
import Index from '@/views/user/Index.vue'; import List from '@/views/user/List.vue'; import View from '@/views/user/View.vue'; import Edit from '@/views/user/Edit.vue'; import Create from '@/views/user/Create.vue';
route
对象,具有四个属性-path
,name
,component
和children
。将path
属性设置为'/user'
,将name
属性定义为'user'
,将component
定义为导入的Index
组件,最后,将children
属性定义为空数组:export default [
{ path: '/user',
name: 'user',
component: Index,
children: [],
}, ]
children
属性中,添加一个新的路由对象,具有三个属性-path
,name
和component
。将path
定义为''
,name
定义为'list'
,最后,将component
属性定义为导入的List
组件:{
path: '',
name: 'list',
component: List, },
route
对象相同的结构。将path
属性定义为':id'
,将name
定义为'view'
,将component
定义为导入的View
组件:{
path: ':id',
name: 'view',
component: View, },
edit
路由创建一个路由对象,并使用与上一个route
对象相同的结构。将path
属性定义为'edit/:id'
,将name
定义为'edit'
,将component
定义为导入的Edit
组件:{
path: 'edit/:id',
name: 'edit',
component: Edit, },
create
路由创建一个路由对象,使用与上一个route
对象相同的结构。将path
属性定义为'create'
,将name
定义为'create'
,将component
定义为导入的Create
组件:{
path: 'create',
name: 'create',
component: Create, },
在src/router
文件夹中打开index.js
。
在src/router
文件夹中导入新创建的user.js
文件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user';
routes
数组中,将导入的UserRoutes
作为解构数组添加:const routes = [
...UserRoutes,
{
path: '*',
redirect: '/user',
}, ];
vue-router
提供了使用子路由作为当前视图或布局的内部组件的能力。这使得可以创建具有特殊布局文件的初始路由,并通过RouterView
组件在此布局中呈现子组件。router.vuejs.org/guide/essentials/nested-routes.html#nested-routes
找到有关嵌套路由的更多信息。vue-router
中处理 404 错误。@vue/cli
@vue/cli-service-global
> vue create http-project
Router
添加为所需功能,如“如何做...”部分在“创建简单路由”配方中所示。创建NotFound
视图
更改路由文件
<template>
部分。按照这些说明正确创建组件:在src/views
文件夹中,创建一个名为NotFound.vue
的新文件并打开它。
创建一个VsRow
组件,在其中创建四个VsCol
组件。所有这些组件都将具有属性vs-w
定义为12
和class
定义为text-center
:
<vs-row>
<vs-col vs-w="12" class="text-center">
<!-- Icon --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Title --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Text --> </vs-col>
<vs-col vs-w="12" class="text-center">
<!-- Button --> </vs-col> </vs-row>
VsCol
组件中,我们将添加一个VsIcon
组件,并将属性 icon 定义为sentiment_dissatisfied
,并将size
定义为large
:<vs-icon
icon="sentiment_dissatisfied"
size="large" />
VsCol
组件中,我们将为页面添加一个标题:<h1>Oops!</h1>
VsCol
组件中,我们需要创建将放置在页面上的文本:<h3>The page you are looking for are not here anymore...</h3>
VsCol
组件上,我们将添加VsButton
组件。此按钮将具有属性type
定义为relief
和to
定义为'/'
:<vs-button
type="relief"
to="/" >
Back to Home...
</vs-button>
<style>
部分。按照这些说明正确创建组件:<style>
标签中添加scoped
标签:<style scoped> </style>
.text-center
的新规则,其中text-align
属性定义为center
,margin-bottom
定义为20px;
:.text-center {
text-align: center;
margin-bottom: 20px; }
在src/router
文件夹中打开index.js
。
导入NotFound
组件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user'; import NotFound from '@/views/NotFound';
routes
数组中,在UserRoutes
之后,添加一个新的route
对象,具有两个属性,path
和redirect
。将path
属性定义为'/'
,将redirect
属性定义为'/user'
:{
path: '/',
redirect: '/user' },
routes
数组的最后位置。这个路由对象将有两个属性,path
和component
。path
属性将被定义为'*'
,component
将被定义为导入的NotFound
视图:{
path: '*',
component: NotFound, },
> npm run serve
vue-router
尝试找到用户想要访问的 URL 的最佳匹配;如果没有匹配项,vue-router
将使用'*'
路径作为这些情况的默认值,其中*
表示用户输入的不在路由列表中的任何值。vue-router
中匹配的过程是由路由的权重决定的,所以我们需要将错误页面放在最底部,这样vue-router
在实际调用NotFound
路由之前需要通过每个可能的路由。router.vuejs.org/guide/essentials/history-mode.html#caveat
找到有关处理 vue-router 历史模式中的 404 错误的更多信息。vue-router
中,可以创建路由守卫-每次路由更改时运行的函数。这些守卫被用作路由管理过程中的中间件。通常将它们用作身份验证中间件或会话验证器。@vue/cli
@vue/cli-service-global
> vue create http-project
Router
作为必需特性,如“创建简单路由”配方中所示。创建身份验证中间件
向路由添加元数据和中间件
将中间件附加到 vue-router 并创建登录页面
<script>
部分。按照这些说明正确创建组件:在src/views
文件夹中,创建一个名为Login.vue
的新文件并打开它。
创建一个包含username
、password
和error
的data
属性:
data: () => ({
username: '',
password: '',
error: false, }),
userSignIn
的方法的methods
属性。此方法将验证username
和password
数据是否完整。如果是,它将在sessionStorage
中创建一个名为'auth'
的新密钥,其中包含username
数据的加密字符串化 JSON。然后,将error
设置为false
并执行$router.replace
将用户重定向到用户列表'/user'
。如果任何字段未通过任何验证,该方法将将错误定义为true
并返回false
:methods: {
userSignIn() {
if (this.username && this.password) {
window.sessionStorage.setItem('auth',
window.btoa(JSON.stringify({
username: this.username
})
) );
this.error = false;
this.$router.replace('/user');
}
this.error = true;
return false;
}, }
<template>
部分。按照这些说明正确创建组件:VsRow
组件的div.container
HTML 元素。VsRow
组件将具有属性vs-align
定义为"center"
和vs-justify
定义为"center"
:<div class="container">
<vs-row
vs-align="center"
vs-justify="center"
>
</vs-row>
</div>
VsRow
组件内部,添加一个带有属性vs-lg
定义为4
,vs-sm
定义为6
和vs-xs
定义为10
的VsCol
组件。然后,在VsCol
组件内部,我们将创建一个带有style
属性定义为margin: 20px;
的VsCard
组件:<vs-col
vs-lg="4"
vs-sm="6"
vs-xs="10" >
<vs-card
style="margin: 20px;"
>
</vs-card>
</vs-col>
VsCard
组件内部,创建一个带有名称为header
的slot
的动态<template>
,一个h3
HTML 元素和您的标题:<template slot="header">
<h3>
User Login
</h3> </template>
VsRow
组件,其中属性vs-align
定义为"center"
,vs-justify
定义为"center"
,并在其中放置两个VsCol
组件,其中属性vs-w
定义为12
:<vs-row
vs-align="center"
vs-justify="center" >
<vs-col vs-w="12">
</vs-col>
<vs-col vs-w="12">
</vs-col>
</vs-row>
VsCol
组件上,我们将添加一个VsInput
组件,其中属性danger
定义为数据error
的值,danger-text
定义为错误时显示的文本,label
定义为"Username"
,placeholder
定义为"Username or e-mail"
,并且v-model
指令绑定到username
:<vs-input
:danger="error"
danger-text="Check your username or email"
label="Username"
placeholder="Username or e-mail"
v-model="username" />
VsCol
组件中,我们将添加一个VsInput
组件,其中属性danger
定义为数据error
的值,danger-text
定义为错误时显示的文本,label
定义为"Password"
,type
定义为password
,placeholder
定义为"Your password"
,并且v-model
指令绑定到password
:<vs-input
:danger="error"
label="Password"
type="password"
danger-text="Check your password"
placeholder="Your password"
v-model="password" />
<template>
,其中包含名为footer
的插槽。在这个<template>
中,我们将添加一个VsRow
组件,其中vs-justify
属性定义为flex-start
,并插入一个VsButton
,其中属性color
定义为success
,type
定义为filled
,icon
定义为account_circle
,size
定义为small
,并且@click
事件监听器指向userSignIn
方法:<template slot="footer">
<vs-row vs-justify="flex-start">
<vs-button
color="success"
type="filled"
icon="account_circle"
size="small"
@click="userSignIn"
>
Sign-in
</vs-button>
</vs-row> </template>
<style>
部分。按照以下说明正确创建组件:<style scoped></style>
container
类和VsInput
组件添加规则:<style scoped>
.container {
height: 100vh;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
} .vs-input {
margin: 5px;
} </style>
> npm run serve
vue-router
中间件也可以称为导航守卫,并且它们可以附加到应用程序路由更改上。这些更改有一些钩子,您可以将其应用于您的中间件。身份验证中间件在路由更改之前发生,因此我们可以处理一切并将用户发送到正确的路由。在src/router
文件夹中,创建一个名为middleware
的新文件夹,然后创建并打开一个名为authentication.js
的新文件。
在这个文件中,我们将创建一个默认的export
函数,它将有三个函数参数 - to
,from
和next
。to
和from
参数是对象,next
参数是一个回调函数:
export default (to, from, next) => { };
true
的经过身份验证的meta
属性,并且我们是否有一个具有'auth'
键的sessionStorage
项。如果通过了这些验证,我们可以执行next
回调:if (to.meta.authenticated && sessionStorage.getItem('auth')) {
return next(); }
meta
属性,并检查它是否为false
值。如果验证通过,我们将执行next
回调:if (!to.meta.authenticated) {
return next(); }
next
回调,传递'/login'
作为参数:next('/login');
在src/router
文件夹中打开user.js
。
在每个route
对象中,添加一个名为meta
的新属性。这个属性将是一个对象,具有一个经过身份验证的key
和一个值定义为true
。我们需要对每个路由都这样做 - 即使是子路由:
import Index from '@/views/user/Index.vue'; import List from '@/views/user/List.vue'; import View from '@/views/user/View.vue'; import Edit from '@/views/user/Edit.vue'; import Create from '@/views/user/Create.vue'; export default [
{ path: '/user',
name: 'user',
component: Index,
meta: {
authenticated: true,
},
children: [
{ path: '',
name: 'list',
component: List,
meta: {
authenticated: true,
},
},
{
path: ':id',
name: 'view',
component: View,
meta: {
authenticated: true,
},
},
{
path: 'edit/:id',
name: 'edit',
component: Edit,
meta: {
authenticated: true,
},
},
{
path: 'create',
name: 'create',
component: Create,
meta: {
authenticated: true,
},
},
],
}, ]
在src/router
文件夹中打开index.js
。
导入新创建的中间件和Login
视图组件:
import Vue from 'vue'; import VueRouter from 'vue-router'; import UserRoutes from './user'; import NotFound from '@/views/NotFound'; import Login from '@/views/Login'; import AuthenticationMiddleware from './middleware/authentication';
route
对象。这个路由对象将path
设置为'/login'
,name
定义为'login'
,component
定义为Login
,并且meta
属性将具有authenticated
键,其值设置为false
:{
path: '/login',
name: 'login',
component: Login,
meta: {
authenticated: false,
}, },
meta
属性authenticated
为false
,因为登录视图是一个公共路由:{
path: '*',
component: NotFound,
meta: {
authenticated: false,
}, },
router
构造函数之后,我们需要在beforeEach
执行中注入中间件:router.beforeEach(AuthenticationMiddleware);
vue-router
进程的每个生命周期中都有一个钩子被执行。对于这个示例,我们选择了beforeEach
钩子来添加我们的中间件。router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
找到有关 vue-router 路由守卫的更多信息。vue-router
的惰性加载技术可以在应用程序中进行更多的代码拆分和更小的最终捆绑包。@vue/cli
@vue/cli-service-global
> vue create http-project
Router
添加为所需的功能,如“如何做...”部分和“创建简单路由”配方中所示。更新路由管理器
更新用户路由
在src/router
文件夹中打开index.js
文件。
在每个具有component
属性的路由中,我们将把组件的直接赋值转换为一个新函数。这将是一个返回 webpack 的import()
方法的箭头函数:
{
path: '/login',
name: 'login',
component: () => import('@/views/Login'),
meta: {
authenticated: false,
}, },
component
属性的route
对象上重复此过程。在src/router
文件夹中打开user.js
文件。
在每个具有component
属性的路由中,我们将把组件的直接赋值转换为一个新函数。这将是一个返回 webpack 的import()
方法的箭头函数。
{
path: '/user',
name: 'user',
component: () => import('@/views/user/Index.vue'),
meta: {
authenticated: true,
},
children: [], },
component
属性的route
对象上重复此过程。export default
方法时,export
和import
是具有预定义值的对象。这意味着当我们import
一个新组件时,该组件已经指向该文件的default export
。vue-router
中调用这个函数时,vue-router
不直接导入组件,而是进行验证检查,确保当前组件导入是一个需要执行的函数。在函数执行后,响应被用作将显示在用户屏幕上的组件。import()
方法是异步的,这个过程可以与其他代码执行同时进行,而不会干扰或阻塞 JavaScript 虚拟机的主线程。router.vuejs.org/guide/advanced/lazy-loading.html
找到有关vue-router
延迟加载的更多信息。webpack.js.org/guides/code-splitting/
找到有关webpack
代码拆分的更多信息。github.com/tc39/proposal-dynamic-import
找到有关 ECMAScript 动态导入提案的更多信息。创建一个简单的 Vuex 存储
创建和理解 Vuex 状态
创建和理解 Vuex 变化
创建和理解 Vuex 操作
创建和理解 Vuex 获取器
使用 Vuex 创建一个动态组件
为开发添加热模块重新加载
创建一个 Vuex 模块
windows-build-tools
的 NPM 包,以便安装以下所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
> npm install -g @vue/cli @vue/cli-service-global
@vue/cli
@vue/cli-service-global
> vue create initial-vuex
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用Spacebar选择选项。
有两种方法可以启动新项目。默认方法是基本的babel
和eslint
项目,没有任何插件或配置,还有手动
模式,您可以选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys)
default (babel, eslint)
❯ Manually select features
Vue-Router
),测试工具,代码检查工具等。选择Babel
,Router
,Vuex
和Linter / Formatter
:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router ❯ Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
package.json
文件中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ **In dedicated config files****In package.json**
? Save this as a preset for future projects? (y/N) n
创建 store
使用 Vuex 创建响应式组件
打开src/store
文件夹中的index.js
。
在state
属性中,添加一个名为counter
的新键,并将值设置为0
:
state: {
counter: 0,
},
mutations
属性中,添加两个新函数,increment
和decrement
。这两个函数都将有一个state
参数,这是当前的 Vuexstate
对象。increment
函数将counter
增加1
,而decrement
函数将counter
减少1
:mutations: {
increment: (state) => {
state.counter += 1;
},
decrement: (state) => {
state.counter -= 1;
},
},
actions
属性中,添加两个新函数,increment
和decrement
。这两个函数都将有一个解构参数commit
,它是调用 Vuex mutation 的函数。在每个函数中,我们将执行commit
函数,将当前函数的名称作为字符串参数传递:actions: {
increment({ commit }) {
commit('increment');
},
decrement({ commit }) {
commit('decrement');
},
},
counter
,并显示两个按钮,一个用于增加counter
,另一个用于减少counter
。<script>
部分:从src
文件夹中打开App.vue
文件。
在文件中创建<script>
部分,使用export default
对象:
<script>
export default {}; </script>
computed
属性,属性名为counter
。在这个属性中,我们需要返回当前的$store.state.counter
:computed: {
counter() {
return this.$store.state.counter;
}, },
methods
属性中创建两个函数,increment
和decrement
。这两个函数都将执行一个带有函数名称作为字符串参数的$store.dispatch
:methods: {
increment() {
this.$store.dispatch('increment');
},
decrement() {
this.$store.dispatch('decrement');
}, },
<template>
部分:在src
文件夹中打开App.vue
文件。
在<template>
部分中,删除div#app
内的所有内容。
创建一个包含计数器变量的h1
HTML 元素。
创建一个带有@click
指令的事件监听器的按钮,调用increment
函数,并将+
作为标签:
<button @click="increment">+</button>
@click
指令的事件监听器的按钮,调用decrement
函数,并将-
作为标签:<button @click="decrement">-</button>
> npm run serve
state
,mutations
和actions
。这些属性作为一个单一的结构,通过注入的$store
原型或导出的store
变量绑定到 Vue 应用程序。state
是一个集中的对象,保存您的信息并使其可供mutation
、actions
或组件使用。改变state
始终需要通过mutation
执行的同步函数。mutation
是一个同步函数,可以改变state
并且是可追踪的,因此在开发时,可以通过 Vuex 存储中执行的所有mutations
进行时间旅行。action
是一个异步函数,可用于保存业务逻辑、API 调用、分派其他actions
和执行mutations
。这些函数是 Vuex 存储中任何更改的常见入口点。vuex.vuejs.org/
找到有关 Vuex 的更多信息。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Router
和Vuex
添加为必需功能。通过vue ui
添加 Vuex
创建Vuex
状态
vue ui
界面自动添加 Vuex,而无需任何努力。我们将学习如何向旧项目添加 Vuex 库,以便继续开发该项目。vue ui
界面添加 Vuex。vue ui
:> vue ui
src
文件夹中有一个名为store
的新文件夹,在main.js
文件中,它被添加到了导入和在 Vue 应用程序中注入store
:import './server/server'; import Vue from 'vue'; import App from './App.vue'; import Vuesax from 'vuesax'; import './style.css'; import router from './router' import store from './store' Vue.use(Vuesax); Vue.config.productionTip = false; new Vue({
router,
store,
render: h => h(App) }).$mount('#app');
在src/store
文件夹中,创建一个名为user
的新文件夹,在这个文件夹里创建一个名为state.js
的新文件。
创建一个新的generateState
函数。这个函数将返回一个 JavaScript 对象,有三个主要属性,data
,loading
和error
。data
属性将是一个 JavaScript 对象,其中有一个名为usersList
的属性,默认为空数组,以及一个名为userData
的属性,其中包含用户的默认对象。loading
属性将默认设置为布尔值false
,error
将有一个默认值初始化为null
:
const generateState = () => ({
data: {
usersList: [],
userData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
},
},
loading: false,
error: null, });
export default
对象,它将是一个 JavaScript 对象,并且我们将解构generateState
函数的返回值:export default { ...generateState() };
在user
文件夹中创建一个名为index.js
的新文件并打开它。
导入新创建的state
:
import state from './state';
export default
文件作为 JavaScript 对象。在这个对象中,我们将添加导入的state
:export default {
state, };
打开src/store
文件夹中的index.js
文件。
从user
文件夹中导入index.js
文件:
import Vue from 'vue'; import Vuex from 'vuex'; import UserStore from './user';
export default
函数中,我们将删除其中的所有属性,并将导入的UserStore
解构对象放入Vuex.Store
参数中:export default new Vuex.Store({
...UserStore, })
vue ui
将 Vuex 作为插件添加时,vue ui
将自动添加所需的文件,并导入所有需要的内容。这是创建 Vuex store
的初始阶段。state
的文件,我们可以使用它来分离store
中状态的开始过程以及如何初始化状态。state
。这是一个很好的做法,因为在 SSR 环境中,服务器的state
始终是相同的,我们需要为每个新连接创建一个新的state
。state
之后,我们需要创建一个默认文件来导出将在user
文件夹中创建的 Vuex 文件。这个文件是对将在文件夹中创建的所有文件(state
,actions
,mutation
和getters
)的简单导入。导入后,我们导出一个带有所需的 Vuex 属性名称的对象,state
,actions
,mutations
和getters
。store
中,我们导入了一个文件,将所有内容聚合并解构到我们的 store 中进行初始化。Vuex
state 是应用程序中的唯一数据源,它就像一个全局数据管理器,不应直接更改。这是因为我们需要防止数据的同时变异。为了避免这种情况,我们总是需要通过 mutations 来改变我们的 state,因为这些函数是同步的,并由 Vuex 控制。vuex.vuejs.org/guide/state.html
找到有关 Vuex state 的更多信息。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Router
和Vuex
作为必需功能。在src/store
文件夹内的user
文件夹中创建一个名为types.js
的新文件,并打开它。
在这个文件中,我们将创建一个export default
的 JavaScript 对象,其中包含一组键,这些键将是我们 mutations 的名称。这些键将是LOADING
、ERROR
、SET_USER_LIST
、SET_USER_DATA
、UPDATE_USER
和REMOVE_USER
:
export default {
LOADING: 'LOADING',
ERROR: 'ERROR',
SET_USER_LIST: 'SET_USER_LIST',
SET_USER_DATA: 'SET_USER_DATA',
UPDATE_USER: 'UPDATE_USER',
REMOVE_USER: 'REMOVE_USER', }
在user
文件夹中创建一个名为mutations.js
的新文件,并打开它。
导入新创建的types.js
文件:
import MT from './types';
setLoading
的新函数,它将接收 Vuex state
作为参数,并在执行时将状态的 loading 属性定义为true
。const setLoading = state => {
state.loading = true; };
setError
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state
的loading
属性设置为false
,将error
属性设置为接收到的payload
参数:const setError = (state, payload) => {
state.loading = false;
state.error = payload; };
setUserList
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state.data
的usersList
属性定义为接收到的payload
参数,将state
的loading
属性设置为false
,将error
属性设置为null
:const setUserList = (state, payload) => {
state.data.usersList = payload;
state.loading = false;
state.error = null; };
setUserData
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将把state.data
的userData
属性定义为接收到的payload
参数,将state
的loading
属性设置为false
,将error
属性设置为null
:const setUserData = (state, payload) => {
state.data.userData = payload;
state.loading = false;
state.error = null; };
updateUser
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将更新state.data
的usersList
属性中的用户数据,将state
的loading
属性定义为false
,将error
属性定义为null
:const updateUser = (state, payload) => {
const userIndex = state.data.usersList.findIndex(u => u.id ===
payload.id);
if (userIndex > -1) {
state.data.usersList[userIndex] = payload;
}
state.loading = false;
state.error = null; };
removeUser
的新函数,它将接收 Vuex state
和payload
作为参数。这个函数将从state.data
的usersList
属性中删除用户数据,将state
的loading
属性定义为false
,将error
属性定义为null
:const removeUser = (state, payload) => {
const userIndex = state.data.usersList.findIndex(u => u.id ===
payload);
if (userIndex > -1) {
state.data.usersList.splice(userIndex, 1);
}
state.loading = false;
state.error = null; };
export default
对象,其中键是我们在types.js
文件中创建的类型,并将每个键定义为我们创建的函数:export default {
[MT.LOADING]: setLoading,
[MT.ERROR]: setError,
[MT.SET_USER_LIST]: setUserList,
[MT.SET_USER_DATA]: setUserData,
[MT.UPDATE_USER]: updateUser,
[MT.REMOVE_USER]: removeUser, }
打开user
文件夹中的index.js
文件。
导入新创建的mutations.js
文件,并将其添加到export default
JavaScript 对象中:
import state from './state';
import mutations from './mutations';
export default {
state,
mutations,
};
mutation
都是一个将作为commit
调用的函数,并且在 Vuex 存储中具有标识符。这个标识符是导出的 JavaScript 对象中的mutation
键。在这个示例中,我们创建了一个文件,将所有标识符作为对象值保存,以便在我们的代码中作为常量使用。mutation
名称的 Vuex actions
。mutation
JavaScript 对象时,我们使用常量作为键,相应的函数作为其值,这样 Vuex 存储在调用时可以执行正确的函数。vuex.vuejs.org/guide/mutations.html
找到有关 Vuex mutations 的更多信息。Vuex
中访问数据可以通过状态本身完成,这可能非常危险,或者通过 getters 完成。Getters 就像是可以预处理并传递数据而不触及或干扰 Vuex 存储状态的数据。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Vuex
作为需要的功能。在src/store/user
文件夹中创建一个名为getters.js
的新文件。
创建一个名为getUsersList
的新函数,并返回state.data.usersList
属性:
function getUsersList(state) {
return state.data.usersList;
}
getter
函数中,函数将始终接收到的第一个参数是 Vuex store
的当前state
。getUserData
的新函数,并返回state.data.userData
属性:function getUserData(state) {
return state.data.userData; }
getUserById
的新函数,并返回另一个函数,该函数接收userId
作为参数。这个返回函数将返回与接收到的userId
相匹配的state.data.usersList
的搜索结果:function getUserById(state) {
return (userId) => {
return state.data.usersList.find(u => u.id === userId);
} }
isLoading
的新函数,并返回state.loading
属性:function isLoading(state) {
return state.loading;
}
hasError
的新函数,并返回state.error
属性:function hasError(state) {
return state.error;
}
export default
JavaScript 对象:export default {
getUsersList,
getUserData,
getUserById,
isLoading,
hasError, };
在src/store/user
文件夹中打开index.js
文件。
导入新创建的getters.js
文件,并将其添加到默认导出的 JavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';
export default {
state,
mutations,
getters,
};
state
发生变化时才会改变返回值。但是,如果将返回值作为高阶函数添加,就可以赋予它更多的功能,使用更复杂的算法并提供特定的数据。vuex.vuejs.org/guide/getters.html
找到有关 Vuex getters 的更多信息。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Router
和Vuex
作为必需的功能。在src/store/user
文件夹中创建一个名为actions.js
的新文件。
从fetchApi
包装器中导入变异类型文件(types.js
)和getHttp
,patchHttp
,postHttp
和deleteHttp
函数:
import {
getHttp,
patchHttp,
deleteHttp,
postHttp,
} from '@/http/fetchApi';
import MT from './types';
createUser
的新的异步
函数,它接收解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,并将userData
作为第二个参数,用于创建用户。添加一个try/catch
语句,在try
上下文中。首先,我们执行commit(MT.LOADING)
,然后我们从 API 中获取用户列表,最后,执行commit(MT.SET_USER_DATA, data)
,将用户列表传递给被突变。如果我们收到异常并进入Catch
语句,我们将执行commit(MT.ERROR, error)
,将收到的错误传递给state
:async function createUser({ commit }, userData) {
try {
commit(MT.LOADING);
await postHttp(`/api/users`, {
data: {
...userData,
}
});
commit(MT.SET_USER_DATA, userData);
} catch (error) {
commit(MT.ERROR, error);
} }
fetchUsersList
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中获取用户列表,最后执行commit(MT.SET_USER_LIST, data)
,将用户列表传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。async function fetchUsersList({ commit }) {
try {
commit(MT.LOADING);
const { data } = await getHttp(`api/users`);
commit(MT.SET_USER_LIST, data);
} catch (error) {
commit(MT.ERROR, error);
} }
fetchUsersData
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的将要获取的userId
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中获取用户列表,最后执行commit(MT.SET_USER_DATA, data)
,将用户列表传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。async function fetchUserData({ commit }, userId) {
try {
commit(MT.LOADING);
const { data } = await getHttp(`api/users/${userId}`);
commit(MT.SET_USER_DATA, data);
} catch (error) {
commit(MT.ERROR, error);
} }
updateUser
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的payload
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后将用户数据提交到 API,最后执行commit(MT.UPDATE_USER, payload)
,将新的用户数据传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。async function updateUser({ commit }, payload) {
try {
commit(MT.LOADING);
await patchHttp(`api/users/${payload.id}`, {
data: {
...payload,
}
});
commit(MT.UPDATE_USER, payload);
} catch (error) {
commit(MT.ERROR, error);
} }
removeUser
的新的异步
函数,它接收一个解构的 JavaScript 对象作为第一个参数,其中包含commit
属性,以及作为第二个参数的userId
。在try
上下文中添加一个try/catch
语句。我们执行commit(MT.LOADING)
,然后从 API 中删除用户数据,最后执行commit(MT.REMOVE_USER, userId)
,将userId
传递给 mutation。如果我们收到异常并进入catch
语句,我们将执行一个commit(MT.ERROR, error)
的 mutation,将收到的错误传递给state
。async function removeUser({ commit }, userId) {
try {
commit(MT.LOADING);
await deleteHttp(`api/users/${userId}`);
commit(MT.REMOVE_USER, userId);
} catch (error) {
commit(MT.ERROR, error);
} }
export default {
createUser,
fetchUsersList,
fetchUserData,
updateUser,
removeUser, }
src/store/user
文件夹的index.js
中导入新创建的actions.js
文件,并将其添加到export default
JavaScript 对象中:import state from './state';
import mutations from './mutations';
import getters from './getters';
import actions from './actions';
export default {
state,
mutations,
getters,
actions,
};
vuex.vuejs.org/guide/actions.html
找到有关 Vuex 操作的更多信息。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Router
和Vuex
作为所需功能。创建用户列表组件
编辑用户列表页面
编辑用户视图页面
编辑用户视图页面
编辑用户创建页面
Vuex
操作。<script>
部分<script>
部分:在src/components
文件夹中创建一个名为userList.vue
的新文件。
从src/mixin
文件夹导入changeRouterMixin
:
import changeRouteMixin from '@/mixin/changeRoute';
export default
的 JavaScript 对象,并添加一个名为mixin
的新 Vue 属性,其默认值为一个数组。将导入的changeRouteMixin
添加到这个数组中:mixins: [changeRouteMixin],
computed
的新 Vue 属性。在这个属性中,创建一个名为userList
的新值。这个属性将是一个返回 Vuex 存储器 gettergetUsersList
的函数:computed: {
userList() {
return this.$store.getters.getUsersList;
}, },
<template>
部分<template>
部分:打开views
文件夹内users
文件夹中的List.vue
文件,并复制VsTable
组件的内容和组件。
打开src/components
文件夹中的userList.vue
文件。
将你从List.vue
文件中复制的内容粘贴到<template>
部分中:
<template>
<vs-table
:data="userList"
search
stripe pagination max-items="10"
style="width: 100%; padding: 20px;"
>
<template slot="thead">
<vs-th sort-key="name">
#
</vs-th>
<vs-th sort-key="name">
Name
</vs-th>
<vs-th sort-key="email">
Email
</vs-th>
<vs-th sort-key="country">
Country
</vs-th>
<vs-th sort-key="phone">
Phone
</vs-th>
<vs-th sort-key="Birthday">
Birthday
</vs-th>
<vs-th>
Actions
</vs-th>
</template>
<template slot-scope="{data}">
<vs-tr :key="index" v-for="(tr, index) in data">
<vs-td :data="data[index].id">
{{data[index].id}}
</vs-td>
<vs-td :data="data[index].name">
{{data[index].name}}
</vs-td>
<vs-td :data="data[index].email">
<a :href="`mailto:${data[index].email}`">
{{data[index].email}}
</a>
</vs-td>
<vs-td :data="data[index].country">
{{data[index].country}}
</vs-td>
<vs-td :data="data[index].phone">
{{data[index].phone}}
</vs-td>
<vs-td :data="data[index].birthday">
{{data[index].birthday}}
</vs-td>
<vs-td :data="data[index].id">
<vs-button
color="primary"
type="filled"
icon="remove_red_eye"
size="small"
@click="changeRoute('view', data[index].id)"
/>
<vs-button
color="success"
type="filled"
icon="edit"
size="small"
@click="changeRoute('edit', data[index].id)"
/>
<vs-button
color="danger"
type="filled"
icon="delete"
size="small"
@click="deleteUser(data[index].id)"
/>
</vs-td>
</vs-tr>
</template>
</vs-table> </template>
<script>
部分<script>
部分:打开views
文件夹内users
文件夹中的List.vue
文件。
从components
文件夹导入新创建的用户列表组件:
import changeRouteMixin from '@/mixin/changeRoute'; import UserTableList from '@/components/userList';
export default
的 JavaScript 对象中,添加一个名为components
的新属性。将该属性声明为 JavaScript 对象,并将导入的UserTableList
组件添加到对象中:components: { UserTableList },
methods
属性中,在getAllUsers
函数中,我们需要更改内容以在调用时执行一个 Vuex 分发。这个方法将执行fetchUsersList
的 Vuex 操作:async getAllUsers() {
this.$store.dispatch('fetchUsersList'); },
deleteUser
函数中,我们需要更改内容以在调用时执行一个 Vuex 分发。这个方法将执行removeUser
的 Vuex 操作,并将userId
作为参数传递:async deleteUser(id) {
this.$store.dispatch('removeUser', id);
await this.getAllUsers(); },
<template>
部分<template>
部分:在view
文件夹内的users
文件夹中打开List.vue
文件。
用新导入的UserTableList
替换VsTable
组件及其内容:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12">
<user-table-list /> </vs-col>
<script>
部分<script>
部分:从view
文件夹内的users
文件夹中打开View.vue
文件。
删除 Vue 的data
属性。
在 Vue 的computed
属性中,添加userData
,返回一个 Vuex 的 getter,getUserData
:
userData() {
return this.$store.getters.getUserData; },
getUserById
方法中,将内容更改为调度一个 Vuex 操作fetchUserData
,传递计算的userId
属性作为参数:async getUserById() {
await this.$store.dispatch('fetchUserData', this.userId); },
<template>
部分<template>
部分了:在view
文件夹内的users
文件夹中打开View.vue
文件。
在 UserForm 组件中,将v-model
指令更改为:value
指令:
<user-form
:value="userData"
disabled />
v-model
指令的语法糖时,可以将输入值声明为:value
指令,并将值更改事件声明为@input
事件监听器。<script>
部分<script>
部分:在view
文件夹内的users
文件夹中打开Edit.vue
文件。
在 Vue 的data
属性中,将数据的名称从userData
更改为tmpUserData
:
data: () => ({
tmpUserData: {
name: '',
email: '',
birthday: '',
country: '',
phone: '',
}, }),
computed
属性中,添加一个名为userData
的新属性,它将返回 Vuex 的 gettergetUserData
:userData() {
return this.$store.getters.getUserData; }
watch
的新 Vue 属性,并添加一个名为userData
的新属性,它将是一个 JavaScript 对象。在这个对象中,添加三个属性,handler
,immediate
和deep
。handler
属性将是一个接收名为newData
的参数的函数,它将tmpUserData
设置为这个参数。immediate
和deep
属性都是设置为true
的布尔属性:watch: {
userData: {
handler(newData) {
this.tmpUserData = newData;
},
immediate: true,
deep: true,
} },
methods
属性中,我们需要更改getUserById
的内容以调度名为fetchUserData
的 Vuex 动作,并将computed
属性userId
作为参数传递:async getUserById() {
await this.$store.dispatch('fetchUserData', this.userId); },
updateUser
方法中,更改内容以调度名为updateUser
的 Vuex 动作,并将tmpUserData
作为参数传递:async updateUser() {
await this.$store.dispatch('updateUser', this.tmpUserData);
this.changeRoute('list'); },
<template>
部分:在view
文件夹内的users
文件夹中打开Edit.vue
。
将UserForm
组件的v-model
指令的目标更改为tmpUserData
:
<vs-col
vs-type="flex"
vs-justify="left"
vs-align="left"
vs-w="12"
style="margin: 20px" >
<user-form
v-model="tmpUserData"
/> </vs-col>
<script>
部分:在view
文件夹内的users
文件夹中打开Create.vue
文件。
更改createUser
方法的内容以调度名为createUser
的 Vuex 动作,并将userData
作为参数传递:
async createUser() {
await this.$store.dispatch('createUser', this.userData);
this.changeRoute('list'); },
vuex.vuejs.org/guide/structure.html
找到有关 Vuex 应用程序结构的更多信息。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Router
和Vuex
添加为所需功能,如“如何做...”部分和“创建简单的 Vuex 存储”教程中所示。打开src/store
文件夹中的index.js
文件。
将export default
转换为一个名为store
的常量,并使其可导出:
export const store = new Vuex.Store({
...UserStore, });
hot-module-reload
插件是否处于活动状态:if (module.hot) {}
hmr
的新常量,其中包含user
文件夹中index.js
,getters.js
,actions.js
和mutations.js
文件的路径:const hmr = [
'./user',
'./user/getters',
'./user/actions',
'./user/mutations', ];
reloadCallback
的新函数。在这个函数中,创建三个常量getters
,actions
和mutations
。每个常量将指向user
文件夹中的等效文件,并调用store.hotUpdate
函数,将一个对象作为参数传递,其中包含您创建的常量的值:const reloadCallback = () => {
const getters = require('./user/getters').default;
const actions = require('./user/actions').default;
const mutations = require('./user/mutations').default; store.hotUpdate({
getters,
actions,
mutations,
}) };
require
函数动态导入的文件末尾添加.default
。accept
函数,将hmr
常量作为第一个参数传递,将reloadCallback
作为第二个参数传递:module.hot.accept(hmr, reloadCallback);
store
:export default store;
vuex.vuejs.org/guide/hot-reload.html
找到有关 Vuex 热重载的更多信息。webpack.js.org/guides/hot-module-replacement/
找到有关 webpack HMR 的更多信息。@vue/cli
@vue/cli-service-global
> vue create vuex-store
Router
和 Vuex
添加为必需功能,如“如何做...”部分和“创建简单的 Vuex 存储”教程中所示。创建新的认证模块
向 Vuex 添加模块
Vuex
模块。这个示例模块将被称为 authentication
,并将存储用户的凭据数据。Vuex
创建 authentication
模块:在 src/store
文件夹中创建一个名为 authentication
的新文件夹。
在这个新创建的文件夹中,创建一个名为 state.js
的新文件,并打开它。
创建一个名为 generateState
的函数,它将返回一个具有 data.username
、data.token
、data.expiresAt
、loading
和 error
属性的 JavaScript 对象:
const generateState = () => ({
data: {
username: '',
token: '',
expiresAt: null,
},
loading: false,
error: null, });
export default
对象。这个对象将是一个 JavaScript 对象。我们将解构 generateState
函数的返回值:export default { ...generateState() };
在 src/store
文件夹中的 authentication
文件夹中创建一个名为 index.js
的新文件,并打开它。
导入新创建的 state.js
文件:
import state from './state';
export default
对象。这个对象将是一个 JavaScript 对象。添加一个名为namespaced
的新属性,其值设置为true
,并添加导入的state
:export default {
namespaced: true,
state, };
打开src/store
文件夹中的index.js
文件。
从authentication
文件夹中导入index.js
文件:
import Vue from 'vue'; import Vuex from 'vuex'; import UserStore from './user'; import Authentication from './authentication';
Vuex.Store
函数中,添加一个名为modules
的新属性,这是一个 JavaScript 对象。然后添加导入的User
和Authentication
模块:export default new Vuex.Store({
...UserStore,
modules: { Authentication,
} })
authentication
的新模块,只有一个状态存在于存储中,并继续使用旧的用户 Vuex 存储,这样将来我们可以将用户存储重构为一个新模块,并将其分离成更具体的、面向领域的架构。vuex.vuejs.org/guide/modules.html
找到有关 Vuex 模块的更多信息。Transition
和TransitionGroup
。Transition
组件钩子执行自定义函数,创建在组件渲染时执行的动画,为组和列表创建动画和过渡,创建可重用的自定义过渡组件,并在组件之间创建无缝过渡。创建你的第一个 CSS 动画
使用 Animate.css 创建自定义过渡类
使用自定义钩子创建交易
在页面渲染时创建动画
为列表和组创建动画
创建自定义过渡组件
在元素之间创建无缝过渡
windows-build-tools
的 NPM 包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行> npm install -g windows-build-tools
命令。> npm install -g @vue/cli @vue/cli-service-global
> vue create {replace-with-recipe-name}
手动选择功能
:? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
CSS 预处理器
作为默认功能之外的附加功能:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:? Save this as a preset for future projects? (y/N) n
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分,删除HelloWorld
组件。添加一个data
属性,并将其定义为一个返回具有名为display
的属性和默认值为true
的 JavaScript 对象的单例函数:<script> export default {
name: 'App',
data: () => ({
display: true,
}), }; </script>
<template>
部分,删除HelloWorld
组件,并添加一个带有文本Toggle
的button
HTML 元素。在img
HTML 元素中,添加一个绑定到display
变量的v-if
指令。最后,在button
HTML 元素中,添加一个click
事件。在事件监听器中,将值定义为一个将display
变量设置为display
变量的否定的匿名函数:<template>
<div id="app">
<button @click="display = !display">
Toggle
</button>
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </div> </template>
Transition
的组件,或者当处理组件列表时,需要使用一个名为TransitionGroup
的组件。Node.js 12+
一个名为cssanimation
的 Vue-CLI 基础项目
@vue/cli
@vue/cli-service-global
cssanimation
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:App.vue
文件。在单文件组件的<template>
部分中,使用Transaction
组件包装img
HTML 元素。在Transaction
组件中,添加一个name
属性,其值为"image"
:<transition name="image">
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
<style>
部分中,创建一个.image-enter-active
类,其中包含一个值为bounce-in .5s
的animation
属性。然后,创建一个.image-leave-active
类,其中包含一个值为bounce-in .5s reverse
的animation
属性:.image-enter-active {
animation: bounce-in .5s; } .image-leave-active {
animation: bounce-in .5s reverse; }
@keyframes bounce-in
CSS 规则。在其中,执行以下操作:创建一个0%
规则,其中包含属性变换和值为scale(0)
。
创建一个50%
规则,其中包含属性变换和值为scale(1.5)
。
创建一个100%
规则,其中包含属性变换和值为scale(1)
:
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
} }
Transition
组件使用预先制作的命名空间来要求必须存在的 CSS 类。这些是-enter-active
,用于组件进入屏幕时,以及-leave-active
,用于组件离开屏幕时。<style>
中创建 CSS 类,用于元素离开和进入屏幕的过渡,以及bounce-in
动画的keyframe
规则集,以定义其行为方式。v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions
找到有关使用 Vue 类进行基于类的动画和过渡的更多信息。developer.mozilla.org/en-US/docs/Web/CSS/@keyframes
找到有关 CSS 关键帧的更多信息。Transition
组件中,可以定义在每个过渡步骤中使用的 CSS 类。通过使用此属性,我们可以使Transition
组件在过渡动画中使用 Animate.css。Transition
组件,以创建自定义过渡效果。Node.js 12+
一个名为animatecss
的 Vue-CLI 基础项目
@vue/cli
@vue/cli-service-global
animatecss
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:> npm install [email protected]
src
文件夹中的main.js
文件并导入 Animate.css 框架:import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
src
文件夹中的App.vue
文件,并将Transition
组件添加为img
HTML 元素的包装器。在Transition
组件中,添加一个名为enter-active-class
的属性,并将其定义为"animated bounceInLeft"
。然后,添加另一个名为leave-active-class
的属性,并将其定义为"animated bounceOutLeft"
:<transition
enter-active-class="animated bounceInLeft"
leave-active-class="animated bounceOutLeft" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
Transition
组件最多可以接收六个属性,可以为交易的每个步骤设置自定义类。这些属性是enter-class
、enter-active-class
、enter-to-class
、leave-class
、leave-active-class
和leave-to-class
。在这个示例中,我们使用了enter-active-class
和leave-active-class
;这些属性定义了元素在屏幕上可见或离开屏幕时的自定义类。bounceInLeft
和bounceOutLeft
来使元素从屏幕中滑入和滑出。enter-active-class
和leave-active-class
属性的类,以查看 CSS 动画在浏览器上的行为。animate.style/
。v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions
找到有关基于类的动画和 Vue 类的过渡的更多信息。animate.style/
找到有关 Animate.css 的更多信息。Transaction
组件具有每个动画生命周期的自定义事件发射器。这些可以用于附加自定义函数和方法,以在动画周期完成时执行。Transaction
组件的自定义事件发射器来执行自定义方法。Node.js 12+
名为transactionhooks
的 Vue-CLI 基础项目
@vue/cli
@vue/cli-service-global
transactionhooks
的新项目,并打开项目文件夹。现在,按照以下步骤:> npm install [email protected]
src
文件夹中打开main.js
文件并导入 Animate.css 框架:import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
src
文件夹中打开App.vue
文件。在单文件组件的<script>
部分,在数据属性中,在单例函数中,添加一个名为status
的新属性,并将其值定义为"appeared"
:data: () => ({
display: true,
status: 'appeared', }),
methods
属性,并将其定义为 JavaScript 对象。在对象内部,添加两个名为onEnter
和onLeave
的属性。在onEnter
属性中,将其定义为一个函数,并在其中将数据status
变量设置为"appeared"
。在onLeave
属性中,将其定义为一个函数,并在其中将数据status
变量设置为"disappeared"
:methods: {
onEnter() {
this.status = 'appeared';
},
onLeave() {
this.status = 'disappeared';
}, },
<template>
部分,添加一个Transition
组件作为img
HTML 元素的包装器。在Transition
组件中,执行以下操作:添加一个名为enter-active-class
的属性,并将其定义为"animated rotateIn"
。
添加另一个名为leave-active-class
的属性,并将其定义为"animated rotateOut"
。
添加事件监听器after-enter
绑定并将其附加到onEnter
方法。
添加事件监听器after-leave
绑定并将其附加到onLeave
方法:
<transition
enter-active-class="animated rotateIn"
leave-active-class="animated rotateOut"
@after-enter="onEnter"
@after-leave="onLeave" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
h1
HTML 元素作为Transition
组件的兄弟元素,并添加文本The image {{ status }}
:<h1>The image {{ status }}</h1>
Transition
组件有八个自定义钩子。 这些钩子由 CSS 动画触发,当它们被触发时,它们会发出自定义事件,可以被父组件使用。 这些自定义事件是before-enter
,enter
,after-enter
,enter-cancelled
,before-leave
,leave
,after-leave
和leave-cancelled
。after-enter
和after-leave
钩子时,当 CSS 动画完成时,屏幕上的文本会相应地更改为每个钩子的事件监听器上定义的函数。v3.vuejs.org/guide/transitions-enterleave.html#javascript-hooks
找到有关转换钩子的更多信息。animate.style/
找到有关 Animate.css 的更多信息。Transition
组件或TransitionGroup
组件来实现这一点。Transition
组件,以便在页面渲染时触发动画。Node.js 12+
一个名为transactionappear
的 Vue-CLI 基础项目
@vue/cli
@vue/cli-service-global
transactionappear
的新项目,并打开项目文件夹。 现在,按照以下步骤:> npm install [email protected]
src
文件夹中的main.js
文件中导入 Animate.css 框架:import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
src
文件夹中的App.vue
文件中,为img
HTML 元素添加一个Transition
组件作为包装器。在Transition
组件中,执行以下操作:添加一个名为appear-active-class
的属性,并将其定义为"animated jackInTheBox"
.
添加一个名为enter-active-class
的属性,并将其定义为"animated jackInTheBox"
。
添加另一个名为leave-active-class
的属性,并将其定义为"animated rollOut"
。
添加appear
属性并将其定义为true
:
<transition
appear
appear-active-class="animated jackInTheBox"
enter-active-class="animated jackInTheBox"
leave-active-class="animated rollOut" >
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </transition>
Transition
组件有一个特殊属性叫做appear
,当启用时,使元素在屏幕上呈现时触发动画。该属性带有三个控制动画 CSS 类的属性,分别为appear-class
、appear-to-class
和appear-active-class
。before-appear
、appear
、after-appear
和appear-cancelled
。jackInTheBox
动画。v3.vuejs.org/guide/transitions-enterleave.html#transitions-on-initial-render
找到有关初始渲染过渡的更多信息。animate.style/
找到有关 Animate.css 的更多信息。TransitionGroup
元素中才能工作。Transition
组件中相同的属性,但要使其工作,您必须为子元素和特定于该组件的组件定义一组特殊指令。Node.js 12+
一个名为transactiongroup
的 Vue-CLI 基础项目
@vue/cli
@vue/cli-service-global
transactiongroup
的新项目并打开项目文件夹。现在,按照以下步骤进行:> npm install [email protected]
src
文件夹中的main.js
文件并导入 Animate.css 框架:import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分中,在data
单例上返回一个名为count
且值为0
的属性:data: () => ({
count: 0, }),
<template>
部分中,删除div#app
HTML 元素内的所有内容。然后,作为div#app
HTML 元素的子元素,创建一个带有tag
属性定义为"ul"
和enter-active-class
属性定义为"animated zoomIn"
的TransitionGroup
组件:<div id="app">
<transition-group
tag="ul"
enter-active-class="animated zoomIn"
></transition-group>
</div>
TransitionGroup
组件的子元素,创建一个带有v-for
指令的li
HTML 元素,对count
变量进行迭代,定义一个名为key
的变量属性,其值为i
,并定义一个style
属性,其值为"float: left"
。作为li
HTML 组件的子元素,创建一个带有src
属性定义为"https://picsum.photos/100"
的img
HTML 组件:<li
v-for="i in count"
:key="i"
style="float: left" >
<img src="https://picsum.photos/100"/> </li>
TransitionGroup
组件的兄弟元素,创建一个带有style
属性定义为"clear: both"
的hr
HTML 元素:<hr style="clear: both"/>
hr
HTML 元素的兄弟,创建一个带有click
事件的button
HTML 元素,将1
添加到当前的count
变量,并将文本设置为Increase
:<button
@click="count = count + 1" >
Increase
</button>
TransitionGroup
元素通过tag
属性中声明的标记创建一个包装器元素。这将通过检查子元素的唯一键来管理将触发动画的自定义元素。因此,TransitionGroup
组件内的所有子元素都需要声明一个key
并且必须是唯一的。ul
和li
HTML 元素创建了一个 HTML 列表,其中TransitionGroup
是使用ul
标签定义的,子元素是使用li
HTML 元素定义的。然后,我们对数字进行了虚拟迭代。这意味着将有一个项目列表,并根据该列表上的项目数量在屏幕上显示图像。button
HTML 元素,每次按下时都会将count
变量的计数增加一。v3.vuejs.org/guide/transitions-list.html#reusable-transitions
找到有关转换组的更多信息。animate.style/
找到有关 Animate.css 的更多信息。Node.js 12+
一个名为customtransition
的 Vue-CLI 基本项目
@vue/cli
@vue/cli-service-global
customtransition
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:> npm install [email protected]
src
文件夹中的main.js
文件中导入 Animate.css 框架:import Vue from 'vue'; import App from './App.vue'; import 'animate.css';
src/components
文件夹中创建一个名为CustomTransition.vue
的新文件,并打开它。在单文件组件的<template>
部分,添加functional
属性以启用组件的函数渲染。然后,创建一个Transition
组件,将appear
变量属性定义为props.appear
。将enter-active-class
属性定义为"animated slideInLeft"
,将leave-active-class
属性定义为"animated slideOutRight"
。最后,在Transition
组件内部,添加一个<slot>
占位符:<template functional>
<transition
:appear="props.appear"
enter-active-class="animated slideInLeft"
leave-active-class="animated slideOutRight"
>
<slot />
</transition> </template>
src
文件夹中的App.vue
文件中打开。在单文件组件的<script>
部分,导入新创建的CustomTransition
组件。在 Vue 对象上,添加一个名为components
的新属性,将其定义为 JavaScript 对象,并添加导入的CustomTransition
组件:<script> import CustomTransition from './components/CustomTransition.vue'; export default {
name: 'App',
components: {
CustomTransition,
}, data: () => ({
display: true,
}), }; </script>
<template>
部分,使用CustomTransition
组件包装img
HTML 元素:<custom-transition>
<img
v-if="display"
alt="Vue logo" src="./assets/logo.png"> </custom-transition>
Transition
组件和过渡 CSS 类的情况下重用过渡:<script>
部分。Transaction
组件作为基础组件。然后,我们使用注入的函数上下文prop.appear
定义了appear
属性,并为过渡添加了动画类,使其在组件呈现时从左侧滑入,销毁时从右侧滑出。img
HTML 元素,并使其作为Transition
组件工作。v3.vuejs.org/guide/transitions-list.html#reusable-transitions
找到有关可重用过渡组件的更多信息。animate.style/
找到有关 Animate.css 的更多信息。Transition
组件和过渡模式属性来定义过渡的方式。Transition
组件和过渡模式属性来创建图像之间的过渡动画。Node.js 12+
一个名为seamlesstransition
的 Vue-CLI 基础项目
@vue/cli
@vue/cli-service-global
seamlesstransition
的新项目,并打开项目文件夹。现在,按照以下步骤进行操作:src
文件夹中打开App.vue
文件。在单文件组件的<style>
部分,创建一个名为.rotate-enter-active,.rotate-leave-active
的属性,并将transition
CSS 样式属性定义为transform .8s ease-in-out;
。然后,创建一个名为.rotate-enter,.rotate-leave-active
的属性,并将transform
CSS 样式属性定义为rotate( -180deg );
,并将transition
定义为.8s ease-in-out;
:.rotate-enter-active, .rotate-leave-active {
transition: transform .8s ease-in-out; } .rotate-enter, .rotate-leave-active {
transform: rotate( -180deg );
transition: transform .8s ease-in-out; }
<template>
部分,用Transition
组件包裹img
HTML 元素。然后,将name
属性定义为rotate
,mode
属性定义为out-in
:<transition
name="rotate"
mode="out-in" ></transition>
Transition
组件中,对img
HTML 元素添加一个key
属性,并将其定义为up
。然后,添加另一个img
HTML 元素并添加一个v-else
指令。添加一个key
属性并将其定义为down
,添加一个src
属性并将其定义为"./assets/logo.png"
,最后添加一个style
属性并将其定义为"transform: rotate(180deg)"
:<img
v-if="display"
key="up"
src="./assets/logo.png"> <img
v-else
key="down"
src="./assets/logo.png"
style="transform: rotate(180deg)" >
Transition
组件有一个特殊的属性叫做mode
,在这里可以定义元素过渡动画的行为。这种行为将创建一组规则,控制动画步骤在Transition
组件内部的发生方式。在组件中可以使用"in-out"
或"out-in"
模式:在"in-out"
行为中,新元素的过渡将首先发生,当它完成后,旧元素的过渡将开始。
在"out-in"
行为中,旧元素的过渡将首先发生,然后新元素的过渡将开始。
"out-in"
模式,这样新元素只会在旧元素完成过渡后才会显示出来。v3.vuejs.org/guide/transitions-enterleave.html
找到有关过渡模式的更多信息。使用 Buefy 创建页面、布局和用户表单
使用 Vuetify 创建页面、布局和用户表单
使用 Ant-Design 创建页面、布局和用户表单
windows-build-tools
的npm
包。为此,请以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
Vue-CLI
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> npm install -g @vue/cli @vue/cli-service-global
Node.js 12+
一个 Vue-CLI 项目
@vue/cli
@vue/cli-service-global
> vue create bulma-vue
手动选择功能
:? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
CSS 预处理器
作为附加功能:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter Unit Testing E2E Testing
Sass/SCSS(使用 node-sass)
:? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) ❯ Sass/SCSS (with node-sass) Less **Stylus**
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
在提交时进行 Lint 和修复
):? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
在专用配置文件中
):? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:? Save this as a preset for future projects? (y/N) n
> vue add buefy
scss
:? Add Buefy style? (Use arrow keys)
none
css
❯ scss
? Include Material Design Icons? (y/N) n
? Include Font Awesome Icons? (y/N) y
在src/components
文件夹中创建一个名为top-menu.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为TopMenu
:
<script> export default {
name: 'TopMenu', }; </script>
<template>
部分,创建一个带有section
类的section
HTML 元素,并添加一个带有container
类的子div
HTML 元素:<section class="section">
<div class="container">
</div>
</section>
div.container
HTML 元素的子级中创建一个b-navbar
组件,并添加一个具有命名插槽brand
的template
占位符组件作为子级。在其中,添加一个带有href
属性定义为#
的b-navbar-item
组件,并添加一个img
HTML 元素作为子级:<b-navbar>
<template slot="brand">
<b-navbar-item href="#">
<img src="https://raw.githubusercontent.com/buefy/buefy/dev
/static/img/buefy-logo.png"
alt="Lightweight UI components for Vue.js based on Bulma"
>
</b-navbar-item>
</template> </b-navbar>
template
占位符之后,创建另一个具有命名插槽start
的template
占位符。在其中,创建两个带有href
属性定义为#
的b-navbar-item
组件。作为同级组件,创建一个带有label
属性定义为Info
的b-navbar-dropdown
组件。在这个组件中,添加两个带有href
属性定义为#
的b-navbar-item
组件作为子级:<template slot="start">
<b-navbar-item href="#">
Home
</b-navbar-item>
<b-navbar-item href="#">
Documentation
</b-navbar-item>
<b-navbar-dropdown label="Info">
<b-navbar-item href="#">
About
</b-navbar-item>
<b-navbar-item href="#">
Contact
</b-navbar-item>
</b-navbar-dropdown> </template>
template
中创建另一个具有命名插槽end
的占位符。创建一个b-navbar-item
组件作为子组件,tag
属性定义为div
,并在该组件的子级中添加一个带有buttons
类的div
HTML 元素。在div
HTML 元素中,创建一个带有button is-primary
类的a
HTML 元素,以及另一个带有button is-light
类的a
HTML 元素:<template slot="end">
<b-navbar-item tag="div">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light">
Log in
</a>
</div>
</b-navbar-item> </template>
在src/components
文件夹中创建一个名为hero-section.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为HeroSection
:
<script> export default {
name: 'HeroSection', }; </script>
<template>
部分,创建一个带有hero is-primary
类的section
HTML 元素,然后添加一个带有hero-body
类的div
HTML 元素作为子级:<section class="hero is-primary">
<div class="hero-body">
</div>
</section>
div.hero-body
HTML 元素内部,创建一个带有container
类的子div
HTML 元素。然后,添加一个带有title
类的h1
HTML 元素作为子级,以及一个带有subtitle
类的h2
HTML 元素:<div class="container">
<h1 class="title">
user Registration
</h1>
<h2 class="subtitle">
Main user registration form
</h2> </div>
在src/components
文件夹中创建一个名为Footer.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个default
JavaScript 对象,其中name
属性定义为FooterSection
:
<script> export default {
name: 'FooterSection', }; </script>
<template>
部分,创建一个带有footer
类的footer
HTML 元素,然后添加一个带有content has-text-centered
类的div
HTML 元素:<footer class="footer">
<div class="content has-text-centered">
</div>
</footer>
div.content
HTML 元素内,创建一个p
HTML 元素作为初始页脚行,并创建第二个p
HTML 元素作为第二行:<p>
<strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy
Thomas</a>
| <strong>Buefy</strong> by
<a href="https://twitter.com/walter_tommasi">Walter
Tommasi</a> </p> <p>
The source code is licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>.
The website content is licensed
<a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC
BY NC SA 4.0</a>.
</p>
在src
文件夹中创建一个名为layouts
的新文件夹,并创建一个名为Main.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入footer-section
组件和top-menu
组件:
import FooterSection from '../components/Footer.vue'; import TopMenu from '../components/top-menu.vue';
default
JavaScript 对象,其中name
属性定义为Mainlayout
,并定义components
属性为导入的组件:export default {
name: 'Mainlayout',
components: {
TopMenu,
FooterSection,
}, };
<template>
部分,创建一个带有子top-menu
组件、一个slot
占位符和footer-section
组件的div
HTML 元素:<template>
<div>
<top-menu />
<slot/>
<footer-section />
</div> </template>
src
文件夹中的App.vue
文件。在单文件组件的<script>
部分,导入main-layout
组件和hero-section
组件:import Mainlayout from './layouts/main.vue'; import HeroSection from './components/heroSection.vue';
default
JavaScript 对象,其中name
属性定义为App
,然后定义components
属性为导入的组件。在 JavaScript 对象中添加data
属性,包括name
、username
、password
、email
、phone
、cellphone
、address
、zipcode
和country
属性:export default {
name: 'App',
components: {
HeroSection,
Mainlayout,
},
data: () => ({
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
}), };
<template>
部分,添加导入的main-layout
组件,并将hero-section
作为子组件添加:<template>
<main-layout>
<hero-section/>
</main-layout>
</template>
hero-section
组件之后,创建一个兄弟section
HTML 元素,带有section
类,并添加一个带有container
类的子div
HTML 元素。在这个div
HTML 元素中,创建一个带有title is-3
类的h1
HTML 元素和一个兄弟hr
HTML 元素:<section class="section">
<div class="container">
<h1 class="title is-3">Personal Information</h1>
<hr/>
</div>
</section>
Name
作为label
的b-field
组件,作为hr
HTML 元素的兄弟,并添加一个带有v-model
指令绑定到name
的子b-input
。对于email
字段,做同样的操作,将label
更改为Email
,并将v-model
指令绑定到email
。在 email 的b-input
中,添加一个定义为email
的type
属性:<b-field label="Name">
<b-input
v-model="name"
/> </b-field> <b-field
label="Email" >
<b-input
v-model="email"
type="email"
/> </b-field>
grouped
属性的b-field
组件作为b-field
组件的兄弟。然后,作为子组件,创建以下内容:一个带有expanded
属性和label
定义为Phone
的b-field
组件。添加一个带有v-model
指令绑定到phone
和type
为tel
的子b-input
组件。
一个带有expanded
属性和label
定义为Cellphone
的b-field
组件。添加一个带有v-model
指令绑定到cellphone
和type
为tel
的子b-input
组件:
<b-field grouped> <b-field
expanded
label="Phone"
>
<b-input
v-model="phone"
type="tel"
/>
</b-field>
<b-field
expanded
label="Cellphone"
>
<b-input
v-model="cellphone"
type="tel"
/>
</b-field> </b-field>
title is-3
类的h1
HTML 元素作为b-field
组件的兄弟,并添加一个hr
HTML 元素作为兄弟。创建一个带有label
定义为Address
的b-field
组件,并添加一个带有v-model
指令绑定到address
的b-input
组件:<h1 class="title is-3">Address</h1> <hr/> <b-field
label="Address" >
<b-input
v-model="address"
/> </b-field>
b-field
组件作为b-field
组件的兄弟,带有grouped
属性。然后,作为子组件,创建以下内容:一个带有expanded
属性和label
定义为Zipcode
的子b-field
组件。添加一个带有v-model
指令绑定到zipcode
和type
属性定义为tel
的b-input
组件。
一个带有expanded
属性和label
定义为Country
的子b-field
组件。添加一个带有v-model
指令绑定到country
和type
属性定义为tel
的b-input
组件:
<b-field grouped>
<b-field
expanded
label="Zipcode"
>
<b-input
v-model="zipcode"
type="tel"
/>
</b-field>
<b-field
expanded
label="Country"
>
<b-input
v-model="country"
/>
</b-field> </b-field>
h1
HTML 元素作为b-field
组件的同级元素,使用title is-3
类,并添加一个hr
HTML 元素作为同级元素。创建一个带有grouped
属性的b-field
组件。创建一个子b-field
组件,带有expanded
属性和label
定义为username
,并添加一个带有v-model
指令绑定到username
的b-input
组件。对于Password
输入,做同样的事情,将label
更改为Password
,在b-input
组件中定义v-model
指令绑定到password
,并添加type
属性为password
:<h1 class="title is-3">user Information</h1> <hr/> <b-field grouped>
<b-field
expanded
label="username"
>
<b-input
v-model="username"
/>
</b-field>
<b-field
expanded
label="Password"
>
<b-input
v-model="password"
type="password"
/>
</b-field> </b-field>
b-field
组件作为b-field
组件的同级元素,定义position
属性为is-right
和grouped
属性。然后,创建两个带有control
类的div
HTML 元素。在第一个div
HTML 元素中,添加一个button
HTML 元素作为子元素,使用button is danger is-light
类,而在第二个div
HTML 元素中,创建一个带有button is-success
类的子button
HTML 元素:<b-field
position="is-right"
grouped
>
<div class="control">
<button class="button is-danger is-light">Cancel</button>
</div>
<div class="control">
<button class="button is-success">Submit</button>
</div>
</b-field>
node-sass
。然后,我们能够使用 Vue-CLI 和 Buefy 插件将 Buefy 框架添加到我们的项目中。使用 Buefy 框架,我们创建了一个布局页面组件,带有页眉菜单组件和页脚组件。bulma.io/
找到有关 Bulma 的更多信息。buefy.org/
找到有关 Buefy 的更多信息。Node.js 12+
一个 Vue-CLI 项目
@vue/cli
@vue/cli-service-global
> vue create vue-vuetify
手动选择特性
:? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
CSS 预处理器
作为默认特性之外的附加特性:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
CSS 预处理器
;选择Sass/SCSS(使用 node-sass)
:? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) ❯ **Sass/SCSS (with node-sass)** Less **Stylus**
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
提交时进行代码检查和修复
):? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
在专用配置文件中
):? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ **In dedicated config files****In package.json**
N
。之后,Vue-CLI 会为你创建一个文件夹并安装依赖项:? Save this as a preset for future projects? (y/N) n
> vue add vuetify
? Choose a preset: (Use arrow keys)
❯ Default (recommended)
Prototype (rapid development)
Configure (advanced)
material.io/design/introduction#goals
找到 Material Design 指南。top-bar
组件,该组件将用于我们页面的布局中:在src/components
文件夹中,创建一个名为TopBar.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个具有name
属性定义为TopBar
的default
JavaScript 对象:
<script> export default {
name: 'TopBar', }; </script>
<template>
部分内,创建一个带有app
,dark
和dense
属性定义为true
,以及color
属性定义为primary
的v-app-bar
组件:<v-app-bar
color="primary" app
dark dense ></v-app-bar>
click
事件的事件监听器的v-app-bar-nav-icon
组件,在按钮被点击时发送一个名为'open-drawer'
的事件:<v-app-bar-nav-icon
@click="$emit('open-drawer')" />
v-app-bar-nav-icon
组件的兄弟,添加一个v-toolbar-title
组件,其中包含页面或应用程序的标题:<v-toolbar-title>Vue.JS 3 Cookbook - Packt</v-toolbar-title>
TopBar
组件中创建的按钮时,这个菜单将被打开:在src/components
文件夹中,创建一个名为DrawerMenu.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个具有三个属性的default
JavaScript 对象:
name
属性,定义为DrawerMenu
。
props
属性,定义为一个 JavaScript 对象,具有一个名为value
的属性。这个属性将是另一个 JavaScript 对象,具有type
,required
和default
属性。type
属性定义为Boolean
,required
属性定义为true
,default
属性定义为false
。
data
属性,作为返回 JavaScript 对象的单例函数。该对象将具有一个menu
属性,我们将其定义为将要使用的菜单项数组。数组将包含具有name
、link
和icon
属性的 Javascript 对象。第一个菜单项的name
定义为Home
,link
定义为*#*
,icon
定义为mdi-home
。第二个菜单项的name
定义为Contact
,link
定义为#
,icon
定义为mdi-email
。最后,第三个菜单项的name
定义为Vuetify
,link
定义为#
,icon
定义为mdi-vuetify
。
<script>
export default {
name: 'DrawerMenu',
props: {
value: {
type: Boolean,
required: true,
default: false,
},
},
data: () => ({
menu: [
{
name: 'Home',
link: '#',
icon: 'mdi-home',
},
{
name: 'Contact',
link: '#',
icon: 'mdi-email',
},
{
name: 'Vuetify',
link: '#',
icon: 'mdi-vuetify',
},
],
}),
};
</script>
<template>
部分,创建一个v-navigation-drawer
组件,其中value
属性作为绑定到value
props 的变量属性,app
属性定义为true
,并在click
事件上添加事件监听器,发送一个'input'
事件:<v-navigation-drawer
:value="value"
app
@input="$emit('input', $event)" ></v-navigation-drawer>
dense
属性定义为true
的v-list
组件。作为子元素,创建一个v-list-item
组件并定义以下内容:v-for
指令遍历menu
项。
key
属性与菜单项的index
。
link
属性定义为true
。
在v-list-item
内部,创建一个带有VIcon
子元素的v-list-item-action
,内部文本为item.icon
。
在v-list-item-action
的同级位置创建一个v-list-item-content
组件,其中v-list-item-title
作为子元素,内部文本为item.name
:
<v-list dense>
<v-list-item
v-for="(item, index) in menu"
:key="index"
link>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{ item.name }}</v-list-item-
title>
</v-list-item-content>
</v-list-item>
</v-list>
在src/components
文件夹中,创建一个名为Layout.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入top-bar
组件和drawer-menu
组件:
import TopBar from './TopBar.vue'; import DrawerMenu from './DrawerMenu.vue';
default
JavaScript 对象,其中name
属性定义为Layout
,然后创建components
属性并导入组件。最后,将data
属性添加为返回 JavaScript 对象的单例函数,其中drawer
属性的值定义为false
:export default {
name: 'Layout',
components: {
DrawerMenu,
TopBar,
},
data: () => ({
drawer: false,
}), };
<template>
部分内,创建一个v-app
组件。作为第一个子元素,添加top-bar
组件,并在open-drawer
事件监听器上添加事件监听器,将drawer
数据属性更改为drawer
属性的否定。然后,作为top-bar
的同级,创建一个drawer-menu
组件,其v-model
指令绑定到drawer
。最后,创建一个v-content
组件,其中包含一个子<slot>
元素:<template>
<v-app>
<top-bar
@open-drawer="drawer = !drawer"
/>
<drawer-menu
v-model="drawer"
/>
<v-content>
<slot/>
</v-content>
</v-app> </template>
<script>
部分:在src
文件夹中,打开App.vue
文件并清空其内容。
导入layout
组件:
import Layout from './components/Layout.vue';
default
JavaScript 对象,其中name
属性定义为App
,然后定义导入组件的components
属性。将computed
和methods
属性定义为空的 JavaScript 对象。然后创建一个data
属性,定义为返回 JavaScript 对象的单例函数。在data
属性中,创建以下内容:一个valid
属性,其值定义为false
;
name
,username
,password
,email
,phone
,cellphone
,address
,zipcode
和country
属性定义为空字符串:
export default {
name: 'App', components: {
Layout,
}, data: () => ({
valid: true,
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
}),
computed: {}, methods: {}, };
computed
属性中,创建一个名为nameRules
的属性;这个属性是一个返回数组的函数,其中包含一个匿名函数,该函数接收一个值并返回该值的验证或错误文本。对passwordRules
和emailRules
属性也做同样的操作。在emailRules
属性中,向返回的数组中添加另一个匿名函数,该函数通过正则表达式检查值是否为有效的电子邮件,如果值不是有效的电子邮件,则返回错误消息:computed: {
nameRules() {
return [
(v) => !!v || 'Name is required',
];
},
passwordRules() {
return [
(v) => !!v || 'Password is required',
];
},
emailRules() {
return [
(v) => !!v || 'E-mail is required',
(v) => /.+@.+\..+/.test(v) || 'E-mail must be valid',
];
}, },
methods
属性内,创建一个名为register
的新属性,它是一个调用$refs.form.validate
函数的函数。还创建另一个名为cancel
的属性,它是另一个调用$refs.form.reset
函数的函数:methods: {
register() {
this.$refs.form.validate();
},
cancel() {
this.$refs.form.reset();
}, },
<template>
部分的时候。在src
文件夹中,打开App.vue
文件。
在<template>
部分,创建一个layout
组件元素,并添加一个带有fluid
属性定义为true
的v-container
组件作为子元素。
<layout>
<v-container
fluid
>
</v-container>
</layout>
v-container
组件内部,创建一个子 HTML h1
元素作为页面标题,以及一个同级的v-subheader
组件作为页面描述。<h1>user Registration</h1> <v-subheader>Main user registration form</v-subheader>
ref
属性定义为form
和lazy-validation
属性定义为true
的v-form
组件。然后,该组件的v-model
指令绑定到valid
变量。创建一个子v-container
组件,其中fluid
属性定义为true
。<v-form
ref="form"
v-model="valid"
lazy-validation >
<v-container
fluid
> </v-container>
</v-form>
v-container
组件内部,创建一个v-row
组件,然后添加一个v-col
组件作为子元素,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。<v-row>
<v-col
cols="12"
>
<v-card
outlined
flat class="mx-auto"
>
</v-card>
</v-col>
</v-row>
v-card
组件的子元素,创建一个带有卡片标题的v-card-title
组件,然后作为同级元素创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件。<v-card-title>
Personal Information
</v-card-title> <v-divider/> <v-container
fluid >
<v-row>
</v-row>
</v-container>
v-row
组件内部,创建一个子v-col
元素,其中cols
属性定义为12
。然后在v-col
组件内部,创建一个v-text-field
,其中v-model
指令绑定到name
变量,rules
变量属性定义为nameRules
计算属性,label
属性定义为Name
,最后,required
属性定义为true
。<v-col
cols="12" >
<v-text-field
v-model="name"
:rules="nameRules"
label="Name"
required
/> </v-col>
v-col
组件的同级元素,创建另一个v-col
组件,其中cols
属性定义为12
。然后,将v-text-field
组件作为子元素添加,其中v-model
指令绑定到email
变量,rules
变量属性定义为emailRules
计算属性,type
属性为email
,label
属性为E-mail
,最后,required
属性定义为true
。<v-col
cols="12" >
<v-text-field
v-model="email"
:rules="emailRules"
type="email"
label="E-mail"
required
/> </v-col>
v-col
组件同级的v-col
组件,并将cols
属性定义为6
。然后,作为子组件添加v-text-field
组件,其中v-model
指令绑定到phone
变量,label
属性定义为Phone
。对于Cellphone
输入,必须更改v-model
指令绑定到cellphone
变量和label
为Cellphone
。<v-col
cols="6" >
<v-text-field
v-model="phone"
label="Phone"
/> </v-col> <v-col
cols="6" >
<v-text-field
v-model="cellphone"
label="Cellphone"
/> </v-col>
地址
卡,作为v-row
组件中v-col
的同级元素。创建一个v-col
组件,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。作为v-card
组件的子元素,创建一个v-card-title
组件作为卡片标题;然后,作为同级元素,创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件:<v-col
cols="12" >
<v-card
outlined
flat class="mx-auto"
>
<v-card-title>
Address
</v-card-title>
<v-divider/>
<v-container
fluid
>
<v-row>
</v-row>
</v-container>
</v-card>
</v-col>
v-container
组件中的v-row
组件内,创建一个v-col
组件,其中cols
属性定义为12
。然后,添加一个v-text-field
作为子组件,其中v-model
指令绑定到address
变量,label
属性定义为Address
:<v-col
cols="12" >
<v-text-field
v-model="address"
label="Address"
/> </v-col>
v-col
组件,其中cols
属性定义为6
。添加一个v-text-field
组件作为子元素。将v-text-field
组件的v-model
指令绑定到zipcode
变量,并将label
属性定义为Zipcode
:<v-col
cols="6" >
<v-text-field
v-model="zipcode"
label="Zipcode"
/> </v-col>
v-col
组件,其中cols
属性定义为6
。作为子元素添加一个v-text-field
组件,其中v-model
指令绑定到country
变量,label
属性定义为Country
:<v-col
cols="6" >
<v-text-field
v-model="country"
label="Country"
/> </v-col>
用户信息
卡作为v-row
组件中v-col
的同级元素。创建一个v-col
组件,其中cols
属性定义为12
。在v-col
组件内部,创建一个带有outlined
属性和flat
定义为true
,以及class
定义为mx-auto
的v-card
组件。作为v-card
组件的子元素,创建一个v-card-title
组件作为卡片标题;然后,作为同级元素,创建一个v-divider
组件。之后,创建一个带有fluid
属性定义为true
的v-container
元素。在v-container
元素内部,创建一个子v-row
组件:<v-col
cols="12" >
<v-card
outlined
flat class="mx-auto"
>
<v-card-title>
user Information
</v-card-title>
<v-divider/>
<v-container
fluid
>
<v-row>
</v-row>
</v-container>
</v-card>
</v-col>
v-container
组件中的v-row
组件内,创建一个v-col
组件,其中cols
属性定义为6
。然后,添加一个v-text-field
作为子组件,其中v-model
指令绑定到username
变量,label
属性定义为username
:<v-col
cols="6" >
<v-text-field
v-model="username"
label="username"
/> </v-col>
v-col
组件,其中cols
属性定义为6
,并添加一个v-text-field
组件作为子级,其中v-model
指令绑定到password
变量,rules
变量属性定义为passwordRules
计算属性,label
属性定义为Password
:<v-col
cols="6" >
<v-text-field
v-model="password"
:rules="passwordRules"
label="Password"
type="password"
required
/> </v-col>
v-row
组件上的v-col
的兄弟,创建一个v-col
组件,其中cols
属性定义为12
,class
属性定义为text-right
。在v-col
组件内部,创建一个v-btn
组件,其中color
属性定义为error
,class
属性为mr-4
,并将click
事件侦听器附加到cancel
方法。最后,创建一个v-btn
组件作为兄弟,其中disabled
变量属性为valid
变量的否定,color
属性为success
,class
属性为mr-4
,并将click
事件侦听器附加到register
方法:<v-col
cols="12"
class="text-right" >
<v-btn
color="error"
class="mr-4"
@click="cancel"
>
Cancel
</v-btn>
<v-btn
:disabled="!valid"
color="success"
class="mr-4"
@click="register"
>
Register
</v-btn> </v-col>
top-bar
组件,其中包含应用程序标题和菜单按钮切换。为了使用菜单,我们创建了drawer-menu
组件来容纳导航项。最后,我们创建了layout
组件来将top-bar
和drawer-menu
组件组合在一起,并添加了一个<slot>
组件来放置页面内容。vuetifyjs.com/en/
找到有关 Vuetify 的更多信息。material.io/
找到有关 Material Design 的更多信息。Node.js 12+
一个 Vue-CLI 项目
@vue/cli
@vue/cli-service-global
> vue create antd-vue
手动选择功能
:? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ **Manually select features**
CSS 预处理器
作为默认功能的附加功能:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router Vuex ❯ CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
CSS 预处理器
;选择Less
:? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules
are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) ❯ Less **Stylus**
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config
ESLint + Prettier
保存时进行 lint
):? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
在专用配置文件中
):? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ **In dedicated config files****In package.json**
N
。之后,Vue-CLI 将为您创建一个文件夹并安装依赖项:? Save this as a preset for future projects? (y/N) n
> vue add ant-design
Fully import
选项:? How do you want to import Ant-Design-Vue?
❯ Fully import
Import on demand
LESS
变量;我们将选择N
:? Do you wish to overwrite Ant-Design-Vue's LESS variables? (y/N)
en_US
:? Choose the locale you want to load
❯ en_US
zh_CN
zh_TW
en_GB
es_ES
ar_EG
bg_BG
(Move up and down to reveal more choices)
top-bar
组件,drawer-menu
组件和layout
包装器。layout
包装器中,我们将有一个top-bar
组件,用于保存用户当前位置的面包屑。现在我们将创建top-bar
组件并使其可用于布局:在src/components
文件夹中,创建一个名为TopBar.vue
的新文件并打开它。
在单文件组件的<script>
部分,我们将导出一个带有name
属性定义为TopBar
的default
JavaScript 对象:
<script> export default {
name: 'TopBar', }; </script>
<style>
部分,我们将使<style>
部分为scoped
,并创建一个名为header-bread
的类。现在,background-color
将被定义为#f0f2f5
,带有一个名为bread-menu
的类,边距定义为16px 0
:<style scoped>
.header-bread {
background-color: #f0f2f5;
} .bread-menu {
margin: 16px 0;
} </style>
<template>
部分,我们将创建一个a-layout-component
组件,class
属性定义为header-bread
。还要添加一个a-breadcrumb
组件作为子元素,class
属性为bread-menu
:<template>
<a-layout-header class="header-bread">
<a-breadcrumb class="bread-menu"> </a-breadcrumb>
</a-layout-header> </template>
a-breadcrumb
组件的子组件,创建两个a-breadcrumb-item
组件,并为每个添加用户位置的方向。在我们的情况下,第一个将是user
,第二个将是Registration Form
:<a-breadcrumb-item>user</a-breadcrumb-item> <a-breadcrumb-item>Registration Form</a-breadcrumb-item>
Drawer
组件:在src/components
文件夹中,创建一个名为Drawer.vue
的文件并打开它。
在单文件组件的<script>
部分,我们将导出一个带有两个属性的default
JavaScript 对象。name
属性定义为Drawer
,data
属性定义为返回 JavaScript 对象的singleton
函数。在data
属性中,创建以下内容:
一个drawer
属性,定义为false
。
一个menu
属性,我们将其定义为将要使用的菜单项数组。菜单数组将包含三个 JavaScript 对象,具有name
和icon
属性。这个数组将包括:
一个 JavaScript 对象,属性name
定义为Home
,icon
定义为home
一个 JavaScript 对象,属性name
定义为Ant Design Vue
,icon
定义为ant-design
一个 JavaScript 对象,属性name
定义为Contact
,icon
定义为mail
:
<script> export default {
name: 'Drawer',
data: () => ({
drawer: false,
menu: [
{ name: 'Home',
icon: 'home',
},
{
name: 'Ant Design Vue',
icon: 'ant-design',
},
{
name: 'Contact',
icon: 'mail',
},
],
}), }; </script>
<template>
部分,创建一个a-layout-sider
组件,v-model
指令绑定到drawer
变量,collapsible
属性定义为true
。作为子组件,创建一个a-menu
组件,default-selected-keys
变量属性定义为['1']
,theme
属性定义为dark
,mode
属性定义为inline
:<template>
<a-layout-sider
v-model="drawer"
collapsible
>
<a-menu
:default-selected-keys="['1']"
theme="dark"
mode="inline"
> </a-menu>
</a-layout-sider> </template>
a-menu
组件内部,创建一个a-menu-item
组件,使用v-for
指令迭代menu
变量,并创建item
和index
临时变量。然后,将key
变量属性定义为index
。创建一个子AIcon
组件,type
变量属性定义为item.icon
,并创建一个兄弟span
HTML 元素,内容为item.name
:<a-menu-item
v-for="(item,index) in menu"
:key="index" >
<a-icon
:type="item.icon"
/>
<span>{{ item.name }}</span> </a-menu-item>
layout
组件。这个组件将把top-bar
组件和Drawer
菜单组件连接起来,作为用户注册页面的包装器:在src/components
文件夹中,创建一个名为Layout.vue
的新文件并打开它。
在单文件组件的<script>
部分,导入top-bar
组件和drawer-menu
组件:
import TopBar from './TopBar.vue'; import Drawer from './Drawer.vue';
default
的 JavaScript 对象,带有一个name
属性,定义为layout
。然后定义components
属性,包括导入的组件。export default {
name: 'layout',
components: {
Drawer,
TopBar,
}, };
<template>
部分,创建一个a-layout
组件,style
属性定义为min-height: 100vh
。然后,将Drawer
组件作为子组件添加。作为drawer
组件的兄弟,创建一个a-layout
组件:<template>
<a-layout
style="min-height: 100vh"
>
<drawer />
<a-layout>
<top-bar />
<a-layout-content style="margin: 0 16px">
<div style="padding: 24px; background: #fff;
min-height: auto;">
<slot />
</div>
</a-layout-content>
<a-layout-footer style="text-align: center">
Vue.js 3 Cookbook | Ant Design ©2020 Created by Ant UED
</a-layout-footer>
</a-layout>
</a-layout> </template>
a-layout
组件添加top-bar
组件和一个兄弟a-layout-content
组件,其中style
属性定义为margin: 0 16px
。作为该组件的子元素,创建一个div
HTML 元素,其中style
属性定义为padding: 24px; background: #fff; min-height: auto;
,并添加一个slot
占位符。最后,创建一个a-layout-footer
组件,其中style
属性定义为text-align:center;
,并添加页面的页脚文本:<top-bar /> <a-layout-content style="margin: 0 16px">
<div style="padding: 24px; background: #fff; min-height: auto;">
<slot />
</div> </a-layout-content> <a-layout-footer style="text-align: center">
Vue.js 3 Cookbook | Ant Design ©2020 Created by Ant UED
</a-layout-footer>
<script>
部分:在src
文件夹中,打开App.vue
文件并清空其内容。
导入layout
组件:
import Layout from './components/Layout.vue';
default
JavaScript 对象,其中name
属性定义为App
,定义导入组件的components
属性,并最后将data
属性定义为返回 JavaScript 对象的单例函数。在data
属性中,创建以下内容:一个labelCol
属性,定义为 JavaScript 对象,其中span
属性和值为4
。
一个wrapperCol
属性,定义为 JavaScript 对象,其中span
属性和值为14
。
一个form
属性,定义为 JavaScript 对象,其中name
,username
,password
,email
,phone
,cellphone
,address
,zipcode
和country
属性都定义为空字符串:
export default {
name: 'App',
components: {
Layout,
},
data() {
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
form: {
name: '',
username: '',
password: '',
email: '',
phone: '',
cellphone: '',
address: '',
zipcode: '',
country: '',
},
};
}, };
<template>
部分了:在src
文件夹中,打开App.vue
文件。
在<template>
部分,创建一个layout
组件元素,并添加一个a-form-model
组件作为子元素,其中model
变量属性绑定到form
,label-col
变量属性绑定到labelCol
,wrapper-col
变量属性绑定到wrapperCol
:
<layout>
<a-form-model
:model="form"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
</a-form-model>
</layout>
layout
组件的兄弟组件,创建一个h1
HTML 元素,页面标题为用户注册
,以及一个p
HTML 元素,页面副标题为主用户注册表单
。然后,创建一个a-card
元素,其中title
属性定义为个人信息
:<h1>
User Registration
</h1> <p>Main user registration form</p> <a-card title="Personal Information"></a-card>
a-card
组件中,创建一个a-form-model-item
组件作为子元素,其中label
属性定义为姓名
,并添加一个子a-input
组件,其中v-model
指令绑定到form.name
变量:<a-form-model-item label="Name">
<a-input v-model="form.name" /> </a-form-model-item>
a-form-model-item
组件,其中label
属性定义为电子邮件
,并添加一个子a-input
组件,其中v-model
指令绑定到form.email
变量,type
属性定义为email
:<a-form-model-item label="Email">
<a-input
v-model="form.email"
type="email"
/> </a-form-model-item>
a-form-model-item
组件,其中label
属性定义为电话
,并添加一个子a-input
组件,其中v-model
指令绑定到form.phone
变量:<a-form-model-item label="Phone">
<a-input v-model="form.phone" /> </a-form-model-item>
a-form-model-item
组件,其中label
属性定义为手机号码
,并添加一个子a-input
组件,其中v-model
指令绑定到form.cellphone
变量:<a-form-model-item label="Cellphone">
<a-input v-model="form.cellphone" /> </a-form-model-item>
a-card
组件的兄弟元素,创建一个a-card
组件,其中title
属性定义为地址
,style
属性定义为margin-top: 16px;
。然后,添加一个子a-form-model-item
组件,其中label
属性定义为地址
,并添加一个子a-input
组件,其中v-model
指令绑定到form.address
变量。<a-card title="Address" style="margin-top: 16px">
<a-form-model-item label="Address">
<a-input v-model="form.address" />
</a-form-model-item> </a-card>
a-card
组件的兄弟元素,创建一个a-form-model-item
组件,其中label
属性定义为邮政编码
,并添加一个子a-input
组件,其中v-model
指令绑定到form.zipcode
变量:<a-form-model-item label="Zipcode">
<a-input v-model="form.zipcode" /> </a-form-model-item>
a-form-model-item
组件,其中label
属性定义为国家
,并添加一个子a-input
组件,其中v-model
指令绑定到form.country
变量:<a-form-model-item label="Country">
<a-input v-model="form.country" /> </a-form-model-item>
a-card
组件的兄弟元素,创建一个a-card
组件,其中title
属性定义为用户信息
,style
属性定义为margin-top: 16px;
。然后,添加一个子a-form-model-item
组件,其中label
属性定义为用户名
,并添加一个子a-input
组件,其中v-model
指令绑定到form.username
变量:<a-card title="user Information" style="margin-top: 16px">
<a-form-model-item label="username">
<a-input v-model="form.username" />
</a-form-model-item> </a-card>
a-form-model-item
组件,其中label
属性定义为密码
,并添加一个子a-input-password
组件,其中v-model
指令绑定到form.password
变量,visibility-toggle
属性定义为true
,type
属性定义为password
:<a-form-model-item label="Password">
<a-input-password
v-model="form.password"
visibility-toggle
type="password"
/> </a-form-model-item>
a-card
组件的一个兄弟组件,创建a-form-model-item
,并将wrapper-col
变量属性定义为 JavaScript 对象{span: 14, offset: 4}
。然后,添加一个子a-button
,其中type
定义为primary
,文本为Create
,另一个a-button
,其中style
属性定义为margin-left: 10px;
,文本为Cancel
:<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary">
Create
</a-button>
<a-button style="margin-left: 10px;">
Cancel
</a-button> </a-form-model-item>
top-bar
组件,用于保存导航面包屑。为了用户导航,我们创建了一个自定义的Drawer
组件,底部带有内联切换按钮。最后,我们创建了layout
组件,将这两个组件放在一起,并添加了一个<slot>
组件来放置页面内容。vue.ant.design/
找到有关 Ant-Design 和 Vue 的更多信息。创建 Netlify 帐户
为在 Netlify 上部署应用程序做准备
为在 GitHub 上的 Netlify 进行自动部署做准备
创建一个 Vercel 帐户
配置 Vercel-CLI 并部署您的项目
为在 GitHub 上的 Vercel 进行自动部署做准备
创建一个 Firebase 项目
配置 Firebase-CLI 并部署您的项目
> npm install -g windows-build-tools
> npm install -g @vue/cli @vue/cli-service-global
> vue create vue-project
Manually select features
:? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
Router
,Vuex
和Linter / Formatter
作为默认功能之外的附加功能:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router ❯ Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
y
(是):? Use history mode for router? (Requires proper server setup for
index fallback in production) (Y**/n) y**
ESLint + Airbnb config
:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
Lint and fix on commit
):? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
In dedicated config files
):? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
N
。之后,Vue-CLI 将为您创建文件夹并安装依赖项:? Save this as a preset for future projects? (y/N) n
一个电子邮件地址
GitHub 账户
一个 GitLab 账户
一个 BitBucket 账户
转到 Netlify 网站www.netlify.com/
,并在页眉菜单中单击注册→。您将被重定向到初始注册页面。
在这个页面上,您可以选择您想要使用的注册 Netlify 的方法。在这个过程中,我们将继续使用电子邮件地址。点击电子邮件按钮,将被重定向到电子邮件注册表单。
填写表单,使用您选择的电子邮件地址和密码。密码规则为最少 8 个字符。填写完表单后,点击注册按钮。然后,您将被重定向到成功页面。
现在,您将在收件箱中收到一封验证电子邮件,您需要这封邮件才能继续使用 Netlify 平台。要继续,请打开您的电子邮件收件箱并检查 Netlify 的电子邮件。
在您的电子邮件收件箱中,打开 Netlify 的电子邮件,然后点击验证电子邮件按钮。此时,将打开一个新窗口,您将能够使用最近注册的电子邮件和密码登录。
在这里,您可以使用您在第 3 步选择的电子邮件地址和密码填写登录表单。之后,点击登录按钮,将被重定向到 Netlify 平台的主窗口。
最后,您将发现自己在 Netlify 平台的主屏幕上,有一个空白页面可以开始在平台上部署。
docs.netlify.com/
找到更多关于 Netlify 的信息。Node.js 12+
一个 Vue 项目
@vue/cli
@vue/cli-service-global
package.json
文件。检查是否已定义build
脚本,如下例所示:"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"lint": "Vue-CLI-service lint" },
> npm run build
确保您的应用程序的build
脚本在主文件夹中创建一个dist
文件夹。
如果您的vue-router
被定义为与历史模式一起工作,您必须在public
文件夹中创建一个_redirects
文件。在这个文件中,您需要添加指示给 Netlify 路由器的指令:
# Netlify settings for single-page application
/* /index.html 200
.gitignore
文件中声明,并且不会发送到您的存储库。package.json
的脚本部分中有build
命令,并验证构建目标是dist
文件夹。_redirects
文件,以指示 Netlify 路由器理解 vue-router 的历史模式。cli.vuejs.org/guide/deployment.html#netlify
上查找有关 Netlify 部署的官方 Vue-CLI 文档的更多信息。docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps
上查找有关 Netlify 路由器重写的更多信息。一个 Netlify 账户
一个 Vue 项目
一个 GitHub 账户
转到 Netlify(www.netlify.com/
),登录并打开您的初始仪表板。在那里,您会找到一个来自 Git 的新站点按钮。您将被重定向到创建新站点页面。
现在,您可以单击 GitHub 按钮打开一个新窗口,以在 GitHub 上进行 Netlify 授权并在那里继续该过程。
使用您的 GitHub 帐户登录,然后您将被重定向到应用程序安装页面。
在此页面上,您可以选择向 Netlify 提供对所有存储库的访问权限,或仅选择的存储库,但请确保使您的应用程序的存储库可用。
在 GitHub 上完成 Netlify 的安装后,您在上一步中授予访问权限的存储库将可以在 Netlify 平台上选择。选择包含您的应用程序的存储库。
完成创建过程,您需要选择用于自动部署的分支。然后,您需要填写应用程序中使用的构建命令,在我们的情况下是npm run build
。打开包含构建文件的文件夹,在我们的情况下是dist
文件夹,并单击“部署站点”按钮。
最后,Netlify-CLI 将启动构建过程,并在构建完成且没有任何错误时发布您的应用程序。
docs.netlify.com/configure-builds/file-based-configuration/
上查找有关高级 Netlify 部署的更多信息。一个 GitHub 账户
一个 GitLab 账户
一个 BitBucket 账户
打开 Vercel 网站(vercel.com/
),并点击顶部栏的注册按钮。您将被重定向到注册页面。
在这里,您可以选择这些存储库管理器中的一个 - GitHub、GitLab 或 BitBucket。我们将继续点击GitHub按钮。选择注册方法后,您将被重定向到授权页面。
在此页面上,您正在授权 Vercel 平台访问您账户上的信息。点击授权按钮后,您将被重定向回 Vercel 仪表板。
最后,您已经创建了 Vercel 账户,并准备好使用。
vercel.com/
找到有关 Vercel 的更多信息。一个 Vercel 账户
一个 Vue 项目
Node.js 12+
@vue/cli
@vue/cli-service-global
vercel
vercel
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:> npm i -g vercel
package.json
文件。检查您是否定义了build
脚本,如下例所示:"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"lint": "Vue-CLI-service lint" },
确保您的应用构建脚本在主文件夹中创建一个dist
文件夹。
在您的project
文件夹中,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> vercel
> No existing credentials found. Please log in:
Enter your email:
输入与您用于登录 Vercel 的仓库管理器相关联的电子邮件地址。您将收到一封带有“验证”按钮的电子邮件;点击它以验证您的电子邮件地址:
一旦您的电子邮件地址得到验证,您就可以使用> vercel
命令在您的终端中部署应用程序。
要将应用程序部署到网络上,我们需要在project
文件夹中执行> vercel
命令,并且在部署之前它会询问一些关于项目设置的问题。第一个问题将涉及项目路径:
? Set up and deploy "~/Versionamento/Vue.js-3.0-Cookbook/chapter-
14/14.5"? [Y/n] y
? Set up and deploy "~/Versionamento/Vue.js-3.0-Cookbook/chapter-
14/14.5"? y
? Which scope do you want to deploy to? ❯ **Heitor Ramon Ribeiro**
n
:? Link to existing project? [Y/n] n
? What's your project's name? vuejscookbook-12-5
package.json
文件所在的位置;在我们的情况下,这将是./
文件夹,或者主项目文件夹:? In which directory is your code located? ./
n
。Auto-detected Project Settings (Vue.js): - Build Command: `npm run build` or `Vue-CLI-service build` - Output Directory: dist - Development Command: Vue-CLI-service serve --port $PORT ? Want to override the settings? [y/N] n
> vercel --prod
vercel.com/docs/cli?query=CLI#getting-started
找到有关 Vercel-CLI 的更多信息。vercel.com/docs/configuration?query=now.json#introduction/configuration-reference
找到有关 Vercel 高级配置的更多信息。Vercel 帐户
存储库管理器上的 Vue 项目
打开您的 Vercel 仪表板(vercel.com/dashboard
)并点击导入项目按钮。
在导入项目页面上,点击继续按钮,位于来自 Git 存储库卡内。
现在,Vercel 网站将询问持有您要导入的项目的存储库的用户是否是您的个人帐户。如果是,请点击是。如果不是,Vercel 将在开始该过程之前将该项目分叉到您的个人帐户。
然后,Vercel 将询问您要将项目绑定到哪个帐户。在我们的情况下,这将是我们的个人帐户。选择它,然后点击继续按钮。
您将被重定向到 GitHub 网页,以授予 Vercel 对您的存储库的访问权限。您可以授予对所有存储库的访问权限,或者只是您想要部署的存储库。在我们的情况下,我们将授予对我们帐户上所有存储库的访问权限。
在您的 GitHub 帐户上安装 Vercel 应用程序后,您将被发送回 Vercel 网页。在这里,您可以定义您正在创建的项目的设置,包括项目名称、您正在使用的预设、构建说明和环境变量。Vercel 将自动检测到我们的项目是一个 Vue-CLI 项目,并为我们配置构建和部署设置。然后,点击部署按钮继续。
Vercel 将启动第一个部署过程。完成后,Vercel 将为您提供应用程序的链接,以及一个打开仪表板的链接。
zeit.co/docs/v2/git-integrations
找到有关 Vercel 与 Git 存储库集成的更多信息。一个 Google 帐户
一个 Vue 项目
打开 Firebase 主页(firebase.google.com/
)并单击页眉菜单中的“登录”链接。如果您已经登录到您的 Google 帐户,请单击“转到控制台”链接。
在控制台页面上,单击“创建项目”按钮以创建新的 Firebase 项目。
Firebase 将要求输入项目名称(您只能使用字母数字字符和空格)。
然后,Firebase 会询问您是否要在此项目中启用 Google Analytics。在我们的情况下,我们将禁用此选项。
最后,您将被重定向到项目概览仪表板。
firebase.google.com
找到有关 Google Firebase 的更多信息。一个 Google 帐户
一个 Vue 项目
一个 Firebase 项目
Node.js 12+
@vue/cli
@vue/cli-service-global
firebase-tools
firebase-tools
,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> npm install -g firebase-tools
> firebase login
Firebase-CLI 将打开一个浏览器窗口,以便您登录到您的 Google 帐户,并允许 Firebase-CLI 访问您的 Google 帐户的部分。(如果浏览器没有自动打开,Firebase-CLI 上会出现一个链接,复制链接,然后粘贴到浏览器中继续。)
在项目的根文件夹中打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> firebase init
Hosting
:**? Which Firebase CLI features do you want to set up
for this folder?**
**Press space to select feature, then Enter to confirm
your choices.**
Database: Deploy Firebase Realtime Database Rules
Firestore: Deploy rules and create indexes for Firestore
Functions: Configure and deploy Cloud Functions ❯ Hosting: Configure and deploy Firebase Hosting sites
Storage: Deploy Cloud Storage security rules Emulators: Set up local emulators for Firebase features
使用现有项目
:? Use an existing project ❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
? **Select a default Firebase project for this directory: (Use arrow**
**keys)** ❯ vue-3-cookbook-firebase-18921 (Vue 3 Cookbook Firebase)
dist
:? **What do you want to use as your project public directory?** **dist**
y
以启用所有 URL 的重写为index.html
,以便我们可以使用vue-router
的历史模式:? Configure as a single-page app (rewrite all urls to /index.html)?
(y/N) y
package.json
文件,并添加一个新的脚本来自动化构建和部署过程:"scripts": {
"serve": "Vue-CLI-service serve",
"build": "Vue-CLI-service build",
"deploy": "npm run build && firebase deploy",
"lint": "Vue-CLI-service lint" },
> npm run deploy
package.json
添加了部署脚本。最后,我们能够部署我们的应用程序并使其对所有人可用。firebase.google.com/docs/hosting
上找到有关 Firebase Hosting 的更多信息。自动加载vue-router
路由
自动加载vuex
模块
创建自定义指令
创建 Vue 插件
使用 Quasar 在 Vue 中创建 SSR,SPA,PWA,Cordova 和 Electron 应用程序
创建更智能的 Vue 观察者和计算属性
使用 Python Flask
创建Nuxt.js
SSR
Vue 应用程序的注意事项和禁忌
Vue-CLI
,Cordova
,Electron
,Quasar
,Nuxt.js
和 Python。windows-build-tools
的npm
包,以便能够安装以下所需的包。为此,请以管理员身份打开 PowerShell 并执行以下命令:> npm install -g windows-build-tools
Vue-CLI
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> npm install -g @vue/cli @vue/cli-service-global
Cordova
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> npm install -g cordova
> npm install -g ios-sim ios-deploy
Electron
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> npm install -g electron
Quasar
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> npm install -g @quasar/cli
Nuxt.js
**,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> npm install -g create-nuxt-app
vue-router
中的路由一样,当应用程序变得更大时,我们会发现大量的文件被手动导入和处理。在这个配方中,我们将学习一个技巧,使用 webpack 的require.context
函数来自动为我们注入文件。@vue/cli
@vue/cli-service-global
Vue-CLI
创建一个新的 Vue 项目,或者使用之前创建的项目:> vue create router-import
CLI 会询问一些问题,这些问题将有助于创建项目。您可以使用箭头键进行导航,使用Enter键继续,使用空格键选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个是手动
模式,您可以在其中选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
Vuex
或Router
(vue-router
),测试工具,代码检查工具等。选择Babel
、Router
和Linter / Formatter
:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support ❯ Router Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
package.json
中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并自动为我们安装包。vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> vue ui
npm
命令:npm run serve
- 本地运行开发服务器
npm run build
- 用于构建和压缩应用程序以进行部署
npm run lint
- 对代码执行 lint
routes
文件夹内的路由文件后,我们需要确保每个路由文件中都有一个默认的export
对象。在src/router
文件夹内的index.js
文件中,删除文件中存在的默认routes
数组:import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default new VueRouter({});
routes
数组,它将由从文件夹中导入的路由填充,并开始导入。这样,requireRoutes
将成为一个对象,其键是文件名,值是文件的 ID:import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const routes = []; const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*\.js$/is, ); const router = new VueRouter({
routes, }); export default router;
routes
数组中,我们需要添加以下代码,并在router
文件夹内创建一个名为routes
的文件夹:import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const routes = []; const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*\.js$/is, ); requireRoutes.keys().forEach((fileName) => {
routes.push({
...requireRoutes(fileName).default,
}); }); const router = new VueRouter({
routes, }); export default router;
routes
文件夹内创建一个新的.js
文件,你的路由就会自动加载到应用程序中。require.context
是 webpack 内置的函数,允许您传入要搜索的目录、一个指示是否应该检查子目录的标志,以及一个匹配文件的正则表达式。require.context
函数,并对其进行预执行,因此导入所需的文件将在最终构建中存在。./routes
定义为文件夹,作为函数的第一个参数自动加载路由。作为函数的第二个参数,我们定义false
,不搜索子目录。最后,作为第三个参数,我们定义/^(?!.*test).*\.js$/is
作为正则表达式,用于搜索.js
文件并忽略文件名中包含.test
的文件。router.js
中,它仍然需要被导入到main.js
文件中。或者,您可以获取import
函数,并将routes
数组传递给router.js
。require.context
的更多信息:webpack.js.org/guides/dependency-management/.
Vuex
模块和存储。为了处理这些模块,我们总是需要通过创建一个包含所有导入文件的文件来导入它们,然后将其导出到 Vuex 存储创建中。require.context
函数的函数,以自动加载并将这些文件注入到 Vuex 存储创建中。@vue/cli
@vue/cli-service-global
Vue-CLI
创建一个新的 Vue 项目,或者使用之前创建的项目:> vue create vuex-import
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个手动
模式,您可以在其中选择更多模式、插件、检查器和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
Vuex
或Router
(vue-router
),测试器,检查器等。选择Babel
,Vuex
和Linter / Formatter
:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router ❯Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
package.json
中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并为我们自动安装包。vue-ui
上检查项目,打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:> vue ui
npm
命令:npm run serve
– 本地运行开发服务器
npm run build
– 为部署构建和压缩应用程序
npm run lint
– 执行代码的 lint
vuex
模块的自动导入,以处理特定文件夹内的路由文件:store
文件夹中创建并放置路由文件后,我们需要确保每个store
文件都有一个默认的export
对象。在src/store
文件夹中的index.js
文件中,我们需要提取stores
或modules
的数组:import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({});
src/store
文件夹中创建另一个名为loader.js
的文件(它将是我们的module
加载器)。重要的是要记住,当使用这个方法时,你将使用vuex
的命名空间,因为所有的stores
都需要作为一个模块使用,并且需要导出为一个单一的 JavaScript 对象。每个文件名将被用作命名空间的引用,并且将被解析为驼峰式文本风格:const toCamel = (s) => s.replace(/([-_][a-z])/ig, (c) => c.toUpperCase()
.replace(/[-_]/g, '')); const requireModule = require.context('./modules/', false,
/^(?!.*test).*\.js$/is); const modules = {}; requireModule.keys().forEach((fileName) => {
const moduleName = toCamel(fileName.replace(/(\.\/|\.js)/g, '')); modules[moduleName] = {
namespaced: true,
...requireModule(fileName).default,
}; }); export default modules;
modules
文件夹中的每个文件,一个好的做法是为每个模块创建一个文件。例如,当你创建一个名为user
的模块时,你需要创建一个名为user.js
的文件,它导入所有的stores
操作、mutations、getters 和 state。这些可以放在一个与模块同名的文件夹中。modules
文件夹的结构将类似于这样:modules
├── user.js
├── user
│ └── types.js
│ └── state.js
│ └── actions.js
│ └── mutations.js
│ └── getters.js
└───────
src/store/modules
文件夹中的user.js
文件将如下所示:import state from './user/state'; import actions from './user/actions'; import mutations from './user/mutations'; import getters from './user/getters'; export default {
state,
actions,
mutations,
getters, };
src/store
文件夹中的index.js
文件中,我们需要添加自动加载的导入模块:import Vue from 'vue'; import Vuex from 'vuex'; import modules from './loader'; Vue.use(Vuex); export default new Vuex.Store({
modules, });
src/store/modules
文件夹中创建一个新的.js
文件,你的vuex
模块就会自动加载到你的应用程序中。require.context
是 webpack 的内置函数,它接收一个目录来执行搜索,一个布尔标志,指示是否在此搜索中包括子目录,以及用于文件名模式匹配的正则表达式(作为参数)。require.context
函数,并预先执行它们,以便导入所需的文件在最终构建中存在。./modules
作为文件夹,false
表示不搜索子目录,/^(?!.*test).*\.js$/is
作为正则表达式来搜索.js
文件并忽略文件名中包含.test
的文件。for
循环将结果添加到vuex
模块的数组中。require.context
的更多信息webpack.js.org/guides/dependency-management/.
v-if
,v-else
和v-for
。@vue/cli
@vue/cli-service-global
Vue-CLI
创建一个新的 Vue 项目,或者使用之前食谱中创建的项目:> vue create vue-directive
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,空格键选择选项。
有两种方法可以启动一个新项目。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,还有一个手动
模式,您可以在其中选择更多模式、插件、代码检查工具和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
Vuex
或Router
(vue-router
),测试工具,代码检查工具等。选择Babel
和Linter / Formatter
:? Check the features needed for your project: (Use arrow keys) ❯ Babel TypeScript Progressive Web App (PWA) SupportRouter
Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
package.json
中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files **In package.json**
? Save this as a preset for future projects? (y/N) n
Vue-CLI
将创建项目,并自动为我们安装软件包。vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> vue ui
npm run serve
- 本地运行开发服务器
npm run build
- 为部署构建和缩小应用程序
npm run lint
- 对代码执行 lint
在src/directives
文件夹中创建名为formMaskInputDirective.js
的文件,并在同一文件夹中创建名为tokens.js
的文件。
在tokens.js
文件中,我们将添加我们的掩码基本令牌。这些令牌将用于识别我们的输入将接受的值类型:
export default {
"#": { pattern: /[\x2A\d]/ },
0: { pattern: /\d/ },
9: { pattern: /\d/ },
X: { pattern: /[0-9a-zA-Z]/ },
S: { pattern: /[a-zA-Z]/ },
A: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleUpperCase() },
a: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleLowerCase() },
"!": { escape: true }
};
token.js
导入令牌并创建我们的函数:import tokens from './tokens';
function maskerValue() {
// Code will be developed in this recipe
}
function eventDispatcher() {
// Code will be developed in this recipe
}
function maskDirective() {
// Code will be developed in this recipe
}
export default maskDirective;
maskDirective
函数中,我们需要检查调用者传递的指令上的绑定值,并检查它是否是有效的绑定。为此,我们首先检查binding
参数上是否存在value
属性,然后将其添加到tokens
中导入的config
变量中:function maskDirective(el, binding) {
let config;
if (!binding.value) return false;
if (typeof config === 'string') {
config = {
mask: binding.value,
tokens,
};
} else {
throw new Error('Invalid input entered');
}
input
HTML 元素。为此,我们将检查指令传递的元素是否具有input
的tagName
,如果没有,我们将尝试在传递的元素中找到input
HTML 元素:let element = el;
if (element.tagName.toLocaleUpperCase() !== 'INPUT') {
const els = element.getElementsByTagName('input');
if (els.length !== 1) {
throw new Error(`v-input-mask directive requires 1 input,
found ${els.length}`);
} else {
[element] = els;
}
}
element.oninput = (evt) => {
if (!evt.isTrusted) return;
let position = element.selectionEnd;
const digit = element.value[position - 1];
element.value = maskerValue(element.value, config.mask,
config.tokens);
while (
position < element.value.length
&& element.value.charAt(position - 1) !== digit
) {
position += 1;
}
if (element === document.activeElement) {
element.setSelectionRange(position, position);
setTimeout(() => {
element.setSelectionRange(position, position);
}, 0);
}
element.dispatchEvent(eventDispatcher('input'));
};
const newDisplay = maskerValue(element.value, config.mask,
config.tokens);
if (newDisplay !== element.value) {
element.value = newDisplay;
element.dispatchEvent(eventDispatcher('input'));
}
return true;
}
// end of maskDirective function
eventDispatcher
函数;这个函数将发出事件,将被v-on
指令监听到:function eventDispatcher(name) {
const evt = document.createEvent('Event');
evt.initEvent(name, true, true);
return evt;
}
maskerValue
函数。该函数接收值、掩码和令牌作为参数。该函数检查当前值与掩码是否匹配,以查看掩码是否完整或值是否是有效令牌。如果一切正常,它将把值传递给输入框:function maskerValue(v, m, tkn) {
const value = v || '';
const mask = m || '';
let maskIndex = 0;
let valueIndex = 0;
let output = '';
while (maskIndex < mask.length && valueIndex < value.length) {
let maskCharacter = mask[maskIndex];
const masker = tkn[maskCharacter];
const valueCharacter = value[valueIndex];
if (masker && !masker.escape) {
if (masker.pattern.test(valueCharacter)) {
output += masker.transform ?
masker.transform(valueCharacter) : valueCharacter;
maskIndex += 1;
}
valueIndex += 1;
} else {
if (masker && masker.escape) {
maskIndex += 1;
maskCharacter = mask[maskIndex];
}
output += maskCharacter;
if (valueCharacter === maskCharacter) valueIndex += 1;
maskIndex += 1;
}
}
let outputRest = '';
while (maskIndex < mask.length) {
const maskCharacter = mask[maskIndex];
if (tkn[maskCharacter]) {
outputRest = '';
break;
}
outputRest += maskCharacter;
maskIndex += 1;
}
return output + outputRest;
}
//end of maskerValue function
main.js
文件中导入掩码指令,并将指令添加到 Vue 中,给指令命名为'input-mask'
:import Vue from 'vue';
import App from './App.vue';
import InputMaskDirective from './directives/formMaskInputDirective';
Vue.config.productionTip = false;
Vue.directive('input-mask', InputMaskDirective);
new Vue({
render: (h) => h(App),
}).$mount('#app');
<template>
部分的input
HTML 元素上调用指令,将token
模板'###-###-###'
作为参数传递给v-input-mask
指令,如下所示:<template>
<div id="app">
<input
type="text"
v-input-mask="'###-###-###'"
v-model="inputMask"
/>
</div>
</template>
<script>
export default {
name: 'app',
data: () => ({
inputMask: '',
}),
};
</script>
bind
。它直接绑定到元素和组件。它有三个参数:element
,binding
和vnode
。main.js
文件中将指令添加到 Vue 中时,它将在整个应用程序中可用,因此该指令已经在App.vue
中,可以被输入框使用。v-input-mask
的同时,我们将第一个参数element
传递给指令,第二个参数binding
是属性的值。@vue/cli
@vue/cli-service-global
Vue-CLI
创建一个新的 Vue 项目,或者使用以前的配方创建的项目:> vue create vue-plugin
CLI 将询问一些问题,这些问题将有助于创建项目。您可以使用箭头键导航,Enter键继续,spacebar选择选项。
有两种启动新项目的方法。默认方法是一个基本的babel
和eslint
项目,没有任何插件或配置,以及手动
模式,您可以在其中选择更多模式、插件、linter 和选项。我们将选择手动
:
? Please pick a preset: (Use arrow keys) default (babel, eslint) ❯ Manually select features
Vuex
或Router
(vue-router
),测试器,linter 等。选择Babel
和Linter / Formatter
:? Check the features needed for your project: (Use arrow keys) ❯ Babel
TypeScript Progressive Web App (PWA) Support Router
Vuex
CSS Pre-processors ❯ Linter / Formatter
Unit Testing E2E Testing
ESLint + Airbnb
配置:? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Use arrow keys) Lint on save ❯ Lint and fix on commit
package.json
中:? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys) ❯ In dedicated config files **In package.json**
? Save this as a preset for future projects? (y/N) n
vue-ui
上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> vue ui
npm run serve
- 本地运行开发服务器
npm run build
- 为部署构建和缩小应用程序
npm run lint
- 执行代码上的 lint
install
函数的对象,当通过Vue.use()
方法调用时将执行该函数。install
函数将接收两个参数:Vue 和将用于实例化插件的选项。$localStorage
和$sessionStorage
:在我们的项目中,我们需要在src/plugin
文件夹中创建一个名为storageManipulator.js
的文件。
在这个文件中,我们将创建插件安装对象-我们将添加默认的插件选项和函数的基本原型:
/* eslint no-param-reassign: 0 */
const defaultOption = {
useSaveFunction: true,
useRetrieveFunction: true,
onSave: value => JSON.stringify(value),
onRetrieve: value => JSON.parse(value),
};
export default {
install(Vue, option) {
const baseOptions = {
...defaultOption,
...option,
};
Vue.prototype.$localStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
Vue.prototype.$sessionStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
},
};
generateStorageObject
函数。这个函数将接收两个参数:第一个是窗口存储对象,第二个是插件选项。通过这样,将可以生成将用于注入到 Vue 中的原型的对象:const generateStorageObject = (windowStorage, options) => ({
set(key, value) {
windowStorage.setItem(
key,
options.useSaveFunction
? options.onSave(value)
: value,
);
},
get(key) {
const item = windowStorage.getItem(key);
return options.useRetrieveFunction ? options.onRetrieve(item) :
item;
},
remove(key) {
windowStorage.removeItem(key);
},
clear() {
windowStorage.clear();
},
});
main.js
中,然后使用Vue.use
函数在我们的 Vue 应用程序中安装插件:import Vue from 'vue';
import App from './App.vue';
import StorageManipulatorPlugin from './plugin/storageManipulator';
Vue.config.productionTip = false;
Vue.use(StorageManipulatorPlugin);
new Vue({
render: h => h(App),
}).$mount('#app');
this.$localStorage
方法或this.$sessionStorage
。Vue.use()
导入我们的插件时,我们告诉 Vue 在导入文件的对象上调用install()
函数并执行它。Vue 将自动将当前 Vue 作为第一个参数传递,并将选项(如果声明了)作为第二个参数传递。install()
函数时,我们首先创建baseOptions
,将默认选项与传递的参数合并,然后将两个新属性注入到 Vue 原型中。这些属性现在因为传递的Vue
参数是应用程序中使用的Vue 全局
,所以在任何地方都可用。generateStorageObject
是浏览器的 Storage API 的纯抽象。我们将其用作插件内原型的生成器。vuejs.org/v2/guide/plugins.html
找到有关 Vue 插件的更多信息。github.com/vuejs/awesome-vue
找到精选的 Vue 插件列表。@quasar/cli
> quasar create quasar-project
Quasar-CLI
将要求您输入项目名称。定义您的项目名称。在我们的情况下,我们选择了quasar_project
:> Project name: quasar_project
Quasar-CLI
将要求输入项目产品名称。这将被移动应用程序用来定义它们的标题名称。在我们的情况下,我们使用了提供的默认名称:> Project product name (must start with letter if building mobile
apps) (Quasar App)
Quasar-CLI
将要求输入项目描述。这在页面被分享时用于搜索引擎的元标记。在我们的情况下,我们使用了提供的默认描述:> Project description: (A Quasar Framework app)
Quasar-CLI
将要求输入项目作者。用package.json
的有效名称填写(例如,Heitor Ribeiro<[email protected]>
):> Author: <You>
Sass with indented syntax
:Pick your favorite CSS preprocessor: (can be changed later) (Use arrow keys) ❯ Sass with indented syntax (recommended)
Sass with SCSS syntax (recommended)
Stylus
None (the others will still be available)
Quasar-CLI
将询问组件和指令的导入策略。我们将使用默认的auto-import
策略:Pick a Quasar components & directives import strategy: (can be
changed later) (Use arrow keys ) ❯ * Auto-import in-use Quasar components & directives - also
treeshakes Quasar; minimum bundle size
* Import everything from Quasar - not treeshaking Quasar;
biggest bundle size
EsLint
:Check the features needed for your project: EsLint
Quasar-CLI
将要求选择 ESLint 的预设。选择Airbnb
预设:Pick an ESLint preset: Airbnb
Quasar-CLI
将要求选择要用于安装项目依赖项的应用程序。在我们的情况下,我们使用了yarn
,因为我们已经安装了它(但您可以选择您喜欢的):Continue to install project dependencies after the project has been
created? (recommended) (Use arrow keys) ❯ Yes, use Yarn (recommended)
Yes, use npm
No, I will handle that myself
> quasar new page About
Quasar-CLI
将自动为我们创建 Vue 页面。我们需要在路由文件中添加对页面的引用,然后该页面将在应用程序中可用:src/router
文件夹中的routes.js
文件,并添加About
页面:const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', name: 'home', component: () =>
import('pages/Index.vue') },
{ path: 'about', name: 'about', component: () =>
import('pages/About.vue') },
],
},
{
path: '*',
component: () => import('pages/Error404.vue'),
}
];
export default routes;
src/pages
文件夹中打开About.vue
文件。您会发现该文件是一个单文件组件,其中有一个空的QPage
组件,因此我们需要在<template>
部分中添加基本标题和页面指示:<template>
<q-page
padding
class="flex flex-start"
>
<h1 class="full-width">About</h1>
<h2>This is an About Us Page</h2>
</q-page>
</template>
<script>
export default {
name: 'PageAbout',
};
</script>
src/layouts
文件夹中的MainLayout.vue
文件中,对于q-drawer
组件,我们需要添加到Home
和About
页面的链接:<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn flat dense round
@click="leftDrawerOpen = !leftDrawerOpen"
aria-label="Menu">
<q-icon name="menu" />
</q-btn>
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<q-drawer v-model="leftDrawerOpen"
bordered content-class="bg-grey-2">
<q-list>
<q-item-label header>Menu</q-item-label>
<q-item clickable tag="a" :to="{name: 'home'}">
<q-item-section avatar>
<q-icon name="home" />
</q-item-section>
<q-item-section>
<q-item-label>Home</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" :to="{name: 'about'}">
<q-item-section avatar>
<q-icon name="school" />
</q-item-section>
<q-item-section>
<q-item-label>About</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
export default {
name: "MyLayout",
data() {
return {
leftDrawerOpen: this.$q.platform.is.desktop
};
}
};
</script>
Quasar-CLI
命令:quasar dev
- 启动开发模式
quasar build
- 构建 SPA
> quasar mode add pwa
Quasar-CLI
将创建一个名为src-pwa
的文件夹,其中将包含我们的service-workers
文件,与我们的主要代码分开。> eslint --fix --ext .js ./src-pwa
service-worker
不在主src
文件夹中?这是因为这些文件专门用于 PWA,并且在除此之外的任何其他情况下都不需要。在不同的构建类型中也会发生相同的情况,例如 Electron,Cordova 和 SSR。root
文件夹中的quasar.conf.js
文件上设置一些特殊标志:pwa: {
// workboxPluginMode: 'InjectManifest',
// workboxOptions: {},
manifest: {
// ...
},
// variables used to inject specific PWA
// meta tags (below are default values)
metaVariables: {
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'default',
appleTouchIcon120: 'statics/icons/apple-icon-120x120.png',
appleTouchIcon180: 'statics/icons/apple-icon-180x180.png',
appleTouchIcon152: 'statics/icons/apple-icon-152x152.png',
appleTouchIcon167: 'statics/icons/apple-icon-167x167.png',
appleSafariPinnedTab: 'statics/icons/safari-pinned-tab.svg',
msapplicationTileImage: 'statics/icons/ms-icon-144x144.png',
msapplicationTileColor: '#000000'
}
}
Quasar-CLI
命令:quasar dev -m pwa
- 以 PWA 模式启动开发模式
quasar build -m pwa
- 将代码构建为 PWA
> quasar mode add ssr
Quasar-CLI
将创建一个名为src-ssr
的文件夹,其中包含我们的extension
和server
启动文件,与我们的主要代码分开。extension
文件不会被babel
转译,并在 Node.js 上下文中运行,因此与 Express 或Nuxt.js
应用程序相同。您可以使用服务器插件,如database
,fileread
和filewrites
。server
启动文件将是我们src-ssr
文件夹中的index.js
文件。与扩展名一样,它不会被babel
转译,并在 Node.js 上下文中运行。对于 HTTP 服务器,它使用 Express,并且如果您配置quasar.conf.js
以向客户端传递 PWA,则可以同时拥有 SSR 和 PWA。root
文件夹中的quasar.conf.js
文件上配置一些特殊标志:ssr: {
pwa: true/false, // should a PWA take over (default: false), or just
// a SPA?
},
Quasar-CLI
命令:quasar dev -m ssr
- 以 SSR 模式启动开发模式
quasar build -m ssr
- 将代码构建为 SSR
quasar serve
- 运行 HTTP 服务器(可用于生产)
> quasar mode add cordova
Quasar-CLI
将询问您一些配置问题:Cordova 应用程序 ID 是什么? (org.cordova.quasar.app)
Cordova 是否可以匿名报告使用统计数据以随时间改进工具? (Y/N) N
Quasar-CLI
将创建一个名为src-cordova
的文件夹,其中将包含一个 Cordova 项目。src-cordova/
├── config.xml
├── packages.json
├── cordova-flag.d.ts
├── hooks/
├── www/
├── platforms/
├── plugins/
src-cordova
文件夹内调用plugman
或cordova plugin add
命令。root
文件夹中的quasar.conf.js
文件上设置一些特殊标志:cordova: { iosStatusBarPadding: true/false, // add the dynamic top padding on
// iOS mobile devices backButtonExit: true/false // Quasar handles app exit on mobile phone
// back button },
Quasar-CLI
命令:quasar.dev/quasar-cli/developing-cordova-apps/preparation#Android-setup
。quasar dev -m cordova -T android
– 以 Android 设备模拟器的形式启动开发模式
quasar build -m cordova -T android
– 作为 Android 构建代码
quasar dev -m cordova -T ios
– 以 iOS 设备模拟器的形式启动开发模式(仅限 macOS)
quasar build -m cordova -T ios
– 以 iOS 设备模拟器的形式启动构建模式(仅限 macOS)
> quasar mode add electron
Quasar-CLI
将创建一个名为src-electron
的文件夹,其中将包含一个 Electron 项目。src-electron/
├── icons/
├── main-process/
├── electron-flag.d.ts
icons
文件夹中,您将找到electron-packager
在构建项目时将使用的图标。在main-process
文件夹中将是您的主要 Electron 文件,分成两个文件:一个仅在开发时调用,另一个在开发和生产时调用。quasar.conf.js
文件上设置一些特殊标志:electron: {
// optional; webpack config Object for
// the Main Process ONLY (/src-electron/main-process/)
extendWebpack (cfg) {
// directly change props of cfg;
// no need to return anything
},
// optional; EQUIVALENT to extendWebpack() but uses webpack-chain;
// for the Main Process ONLY (/src-electron/main-process/)
chainWebpack (chain) {
// chain is a webpack-chain instance
// of the Webpack configuration
},
bundler: 'packager', // or 'builder'
// electron-packager options
packager: {
//...
},
// electron-builder options
builder: {
//...
}
},
packager
键使用electron-packager
模块的 API 选项,builder
键使用electron-builder
模块的 API 选项。Quasar-CLI
命令:quasar dev -m electron
– 以 Electron 模式启动开发模式
quasar build -m electron
– 以 Electron 模式构建代码
quasar.dev/introduction-to-quasar
的文档中了解更多关于 Quasar 框架的信息quasar.dev/quasar-cli/developing-spa/introduction
上阅读有关 Quasar 的 SPA 开发的更多信息quasar.dev/quasar-cli/developing-pwa/introduction
上阅读有关 Quasar 的 PWA 开发的更多信息quasar.dev/quasar-cli/developing-ssr/introduction
上阅读有关 Quasar 的 SSR 开发的更多信息quasar.dev/quasar-cli/developing-cordova-apps/introduction
上阅读有关 Quasar 的移动应用开发的更多信息cordova.apache.org
上阅读有关 Cordova 项目的更多信息quasar.dev/quasar-cli/developing-electron-apps/introduction
上阅读有关 Quasar 的桌面应用程序开发的更多信息electronjs.org/
上阅读有关 Electron 项目的更多信息github.com/electron/electron-packager
上阅读有关electron-packager
的更多信息electron.github.io/electron-packager/master/interfaces/electronpackager.options.html
找到electron-packager
选项 API。www.electron.build/
了解更多关于electron-build
的信息。www.electron.build/configuration/configuration
找到electron-build
选项 API。non-cached
计算和deep-watched
值。<script>
export default {
watch: {
myField: 'myFunction',
},
data: () => ({
myField: '',
}),
methods: {
myFunction() {
console.log('Watcher using method name.');
},
},
};
</script>
deep
属性使其无论值的变化深度如何都执行:<script>
export default {
watch: {
myDeepField: {
handler(newVal, oldVal) {
console.log('Using Immediate Call, and Deep Watch');
console.log('New Value', newVal);
console.log('Old Value', oldVal);
},
deep: true,
immediate: true,
},
},
data: () => ({
myDeepField: '',
}),
};
</script>
<script>
export default {
watch: {
myMultiField: [
'myFunction',
{
handler(newVal, oldVal) {
console.log('Using Immediate Call, and Deep Watch');
console.log('New Value', newVal);
console.log('Old Value', oldVal);
},
immediate: true,
},
],
},
data: () => ({
myMultiField: '',
}),
methods: {
myFunction() {
console.log('Watcher Using Method Name');
},
},
};
</script>
cache
属性设置为false
,您可以使计算属性始终更新值,而不是缓存值:<script>
export default {
computed: {
field: {
get() {
return Date.now();
},
cache: false,
},
},
};
</script>
dateIso
属性get
和set
值:<script>
export default {
data: () => ({
dateMs: '',
}),
computed: {
dateIso: {
get() {
return new Date(this.dateMs).toISOString();
},
set(v) {
this.dateMs = new Date(v).getTime();
},
},
},
};
</script>
vuejs.org/v2/api/#watch
找到有关 Vue watch
API 的更多信息。vuejs.org/v2/api/#computed
找到有关 Vue computed
API 的更多信息。Nuxt.js
是一个服务器端渲染框架,可以在服务器端渲染所有内容并将其加载。通过这个过程,页面获得了 SEO 的力量和在渲染之前快速的 API 获取。Node.js 12+
Python
create-nuxt-app
create-nuxt-app
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> npm install -g create-nuxt-app
flask
flask-restful
flask-cors
flask
,flask-restful
和flask-cors
,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:> **pip** install **flask**
> **pip install flask-restful**
> **pip install flask-cors**
Nuxt.js
。> **pip** install flask
> **pip install flask-restful**
> **pip install flask-cors**
server
的文件夹,并在其中创建一个名为app.py
的文件:import sqlite3 as sql
from flask import Flask
from flask_restful import Resource, Api, reqparse
from flask_cors import CORS
app = Flask(__name__)
api = Api(app)
CORS(app)
parser = reqparse.RequestParser()
conn = sql.connect('tasks.db')
conn.execute('CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY
KEY AUTOINCREMENT, task TEXT)')
conn.close()
ToDo
类,并在类的构造函数中连接到数据库并选择所有的tasks
:class ToDo(Resource):
def get(self):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('SELECT * from tasks')
tasks = cur.fetchall()
con.close()
return {
'tasks': tasks
}
task
作为参数,并将带有添加的task
、添加的status
的对象添加到任务列表中,然后将列表返回给用户: def post(self):
parser.add_argument('task', type=str)
args = parser.parse_args()
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('INSERT INTO tasks(task) values ("
{}")'.format(args['task']))
con.commit()
con.close()
return {
'status': True,
'task': '{} added.'.format(args['task'])
}
task
和id
作为函数的参数。然后,这个函数将使用当前的id
更新task
,并将更新后的task
和更新的status
返回给用户:def put(self, id):
parser.add_argument('task', type=str)
args = parser.parse_args()
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('UPDATE tasks set task = "{}" WHERE id =
{}'.format(args['task'], id))
con.commit()
con.close()
return {
'id': id,
'status': True,
'task': 'The task {} was updated.'.format(id)
}
task
的ID
,然后将返回被移除的ID
、status
和task
给用户: def delete(self, id):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('DELETE FROM tasks WHERE id = {}'.format(id))
con.commit()
con.close()
return {
'id': id,
'status': True,
'task': 'The task {} was deleted.'.format(id)
}
'/'
路由上将ToDo
类作为 API 的资源,并初始化应用程序:api.add_resource(ToDo, '/', '/<int:id>')
if __name__ == '__main__':
app.run(debug=True)
> python server/app.py
http://localhost:5000
上运行并监听。Nuxt.js
应用程序。使用Nuxt.js
的create-nuxt-app
CLI,我们将创建它并为其选择一些选项。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:> create-nuxt-app client
Nuxt-CLI
开始创建项目时,它将首先要求项目名称。在我们的情况下,我们将选择client
作为名称:**Project Name:** **client**
JavaScript
:> Programming language: (Use arrow keys)
❯ JavaScript
TypeScript
Nuxt-CLI
将要求选择将用于安装依赖项的软件包管理器。在我们的情况下,我们选择Yarn
,但您可以选择您喜欢的软件包管理器:> Package manager: (Use arrow keys)
❯ Yarn
npm
Nuxt-CLI
将要求选择在项目中使用的 UI 框架。从可用列表中选择Bulma
:**> UI Framework:** **Bulma**
Nuxt-CLI
将询问您是否要为项目选择额外的模块。我们将从当前模块列表中选择 Axios
:**> Nuxt.JS modules:** **Axios**
Nuxt-CLI
将询问我们想要在项目上使用的 linting 工具;我们将选择 None
:**> Choose Linting tools:** **None**
Nuxt-CLI
将询问我们想要在项目上实现的测试框架;我们将选择 None
:**> Choose Test Framework: None**
Nuxt-CLI
将询问项目将使用的渲染模式;我们将选择 Universal (SSR)
:**> Choose Rendering Mode: Universal (SSR)**
Nuxt-CLI
将询问将在构建结构上使用的部署目标;我们将选择 Server (Node.js hosting)
:> Deployment target: Server (Node.js hosting)
Nuxt-CLI
将询问我们想要使用的开发工具配置;我们将选择 jsconfig.json
:> Development tools: jsconfig.json
client
文件夹。nuxt
配置文件中声明它,方法如下:在 client
文件夹中打开 nuxt.config.js
。
然后,更新 CSS 属性并添加 Bulma 导入,以使其在应用程序的全局范围内可用:
export default {
/* We need to change only the css property for now, */
/* the rest we will maitain the same */
/*
** Global CSS
*/
css: ['bulma/css/bulma.css'],
}
axios
插件:nuxt.config.js
文件,并添加 axios
属性:export default {
/* We need to change only the axios property for now, */
/* the rest we will maitain the same */
axios: {},
}
axios
属性上,添加以下配置属性:HOST
并将其定义为 '127.0.0.1'
PORT
并将其定义为 '5000'
https
并将其定义为 false
debug
并将其定义为 true
:
axios: {
HOST: '127.0.0.1',
PORT: '5000',
https: false,
debug: true, // Only on development
},
Nuxt.js
自带一些预先编程的 npm
脚本。您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行其中之一:npm run dev
- 以开发模式运行服务器
npm run build
- 使用 webpack 构建文件并对生产环境进行 CSS 和 JS 的最小化
npm run generate
- 为每个路由生成静态 HTML 页面
npm start
- 在运行构建命令后,启动生产服务器
<script>
部分<script>
部分:在client/components
文件夹中,创建一个名为TodoList.vue
的文件并打开它。
然后,我们将导出一个default
的 JavaScript 对象,其中name
属性定义为TodoList
,然后将beforeMount
生命周期钩子定义为一个异步函数。将computed
和methods
属性定义为一个空的 JavaScript 对象。然后,创建一个data
属性,定义为返回一个 JavaScript 对象的单例函数。在data
属性中,创建一个taskList
属性,定义为一个空数组:
export default {
name: 'TodoList',
data: () => ({
taskList: [],
}),
computed: {},
async beforeMount() {},
methods: {},
};
computed
属性中,创建一个名为taskObject
的新属性。这个computed
属性将返回Object.fromEntries(new Map(this.taskList))
的结果:taskObject() {
return Object.fromEntries(new Map(this.taskList));
},
methods
属性中,创建一个名为getTask
的新方法 - 它将是一个异步函数。这个方法将从服务器获取任务,然后将使用响应来定义taskList
属性:async getTasks() {
try {
const { tasks } = await
this.$axios.$get('http://localhost:5000');
this.taskList = tasks;
} catch (err) {
console.error(err);
}
},
deleteTask
方法。这个方法将是一个异步函数,并将接收一个id
作为参数。使用这个参数,它将执行一个 API 执行来删除任务,然后执行getTask
方法:async deleteTask(i) {
try {
const { status } = await
this.$axios.$delete(`http://localhost:5000/${i}`);
if (status) {
await this.getTasks();
}
} catch (err) {
console.error(err);
}
},
beforeMount
生命周期钩子中,我们将执行getTask
方法:async beforeMount() {
await this.getTasks();
},
<template>
部分的时候了:在client/components
文件夹中,打开TodoList.vue
文件。
在<template>
部分,创建一个div
HTML 元素,并添加值为box
的class
属性:
<div class="box"></div>
div.box
HTML 元素的子元素,创建一个div
HTML 元素,class
属性定义为content
,具有一个子元素定义为ol
HTML 元素,属性type
定义为1
:<div class="content">
<ol type="1"></ol>
</div>
ol
HTML 元素的子元素,创建一个li
HTML 元素,其中v-for
指令定义为(task, i) in taskObject
,key
属性定义为一个变量i
:<li
v-for="(task, i) in taskObject"
:key="i">
</li>
ol
HTML 元素的子元素,将{{ task }}
添加为内部文本,并作为文本的兄弟元素,创建一个button
HTML 元素,class
属性定义为delete is-small
,@click
事件监听器定义为deleteTask
方法,传递i
变量作为参数:{{ task }}
<button
class="delete is-small"
@click="deleteTask(i)"
/>
<script>
部分:在client/components
文件夹中,创建一个名为TodoForm.vue
的文件并打开它。
然后,我们将导出一个default
JavaScript 对象,其中name
属性定义为TodoForm
,然后将methods
属性定义为空的 JavaScript 对象。然后,创建一个data
属性,定义为返回 JavaScript 对象的单例函数。在data
属性中,创建一个task
属性作为空数组:
export default {
name: 'TodoForm',
data: () => ({
task: '',
}),
methods: {},
};
methods
属性中,创建一个名为save
的方法,这将是一个异步函数。这个方法将task
发送到 API,如果 API 接收到Ok 状态
,它将发出一个带有task
的'new-task'
事件,并清除task
属性:async save() {
try {
const { status } = await
this.$axios.$post('http://localhost:5000/', {
task: this.task,
});
if (status) {
this.$emit('new-task', this.task);
this.task = '';
}
} catch (err) {
console.error(err);
}
},
<template>
部分了:在client/components
文件夹中,打开名为TodoForm.vue
的文件。
在<template>
部分中,创建一个div
HTML 元素,并添加class
属性,值为box
:
<div class="box"></div>
div.box
HTML 元素内部,创建一个div
HTML 元素,class
属性定义为field has-addons
:<div class="field has-addons"></div>
div.field.has-addons
HTML 元素内部,创建一个子div
HTML 元素,class
属性定义为control is-expanded
,并添加一个子输入 HTML 元素,v-model
指令定义为task
属性。然后,将class
属性定义为input
,type
属性定义为text
,placeholder
定义为ToDo Task
。最后,在@keypress.enter
事件监听器中,定义save
方法:<div class="control is-expanded">
<input
v-model="task"
class="input"
type="text"
placeholder="ToDo Task"
@keypress.enter="save"
>
</div>
div.control.is-expanded
HTML 元素的同级位置,创建一个div
HTML 元素,class
属性定义为control
,并添加一个子a
HTML 元素,class
属性定义为button is-info
,在@click
事件监听器中,将其定义为save
方法。在a
HTML 元素的内部文本中,添加Save Task
文本:<div class="control">
<a
class="button is-info"
@click="save"
>
Save Task
</a>
</div>
client/layouts
文件夹中,打开名为default.vue
的文件,删除文件的<style>
部分,并将<template>
部分更改为以下内容:<template>
<nuxt />
</template>
TodoList
并添加一个新的TodoItem
。<script>
部分:在client/pages
文件夹中打开index.vue
文件。
导入我们创建的todo-form
和todo-list
组件,然后我们将导出一个带有components
属性的default
JavaScript 对象,其中包含导入的组件。
<script>
import TodoForm from '../components/TodoForm.vue';
import TodoList from '../components/TodoList.vue';
export default {
components: { TodoForm, TodoList },
};
</script>
<template>
部分的时候了:在client/pages
文件夹中,打开index.vue
文件。
在<template>
部分,创建一个div
HTML 元素,作为子元素添加一个class
属性定义为hero is-primary
的section
HTML 元素。然后,作为section
HTML 元素的子元素,创建一个class
属性定义为hero-body
的div
HTML 元素。作为div.hero-body
HTML 元素的子元素,创建一个class
属性定义为container
的div
HTML 元素,并作为子元素添加一个class
定义为title
的h1
HTML 元素,其中内部文本为Todo App
。
<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<h1 class="title">
Todo App
</h1>
</div>
</div> </section>
section.hero.is-primary
HTML 元素的兄弟元素,创建一个section
HTML 元素,其中class
属性定义为section
,style
属性定义为padding: 1rem
。作为子元素添加一个class
属性定义为container
的div
HTML 元素,其中包含一个ref
属性定义为list
的todo-list
组件。<section
class="section"
style="padding: 1rem" >
<div class="container">
<todo-list
ref="list"
/>
</div> </section>
section.section
HTML 元素的兄弟元素,创建一个section
HTML 元素,其中class
属性定义为section
,style
属性定义为padding: 1rem
。作为子元素添加一个class
属性定义为container
的div
HTML 元素,其中包含一个todo-form
组件,其中@new-task
事件监听器定义为$refs.list.getTasks()
。<section
class="section"
style="padding: 1rem" >
<div class="container">
<todo-form
@new-task="$refs.list.getTasks()" />
</div> </section>
Nuxt.js
提供的 SSR 平台之间的集成。Nuxt.js
SSR 可以在幕后执行很多操作,但完成后会变得空闲,等待用户操作。palletsprojects.com/p/flask/.
了解有关 Flask 和 Python 内部的 HTTP 项目的更多信息。Nuxt.js
的信息,可以在nuxtjs.org/guide/.
阅读文档。Nuxt.js
对 Axios 的实现以及如何配置它和使用插件,可以在axios.nuxtjs.org/options.
阅读文档。bulma.io.
找到更多信息。v-html
指令。Vue-CLI
项目中,选择了检查器选项后,将会创建一个名为.eslintrc.js
的文件,以及项目文件。在这个文件中,一组基本规则将被预先确定。以下是一个ESLint + AirBnb
项目的一组良好实践规则的示例:module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'plugin:vue/recommended',
'plugin:vue/strongly-recommended',
'@vue/airbnb',
],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
};
尽可能使用经过身份验证和加密的 API。请记住,JWT 本身并不是加密的;您需要添加加密层(JWE)才能拥有完整的 JSON。
如果您想存储 API 令牌,请始终使用SessionStorage
。
在将用户输入的 HTML 发送到服务器之前,始终对其进行消毒。
在将 HTML 呈现到 DOM 之前,始终对其进行消毒。
永远要对用户输入的RegeExp
进行转义;它将被执行,以防止任何 CPU 线程攻击。
始终捕获错误,并且不要向用户显示任何堆栈跟踪,以防止任何代码操纵。
永远不要使用eval()
;它会使您的代码运行缓慢,并为恶意代码在您的代码内执行打开一扇门。
永远不要呈现来自用户的任何输入而没有经过消毒或转义的数据。
永远不要在 DOM 上呈现任何未经过消毒的 HTML。
永远不要将 API 令牌存储在LocalStorage
中。
永远不要在 JWT 对象中存储敏感数据。
始终为您的 props 添加类型验证,并在可能的情况下进行验证检查。
避免全局注册组件;使用本地组件。
尽可能使用延迟加载的组件。
使用$refs
而不是直接 DOM 操作。
永远不要在窗口或任何全局范围上存储Vue
,$vm
,$store
或任何应用程序变量。
永远不要修改 Vue 原型;如果需要向原型添加新变量,请创建一个新的 Vue 插件。
不建议直接在组件之间建立连接,因为这将使组件绑定到父级或子级。
github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md
和html5sec.org/
找到有关 XSS(跨站点脚本)的更多信息。eslint.vuejs.org/
找到有关eslint-vue-plugin
的更多信息。github.com/i0natan/nodebestpractices#6-security-best-practices
了解有关 Node.js 安全最佳实践的更多信息。quasar.dev/security/dos-and-donts
找到有关 Vue 应用程序的 dos 和 don'ts 的更多信息。