Vue-与-GraphQL-应用构建指南-全-
Vue 与 GraphQL 应用构建指南(全)
原文:
zh.annas-archive.org/md5/60CC414A1AE322EC97E6A0F8A5BBE3AD
译者:飞龙
前言
自 2012 年 Facebook 发布以来,GraphQL 已经席卷了互联网。像 Airbnb 和 Audi 这样的大公司已经开始采用它,而中小型公司现在也意识到了这种基于查询的 API 的潜力。
GraphQL 起初可能看起来很奇怪,但当你开始阅读和体验更多时,你就不会再想使用 REST API 了。
通过本书中的示例,你将学习如何从头开始构建一个完整的实时聊天应用程序。首先创建一个 AWS Amplify 环境,然后深入开发你的第一个 GraphQL 模式。然后学习如何添加 AppSync GraphQL 客户端并创建你的第一个 GraphQL 变异。本书还将帮助你发现 GraphQL 的简单性和数据获取能力,使前端开发人员能够轻松与服务器通信。最后,你将了解如何使用 Quasar Framework 创建应用程序组件和布局。最后,你将了解如何在应用程序中创建 Vuex 模块来管理应用程序状态,使用 GraphQL 客户端获取数据,并将应用程序部署到 Web 上。
这本书适合谁
这本书适合中级 Vue.js 开发人员,他们想迈出全栈开发的第一步。如果你想了解更多关于使用自定义业务规则开发 Vuex 以及创建入门级企业架构应用程序,那么这本书适合你。在开始阅读本书之前,需要具备 Vue.js 和 JavaScript 的基础知识。
这本书涵盖了什么
第一章,数据绑定、表单验证、事件和计算属性,讨论了基本的 Vue 开发和组件概念,包括v-model
、事件监听器、计算属性和for
循环。读者将介绍如何使用 Vuelidate 插件进行表单验证以及如何在 Vue 组件上使用它,以及如何使用vue-devtools
调试 Vue 组件。
第二章,组件、混入和功能组件,引导读者通过不同的方法构建组件,包括用于内容的自定义插槽、验证的 props、功能组件以及为了代码重用性而创建的混入。然后介绍了一系列不同的方法来访问子组件的数据,创建依赖注入组件和动态注入组件,以及如何延迟加载组件。
第三章,“设置我们的聊天应用程序-AWS Amplify 环境和 GraphQL”,介绍了 AWS Amplify CLI,介绍了如何创建 Amplify 环境。创建他们的身份验证网关与 AWS Cognito,一个 S3 文件托管桶,最后创建 GraphQL API。在这个过程中,读者将创建用于前端和后端通信的驱动程序。
第四章,“创建自定义应用程序组件和布局”,从现在开始,读者将开始开发应用程序。在这一章中,读者将创建用于聊天应用程序页面的组件。读者将创建组件,如PasswordInput
,AvatarInput
,EmailInput
等。
第五章,“创建用户 Vuex、页面和路由”,引导读者构建应用程序的第一个 Vuex 模块,用于管理用户业务规则和存储用户数据。然后读者将创建用户相关的注册、编辑和验证页面。最后,读者将把这些页面添加到 vue-router 模式中。
第六章,“创建聊天和消息 Vuex、页面和路由”,读者将继续创建应用程序的 Vuex 模块。现在是创建聊天模块的时候了。这个模块将包含用户之间通信的业务规则和存储聊天数据。最后,用户将创建与聊天列表和聊天页面相关的页面,然后将其添加到 vue-router 模式中。
第七章,“将您的应用程序转变为 PWA 并部署到 Web”,在这最后一章中,读者将通过将应用程序转变为 PWA 应用程序,为 iOS 设备添加更新通知和安装横幅来完成应用程序。最后,用户将把应用程序部署到 Web 上。
为了充分利用本书
本书从 第二章 组件、混合和功能组件 开始使用 Vue.js 2.7,因为这是写作时 Quasar Framework 的最新支持版本。本书将在 第三章 设置我们的聊天应用 - AWS Amplify 环境和 GraphQL 中使用 Vue.js 3 的代码。所有代码将在 GitHub 存储库的最终版本发布时进行更新:github.com/PacktPublishing/Building-Vue.js-Applications-with-GraphQL
您需要安装 Node.js 12+,将 Vue CLI 更新到最新版本,并且需要一个良好的代码编辑器。其他要求将在每个示例中介绍。所有软件要求都适用于 Windows、macOS 和 Linux。
以下是总结所有要求的表格:
章节编号 | 书中涉及的软件/硬件 | 下载链接 | 操作系统要求 |
---|---|---|---|
1 到 7 | Vue CLI 4.X | cli.vuejs.org/ |
Windows / Linux / macOS |
3 到 7 | Quasar-CLI 1.X | quasar.dev/ |
Windows / Linux / macOS |
3 到 7 | Visual Studio Code 1.4.X 和 IntelliJ WebStorm 2020.2 | code.visualstudio.com/ |
Windows / Linux / macOS |
3 到 7 | AWS Amplify CLI 3.3.X | aws.amazon.com/appsync/resources/ |
Windows / Linux / macOS |
1 到 7 | Node.js 12+- | nodejs.org/en/download/ |
Windows / Linux / macOS |
如果您使用的是本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一部分中提供)。这样做将帮助您避免与复制和粘贴代码相关的任何潜在错误。
下载示例代码文件
您可以从您在 www.packt.com 的帐户中下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
-
在 www.packt.com 上登录或注册。
-
选择 Support 选项卡。
-
点击 Code Downloads。
-
在 Search 框中输入书名,并按照屏幕上的说明操作。
一旦文件下载完成,请确保使用最新版本的解压软件解压文件夹:
-
Windows 的 WinRAR/7-Zip
-
Mac 的 Zipeg/iZip/UnRarX
-
Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Building-Vue.js-Applications-with-GraphQL
。如果代码有更新,将在现有的 GitHub 存储库上更新。
我们还有其他代码包,来自我们丰富的图书和视频目录,可以在github.com/PacktPublishing/
上找到。去看看吧!
使用的约定
本书中使用了许多文本约定。
CodeInText
:表示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 账号。这是一个例子:“为了做到这一点,以管理员身份打开 PowerShell 并执行> npm install -g windows-build-tools
命令。”
一个代码块设置如下:
<template>
<header>
<div id="blue-portal" />
</header>
</header>
任何命令行输入或输出都以以下方式书写:
> npm run serve
粗体:表示一个新术语,一个重要词,或者你在屏幕上看到的词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“点击电子邮件按钮,将被重定向到电子邮件注册表格”
警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。
部分
在本书中,你会经常看到几个标题(准备工作,如何做,它是如何工作的,还有更多,和另请参阅)。
为了清晰地说明如何完成一个食谱,使用以下部分:
准备工作
这一部分告诉你在食谱中可以期待什么,并描述如何设置任何软件或食谱所需的任何初步设置。
如何做…
这一部分包含了遵循食谱所需的步骤。
它是如何工作的…
这一部分通常包括对前一部分发生的事情的详细解释。
还有更多…
这一部分包括了有关食谱的额外信息,以使你对食谱更加了解。
另请参阅
这一部分为食谱提供了其他有用信息的链接。
第一章:数据绑定、事件和计算属性
数据是当今世界上最有价值的资产,知道如何管理它是必须的。在 Vue 中,我们有权利选择如何收集这些数据,按照我们的意愿进行操作,并将其传递到服务器。
在本章中,我们将更多地了解数据处理和数据处理过程,表单验证,数据过滤,如何向用户显示这些数据,以及如何以与应用程序内部不同的方式呈现它。
我们将学习如何使用各种vue-devtools
,以便我们可以深入了解 Vue 组件并查看我们的数据和应用程序发生了什么。
在本章中,我们将涵盖以下配方:
-
使用 Vue CLI 创建您的第一个项目
-
创建 hello world 组件
-
创建具有双向数据绑定的输入表单
-
在元素上添加事件监听器
-
从输入中删除
v-model
指令 -
创建动态待办事项列表
-
创建计算属性并了解它们的工作原理
-
使用自定义过滤器显示更清洁的数据和文本
-
为列表创建过滤器和排序器
-
创建条件过滤器以对列表数据进行排序
-
添加自定义样式和过渡
-
使用
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
使用 Vue CLI 创建您的第一个项目
当 Vue 团队意识到开发人员在创建和管理他们的应用程序时遇到问题时,他们看到了一个机会,可以创建一个工具来帮助世界各地的开发人员。有了这个,Vue CLI 项目诞生了。
Vue CLI 工具是一个在 terminal 命令行中使用的 CLI 工具,如 Windows PowerShell、Linux Bash 或 macOS Terminal。它被创建为 Vue 开发的起点,开发人员可以启动一个项目并顺利地管理和构建它。Vue CLI 团队的重点是为开发人员提供更多时间思考代码,花费更少的时间在工具上,将他们的代码投入生产,添加新的插件或简单的 hot-module-reload
。
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 等。对于这个项目,我们将选择
CSS 预处理器
并按 Enter 继续:
? Check the features needed for your project: (Press <space> to
select, <a> to toggle all, <i> to invert selection)
❯ Choose Vue version
❯ Babel
TypeScript
Progressive Web App (PWA) Support
Router
Vuex
CSS Pre-processors
❯ Linter / Formatter
Unit Testing
E2E Testing
- CLI 会要求你选择一个 Vue 版本来启动你的应用程序。我们将在这里选择
3.x (Preview)
。按 Enter 继续:
? Choose a version of Vue.js that you want to start the project with
(Use arrow keys)
2.x
❯ 3.x (Preview)
- 可以选择与 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
- 一旦代码检查规则被设置,我们需要定义它们何时应用于我们的代码。它们可以在保存时应用,或者在提交时进行修复:
? Pick additional lint features:
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
- CLI 将自动创建以步骤 1中设置的名称命名的文件夹,安装所有内容并配置项目。
有了这些,现在您可以导航并运行项目了。Vue CLI 项目的基本命令如下:
-
npm run serve
:在本地运行开发服务器 -
npm run build
:用于构建和缩小应用程序以进行部署 -
npm run lint
:对代码执行 lint
您可以通过终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)执行这些命令。
还有更多...
CLI 内部有一个名为 Vue UI 的工具,可帮助您管理 Vue 项目。这个工具将负责项目的依赖关系、插件和配置。
Vue UI 工具中的每个npm
脚本都被称为一个任务,在这些任务中,您可以收集实时统计数据,如资产、模块和依赖项的大小;错误或警告的数量;以及更深入的网络数据,以微调您的应用程序。
要进入 Vue UI 界面,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue ui
另请参阅
-
您可以在
cli.vuejs.org/guide/
找到有关 Vue CLI 项目的更多信息。 -
你可以在
cli.vuejs.org/dev-guide/plugin-dev.html
找到有关 Vue CLI 插件开发的更多信息。
创建 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键继续,使用Spacebar选择选项。选择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 { createApp } from 'vue'; import './style.css'; import App from './App.vue'; createApp(App).mount('#app');
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
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.vue'; export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput,
}, }; </script>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
当您创建一个 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.vue'; export default {
name: 'TodoApp',
components: {
CurrentTime,
TaskInput,
},
methods: {
addNewTask(task) {
alert(`New task added: ${task}`);
},
}, }; </script>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
以下是渲染和运行的组件:
它是如何工作的...
Vue 使用v-on
事件处理指令来读取 HTML 事件。当我们将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 项目,就像我们在使用 Vue CLI 创建您的第一个项目配方中学到的那样,或者使用向元素添加事件监听器配方中的项目。
如何操作...
通过执行以下步骤,我们将从输入中删除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
记得始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
它是如何工作的...
作为一种语法糖,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 项目,就像我们在使用 Vue CLI 创建第一个项目这个教程中学到的那样,或者使用从输入中删除 v-model 指令这个教程中的项目。
如何做...
制作待办事项应用程序涉及一些基本原则-它必须包含一个任务列表,任务可以标记为已完成和未完成,并且列表可以进行过滤和排序。现在,我们将学习如何将任务添加到任务列表中。
按照以下步骤使用 Vue 和从之前教程中获得的信息创建一个动态的待办事项列表:
- 在
App.vue
文件中,我们将创建我们的任务数组。每当TaskInput.vue
组件发出消息时,这个任务将被填充。我们将向这个数组添加一个包含任务以及任务创建的当前日期的对象。目前,任务完成的日期将被留空。为了做到这一点,在组件的<script>
部分,我们需要创建一个接收任务并将任务与当前日期添加到taskList
数组中的方法:
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput.vue';
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>
始终重要的是要记住迭代器中的键必须是唯一的。这是因为render
函数需要知道哪些元素已更改。在此示例中,我们添加了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
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
当我们从组件接收到发射的消息时,我们使用更多数据对消息进行了处理,并将其推送到本地数组变量中。
在模板中,我们迭代此数组,将其转换为任务列表。这显示了我们需要完成的任务、标记任务完成的复选框以及任务完成的时间。
当用户单击复选框时,它会执行一个函数,该函数将当前任务标记为已完成。如果任务已经完成,该函数将将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 项目。
操作步骤
按照以下步骤创建一个计算属性并了解它的工作原理:
- 在
App.vue
文件的<script>
部分,我们将在data
和method
之间添加一个新属性,称为computed
。这是computed
属性将被放置的地方。我们将创建一个名为displayList
的新计算属性,用于在模板上呈现最终列表:
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput.vue';
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
记得始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
工作原理
当使用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 项目。
如何做...
按照以下步骤创建您的第一个自定义 Vue 过滤器:
- 在
App.vue
文件中,在<script>
部分,在方法中,创建一个formatDate
函数。这个函数将接收value
作为参数并输入过滤器管道。我们可以检查value
是否是一个数字,因为我们知道我们的时间是基于 Unix 时间戳格式的。如果它是一个数字,我们将根据当前浏览器位置进行格式化,并返回该格式化的值。如果值不是一个数字,我们只是返回传递的值。
<script>
import CurrentTime from './components/CurrentTime.vue';
import TaskInput from './components/TaskInput.vue';
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
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
过滤器是接收一个值并必须返回一个值以在文件的<template>
部分中显示或在 Vue 属性中使用的方法。
当我们将值传递给formatDate
方法时,我们知道它是一个有效的 Unix 时间戳,因此可以调用一个新的Date
类构造函数,将value
作为参数传递,因为 Unix 时间戳是一个有效的日期构造函数。
我们过滤器背后的代码是Intl.DateTimeFormat
函数,这是一个本地函数,可用于格式化和解析日期到指定的位置。要获取本地格式,我们可以使用全局变量navigator
。
另请参阅
您可以在developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
找到有关Intl.DateTimeFormat
的更多信息。
为列表创建过滤器和排序器
在处理列表时,通常会遇到原始数据。有时,您需要对这些数据进行过滤,以便用户可以阅读。为此,我们需要一组计算属性来形成最终的过滤器和排序器。
在这个教程中,我们将学习如何创建一个简单的过滤器和排序器,来控制我们最初的待办任务列表。
准备工作
这个教程的先决条件是 Node.js 12+。
本教程所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
我们可以继续进行待办事项列表项目,或者按照我们在使用 Vue CLI 创建您的第一个项目教程中学到的内容,创建一个新的 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
记得始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
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 项目。
如何做...
按照以下步骤添加条件过滤器以对列表数据进行排序:
- 在
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
请记住始终执行命令 npm run lint --fix
,以自动修复任何代码 lint 错误。
组件已呈现并运行:
工作原理...
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 项目,就像我们在使用 Vue CLI 创建您的第一个项目中学到的那样。
如何做...
按照以下步骤为您的组件添加自定义样式和过渡效果:
- 在
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
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
工作原理...
每当我们的应用程序中的新项目被标记为已完成时,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
扩展,可以调试我们的应用程序,在更改代码之前尝试新数据,执行函数而无需直接在代码中调用它们,等等。
在本教程中,我们将学习如何使用各种开发工具来了解我们的应用程序,并了解它们如何帮助我们的调试过程。
准备工作
本教程的先决条件是 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
我们可以继续进行我们的待办事项列表项目,或者使用 Vue CLI 创建一个新的 Vue 项目,就像我们在使用 Vue CLI 创建您的第一个项目中学到的那样。
如何做...
在开发任何 Vue 应用程序时,始终将vue-devtools
作为良好的实践进行开发。
按照以下步骤了解如何使用vue-devtools
以及如何正确调试 Vue 应用程序:
- 要进入
vue-devtools
,您需要在浏览器中安装它,因此请查看本教程的准备就绪部分,获取 Chrome 或 Firefox 扩展程序的链接。在您的 Vue 开发应用程序中,进入浏览器开发者检查器模式。将出现一个名为 Vue 的新标签页:
- 您将看到的第一个标签页是组件标签页。此标签显示应用程序组件树。如果单击组件,您将能够查看所有可用数据,计算属性以及插件(如
vuelidate
,vue-router
或vuex
)注入的额外数据。您可以编辑此数据以实时查看应用程序中的更改:
- 第二个标签页用于 Vuex 开发。此标签将显示变化的历史记录,当前状态和获取器。可以检查每个变化的传递负载,并进行时间旅行变化,以回到过去并查看状态中的 Vuex 更改:
- 第三个标签页专门用于应用程序中的事件发射器。在此处显示应用程序中发射的所有事件。您可以通过单击事件来检查发射的事件。通过这样做,您可以看到事件的名称,类型,事件源(在本例中是组件)以及负载:
- 第四个标签页专门用于 vue-router 插件。在那里,您可以查看其导航历史,以及传递给新路由的所有元数据。这是您可以检查应用程序中所有可用路由的地方:
- 第五个选项卡是性能选项卡。在这里,您可以检查组件的加载时间以及应用程序实时运行的每秒帧数。以下屏幕截图显示了当前应用程序的每秒帧数,以及所选组件的每秒帧数:
以下屏幕截图显示了组件的生命周期钩子性能以及执行每个钩子所需的时间:
- 第六个选项卡是您的设置选项卡。在这里,您可以管理扩展程序并更改其外观,内部行为以及在 Vue 插件中的行为:
- 最后一个选项卡是
vue-devtools
的刷新按钮。有时,当发生热模块重新加载
或当应用程序组件树中发生一些复杂事件时,扩展程序可能会丢失对发生情况的跟踪。此按钮强制扩展程序重新加载并再次读取 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
创建一个可视化模板组件
组件可以是数据驱动的、无状态的、有状态的或简单的可视化组件。但是什么是可视化组件?可视化组件是一个只有一个目的的组件:视觉操作。
一个可视化组件可以有一个简单的作用域 CSS 和一些div
HTML 元素,或者它可以是一个更复杂的组件,可以实时计算元素在屏幕上的位置。
在这个示例中,我们将创建一个遵循 Material Design 指南的卡片包装组件。
准备工作
此示例的先决条件是 Node.js 12+。
此示例所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
如何做...
要启动我们的组件,我们需要使用 Vue CLI 创建一个新的 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>
- 在
App.vue
文件中,我们需要导入我们的组件以便能够看到它:
<template>
<div id='app'>
<material-card-box />
</div>
</template>
<script>
import MaterialCardBox from './components/MaterialCardBox.vue';
export default {
name: 'app',
components: {
MaterialCardBox
}
}
</script>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
工作原理...
视觉组件是一个将包装任何组件并将包装数据与自定义样式放在一起的组件。由于此组件与其他组件混合,因此它可以形成一个新的组件,而无需您在代码中重新应用或重写任何样式。
另请参阅
-
关于 Scoped CSS 的更多信息,请访问
vue-loader.vuejs.org/guide/scoped-css.html#child-component-root-elements
。 -
关于 Material Design 卡片的更多信息,请访问
material.io/components/cards/
。 -
查看
fonts.google.com/specimen/Roboto
上的 Roboto 字体系列。
使用插槽和命名插槽将数据放入组件中
有时,拼图的一些部分会丢失,你会发现自己有一个空白的地方。想象一下,你可以用自己制作的一块填补那个空白的地方 - 而不是拼图盒子里原来的那块。这大致类似于 Vue 插槽的作用。
Vue 插槽就像是组件中的开放空间,其他组件可以用文本、HTML 元素或其他 Vue 组件填充。你可以在组件中声明插槽的位置和行为方式。
通过这种技术,你可以创建一个组件,并在需要时轻松自定义它。
准备工作
这个食谱的先决条件是 Node.js 12+。
此食谱所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
要完成这个食谱,我们将使用我们的 Vue 项目和 Vue CLI,就像在创建可视化模板组件食谱中所做的那样。
操作步骤
按照以下说明在组件中创建插槽和命名插槽:
-
在
components
文件夹中打开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
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
插槽是可以放置任何可以呈现到 DOM 中的东西的地方。我们选择插槽的位置,并告诉组件在接收到任何信息时在哪里呈现。
在这个示例中,我们使用了命名插槽,这些插槽旨在与需要多个插槽的组件一起使用。要在 Vue 单文件(.vue
)的<template>
部分中放置组件内的任何信息,您需要添加v-slot:
指令,以便 Vue 知道在哪里放置传递下来的信息。
另请参阅
-
您可以在
v3.vuejs.org/guide/component-slots.html
找到有关 Vue 插槽的更多信息。 -
您可以在
material.io/components/cards/#anatomy
找到有关 Material Design 卡片解剖的更多信息。
向组件传递数据并验证数据
到目前为止,您知道如何通过插槽将数据放入组件中,但这些插槽是为 HTML DOM 元素或 Vue 组件而设计的。有时,您需要传递诸如字符串、数组、布尔值甚至对象之类的数据。
整个应用程序就像一个拼图,其中每个部分都是一个组件。组件之间的通信是其中的重要部分。向组件传递数据是连接拼图的第一步,而验证数据是连接部件的最后一步。
在这个示例中,我们将学习如何向组件传递数据并验证传递给它的数据。
准备工作
这个食谱的先决条件是 Node.js 12+。
这个食谱所需的 Node.js 全局对象如下:
-
@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>
- 要运行服务器并查看你的组件,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve
记得总是执行命令 npm run lint --fix
,自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
每个 Vue 组件都是一个 JavaScript 对象,有一个渲染函数。当需要在 HTML DOM 中渲染它时,会调用这个渲染函数。单文件组件是这个对象的一个抽象。
当我们声明我们的组件具有可以传递的唯一 props 时,它为其他组件或 JavaScript 打开了一个小门,以在我们的组件内放置信息。然后,我们可以在组件内使用这些值来渲染数据,进行一些计算,或者制定视觉规则。
在我们的情况下,使用单文件组件,我们将这些规则作为 HTML 属性传递,因为vue-template-compiler
将获取这些属性并将其转换为 JavaScript 对象。
当这些值传递给我们的组件时,Vue 会检查传递的属性是否与正确的类型匹配,然后我们对每个值执行验证函数,以查看它是否与我们期望的匹配。
完成所有这些后,组件的生命周期继续,我们可以渲染我们的组件。
另请参阅
-
您可以在
v3.vuejs.org/guide/component-props.html
找到有关props
的更多信息。 -
您可以在
vue-loader.vuejs.org/guide/
找到有关vue-template-compiler
的更多信息。
创建功能组件
功能组件的美丽之处在于它们的简单性。它们是无状态组件,没有任何数据、计算属性,甚至生命周期。它们只是在传递的数据发生变化时调用的渲染函数。
您可能想知道这有什么用。嗯,功能组件是 UI 组件的完美伴侣,它们不需要在内部保留任何数据,或者只是渲染组件而不需要任何数据操作的可视组件。
顾名思义,它们类似于函数组件,除了渲染函数外没有其他内容。它们是组件的精简版本,专门用于性能渲染和可视元素。
准备工作
此配方的先决条件是 Node.js 12+。
此配方所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
要完成此配方,我们将使用我们的 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>
- 我们需要创建一个带有基本
class
属性按钮的 HTML 元素,并且一个基于接收到的props
对象的动态class
属性。与普通组件相比,我们需要指定props
属性以使用函数组件。对于按钮的样式,我们需要创建一个基于$props
的动态style
属性。为了直接将所有事件监听器传递给父级,我们可以调用v-bind
指令并传递$attrs
属性。这将绑定所有事件监听器,而无需我们声明每一个。在按钮内部,我们将添加一个用于视觉增强的div
HTML 元素,并添加<slot>
,文本将放置在其中:
<template>
<button
tabindex="0"
class="button"
:class="{
round: $props.isRound,
isFlat: $props.isFlat,
}"
:style="{
background: $props.backgroundColor,
color: $props.textColor
}"
v-bind="$attrs"
>
<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>
- 在
App.vue
文件中,我们需要导入我们的组件才能看到它:
<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"
:main-text="`
The path of the righteous man is beset on all sides by the
iniquities of the selfish and the tyranny of evil men.`"
>
<template v-slot:action>
<MaterialButton
background-color="#027be3"
text-color="#fff"
>
Action 1
</MaterialButton>
<MaterialButton
background-color="#26a69a"
text-color="#fff"
is-flat
>
Action 2
</MaterialButton>
</template>
</MaterialCardBox>
</div>
</template>
<script>
import MaterialCardBox from './components/MaterialCardBox.vue';
import MaterialButton from './components/MaterialButton.vue';
export default {
name: 'App',
components: {
MaterialButton,
MaterialCardBox,
},
};
</script>
<style>
body {
font-size: 14px;
}
</style>
- 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
npm run serve
记得总是执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
它是如何工作的...
函数组件就像渲染函数一样简单。它们没有任何类型的数据、函数或对外部世界的访问。
它们最初是作为 JavaScript 对象render()
函数在 Vue 中引入的;后来,它们被添加到vue-template-compiler
中,用于 Vue 单文件应用程序。
功能性组件通过接收两个参数createElement
和context
来工作。正如我们在单文件中看到的,我们只能访问元素,因为它们不在 JavaScript 对象的this
属性中。这是因为当上下文传递给渲染函数时,没有this
属性。
功能性组件在 Vue 上提供了最快的渲染速度,因为它不依赖于组件的生命周期来检查渲染;它只在数据改变时渲染。
另请参阅
- 您可以在
www.npmjs.com/package/is-color
找到有关is-color
模块的更多信息。
访问您的子组件的数据
通常,父子通信是通过事件或 props 来完成的。但有时,您需要访问存在于子函数或父函数中的数据、函数或计算属性。
Vue 为我们提供了双向交互的方式,从而打开了使用 props 和事件监听器等通信和事件的大门。
还有另一种访问组件之间数据的方法:直接访问。这可以通过在单文件组件中使用模板中的特殊属性来完成,或者通过直接调用 JavaScript 中的对象来完成。有些人认为这种方法有点懒惰,但有时确实没有其他方法可以做到这一点。
准备工作
这个食谱的先决条件是 Node.js 12+。
这个食谱所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
为了完成这个食谱,我们将使用我们的 Vue 项目和 Vue CLI,就像在创建功能性组件的食谱中一样。
如何做...
我们将把这个食谱分成四个部分。前三部分将涵盖新组件的创建-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
webpack 将处理 JavaScript 文件中导入的.css
文件。这将有助于开发,因为您无需在其他地方重新导入文件:
import { createApp } from 'vue'; import App from './App.vue'; import './style/materialIcons.css'; createApp(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
记住始终执行命令 npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
创建 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
请记住始终执行命令npm run lint --fix
,以自动修复任何代码 lint 错误。
这是渲染和运行的组件:
创建 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.vote(0);
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.vote(5);
},
},
工作原理...
当ref
属性添加到组件时,Vue 会将对被引用元素的链接添加到 JavaScript 的this
属性对象内的$refs
属性中。从那里,您可以完全访问组件。
这种方法通常用于操作 HTML DOM 元素,而无需调用文档查询选择器函数。
然而,该属性的主要功能是直接访问 Vue 组件,使您能够执行函数并查看组件的计算属性、变量和已更改的变量 - 这就像从外部完全访问组件一样。
还有更多...
与父组件可以访问子组件的方式相同,子组件可以通过在this
对象上调用$parent
来访问父组件。事件可以通过调用$root
属性来访问 Vue 应用程序的根元素。
另请参阅
您可以在v3.vuejs.org/guide/migration/custom-directives.html#edge-case-accessing-the-component-instance
找到有关父子通信的更多信息。
创建一个动态注入的组件
有些情况下,您的组件可以根据您收到的变量的类型或您拥有的数据类型来定义;然后,您需要在不需要设置大量 Vue v-if
、v-else-if
和v-else
指令的情况下即时更改组件。
在这些情况下,最好的做法是使用动态组件,当计算属性或函数可以定义要呈现的组件时,并且决定是实时进行的。
如果有两种响应,这些决定有时可能很容易做出,但如果有一个长的开关情况,那么它们可能会更复杂,其中您可能有一个需要使用的长列表可能组件。
准备就绪
此配方的先决条件是 Node.js 12+。
此配方所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
为了完成这个配方,我们将使用我们的 Vue 项目和 Vue CLI,就像我们在访问你的子组件数据配方中所做的那样。
如何做...
按照以下步骤创建一个动态注入的组件:
-
打开
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
。我们将把由这两个其他组件定义的所有绑定属性放在这个新组件中,包括放在<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 传递下来。
另请参阅
您可以在v3.vuejs.org/guide/component-dynamic-async.html#dynamic-async-components
找到有关动态组件的更多信息。
创建一个依赖注入组件
直接从子组件或父组件访问数据而不知道它们是否存在可能非常危险。
在 Vue 中,可以使您的组件行为像一个接口,并且具有一个在开发过程中不会改变的常见和抽象函数。依赖注入的过程是开发世界中的一个常见范例,并且在 Vue 中也已经实现。
使用 Vue 的内部依赖注入有一些优缺点,但这总是一种确保您的子组件在开发时知道从父组件可以期望什么的好方法。
准备工作
这个配方的先决条件是 Node.js 12+。
这个配方所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
要完成这个配方,我们将使用我们的 Vue 项目和 Vue CLI,就像在创建一个动态注入组件配方中所做的那样。
如何做到...
按照以下步骤创建一个依赖注入组件:
-
打开
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: 'StartRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingInput need to be a child of
StartRating');
},
},
},
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
的注入属性,如果父组件没有提供这个值,它将在控制台中记录一个错误。
使用组件注入通常用于提供和维护绑定组件之间的公共接口,比如菜单和项目。项目可能需要一些存储在菜单中的函数或数据,或者我们可能需要检查它是否是菜单的子组件。
依赖注入的主要缺点是共享元素上不再具有响应性。因此,它主要用于共享函数或检查组件链接。
另请参阅
您可以在v3.vuejs.org/guide/component-provide-inject.html#provide-inject
找到有关组件依赖注入的更多信息。
创建一个组件 mixin
有时你会发现自己一遍又一遍地重写相同的代码。然而,有一种方法可以防止这种情况,并让自己更加高效。
为此,您可以使用所谓的mixin
,这是 Vue 中的一个特殊代码导入,它将外部代码部分连接到当前组件。
准备工作
这个食谱的先决条件是 Node.js 12+。
这个食谱所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
为了完成这个示例,我们将使用我们的 Vue 项目和 Vue CLI,就像我们在创建一个依赖注入组件示例中所做的那样。
如何做...
按照以下步骤创建一个组件混合:
-
打开
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
数据。最后,我们需要导入starRatingChildMixin
和starRatingBaseMixin
:
<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
属性的顺序也很重要,因为它们将被作为for
循环进行检查和导入,因此最后一个mixin
不会改变任何祖先的属性。
在这里,我们将代码中的许多重复部分拆分成了四个不同的小的 JavaScript 文件,这样更容易维护并提高了生产力,而无需重写代码。
另请参阅
您可以在v3.vuejs.org/guide/mixins.html#mixins
找到有关混合的更多信息。
延迟加载您的组件
webpack
和 Vue 天生就是一对。当将webpack
作为 Vue 项目的打包工具时,可以使组件在需要时异步加载。这通常被称为延迟加载。
准备工作
此教程的先决条件是 Node.js 12+。
此教程所需的 Node.js 全局对象如下:
-
@vue/cli
-
@vue/cli-service-global
要完成此教程,我们将使用我们的 Vue 项目和 Vue CLI,就像在创建组件混合教程中所做的那样。
如何做...
按照以下步骤使用延迟加载技术导入您的组件:
-
打开
App.vue
文件。 -
在组件的
<script>
部分,从 Vue 中导入defineAsyncComponent
API,并将lazyLoad
组件函数作为defineAsyncComponent
函数的参数传递:
<script>
import { defineAsyncComponent } from 'vue';
import StarRating from './components/StarRating.vue';
export default {
name: 'App',
components: {
StarRating,
MaterialButton: defineAsyncComponent(() => import('./components/MaterialButton.vue')),
MaterialCardBox: defineAsyncComponent(() => import('./components/MaterialCardBox.vue')),
},
methods: {
resetVote() {
this.$refs.starRating.vote(0);
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.vote(5);
},
},
};
</script>
<style>
body {
font-size: 14px;
}
</style>
它是如何工作的...
Vue 现在使用一个名为defineAsyncComponent
的新 API 来将组件标识为异步组件,并将另一个返回import()
方法的函数作为参数传递。
当我们为每个组件声明一个返回import()
函数的函数时,webpack
知道这个导入函数将进行代码拆分,并将使组件成为捆绑包中的一个新文件。
另请参阅
-
您可以在
v3.vuejs.org/guide/component-dynamic-async.html#dynamic-async-components
找到有关异步组件的更多信息。 -
您可以在
github.com/tc39/proposal-dynamic-import
找到有关 TC39 动态导入的更多信息。
第三章:设置我们的聊天应用程序 - AWS Amplify 环境和 GraphQL
自从 Facebook 在 2012 年推出 GraphQL 以来,它就像飓风一样席卷了网络。大公司开始采用它,而中小型公司也看到了这种基于查询的 API 的潜力。
一开始看起来很奇怪,但随着您开始阅读和体验更多,您就不想再使用 REST API 了。简单性和数据获取能力使前端开发人员的生活变得更轻松,因为他们可以只获取他们想要的内容,而不必受限于只提供单个信息片段的端点。
这是一个漫长的配方的开始,所有的配方都将形成一个完整的聊天应用程序,但您可以在不需要编写整个章节的情况下,在配方中学习有关 GraphQL 和 AWS Amplify 的知识。
在本章中,我们将学习更多关于 AWS Amplify 环境和 GraphQL 的知识,以及如何将其添加到我们的应用程序并使其可用作通信驱动程序。
在本章中,我们将涵盖以下配方:
-
创建您的 AWS Amplify 环境
-
创建您的第一个 GraphQL API
-
将 GraphQL 客户端添加到您的应用程序
-
为您的应用程序创建 AWS Amplify 驱动程序
技术要求
在本章中,我们将使用 Node.js、AWS Amplify 和 Quasar Framework。
注意,Windows 用户!您需要安装一个名为windows-build-tools
的 NPM 包,以便能够安装所需的软件包。要执行此操作,请以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Quasar Framework,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @quasar/cli
要安装 AWS Amplify,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @aws-amplify/cli
创建您的 AWS Amplify 环境
借助 AWS Amplify 的帮助,我们可以在几分钟内创建一个后端环境,其中包括 NoSQL 数据库、GraphQL 解析器和一个在线存储桶,供我们在开发后部署我们的应用程序。
为了创建 Vue 应用程序,我们将使用 Quasar Framework。这是一个基于 Vue 的框架,提供了开发应用程序所需的所有工具、结构和组件。
在这个配方中,我们将学习如何创建我们的 AWS 账户,在本地配置 AWS Amplify 环境,并使用 Quasar Framework 创建我们的初始项目。
准备就绪
这个教程的先决条件是 Node.js 12+。
所需的 Node.js 全局对象如下:
-
@aws-amplify/cli
-
@quasar/cli
如何做...
我们将把这个教程的任务分成四个部分:创建 AWS 账户,配置 AWS Amplify,创建您的 Quasar 项目,以及初始化 AWS Amplify 项目。
创建 AWS 账户
在这里,我们将学习如何在 AWS 门户上创建一个账户,以便我们可以访问 AWS 控制台:
-
在网站上,点击“创建 AWS 账户”按钮。
-
选择创建一个“专业”账户或一个“个人”账户(因为我们将要探索平台并为自己开发示例应用程序,最好选择“个人”账户)。
-
现在亚马逊将要求您提供付款信息,以防您的使用超出了免费套餐限制。
-
现在是确认您的身份的时候 - 您需要提供一个有效的电话号码,亚马逊将用它来发送您需要输入的 PIN 码。
-
在收到 PIN 码后,您将看到一个成功的屏幕和一个“继续”按钮。
-
现在您需要为您的账户选择一个计划;您可以选择此教程的“基本计划”选项。
-
现在您已经完成,可以登录到您的 Amazon AWS 账户控制台。
配置 AWS Amplify
让我们配置本地 AWS Amplify 环境,以准备开始开发我们的聊天应用程序:
- 要设置 AWS Amplify,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> amplify configure
-
浏览器将打开,您需要登录到您的 AWS 控制台账户。
-
登录后,返回终端并按Enter。CLI 将要求您选择您希望应用程序执行的服务器区域。建议在
us-east-1
上运行。 -
选择区域后,CLI 将要求您为身份和访问管理(IAM)定义用户名。您可以按Enter使用默认值,也可以输入您想要的值(但必须是唯一的)。
-
现在浏览器将打开以定义您指定的用户的用户详细信息。点击“下一步:权限”按钮转到下一个屏幕。
-
点击“下一步:标签”按钮转到 AWS 标签屏幕。在这个屏幕上,点击“下一步:审核”按钮来审查您定义的设置。
-
现在你可以点击“创建用户”按钮来创建用户并转到访问密钥屏幕。
-
最后,在此屏幕上,等待访问密钥 ID 和秘密访问密钥可用。在浏览器中复制访问密钥 ID,粘贴到终端中,然后按“Enter”键。
-
粘贴访问密钥 ID 后,您必须返回浏览器,点击秘密访问密钥上的“显示”链接,复制该值,粘贴到终端中,然后按“Enter”键。
-
最后,您需要定义 AWS 配置文件名称(您可以通过按“Enter”键使用默认值)。
您现在已在计算机上设置了 AWS Amplify 环境。
创建您的 Quasar 项目
现在我们将创建 Quasar Framework 项目,这将是我们的聊天应用程序:
- 要创建您的 Quasar Framework 应用程序,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar create chat-app
- Quasar CLI 将要求输入项目名称;它需要是有效的 npm 软件包名称:
> ? Project name (internal usage for dev) chat-app
- CLI 将要求输入产品名称(通常用于渐进式 Web 应用程序(PWA),混合移动应用程序和 Electron 应用程序):
? Project product name (must start with letter if building mobile
apps) Chat App
- 之后,CLI 将要求输入项目描述,这将用于混合应用程序和 PWA:
? Project description A Chat Application
- 现在 CLI 将要求输入项目的作者。通常,这是您的 npm 或 Git 配置的作者:
? Author Heitor Ramon Ribeiro <heitor.ramon@example.com>
- 现在您可以选择 CSS 预处理器。我们将选择
Stylus
(您可以选择最适合您的预处理器):
? Pick your favorite CSS preprocessor: (can be changed later)
Sass with indented syntax (recommended)
Sass with SCSS syntax (recommended)
❯ Stylus
None (the others will still be available)
- Quasar 有两种将组件、指令和插件导入构建系统的方法。您可以通过在
quasar.conf.js
中声明来手动执行,也可以通过自动导入您在代码中使用的组件、指令和插件来自动执行。我们将使用自动导入方法:
? Pick a Quasar components & directives import strategy: (can be changed later) (Use arrow key s)
❯ * Auto-import in-use Quasar components & directives - slightly
higher compile time; next to minimum bundle size; most
convenient
* Manually specify what to import - fastest compile time; minimum
bundle size; most tedious
* Import everything from Quasar - not treeshaking Quasar; biggest
bundle size; convenient
- 现在我们必须选择要添加到项目中的默认功能;我们将选择
ESLint
、Vuex
、Axios
和Vue-i18n
:
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯ ESLint
Vuex
TypeScript
Axios
Vue-i18n
IE11 support
- 现在您可以选择要在项目中使用的
ESLint
预设;在这种情况下,我们将选择AirBnB
:
? Pick an ESLint preset: (Use arrow keys)
Standard (https://github.com/standard/standard)
❯ Airbnb (https://github.com/airbnb/javascript)
Prettier (https://github.com/prettier/prettier)
- 您需要定义一个 Cordova/Capacitor ID(即使您不构建混合应用程序,也可以使用默认值):
? Cordova/Capacitor id (disregard if not building mobile apps)
org.cordova.quasar.app
- 最后,您可以选择要运行的软件包管理器,并安装您需要运行代码的软件包:
? Should we run `npm install` for you after the project has been
created? (recommended) (Use arrow keys)
Yes, use Yarn (recommended)
❯ Yes, use NPM
No, I will handle that myself
初始化 AWS Amplify 项目
要初始化您的 AWS Amplify 项目,请执行以下步骤:
- 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify init
- Amplify CLI 将要求输入项目名称:
? Enter a name for the project: chatapp
- 然后,您需要为您的机器上正在运行的当前项目定义一个环境:
? Enter a name for the environment: dev
- 现在您可以选择您将在项目中使用的默认编辑器:
? Choose your default editor: (Use arrow keys)
❯ Visual Studio Code
Atom Editor
Sublime Text
InteliJ IDEA
Vim (via Terminal, Mac OS only)
Emac (via Terminal, Mac OS only)
None
- 您需要决定由 AWS Amplify 托管的项目类型。在我们的情况下,这将是一个 JavaScript 应用程序:
? Choose the type of app that you're building? (recommended) (Use
arrow keys)
android
ios
❯ javascript
- 对于框架,因为我们将使用 Quasar Framework 作为基础,我们需要从所呈现的框架列表中选择“无”:
? What javascript framework are you using? (recommended) (Use arrow
keys)
angular
ember
ionic
react
react-native
vue
❯ none
- 您将需要定义应用程序的源路径;您可以将源目录路径保留为默认值
src
。然后按Enter继续:
? Source Directory Path: (src)
- 对于分发目录,由于 Quasar 使用不同类型的路径组织,我们需要将其定义为
dist/spa
:
? Distribution Directory Path: dist/spa
- AWS Amplify 将在部署之前使用的构建命令,我们将将其定义为
quasar build
:
? Build Command: quasar build
- 对于启动命令,我们需要使用 Quasar 内置的
quasar dev
命令:
? Start Command: quasar dev
对于 Windows 用户,由于 Amplify 和 WSL 不兼容,您可能需要将启动命令定义如下:
? Start Command: quasar.cmd dev
- 现在 CLI 会询问我们是否要为此配置使用本地 AWS 配置文件:
? Do you want to use an AWS profile: y
- 我们将选择之前创建的默认配置文件:
? Please choose the profile you want to use: (Use arrow keys)
❯ default
- CLI 完成初始化过程后,我们需要向项目添加托管。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify add hosting
- CLI 会询问您的应用程序的托管过程。选择“使用 Amplify Console 进行托管”,然后按Enter继续:
? Select the plugin module to execute
❯ Hosting with Amplify Console (Managed hosting with custom domains,
Continuous deployment)
Amazon CloudFront and S3
- 然后 CLI 会询问您部署过程将如何进行;选择“手动部署”,然后按Enter继续:
? Choose a type (Use arrow keys)
Continuous deployment (Git-based deployments)
❯ Manual deployment
Learn more
- 当您完成所有操作后,要完成此过程,您需要发布它。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify publish
- 您将被问及是否要继续发布,您可以接受。完成所有操作后,浏览器将打开默认的 Quasar Framework 首页:
它是如何工作的...
AWS Amplify 是 Web 开发人员的一体化解决方案,提供了一整套工具,从托管应用程序到后端开发。
我们能够快速轻松地构建应用程序并将其上线,完全没有遇到基础设施方面的问题。
在这个步骤中,我们设法创建了我们的 AWS 账户,并为本地开发和网页部署准备好了我们的第一个 AWS Amplify 环境。此外,我们还能够创建了将用作聊天应用程序的 Quasar Framework 项目,并将其部署到 AWS 基础设施中,以准备应用程序的未来发布。
另请参阅
-
您可以在
aws.amazon.com/amplify/
找到有关 AWS Amplify 的更多信息。 -
您可以在
docs.amplify.aws/
找到有关 AWS Amplify 框架的更多信息。 -
您可以在
quasar.dev/
找到有关 Quasar Framework 的更多信息。
创建您的第一个 GraphQL API
AWS Amplify 提供了在简单步骤和许多附加选项(包括身份验证、部署和环境)的情况下,开箱即用地拥有 GraphQL API 的可能性。这使我们能够仅使用 GraphQL SDL 模式快速开发 API,并且 AWS Amplify 将为连接构建 API、DynamoDB 实例和代理服务器。
在这个步骤中,我们将学习如何使用 AWS Amplify 创建 GraphQL API,并为身份验证添加 AWS Cognito 功能。
准备工作
此步骤的先决条件如下:
-
上一个步骤的项目
-
Node.js 12+
所需的 Node.js 全局对象是@aws-amplify/cli
。
要安装 AWS Amplify,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm install -g @aws-amplify/cli
在这个步骤中,我们将使用创建您的 AWS Amplify 环境步骤中的项目。请先完成该步骤中的说明。
如何做...
要启动我们的 GraphQL API,我们将继续使用在创建您的 AWS Amplify 环境步骤中创建的项目。
这个步骤将分为两部分:创建 AWS Cognito 和创建 GraphQL API。
创建 AWS Cognito 身份验证
为了给我们的 API 和应用程序增加一层安全性,我们将使用 AWS Cognito 服务。这将提供对用户和身份验证的控制作为服务:
- 要初始化您的 AWS Cognito 配置,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify auth add
- 现在 CLI 会要求您选择用于创建 Cognito 服务的配置类型。这些是预先制定的规则和配置的选择。我们将选择
默认配置
:
Do you want to use default authentication and security configuration: (Use arrow keys)
❯ Default configuration Default configuration with Social Provider (Federation)
Manual configuration
I want to learn more.
- 之后,您需要选择用户将如何登录;因为我们正在构建一个聊天应用程序,我们将选择
电子邮件
:
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in: (Use arrow keys)
Username
❯ Email Phone Number
Email and Phone Number
I want to learn more.
- 对于 AWS Cognito,不需要选择更高级的设置。我们可以通过选择
不,我完成了。
来跳过这一步。
Do you want to configure advanced settings: (Use arrow keys)
❯ No, I am done. Yes, I want to make some additional changes.
- 最后,我们需要将这个配置推送到云端。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify auth push
- 您将被问及是否要继续 - 输入
y
,CLI 将发布配置到 AWS Cognito 云:
? Are you sure you want to continue: y
创建 GraphQL API
在这部分,我们将把说明分为两部分,首先创建 GraphQL SDL 模式,然后创建 GraphQL API。
创建 GraphQL SDL 模式
要使用 AWS Amplify 创建 GraphQL API,首先需要创建一个 GraphQL SDL 模式。AWS Amplify 将使用该模式生成 API 的数据库和解析器:
-
在
src
文件夹中创建一个名为chatApi.graphql
的新文件,并打开它。 -
创建我们基本的
S3Object
模式类型,这是一个简单的模型,用于管理放置在 AWS S3 存储桶中的文件的存储:
type S3Object {
bucket: String!
region: String!
key: String! }
- 然后我们将创建我们的
用户类型
。这就像一个带有更多规则附加的数据库模型。这个类型
将有一个@auth
规则,只允许所有者,在这种情况下是用户
,执行创建
、更新
和删除
操作。之后,我们将声明用户
字段:
type User
@model(subscriptions: null) @auth(rules: [
{ allow: owner, ownerField: "id", queries: null },
{ allow: owner, ownerField: "owner", queries: null },
]) {
id: ID!
email: String!
username: String!
avatar: S3Object
name: String
conversations: [ConversationLink] @connection(name: "UserLinks")
messages: [Message] @connection(name: "UserMessages", keyField: "authorId")
createdAt: String
updatedAt: String }
- 我们的
用户
将与另一个用户进行对话。我们将创建一个对话类型
,为了保护这个对话,我们将添加一个@auth
规则,以确保只有这个对话的成员可以看到用户之间交换的消息。在messages
字段中,我们将创建一个与消息类型
的@connection
,并在关联字段中创建一个与对话链接类型
的@connection
:
type Conversation
@model(
mutations: { create: "createConversation" }
queries: { get: "getConversation" }
subscriptions: null ) @auth(rules: [{ allow: owner, ownerField: "members" }]) {
id: ID!
messages: [Message] @connection(name: "ConversationMessages",
sortField: "createdAt")
associated: [ConversationLink] @connection(name:
"AssociatedLinks")
name: String!
members: [String!]!
createdAt: String
updatedAt: String }
- 对于
消息类型
,我们需要添加一个@auth
装饰器规则,只允许所有者对其进行操作。我们需要创建一个@connection
装饰器,将author
字段连接到用户类型
,并创建一个@connection
装饰器,将conversation
字段连接到对话类型
:
type Message
@model(subscriptions: null, queries: null) @auth(rules: [{ allow: owner, ownerField: "authorId", operations: [create, update, delete]}]) {
id: ID!
author: User @connection(name: "UserMessages", keyField:
"authorId")
authorId: String
content: String!
conversation: Conversation! @connection(name: "ConversationMessages")
messageConversationId: ID!
createdAt: String
updatedAt: String }
- 现在我们正在使用
type ConversationLink
将对话链接在一起。这个type
需要user
字段具有@connection
装饰器到User
和@connection
对话到type Conversation
:
type ConversationLink
@model(
mutations: { create: "createConversationLink", update:
"updateConversationLink" }
queries: null
subscriptions: null ) {
id: ID!
user: User! @connection(name: "UserLinks")
conversationLinkUserId: ID
conversation: Conversation! @connection(name: "AssociatedLinks")
conversationLinkConversationId: ID!
createdAt: String
updatedAt: String }
- 最后,我们需要创建一个
type Subscription
来在 GraphQL API 内部具有事件处理程序。Subscription
类型会监听并处理特定变化的特定变化,createConversationLink
和createMessage
,两者都会在数据库内触发事件:
type Subscription {
onCreateConversationLink(conversationLinkUserId: ID!):
ConversationLink
@aws_subscribe(mutations: ["createConversationLink"])
onCreateMessage(messageConversationId: ID!): Message
@aws_subscribe(mutations: ["createMessage"])
onCreateUser: User
@aws_subscribe(mutations: ["createUser"])
onDeleteUser: User
@aws_subscribe(mutations: ["deleteUser"])
onUpdateUser: User
@aws_subscribe(mutations: ["updateUser"]) }
使用 AWS Amplify 创建 GraphQL API
在这里,我们将使用 AWS Amplify API 使用先前创建的 GraphQL 模式来创建我们的 GraphQL API:
- 要初始化您的 AWS Amplify API 配置,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify add api
- 在这里,CLI 将询问您要创建什么类型的 API。我们将选择
GraphQL
:
? Please select from one of the below mentioned services: (Use arrow
keys)
❯ GraphQL REST
- 现在 CLI 将要求输入 API 名称(您可以选择):
? Provide API name: chatapp
- 在这里,我们将选择 API 将使用的身份验证方法。由于我们将使用 AWS Cognito,我们需要选择
Amazon Cognito User Pool
选项:
? Choose the default authorization type for the API: (Use arrow
keys)
API key
❯ Amazon Cognito User Pool IAM
OpenID Connect
- 然后 CLI 将询问您是否要在 API 上配置更多设置;我们将选择
No, I am done.
选项:
? Do you want to configure advanced settings for the GraphQL API:
(Use arrow keys)
❯ No, I am done. Yes, I want to make some additional changes.
- 现在我们将被问及是否有注释的 GraphQL 模式;由于我们之前已经编写了一个,我们需要输入
y
:
? Do you have an annotated GraphQL schema?: y
- 在这里,我们需要输入刚刚创建的文件的路径
./src/chatApi.graphql
:
? Provide your schema file path: ./src/chatApi.graphql
- 完成后,我们需要将配置推送到 AWS Amplify。要执行此操作,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify push
- 当询问是否要继续时,输入
y
:
? Are you sure you want to continue?: y
- CLI 将询问您是否要为新创建的 GraphQL API 生成代码;再次输入
y
:
? Do you want to generate code for your newly created GraphQL API: y
- 在这里,您可以选择 CLI 要使用的语言来创建项目中使用的通信文件。我们将选择
javascript
,但您可以选择最符合您需求的语言:
? Choose the code generation language target: (Use arrow keys)
❯ javascript
typescript
flow
- CLI 将询问要放置将生成的文件的位置,我们将使用默认值:
? Enter the file name pattern of graphql queries, mutation and
subscriptions: (src/graphql/***/**.js)
- 现在 CLI 将询问有关 GraphQL 操作的生成。由于我们正在创建我们的第一个 GraphQL API,我们将选择
y
,因此 CLI 将为我们创建所有文件:
? Do you want to generate/update all possible GraphQL operations -
queries, mutations and subscriptions: y
- 最后,我们可以定义文件中模式的最大深度,我们将使用默认值
2
:
? Enter maximum statement depth [increase from default if your
schema is deeply nested]: (2)
- 当你完成所有的事情后,我们需要将配置发布到 AWS Amplify。要做到这一点,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify publish
工作原理...
在创建一个带有 AWS Amplify 的 GraphQL API 的过程中,我们需要一个预先构建的模式,用于生成数据库和端点。这个模式是基于 GraphQL SDL 语言的。Amplify 已经在 SDL 中添加了更多的装饰符,这样我们就可以在 API 的开发中拥有更广泛的可能性。
与此同时,我们需要创建一个 AWS Cognito 用户池,用于保存将在应用程序上注册的用户。这是为了在应用程序外部管理和维护身份验证层,并作为一个服务使用,可以提供更多功能,包括双因素身份验证、必填字段和恢复模式。
最后,在一切都完成之后,我们的 API 已经在 AWS Amplify 上发布,并准备好进行开发,具有可以用作开发环境的 URL。
另请参阅
-
你可以在
graphql.org/learn/schema/
找到更多关于 GraphQL SDL 的信息。 -
你可以在
docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js
找到更多关于 AWS Amplify API 的信息。 -
你可以在
docs.amplify.aws/lib/auth/getting-started/q/platform/js
找到更多关于 AWS Amplify 身份验证的信息。
将 GraphQL 客户端添加到你的应用程序
Apollo Client 目前是 JavaScript 生态系统中最好的 GraphQL 客户端实现。它有一个庞大的社区支持,并得到了大公司的支持。
我们的 AWS Amplify GraphQL API 的实现在后端使用了 Apollo Server,因此 Apollo Client 的使用将是一个完美的匹配。AWS AppSync 也使用他们自己的 Apollo 实现作为客户端,所以我们仍然会使用 Apollo 作为客户端,但不是直接使用。
在这个配方中,我们将学习如何将 GraphQL 客户端添加到我们的应用程序中,以及如何连接到 AWS Amplify GraphQL 服务器来执行查询。
准备工作
这个配方的先决条件如下:
-
上一个配方的项目
-
Node.js 12+
所需的 Node.js 全局对象如下:
-
@aws-amplify/cli
-
@quasar/cli
在这个示例中,我们将使用创建您的第一个 GraphQL API示例中的项目。在遵循本示例之前,请按照上一个示例中的步骤进行操作。
如何做...
我们将使用 Amplify 客户端将 GraphQL 客户端添加到我们的应用程序中。按照以下步骤创建 GraphQL 驱动程序:
- 要安装使用 GraphQL 客户端所需的软件包,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save graphql aws-amplify graphql-tag aws-appsync
-
在
boot
文件夹中创建一个名为amplify.js
的新文件,并打开它。 -
在这个文件中,我们将导入
aws-amplify
包和 AWS Amplify CLI 在配置过程中为我们创建的aws-exports.js
文件。我们将使用我们拥有的配置来配置 Amplify。为了使 Quasar 引导文件起作用,我们需要导出一个default
空函数:
import Amplify from 'aws-amplify'; import AwsExports from '../aws-exports'; Amplify.configure(AwsExports); export default () => {};
- 在
root
文件夹中的quasar.conf.js
文件中,我们需要向webpack
捆绑器添加新规则。要做到这一点,找到extendWebpack
函数。在函数的第一行之后,创建两个新规则给捆绑器,第一个规则将添加graphql-loader
webpack 加载程序,第二个规则将允许捆绑器理解.mjs
文件:
// The rest of the quasar.conf.js... extendWebpack (cfg) {
//New rules that need to be added
cfg.module.rules.push({
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
}); cfg.module.rules.push({
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
});
// Maintain these rules cfg.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /node_modules/,
options: {
formatter:
require('eslint').CLIEngine.getFormatter('stylish'),
},
}); cfg.resolve.alias = {
...cfg.resolve.alias,
driver: path.resolve(__dirname, './src/driver'),
}; }, // The rest of the quasar.conf.js...
-
现在,在
src/driver
文件夹中创建一个名为graphql.js
的新文件,并打开它。 -
在这个文件中,我们需要从
aws-appsync
包中导入AWSAppSyncClient
,从aws-amplify
包中导入Auth
,并从src
文件夹中的aws-exports.js
文件中导入AwsExports
。然后,我们需要使用aws-exports
的配置实例化AWSAppSyncClient
,并导出客户端的这个实例化:
import AWSAppSyncClient from 'aws-appsync'; import { Auth } from 'aws-amplify'; import AwsExports from '../aws-exports'; export default new AWSAppSyncClient({
url: AwsExports.aws_appsync_graphqlEndpoint,
region: AwsExports.aws_appsync_region,
auth: {
type: AwsExports.aws_appsync_authenticationType,
jwtToken: async () => (await
Auth.currentSession()).idToken.jwtToken,
}, });
- 在
quasar.conf.js
文件中的root
文件夹中,我们需要将新创建的amplify.js
文件添加到引导序列中,该文件位于boot
文件夹中。要做到这一点,找到boot
数组,并在末尾添加文件在boot
文件夹中的路径作为字符串,不包括扩展名。在我们的情况下,这将是'amplify'
:
// The rest of the quasar.conf.js...
boot: [ 'axios',
'amplify' ], // The rest of the quasar.conf.js...
它是如何工作的...
我们在全局范围内将aws-amplify
包添加到我们的应用程序中,并通过新的graphql.js
文件中的导出条目使其可用于使用。这使得在应用程序中可以使用AWSAmplifyAppSync
。
使用 Quasar Framework 的引导过程,我们能够在 Vue 应用程序开始在屏幕上呈现之前实例化 Amplify。
另请参阅
-
您可以在
docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js
找到有关 AWS Amplify AppSync 的更多信息。 -
您可以在
quasar.dev/quasar-cli/developing-ssr/writing-universal-code#Boot-Files
找到有关 Quasar Framework 引导文件的更多信息。
为您的应用程序创建 AWS Amplify 驱动程序
为了与 AWS Amplify 服务进行通信,我们需要使用他们的 SDK。这个过程是重复的,可以合并到我们将要使用的每个 Amplify 服务的驱动程序中。
在这个示例中,我们将学习如何创建通信驱动程序,以及如何使用 AWS Amplify 进行操作。
准备工作
这个示例的先决条件如下:
-
上一个示例的项目
-
Node.js 12+
所需的 Node.js 全局对象如下:
-
@aws-amplify/cli
-
@quasar/cli
在这个示例中,我们将使用将 GraphQL 客户端添加到您的应用程序示例中的项目。请先完成该示例中的说明。
如何做...
在这个示例中,我们将其分为三个部分:第一部分将用于 AWS 存储驱动程序,第二部分将用于 Amplify Auth 驱动程序,最后,我们将看到 Amplify AppSync 实例的创建。
创建 AWS Amplify 存储驱动程序
要创建 AWS Amplify 存储驱动程序,我们首先需要创建 AWS Amplify 存储基础设施,并在我们的环境中设置好,之后我们需要创建 AWS Amplify 存储 SDK 与我们的应用程序之间的通信驱动程序。
添加 AWS Amplify 存储
在这部分,我们将向我们的 Amplify 服务列表中添加 AWS S3 功能。这是必需的,这样我们就可以在 AWS S3 云基础设施上保存文件:
- 首先,我们需要向项目添加 AWS 存储。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹并执行以下命令:
> amplify add storage
- 现在我们需要选择将上传什么内容。我们需要选择
内容(图片、音频、视频等)
:
? Please select from one of the below mentioned services: (Use arrow
keys)
❯ Content (Images, audio, video, etc.) NoSQL Database
- 我们需要为资源添加一个名称。我们将其称为
bucket
:
? Please provide a friendly name for your resource that will be used
to label this category in the project: bucket
- 现在我们需要提供一个 AWS S3 存储桶名称。我们将其称为
chatappbucket
:
? Please provide bucket name: chatappbucket
- 然后我们需要选择谁可以操作存储桶文件。由于应用程序将仅基于授权,我们需要选择
仅授权用户
:
? Who should have access: (Use arrow keys)
❯ Auth users only Auth and guest users
- 现在您需要选择用户在存储桶中的访问级别:
? What kind of access do you want for Authenticated users?
create/update
read
❯ delete
- 当被问及创建自定义 Lambda 触发器时,选择
n
:
? Do you want to add a Lambda Trigger for you S3 Bucket: n
- 最后,我们需要将更改推送到云端。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify push
- 当您完成所有操作后,我们需要将配置发布到 AWS Amplify。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify publish
创建 Amplify Storage 驱动程序
在这部分,我们将创建与 Amplify Storage 通信的驱动程序。该驱动程序将处理我们应用程序中的文件上传:
-
在
src/driver
文件夹中创建一个名为bucket.js
的新文件并打开它。 -
从
aws-amplify
包中导入Storage
类,从quasar
中导入uid
函数,以及AwsExports
:
import { Storage } from 'aws-amplify'; import { uid } from 'quasar'; import AwsExports from '../aws-exports';
- 创建一个名为
uploadFile
的异步函数,它接收三个参数:file
,name
和type
。name
参数的默认值为uid()
,type
参数的默认值为'image/png'
。在这个函数中,我们将调用Storage.put
函数,传递name
和file
作为参数,作为第三个参数,我们将传递一个 JavaScript 对象,其中contentType
属性定义为接收到的type
,并且accept
属性定义为'**/**'
。上传完成后,我们将返回一个具有bucket
,region
和uploadedFile
属性的 JavaScript 对象:
export async function uploadFile(file, name = uid(), type = 'image/png') {
try {
const uploadedFile = await Storage.put(name, file, {
contentType: type,
accept: '*/*',
}); return {
...uploadedFile,
bucket: AwsConfig.aws_user_files_s3_bucket,
region: AwsConfig.aws_user_files_s3_bucket_region,
};
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
getFile
的异步函数,它接收name
参数,默认值为空字符串。在函数内部,我们将返回Storage.get
,传递name
参数和设置为public
级别的选项:
export async function getFile(name = '') {
try {
return await Storage.get(name, { level: 'public' });
} catch (err) {
return Promise.reject(err);
} }
- 最后,导出一个默认的 JavaScript 对象,并将创建的函数
uploadFile
和getFile
作为属性添加进去:
export default {
uploadFile,
getFile, };
创建 Amplify Auth 驱动程序
现在我们将创建认证驱动程序。该驱动程序负责处理应用程序中的所有认证请求并获取用户信息:
-
在
src/driver
文件夹中创建一个名为auth.js
的新文件并打开它。 -
在新创建的文件中,从
aws-amplify
包中导入Auth
类:
import { Auth } from 'aws-amplify';
- 创建一个名为
signIn
的新异步函数。它将接收email
和password
作为参数,并且该函数将返回Auth.signIn
函数,传递email
和password
作为参数:
export async function signIn(email = '', password = '') {
try {
return Auth.signIn({
username: email,
password,
});
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
signUp
的新异步函数,该函数将接收email
和password
作为参数。该函数将返回Auth.signUp
函数,传递一个带有这些属性的 JavaScript 对象作为参数:username
、password
、attributes
和validationData
。
username
属性将是作为参数接收的email
值。
password
属性将是作为参数接收的password
值。
attributes
属性将是一个带有email
属性的 JavaScript 对象,该属性将作为参数接收:
export async function signUp(email = '', password = '') {
try {
return Auth.signUp({
username: email,
password: `${password}`,
attributes: {
email,
},
validationData: [],
});
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
validateUser
的新异步函数,该函数将接收username
和code
作为参数。该函数等待Auth.confirmSignUp
函数的响应,将username
和code
作为参数传递给该函数,并在完成时返回true
:
export async function validateUser(username = '', code = '') {
try {
await Auth.confirmSignUp(username, `${code}`); return Promise.resolve(true);
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
resendValidationCode
的新异步函数,该函数将接收username
作为参数。该函数返回Auth.resendSignUp
函数,将username
作为参数:
export async function resendValidationCode(username = '') {
try {
return Auth.resendSignUp(username);
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
signOut
的新异步函数,该函数返回Auth.signOut
函数:
export async function signOut() {
try {
return Auth.signOut();
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
changePassword
的新异步函数,该函数将接收oldPassword
和newPassword
作为参数。该函数等待获取当前经过身份验证的用户,并返回Auth.changePassword
函数,将获取的user
、oldPassword
和newPassword
作为参数:
export async function changePassword(oldPassword = '', newPassword = '') {
try {
const user = await Auth.currentAuthenticatedUser();
return Auth.changePassword(user, `${oldPassword}`, `${newPassword}`);
} catch (err) {
return Promise.reject(err);
} }
- 创建一个名为
getCurrentAuthUser
的新异步函数;该函数将获取当前经过身份验证的用户,并返回一个带有id
、email
和username
属性的 JavaScript 对象:
export async function getCurrentAuthUser() {
try {
const user = await Auth.currentAuthenticatedUser(); return Promise.resolve({
id: user.username,
email: user.signInUserSession.idToken.payload.email,
username: user.username,
});
} catch (err) {
return Promise.reject(err);
} }
创建 Amplify AppSync 实例
在经过身份验证的情况下与 AWS Amplify API 通信,我们需要创建一个新的 AWS Amplify AppSync API 实例,其中包含用户身份验证信息:
-
在
src/driver
文件夹中创建一个名为appsync.js
的新文件并打开它。 -
在新创建的文件中,从
aws-amplify
包中导入Auth
和API
,从@aws-amplify/api
包中导入GRAPHQL_AUTH_MODE
枚举,以及 AWS 配置:
import { Auth, API } from 'aws-amplify'; import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; import AwsExports from '../aws-exports';
- 通过执行
API.configure
函数从aws-amplify
包中配置 API,传递一个 JavaScript 对象作为参数,其中包含url
、region
和auth
的属性。
在url
属性中,传递 GraphQL 端点 URL 的配置。
在region
属性中,传递当前正在使用的 AWS 区域的配置。
在auth
属性中,我们需要传递一个具有两个属性type
和jwtToken
的 JavaScript 对象。
我们需要将type
属性设置为GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS
。
在jwtToken
中,我们将传递一个异步函数,该函数将返回当前登录用户的令牌:
API.configure({
url: awsconfig.aws_appsync_graphqlEndpoint,
region: awsconfig.aws_appsync_region,
auth: {
type: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(),
}, });
- 最后,我们将
API
导出为名为AuthAPI
的常量:
export const AuthAPI = API;
工作原理...
在这个示例中,我们学习了如何将应用程序的责任分离为可以在多个领域重复使用而无需重写整个代码的驱动程序。通过这个过程,我们能够创建一个用于 Amplify 存储的驱动程序,可以异步发送文件,并且这些文件被保存在 AWS S3 服务器上的存储桶中。
在我们对 Auth 驱动程序的工作中,我们能够创建一个可以管理 Amplify 身份验证 SDK 并在需要时提供信息并封装特殊功能以使在我们的应用程序中执行任务更容易的驱动程序。
最后,在 Amplify AppSync API 中,我们成功实例化了 API 连接器,并使用了所有需要的身份验证标头,以便应用程序可以在没有任何问题的情况下执行,并且用户可以在请求时访问所有信息。
另请参阅
-
在
docs.amplify.aws/lib/storage/getting-started/q/platform/js
上查找有关 AWS Amplify Storage 的更多信息。 -
在
docs.amplify.aws/lib/auth/getting-started/q/platform/js
上查找有关 AWS Amplify Auth 的更多信息。 -
在
docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js
上查找有关 AWS Amplify AppSync 的更多信息。
第四章:创建自定义应用程序组件和布局
要开始我们应用程序的开发,我们需要创建整个应用程序将使用的自定义组件和输入。这些组件将采用无状态的方法创建。
我们将开发UsernameInput
组件,PasswordInput
组件,EmailInput
组件和AvatarInput
组件。我们还将开发应用程序页面和聊天布局的基本布局,它将包装聊天页面。
在本章中,我们将涵盖以下示例:
-
为应用程序创建自定义输入
-
创建应用程序布局
技术要求
在本章中,我们将使用Node.js和Quasar Framework。
注意,Windows 用户!您需要安装一个名为windows-build-tools
的npm
包,以便能够安装所需的包。要做到这一点,以管理员身份打开 PowerShell 并执行以下命令:
> npm install -g windows-build-tools
要安装 Quasar Framework,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install -g @quasar/cli
为应用程序创建自定义输入
创建应用程序需要创建大量的表单。所有这些表单都需要输入,这些输入很可能在应用程序中重复出现。
在这个示例中,我们将创建自定义输入表单,我们将在几乎每个表单中使用它们。
创建自定义输入表单的过程有助于开发人员节省调试时间,代码的可重用性和未来的改进。
准备工作
这个示例的先决条件如下:
-
最后的示例项目
-
Node.js 12+
所需的 Node.js 全局对象如下:
-
@aws-amplify/cli
-
@quasar/cli
要开始我们的自定义组件,我们将继续使用在第三章设置我们的聊天应用程序 - AWS Amplify 环境和 GraphQL中创建的项目。
如何做...
为了更好地重用代码,我们将创建单独的组件来处理应用程序上的自定义表单。在这种情况下,我们将创建六个组件:
-
UsernameInput
-
PasswordInput
-
NameInput
-
EmailInput
-
AvatarInput
-
AvatarDisplay
所以,让我们开始吧。
创建 UsernameInput 组件
UsernameInput
将负责处理用户名的检查和验证,这样我们就不需要在每个需要使用它的页面上重新编写所有规则。
单文件组件<script>
部分
在这里,我们将创建UsernameInput
组件的<script>
部分:
-
在
src/components
文件夹中创建一个名为UsernameInput.vue
的新文件,并打开它。 -
创建一个带有
name
和props
属性的默认导出的 JavaScript 对象:
export default {
name: '',
props: {}, };
- 对于
name
属性,将其定义为"UsernameInput"
:
name: 'UsernameInput',
- 对于
props
属性,将其定义为一个 JavaScript 对象,并添加一个名为value
的新属性,它也将是一个具有type
,default
和required
属性的 JavaScript 对象。type
属性需要定义为String
,default
为''
,required
为false
:
props: {
value: {
type: String,
default: '',
required: false,
},
},
单文件组件<template>
部分
在这里,我们将创建UsernameInput
组件的<template>
部分:
-
在
<template>
部分,创建一个QInput
组件。创建两个动态属性,value
和rules
。现在,value
将绑定到value
属性,rules
属性将接收一个数组。数组的第一项是一个函数,用于验证输入,第二项是出现错误时的消息。 -
将
outlined
和lazy-rules
属性设置为true
,并将label
属性定义为"Your Username"
。 -
最后,通过创建一个
v-on
指令,使用$listeners
Vue API 作为值来为事件创建事件侦听器。
完成所有步骤后,您的最终代码应该像这样:
<template>
<q-input
:value="value"
:rules="[ val => (val && val.length > 5 || 'Please type a valid
Username')]"
outlined
label="Your Username"
lazy-rules
v-on="$listeners"
/> </template>
这是您的组件呈现出来的样子:
创建一个 PasswordInput 组件
PasswordInput
将是一个组件,具有特殊逻辑,通过单击按钮切换密码的可见性。我们将在这个组件中包装这个逻辑,这样每次使用这个组件时就不需要重新编写它。
单文件组件<script>
部分
在这部分,我们将创建PasswordInput
组件的<script>
部分:
-
在
components
文件夹中创建一个名为PasswordInput.vue
的新文件,并打开它。 -
创建一个默认导出的 JavaScript 对象,具有三个属性,
name
,props
和data
:
export default {
name: '',
props: {},
data: () => (), };
- 对于
name
属性,将值定义为"PasswordInput"
:
name: 'PasswordInput',
- 对于
props
属性,添加两个属性,value
和label
,都是 JavaScript 对象。每个对象内部应该有三个属性:type
,default
和required
。将value.type
设置为String
,value.default
设置为''
,value.required
设置为false
。然后,将label.type
设置为String
,label.default
设置为'Your Password'
,label.required
设置为false
:
props: {
value: {
type: String,
default: '',
required: false,
},
label: {
type: String,
default: 'Your password',
required: false,
}, },
- 最后,在
data
属性中,添加一个 JavaScript 对象作为返回值,其中isPwd
值设置为true
:
data: () => ({
isPwd: true, }),
单文件组件部分
现在我们将创建PasswordInput
的<template>
部分。按照以下说明来实现正确的输入组件:
-
在<template>
部分,创建一个QInput
组件,并将value
,label
和rules
属性添加为变量。value
将绑定到value
属性,label
将绑定到label
属性,rules
将接收一个函数数组,用于执行对表单输入的基本验证。
-
对于type
属性,将其定义为一个变量,并将其设置为对isPwd
的三元验证,在"password"
和"text"
之间切换。
-
将outlined
和lazy-rules
属性设置为true
。
-
创建一个hint
变量属性,并将其定义为三元运算符,它将检查当前值的长度是否匹配最小值大小;否则,它将向用户显示一条消息。
-
然后,通过创建一个v-on
指令并使用$listeners
Vue API 作为值来为事件创建事件侦听器。
-
在QInput
模板内部,我们将添加一个子组件,该组件将占据一个命名插槽v-slot:append
,该插槽将容纳一个QIcon
组件。
-
对于QIcon
组件,定义name
属性以对isPwd
变量进行响应,因此当isPwd
设置为true
时,它将是'visibility_off'
,或者当isPwd
设置为false
时,它将是'visibility'
。将class
属性定义为"cursor-pointer"
,以便鼠标具有实际鼠标的外观和"hover hand icon"
,并在@click
事件侦听器上,我们将设置isPwd
为当前isPwd
的相反值。
完成所有步骤后,您的最终代码应该像这样:
<template>
<q-input
:value="value"
:type="isPwd ? 'password' : 'text'"
:rules="[ val => val.length >= 8 || 'Your password need to have 8
or more characters', val => val !== null && val !== '' ||
'Please type your password']"
:hint=" value.length < 8 ? 'Your password has a minimum of 8
characters' : ''"
:label="label"
outlined
lazy-rules
v-on="$listeners"
>
<template v-slot:append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
/>
</template>
</q-input> </template>
这是您的组件呈现的方式:

创建 NameInput 组件
在我们创建的所有组件中,NameInput
组件是最简单的,几乎没有改变QInput
组件的行为,只是添加了验证规则和一些个性化。
单文件组件
PasswordInput
的<template>
部分。按照以下说明来实现正确的输入组件:在<template>
部分,创建一个QInput
组件,并将value
,label
和rules
属性添加为变量。value
将绑定到value
属性,label
将绑定到label
属性,rules
将接收一个函数数组,用于执行对表单输入的基本验证。
对于type
属性,将其定义为一个变量,并将其设置为对isPwd
的三元验证,在"password"
和"text"
之间切换。
将outlined
和lazy-rules
属性设置为true
。
创建一个hint
变量属性,并将其定义为三元运算符,它将检查当前值的长度是否匹配最小值大小;否则,它将向用户显示一条消息。
然后,通过创建一个v-on
指令并使用$listeners
Vue API 作为值来为事件创建事件侦听器。
在QInput
模板内部,我们将添加一个子组件,该组件将占据一个命名插槽v-slot:append
,该插槽将容纳一个QIcon
组件。
对于QIcon
组件,定义name
属性以对isPwd
变量进行响应,因此当isPwd
设置为true
时,它将是'visibility_off'
,或者当isPwd
设置为false
时,它将是'visibility'
。将class
属性定义为"cursor-pointer"
,以便鼠标具有实际鼠标的外观和"hover hand icon"
,并在@click
事件侦听器上,我们将设置isPwd
为当前isPwd
的相反值。
<template>
<q-input
:value="value"
:type="isPwd ? 'password' : 'text'"
:rules="[ val => val.length >= 8 || 'Your password need to have 8
or more characters', val => val !== null && val !== '' ||
'Please type your password']"
:hint=" value.length < 8 ? 'Your password has a minimum of 8
characters' : ''"
:label="label"
outlined
lazy-rules
v-on="$listeners"
>
<template v-slot:append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
/>
</template>
</q-input> </template>

NameInput
组件是最简单的,几乎没有改变QInput
组件的行为,只是添加了验证规则和一些个性化。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)