Vue-与-GraphQL-应用构建指南-全-

Vue 与 GraphQL 应用构建指南(全)

原文:zh.annas-archive.org/md5/60CC414A1AE322EC97E6A0F8A5BBE3AD

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

自 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。在这个过程中,读者将创建用于前端和后端通信的驱动程序。

第四章,“创建自定义应用程序组件和布局”,从现在开始,读者将开始开发应用程序。在这一章中,读者将创建用于聊天应用程序页面的组件。读者将创建组件,如PasswordInputAvatarInputEmailInput等。

第五章,“创建用户 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 并注册,以便文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 在 www.packt.com 上登录或注册。

  2. 选择 Support 选项卡。

  3. 点击 Code Downloads。

  4. 在 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.jsVue CLI

注意,Windows 用户 - 您需要安装一个名为windows-build-toolsnpm包,以便能够安装以下所需的包。要做到这一点,以管理员身份打开 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 项目,请按照以下步骤进行:

  1. 我们需要在 Terminal (macOS 或 Linux) 或 Command Prompt/PowerShell (Windows) 中执行以下命令:
> vue create my-first-project
  1. CLI 会询问一些问题,这些问题将帮助你创建项目。你可以使用箭头键进行导航,Enter 键继续,Spacebar 选择选项:
?  Please pick a preset: (Use arrow keys)  default (babel, eslint) ❯ **Manually select features** ‌
  1. 有两种方法可以启动一个新项目。默认方法是一个基本的 babel 和 eslint 项目,没有任何插件或配置,但也有 手动 模式,你可以选择更多模式、插件、linters 和选项。我们将选择 手动 模式。

  2. 在这一点上,我们将被询问关于我们希望为我们的项目选择的功能。这些功能是一些 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
  1. 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)
  1. 可以选择与 Vue 一起使用的主要 层叠样式表 (CSS) 预处理器,即 SassLess 和 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 
  1. 现在是时候格式化您的代码了。您可以在AirBnBStandardPrettier之间进行选择,并使用基本配置。在ESLint中导入的这些规则总是可以自定义,没有任何问题,并且有一个完美的规则适合您的需求。找出对您来说最好的方法,然后执行以下操作:
?  Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ❯ **ESLint + Airbnb config** ESLint + Standard config 
  ESLint + Prettier
  1. 一旦代码检查规则被设置,我们需要定义它们何时应用于我们的代码。它们可以在保存时应用,或者在提交时进行修复:
? Pick additional lint features: 
 Lint on save
❯ Lint and fix on commit 
  1. 一旦所有这些插件、代码检查器和处理器都被定义,我们需要选择设置和配置存储的位置。最好的存储位置是在一个专用文件中,但也可以将它们存储在package.json文件中:
?  Where do you prefer placing config for Babel, ESLint, etc.?  (Use  
  arrow keys) ❯ **In dedicated config files** In package.json
  1. 现在,您可以选择是否将此选择作为将来项目的预设,这样您就不需要再次重新选择所有内容。
?  Save this as a preset for future projects?  (y/N) n
  1. 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

另请参阅

创建 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组件:

  1. 让我们在src/components文件夹中创建一个名为CurrentTime.vue的新文件。

  2. 在这个文件中,我们将从组件的<template>部分开始。它将是一个阴影框卡片,显示当前日期,格式化:

<template>
  <div class='cardBox'>
    <div class='container'>
      <h2>Today is:</h2>
      <h3>{{ getCurrentDate }}</h3>
    </div>
  </div>
</template>
  1. 现在,我们需要创建<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>
  1. 为了为我们的盒子设置样式,我们需要在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%;}
}
  1. App.vue文件中,我们需要导入我们的组件,这样我们才能看到它:
<template>
  <div id='app'>
    <current-time />
  </div>
</template>

<script>
import CurrentTime from './components/CurrentTime.vue';

export default {
  name: 'app',
  components: {
    CurrentTime
  }
}
</script>
  1. main.js文件中,我们需要导入style.css文件,以便它包含在 Vue 应用程序中:
import { createApp } from 'vue'; import './style.css'; import App from './App.vue';   createApp(App).mount('#app');
  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

记住始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

它是如何工作的...

Vue 组件几乎与 Node.js 包一样工作。要在代码中使用它,您需要导入组件,然后在要使用的组件的components属性中声明它。

就像一堵砖墙,Vue 应用程序由调用和使用其他组件的组件组成。

对于我们的组件,我们使用了Intl.DateTimeFormat函数,这是一个本机函数,可用于将日期格式化和解析为声明的位置。为了获得本地格式,我们使用了 navigator 全局变量。

另请参阅

使用双向数据绑定创建输入表单

在网上收集数据,我们使用 HTML 表单输入。在 Vue 中,可以使用双向数据绑定方法,其中 DOM 上输入的值传递给 JavaScript,反之亦然。

这使得 Web 表单更加动态,使您有可能在保存或将数据发送回服务器之前管理、格式化和验证数据。

准备工作

此配方的先决条件是 Node.js 12+。

此配方所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

要启动我们的组件,我们可以使用 Vue CLI 创建我们的 Vue 项目,就像我们在使用 Vue CLI 创建您的第一个项目配方中学到的那样,或者使用创建 hello world 组件配方中的项目。

如何做...

按照以下步骤创建具有双向数据绑定的输入表单:

  1. 让我们在src/components文件夹中创建一个名为TaskInput.vue的新文件。

  2. 在这个文件中,我们将创建一个组件,它将有一个文本输入和一些显示文本。这个文本将基于文本输入中键入的内容。在组件的<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>
  1. 现在,在组件的<script>部分,我们将对其命名并将任务添加到data属性中。由于数据始终需要返回一个Object,我们将使用箭头函数直接返回一个Object
<script>
export default {
  name: 'TaskInput',
  data: () => ({
    task: '',
  }),
};
</script>
  1. 我们需要为这个组件添加一些样式。在组件的<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>
  1. 现在,我们需要将这个组件导入到我们的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> 
  1. 要运行服务器并查看您的组件,您需要打开终端(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 中的元素添加事件监听器:

  1. 创建一个新组件或打开TaskInput.vue文件。

  2. <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>
  1. 在组件的<script>部分,我们需要添加一个处理点击事件的方法。这个方法将被命名为addTask。它将发出一个名为add-task的事件,并将任务发送到数据中。之后,组件上的任务将被重置:
<script>
export default {
 name: 'TaskInput',
 data: () => ({
 task: '',
 }),
  methods: {
    addTask(){
      this.$emit('add-task', this.task);
      this.task = '';
    },
  }
};
</script>
  1. 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>
  1. 现在,让我们创建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> 
  1. 要运行服务器并查看您的组件,您需要打开终端(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指令的糖语法:

  1. 打开TaskInput.vue文件。

  2. 在组件的<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>
  1. 要运行服务器并查看您的组件,您需要打开终端(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 和从之前教程中获得的信息创建一个动态的待办事项列表:

  1. 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>
  1. 现在,我们需要在<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()函数到索引中以生成唯一键,因为当减少元素数量时,数组的第一个元素的索引始终是相同的数字。

  1. 我们需要在App.vue文件的methods属性上创建changeStatus函数。此函数将接收任务的索引作为参数,然后转到任务数组并更改finishedAt属性,这是我们标记任务完成的标记。
changeStatus(taskIndex){
  const task = this.taskList[taskIndex];
    if(task.finishedAt){
      task.finishedAt = undefined;
    } else {
      task.finishedAt = Date.now();
    }
}
  1. 现在,我们需要将任务文本添加到屏幕左侧。在组件的<style>部分,我们将使其具有作用域并添加自定义类:
<style scoped>
  .taskList li{
    text-align: left;
  }
</style>
  1. 运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

请记住始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

它是如何工作的...

当我们从组件接收到发射的消息时,我们使用更多数据对消息进行了处理,并将其推送到本地数组变量中。

在模板中,我们迭代此数组,将其转换为任务列表。这显示了我们需要完成的任务、标记任务完成的复选框以及任务完成的时间。

当用户单击复选框时,它会执行一个函数,该函数将当前任务标记为已完成。如果任务已经完成,该函数将将finishedAt属性设置为undefined

另请参阅

创建计算属性并了解其工作原理

想象一下,每次您需要获取处理过的数据时,您都需要执行一个函数。想象一下,您需要获取需要经过一些处理的特定数据,并且您需要每次通过函数执行它。这种类型的工作不容易维护。计算属性存在是为了解决这些问题。使用计算属性使得更容易获取需要预处理甚至缓存的数据,而无需执行任何其他外部记忆函数。

准备工作

这个配方的先决条件是 Node.js 12+。

此处所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

您可以继续进行我们的待办事项项目,或者按照我们在使用 Vue CLI 创建您的第一个项目中学到的内容创建一个新的 Vue 项目。

操作步骤

按照以下步骤创建一个计算属性并了解它的工作原理:

  1. App.vue文件的<script>部分,我们将在datamethod之间添加一个新属性,称为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属性只是返回变量的缓存值,而不是直接的变量本身。

  1. 现在,对于<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>
  1. 要运行服务器并查看您的组件,您需要打开终端(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 过滤器:

  1. 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>
  1. 对于组件的<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>
  1. 要运行服务器并查看您的组件,请打开终端(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 项目。

如何做...

按照以下步骤为您的列表添加一组过滤器和排序器:

  1. App.vue文件的<script>部分,我们将添加新的计算属性;这些将用于排序和过滤。我们将添加三个新的计算属性:baseListfilteredListsortedListbaseList属性将是我们的第一个操作。我们将通过Array.map向任务列表添加一个id属性。由于 JavaScript 数组从零开始,我们将在数组的索引上添加1filteredList属性将过滤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>
  1. <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>
  1. 我们还需要更新changeStatus方法中的函数。由于索引现在从1开始,我们需要将数组的索引减一,以便在更新之前获得元素的真实索引:
changeStatus(taskId) {
    const task = this.taskList[taskId - 1];

    if (task.finishedAt) {
      task.finishedAt = undefined;
    } else {
      task.finishedAt = Date.now();
    }
}
  1. 要运行服务器并查看组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

记得始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

它是如何工作的...

computed属性一起工作,作为列表的缓存,并确保在操作元素时没有副作用:

  1. 对于baseList属性,我们创建了一个具有相同任务的新数组,但为任务添加了一个新的id属性。

  2. 对于filteredList属性,我们使用baseList属性,只返回未完成的任务。

  3. 对于sortedList属性,我们按照它们的 ID,按降序对filteredList属性上的任务进行排序。

当所有操作完成时,displayList属性返回了被操作的数据的结果。

另请参阅

创建条件过滤器以对列表数据进行排序

现在您已经完成了上一个食谱,您的数据应该被过滤和排序,但您可能需要检查过滤后的数据或需要更改排序方式。在这个食谱中,您将学习如何创建条件过滤器并对列表上的数据进行排序。

使用一些基本原则,可以收集信息并以许多不同的方式显示它。

准备就绪

此食谱的先决条件是 Node.js 12+。

此食谱所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

我们可以继续进行我们的待办事项列表项目,或者按照我们在 使用 Vue CLI 创建您的第一个项目 食谱中学到的内容创建一个新的 Vue 项目。

如何做...

按照以下步骤添加条件过滤器以对列表数据进行排序:

  1. App.vue 文件的 <script> 部分,我们将更新 computed 属性;即 filteredListsortedListdisplayList。我们需要向我们的项目添加三个新变量:hideDonereversesortById。所有三个变量都将是布尔变量,并且将以默认值 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>
  1. 对于 <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>
  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve 

请记住始终执行命令 npm run lint --fix,以自动修复任何代码 lint 错误。

组件已呈现并运行:

工作原理...

computed 属性一起作为列表的缓存,并确保在操作元素时没有任何副作用。通过条件过程,可以通过变量更改过滤和排序过程的规则,并且显示会实时更新:

  1. 对于 filteredList 属性,我们取 baseList 属性并仅返回未完成的任务。当 hideDone 变量为 false 时,我们返回完整的列表而不进行任何过滤。

  2. 对于sortedList属性,我们对filteredList属性上的任务进行了排序。当sortById变量为true时,列表按 ID 降序排序;当为false时,按任务的完成时间升序排序。

  3. 对于displayList属性,当reverse变量为true时,最终列表被反转。

当所有操作完成时,displayList属性将返回被操作的数据的结果。

这些computed属性由用户屏幕上的复选框控制,因此用户可以完全控制他们可以看到什么以及如何看到它。

另请参阅

添加自定义样式和过渡效果

向您的组件添加样式是一个很好的做法,因为它可以让您更清楚地向用户展示发生了什么。通过这样做,您可以向用户显示视觉响应,并为他们提供更好的应用体验。

在这个示例中,我们将学习如何添加一种新的条件类绑定。我们将使用混合了 CSS 效果和每个新的 Vue 更新带来的重新渲染。

准备工作

此示例的先决条件是 Node.js 12+。

此处所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

我们可以继续进行待办事项列表项目,或者使用 Vue CLI 创建一个新的 Vue 项目,就像我们在使用 Vue CLI 创建您的第一个项目中学到的那样。

如何做...

按照以下步骤为您的组件添加自定义样式和过渡效果:

  1. 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>
  1. 对于组件的<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>
  1. 要在任务完成时将效果添加到背景中,在<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>
  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve 

请记住始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

工作原理...

每当我们的应用程序中的新项目被标记为已完成时,displayList属性都会更新并触发组件的重新渲染。

因此,我们的taskDone CSS 类附加了一个动画,该动画在渲染时执行,显示绿色背景。

参见

使用 vue-devtools 调试您的应用程序

vue-devtools对于每个 Vue 开发人员都是必不可少的。这个工具向我们展示了 Vue 组件、路由、事件和 Vuex 的深度。

借助vue-devtools扩展,可以调试我们的应用程序,在更改代码之前尝试新数据,执行函数而无需直接在代码中调用它们,等等。

在本教程中,我们将学习如何使用各种开发工具来了解我们的应用程序,并了解它们如何帮助我们的调试过程。

准备工作

本教程的先决条件是 Node.js 12+。

本教程所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

您需要在浏览器中安装vue-devtools扩展程序:

我们可以继续进行我们的待办事项列表项目,或者使用 Vue CLI 创建一个新的 Vue 项目,就像我们在使用 Vue CLI 创建您的第一个项目中学到的那样。

如何做...

在开发任何 Vue 应用程序时,始终将vue-devtools作为良好的实践进行开发。

按照以下步骤了解如何使用vue-devtools以及如何正确调试 Vue 应用程序:

  1. 要进入vue-devtools,您需要在浏览器中安装它,因此请查看本教程的准备就绪部分,获取 Chrome 或 Firefox 扩展程序的链接。在您的 Vue 开发应用程序中,进入浏览器开发者检查器模式。将出现一个名为 Vue 的新标签页:

  1. 您将看到的第一个标签页是组件标签页。此标签显示应用程序组件树。如果单击组件,您将能够查看所有可用数据,计算属性以及插件(如vuelidatevue-routervuex)注入的额外数据。您可以编辑此数据以实时查看应用程序中的更改:

  1. 第二个标签页用于 Vuex 开发。此标签将显示变化的历史记录,当前状态和获取器。可以检查每个变化的传递负载,并进行时间旅行变化,以回到过去并查看状态中的 Vuex 更改:

  1. 第三个标签页专门用于应用程序中的事件发射器。在此处显示应用程序中发射的所有事件。您可以通过单击事件来检查发射的事件。通过这样做,您可以看到事件的名称,类型,事件源(在本例中是组件)以及负载:

  1. 第四个标签页专门用于 vue-router 插件。在那里,您可以查看其导航历史,以及传递给新路由的所有元数据。这是您可以检查应用程序中所有可用路由的地方:

  1. 第五个选项卡是性能选项卡。在这里,您可以检查组件的加载时间以及应用程序实时运行的每秒帧数。以下屏幕截图显示了当前应用程序的每秒帧数,以及所选组件的每秒帧数:

以下屏幕截图显示了组件的生命周期钩子性能以及执行每个钩子所需的时间:

  1. 第六个选项卡是您的设置选项卡。在这里,您可以管理扩展程序并更改其外观,内部行为以及在 Vue 插件中的行为:

  1. 最后一个选项卡是vue-devtools的刷新按钮。有时,当发生热模块重新加载或当应用程序组件树中发生一些复杂事件时,扩展程序可能会丢失对发生情况的跟踪。此按钮强制扩展程序重新加载并再次读取 Vue 应用程序状态。

另请参阅

您可以在github.com/vuejs/vue-devtools找到有关vue-devtools的更多信息。

第二章:组件、混合和功能性组件

构建 Vue 应用就像拼图一样。每个拼图的一部分都是一个组件,每个拼图都有一个槽要填充。

组件在 Vue 开发中扮演着重要角色。在 Vue 中,你的代码的每一部分都将是一个组件 - 它可以是布局、页面、容器或按钮,但最终,它都是一个组件。学习如何与它们交互和重用它们是清理代码和提高 Vue 应用性能的关键。组件是最终会在屏幕上渲染出东西的代码,无论它的大小是多少。

在这一章中,我们将学习如何制作一个可在多个地方重复使用的可视化组件。我们将使用插槽在组件内放置数据,为了快速渲染创建功能性组件,实现父子组件之间的直接通信,并异步加载我们的组件。

然后,我们将把所有这些部分放在一起,创建一个既是美丽的拼图又是 Vue 应用的拼图。

在这一章中,我们将涵盖以下示例:

  • 创建一个可视化模板组件

  • 使用插槽和命名插槽在组件内放置数据

  • 向您的组件传递数据并验证数据

  • 创建功能性组件

  • 访问子组件的数据

  • 创建一个动态注入组件

  • 创建一个依赖注入组件

  • 创建一个mixin组件

  • 延迟加载您的组件

让我们开始吧!

技术要求

在这一章中,我们将使用Node.jsVue-CLI

注意 Windows 用户:您需要安装一个名为windows-build-toolsnpm包才能安装所需的包。为此,请以管理员身份打开 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  ‌

现在,按照以下步骤创建一个视觉模板组件:

  1. src/components文件夹中创建一个名为MaterialCardBox.vue的新文件。

  2. 在此文件中,我们将开始处理组件的模板。我们需要为卡片创建一个框。通过使用 Material Design 指南,此框将具有阴影和圆角:

<template>
  <div class="cardBox elevation_2">
    <div class="section">
      This is a Material Card Box
    </div>
  </div>
</template>
  1. 在我们组件的<script>部分中,我们将只添加我们的基本名称:
<script>
  export default {
    name: 'MaterialCardBox',
  };
</script>
  1. 我们需要创建我们的高程 CSS 规则。为此,请在style文件夹中创建一个名为elevation.css的文件。在那里,我们将创建从024的高程,以便我们可以遵循 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);
}
  1. 在组件的<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>
  1. 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>
  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

请记住始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

工作原理...

视觉组件是一个将包装任何组件并将包装数据与自定义样式放在一起的组件。由于此组件与其他组件混合,因此它可以形成一个新的组件,而无需您在代码中重新应用或重写任何样式。

另请参阅

使用插槽和命名插槽将数据放入组件中

有时,拼图的一些部分会丢失,你会发现自己有一个空白的地方。想象一下,你可以用自己制作的一块填补那个空白的地方 - 而不是拼图盒子里原来的那块。这大致类似于 Vue 插槽的作用。

Vue 插槽就像是组件中的开放空间,其他组件可以用文本、HTML 元素或其他 Vue 组件填充。你可以在组件中声明插槽的位置和行为方式。

通过这种技术,你可以创建一个组件,并在需要时轻松自定义它。

准备工作

这个食谱的先决条件是 Node.js 12+。

此食谱所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

要完成这个食谱,我们将使用我们的 Vue 项目和 Vue CLI,就像在创建可视化模板组件食谱中所做的那样。

操作步骤

按照以下说明在组件中创建插槽和命名插槽:

  1. components文件夹中打开MaterialCardBox.vue文件。

  2. 在组件的<template>部分,我们需要为卡片添加四个主要部分。这些部分基于 Material Design 卡片的结构,分别是headermediamain sectionaction区域。我们将使用默认插槽来放置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>
  1. 现在,我们需要为组件创建文本 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;
}
  1. 在组件的<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>
  1. 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 />部分内。

  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

请记住始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

它是如何工作的...

插槽是可以放置任何可以呈现到 DOM 中的东西的地方。我们选择插槽的位置,并告诉组件在接收到任何信息时在哪里呈现。

在这个示例中,我们使用了命名插槽,这些插槽旨在与需要多个插槽的组件一起使用。要在 Vue 单文件(.vue)的<template>部分中放置组件内的任何信息,您需要添加v-slot:指令,以便 Vue 知道在哪里放置传递下来的信息。

另请参阅

向组件传递数据并验证数据

到目前为止,您知道如何通过插槽将数据放入组件中,但这些插槽是为 HTML DOM 元素或 Vue 组件而设计的。有时,您需要传递诸如字符串、数组、布尔值甚至对象之类的数据。

整个应用程序就像一个拼图,其中每个部分都是一个组件。组件之间的通信是其中的重要部分。向组件传递数据是连接拼图的第一步,而验证数据是连接部件的最后一步。

在这个示例中,我们将学习如何向组件传递数据并验证传递给它的数据。

准备工作

这个食谱的先决条件是 Node.js 12+。

这个食谱所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

要完成这个食谱,我们将继续使用来自 使用插槽和命名插槽在组件内放置数据 食谱的项目。

如何做...

按照以下说明传递数据给组件并验证它:

  1. 打开 src/components 文件夹中的 MaterialCardBox.vue 文件。

  2. 在组件的 <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>
  1. 在组件的 <script> 部分的 computed 属性中,我们需要创建一组用于渲染卡片的视觉操作规则。这些规则被称为 showMediaContentshowActionsButtonsshowHeadercardElevation。每个规则将检查接收到的 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)}`;
  }, },
  1. 在添加了视觉操作规则之后,我们需要将创建的规则添加到组件的 <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>
  1. 要运行服务器并查看你的组件,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:
> npm run serve

记得总是执行命令 npm run lint --fix,自动修复任何代码 lint 错误。

这是渲染和运行的组件:

它是如何工作的...

每个 Vue 组件都是一个 JavaScript 对象,有一个渲染函数。当需要在 HTML DOM 中渲染它时,会调用这个渲染函数。单文件组件是这个对象的一个抽象。

当我们声明我们的组件具有可以传递的唯一 props 时,它为其他组件或 JavaScript 打开了一个小门,以在我们的组件内放置信息。然后,我们可以在组件内使用这些值来渲染数据,进行一些计算,或者制定视觉规则。

在我们的情况下,使用单文件组件,我们将这些规则作为 HTML 属性传递,因为vue-template-compiler将获取这些属性并将其转换为 JavaScript 对象。

当这些值传递给我们的组件时,Vue 会检查传递的属性是否与正确的类型匹配,然后我们对每个值执行验证函数,以查看它是否与我们期望的匹配。

完成所有这些后,组件的生命周期继续,我们可以渲染我们的组件。

另请参阅

创建功能组件

功能组件的美丽之处在于它们的简单性。它们是无状态组件,没有任何数据、计算属性,甚至生命周期。它们只是在传递的数据发生变化时调用的渲染函数。

您可能想知道这有什么用。嗯,功能组件是 UI 组件的完美伴侣,它们不需要在内部保留任何数据,或者只是渲染组件而不需要任何数据操作的可视组件。

顾名思义,它们类似于函数组件,除了渲染函数外没有其他内容。它们是组件的精简版本,专门用于性能渲染和可视元素。

准备工作

此配方的先决条件是 Node.js 12+。

此配方所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

要完成此配方,我们将使用我们的 Vue 项目和 Vue CLI,就像在将数据传递给您的组件并验证数据配方中所做的那样。

如何做...

按照以下说明创建一个 Vue 功能组件:

  1. src/components文件夹中创建一个名为MaterialButton.vue的新文件。

  2. 在这个组件中,我们需要验证我们将接收的 prop 是否是有效的颜色。为此,在项目中安装is-color模块。您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> npm install --save is-color
  1. 在我们组件的<script>部分,我们需要创建props对象,函数组件将接收该对象。由于函数组件只是一个没有状态的渲染函数,因此它是无状态的 - 组件的<script>部分被简化为propsinjectionsslots。将有四个props对象:backgroundColortextColorisRoundisFlat。在安装组件时,这些将不是必需的,因为我们在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>
  1. 我们需要创建一个带有基本class属性按钮的 HTML 元素,并且一个基于接收到的props对象的动态class属性。与普通组件相比,我们需要指定props属性以使用函数组件。对于按钮的样式,我们需要创建一个基于$props的动态style属性。为了直接将所有事件监听器传递给父级,我们可以调用v-bind指令并传递$attrs属性。这将绑定所有事件监听器,而无需我们声明每一个。在按钮内部,我们将添加一个用于视觉增强的divHTML 元素,并添加<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>
  1. 现在,让我们把它弄得漂亮一点。在组件的<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>
  1. 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>
  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
npm run serve

记得总是执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

它是如何工作的...

函数组件就像渲染函数一样简单。它们没有任何类型的数据、函数或对外部世界的访问。

它们最初是作为 JavaScript 对象render()函数在 Vue 中引入的;后来,它们被添加到vue-template-compiler中,用于 Vue 单文件应用程序。

功能性组件通过接收两个参数createElementcontext来工作。正如我们在单文件中看到的,我们只能访问元素,因为它们不在 JavaScript 对象的this属性中。这是因为当上下文传递给渲染函数时,没有this属性。

功能性组件在 Vue 上提供了最快的渲染速度,因为它不依赖于组件的生命周期来检查渲染;它只在数据改变时渲染。

另请参阅

访问您的子组件的数据

通常,父子通信是通过事件或 props 来完成的。但有时,您需要访问存在于子函数或父函数中的数据、函数或计算属性。

Vue 为我们提供了双向交互的方式,从而打开了使用 props 和事件监听器等通信和事件的大门。

还有另一种访问组件之间数据的方法:直接访问。这可以通过在单文件组件中使用模板中的特殊属性来完成,或者通过直接调用 JavaScript 中的对象来完成。有些人认为这种方法有点懒惰,但有时确实没有其他方法可以做到这一点。

准备工作

这个食谱的先决条件是 Node.js 12+。

这个食谱所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

为了完成这个食谱,我们将使用我们的 Vue 项目和 Vue CLI,就像在创建功能性组件的食谱中一样。

如何做...

我们将把这个食谱分成四个部分。前三部分将涵盖新组件的创建-StarRatingInputStarRatingDisplayStarRating,而最后一部分将涵盖数据和函数访问的直接父子操作。

创建星级评分输入

在这个食谱中,我们将创建一个基于五星评级系统的星级评分输入。

按照以下步骤创建自定义星级评分输入:

  1. src/components文件夹中创建一个名为StarRatingInput.vue的新文件。

  2. 在组件的 <script> 部分中,在 props 属性中创建一个 maxRating 属性,它是一个数字,非必需,并具有默认值 5。在 data 属性中,我们需要创建我们的 rating 属性,其默认值为 0。在 methods 属性中,我们需要创建三种方法:updateRatingemitFinalVotinggetStarNameupdateRating 方法将评分保存到数据中,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>
  1. 在组件的 <template> 部分中,我们需要创建一个 <slot> 组件,以便我们可以在星级评分之前放置文本。我们将根据通过 props 属性接收到的 maxRating 值创建一个动态星星列表。创建的每个星星都将在 mouseenterfocusclick 事件中附加一个监听器。当触发 mouseenterfocus 时,将调用 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>
  1. 我们需要将 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;
}
  1. 打开 main.js 文件并将创建的样式表导入其中。css-loader webpack 将处理 JavaScript 文件中导入的 .css 文件。这将有助于开发,因为您无需在其他地方重新导入文件:
import { createApp } from 'vue'; import App from './App.vue'; import './style/materialIcons.css';   createApp(App).mount('#app'); 
  1. 为了给我们的组件设置样式,我们将在 src/style 文件夹中创建一个名为 starRating.css 的通用样式文件。在那里,我们将添加在 StarRatingDisplayStarRatingInput 组件之间共享的通用样式:
.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; }
  1. 在组件的 <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>
  1. 运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

记住始终执行命令 npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

创建 StarRatingDisplay 组件

现在我们有了输入,我们需要一种方法来向用户显示所选的选择。按照以下步骤创建StarRatingDisplay组件:

  1. src/components文件夹中创建一个名为StarRatingDisplay.vue的新组件。

  2. 在组件的<script>部分,在props属性中,我们需要创建三个新属性:maxRatingratingvotes。它们三个都将是数字,非必需的,并具有默认值。在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>
  1. <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>
  1. 在组件的<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>
  1. 要运行服务器并查看您的组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm run serve

请记住始终执行命令npm run lint --fix,以自动修复任何代码 lint 错误。

这是渲染和运行的组件:

创建 StarRating 组件

现在我们已经创建了输入和显示,我们需要将它们合并到一个单独的组件中。这个组件将是我们在应用程序中使用的最终组件。

按照以下步骤创建最终的StarRating组件:

  1. src/components文件夹中创建一个名为StarRating.vue的新文件。

  2. 在组件的<script>部分,我们需要导入StarRatingDisplayStarRatingInput组件。在props属性中,我们需要创建三个新属性:maxRatingratingvotes。所有这三个属性都将是数字,非必需的,并具有默认值。在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>
  1. 对于<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 星。

按照以下步骤来理解和操作子组件中的数据:

  1. App.vue文件中,在组件的<template>部分,删除MaterialCardBox组件的main-text属性,并将其设置为组件的默认插槽。

  2. 在放置的文本之前,我们将添加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>
  1. 在组件的<script>部分,我们将创建一个methods属性,并添加两个新方法:resetVoteforceVote。这些方法将访问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-ifv-else-ifv-else指令的情况下即时更改组件。

在这些情况下,最好的做法是使用动态组件,当计算属性或函数可以定义要呈现的组件时,并且决定是实时进行的。

如果有两种响应,这些决定有时可能很容易做出,但如果有一个长的开关情况,那么它们可能会更复杂,其中您可能有一个需要使用的长列表可能组件。

准备就绪

此配方的先决条件是 Node.js 12+。

此配方所需的 Node.js 全局对象如下:

  • @vue/cli

  • @vue/cli-service-global

为了完成这个配方,我们将使用我们的 Vue 项目和 Vue CLI,就像我们在访问你的子组件数据配方中所做的那样。

如何做...

按照以下步骤创建一个动态注入的组件:

  1. 打开StarRating.vue组件。

  2. 在组件的<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>
  1. 在组件的<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,就像在创建一个动态注入组件配方中所做的那样。

如何做到...

按照以下步骤创建一个依赖注入组件:

  1. 打开StarRating.vue组件。

  2. 在组件的<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>
  1. 打开StarRatingDisplay.vue文件。

  2. 在组件的<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>
  1. 打开StarRatingInput.vue文件。

  2. 在组件的<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 将检查StarRatingDisplayStarRatingInput组件中的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,就像我们在创建一个依赖注入组件示例中所做的那样。

如何做...

按照以下步骤创建一个组件混合:

  1. 打开StarRating.vue组件。

  2. <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,
  },
  }, };
  1. 回到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>
  1. 现在,我们将打开StarRatingDisplay.vue文件。

  2. <script>部分,我们将inject属性提取到一个名为starRatingChild.js的新文件中,该文件将被创建在mixins文件夹中。这将是我们的inject属性的mixin

export default {
  inject: {
    starRating: {
      default() {
        console.error('StarRatingDisplay need to be a child of 
          StarRating');
      },
    },
  },
};
  1. 回到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';
    },
  },
};
  1. 回到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>
  1. 打开StarRatingInput.vue文件。

  2. <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,
    },
  },
};
  1. 回到StarRatingInput.vue文件,我们需要将rating数据属性重命名为rank,并且在getStarName方法中,我们需要添加一个新的常量,它将接收rating属性或rank数据。最后,我们需要导入starRatingChildMixinstarRatingBaseMixin
<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,就像在创建组件混合教程中所做的那样。

如何做...

按照以下步骤使用延迟加载技术导入您的组件:

  1. 打开App.vue文件。

  2. 在组件的<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知道这个导入函数将进行代码拆分,并将使组件成为捆绑包中的一个新文件。

另请参阅

第三章:设置我们的聊天应用程序 - 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 控制台:

  1. 转到aws.amazon.com

  2. 在网站上,点击“创建 AWS 账户”按钮。

  3. 选择创建一个“专业”账户或一个“个人”账户(因为我们将要探索平台并为自己开发示例应用程序,最好选择“个人”账户)。

  4. 现在亚马逊将要求您提供付款信息,以防您的使用超出了免费套餐限制。

  5. 现在是确认您的身份的时候 - 您需要提供一个有效的电话号码,亚马逊将用它来发送您需要输入的 PIN 码。

  6. 在收到 PIN 码后,您将看到一个成功的屏幕和一个“继续”按钮。

  7. 现在您需要为您的账户选择一个计划;您可以选择此教程的“基本计划”选项。

  8. 现在您已经完成,可以登录到您的 Amazon AWS 账户控制台。

配置 AWS Amplify

让我们配置本地 AWS Amplify 环境,以准备开始开发我们的聊天应用程序:

  1. 要设置 AWS Amplify,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> amplify configure
  1. 浏览器将打开,您需要登录到您的 AWS 控制台账户。

  2. 登录后,返回终端并按Enter。CLI 将要求您选择您希望应用程序执行的服务器区域。建议在us-east-1上运行。

  3. 选择区域后,CLI 将要求您为身份和访问管理IAM)定义用户名。您可以按Enter使用默认值,也可以输入您想要的值(但必须是唯一的)。

  4. 现在浏览器将打开以定义您指定的用户的用户详细信息。点击“下一步:权限”按钮转到下一个屏幕。

  5. 点击“下一步:标签”按钮转到 AWS 标签屏幕。在这个屏幕上,点击“下一步:审核”按钮来审查您定义的设置。

  6. 现在你可以点击“创建用户”按钮来创建用户并转到访问密钥屏幕。

  7. 最后,在此屏幕上,等待访问密钥 ID 和秘密访问密钥可用。在浏览器中复制访问密钥 ID,粘贴到终端中,然后按“Enter”键。

  8. 粘贴访问密钥 ID 后,您必须返回浏览器,点击秘密访问密钥上的“显示”链接,复制该值,粘贴到终端中,然后按“Enter”键。

  9. 最后,您需要定义 AWS 配置文件名称(您可以通过按“Enter”键使用默认值)。

您现在已在计算机上设置了 AWS Amplify 环境。

创建您的 Quasar 项目

现在我们将创建 Quasar Framework 项目,这将是我们的聊天应用程序:

  1. 要创建您的 Quasar Framework 应用程序,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> quasar create chat-app
  1. Quasar CLI 将要求输入项目名称;它需要是有效的 npm 软件包名称:
> ? Project name (internal usage for dev) chat-app
  1. CLI 将要求输入产品名称(通常用于渐进式 Web 应用程序PWA),混合移动应用程序和 Electron 应用程序):
? Project product name (must start with letter if building mobile 
  apps) Chat App
  1. 之后,CLI 将要求输入项目描述,这将用于混合应用程序和 PWA:
? Project description A Chat Application
  1. 现在 CLI 将要求输入项目的作者。通常,这是您的 npm 或 Git 配置的作者:
? Author Heitor Ramon Ribeiro <heitor.ramon@example.com>
  1. 现在您可以选择 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)
  1. 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
  1. 现在我们必须选择要添加到项目中的默认功能;我们将选择ESLintVuexAxiosVue-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
  1. 现在您可以选择要在项目中使用的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)
  1. 您需要定义一个 Cordova/Capacitor ID(即使您不构建混合应用程序,也可以使用默认值):
? Cordova/Capacitor id (disregard if not building mobile apps) 
  org.cordova.quasar.app
  1. 最后,您可以选择要运行的软件包管理器,并安装您需要运行代码的软件包:
? 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 项目,请执行以下步骤:

  1. 打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify init
  1. Amplify CLI 将要求输入项目名称:
? Enter a name for the project: chatapp
  1. 然后,您需要为您的机器上正在运行的当前项目定义一个环境:
? Enter a name for the environment: dev
  1. 现在您可以选择您将在项目中使用的默认编辑器:
? 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
  1. 您需要决定由 AWS Amplify 托管的项目类型。在我们的情况下,这将是一个 JavaScript 应用程序:
? Choose the type of app that you're building? (recommended) (Use 
   arrow keys) 
  android 
  ios 
❯ javascript
  1. 对于框架,因为我们将使用 Quasar Framework 作为基础,我们需要从所呈现的框架列表中选择“无”:
? What javascript framework are you using? (recommended) (Use arrow 
  keys) 
  angular 
  ember
  ionic
  react
  react-native
  vue 
❯ none
  1. 您将需要定义应用程序的源路径;您可以将源目录路径保留为默认值src。然后按Enter继续:
? Source Directory Path: (src)
  1. 对于分发目录,由于 Quasar 使用不同类型的路径组织,我们需要将其定义为dist/spa
? Distribution Directory Path: dist/spa
  1. AWS Amplify 将在部署之前使用的构建命令,我们将将其定义为quasar build
? Build Command: quasar build
  1. 对于启动命令,我们需要使用 Quasar 内置的quasar dev命令:
? Start Command: quasar dev

对于 Windows 用户,由于 Amplify 和 WSL 不兼容,您可能需要将启动命令定义如下:

? Start Command: quasar.cmd dev
  1. 现在 CLI 会询问我们是否要为此配置使用本地 AWS 配置文件:
? Do you want to use an AWS profile: y
  1. 我们将选择之前创建的默认配置文件:
? Please choose the profile you want to use: (Use arrow keys) 
❯ default
  1. CLI 完成初始化过程后,我们需要向项目添加托管。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify add hosting
  1. 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 
  1. 然后 CLI 会询问您部署过程将如何进行;选择“手动部署”,然后按Enter继续:
? Choose a type (Use arrow keys)
  Continuous deployment (Git-based deployments) 
❯ Manual deployment 
  Learn more 
  1. 当您完成所有操作后,要完成此过程,您需要发布它。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify publish
  1. 您将被问及是否要继续发布,您可以接受。完成所有操作后,浏览器将打开默认的 Quasar Framework 首页:

它是如何工作的...

AWS Amplify 是 Web 开发人员的一体化解决方案,提供了一整套工具,从托管应用程序到后端开发。

我们能够快速轻松地构建应用程序并将其上线,完全没有遇到基础设施方面的问题。

在这个步骤中,我们设法创建了我们的 AWS 账户,并为本地开发和网页部署准备好了我们的第一个 AWS Amplify 环境。此外,我们还能够创建了将用作聊天应用程序的 Quasar Framework 项目,并将其部署到 AWS 基础设施中,以准备应用程序的未来发布。

另请参阅

创建您的第一个 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 服务。这将提供对用户和身份验证的控制作为服务:

  1. 要初始化您的 AWS Cognito 配置,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify auth add
  1. 现在 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.
  1. 之后,您需要选择用户将如何登录;因为我们正在构建一个聊天应用程序,我们将选择电子邮件
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.
  1. 对于 AWS Cognito,不需要选择更高级的设置。我们可以通过选择不,我完成了。来跳过这一步。
Do you want to configure advanced settings: (Use arrow keys) 
❯ No, I am done.  Yes, I want to make some additional changes.
  1. 最后,我们需要将这个配置推送到云端。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify auth push
  1. 您将被问及是否要继续 - 输入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 的数据库和解析器:

  1. src文件夹中创建一个名为chatApi.graphql的新文件,并打开它。

  2. 创建我们基本的S3Object模式类型,这是一个简单的模型,用于管理放置在 AWS S3 存储桶中的文件的存储:

type S3Object {
  bucket: String!
  region: String!
  key: String! } 
  1. 然后我们将创建我们的用户类型。这就像一个带有更多规则附加的数据库模型。这个类型将有一个@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 }
  1. 我们的用户将与另一个用户进行对话。我们将创建一个对话类型,为了保护这个对话,我们将添加一个@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 }
  1. 对于消息类型,我们需要添加一个@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 }
  1. 现在我们正在使用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 }
  1. 最后,我们需要创建一个type Subscription来在 GraphQL API 内部具有事件处理程序。Subscription类型会监听并处理特定变化的特定变化,createConversationLinkcreateMessage,两者都会在数据库内触发事件:
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:

  1. 要初始化您的 AWS Amplify API 配置,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify add api
  1. 在这里,CLI 将询问您要创建什么类型的 API。我们将选择GraphQL
? Please select from one of the below mentioned services: (Use arrow 
  keys) 
❯ GraphQL  REST
  1. 现在 CLI 将要求输入 API 名称(您可以选择):
? Provide API name: chatapp
  1. 在这里,我们将选择 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
  1. 然后 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.
  1. 现在我们将被问及是否有注释的 GraphQL 模式;由于我们之前已经编写了一个,我们需要输入y
? Do you have an annotated GraphQL schema?: y
  1. 在这里,我们需要输入刚刚创建的文件的路径./src/chatApi.graphql
? Provide your schema file path: ./src/chatApi.graphql
  1. 完成后,我们需要将配置推送到 AWS Amplify。要执行此操作,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify push
  1. 当询问是否要继续时,输入y
? Are you sure you want to continue?: y
  1. CLI 将询问您是否要为新创建的 GraphQL API 生成代码;再次输入y
? Do you want to generate code for your newly created GraphQL API: y
  1. 在这里,您可以选择 CLI 要使用的语言来创建项目中使用的通信文件。我们将选择javascript,但您可以选择最符合您需求的语言:
? Choose the code generation language target: (Use arrow keys) 
❯ javascript
  typescript
  flow
  1. CLI 将询问要放置将生成的文件的位置,我们将使用默认值:
? Enter the file name pattern of graphql queries, mutation and
  subscriptions: (src/graphql/***/**.js) 
  1. 现在 CLI 将询问有关 GraphQL 操作的生成。由于我们正在创建我们的第一个 GraphQL API,我们将选择y,因此 CLI 将为我们创建所有文件:
? Do you want to generate/update all possible GraphQL operations - 
  queries, mutations and subscriptions: y 
  1. 最后,我们可以定义文件中模式的最大深度,我们将使用默认值2
? Enter maximum statement depth [increase from default if your 
  schema is deeply nested]: (2) 
  1. 当你完成所有的事情后,我们需要将配置发布到 AWS Amplify。要做到这一点,你需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify publish 

工作原理...

在创建一个带有 AWS Amplify 的 GraphQL API 的过程中,我们需要一个预先构建的模式,用于生成数据库和端点。这个模式是基于 GraphQL SDL 语言的。Amplify 已经在 SDL 中添加了更多的装饰符,这样我们就可以在 API 的开发中拥有更广泛的可能性。

与此同时,我们需要创建一个 AWS Cognito 用户池,用于保存将在应用程序上注册的用户。这是为了在应用程序外部管理和维护身份验证层,并作为一个服务使用,可以提供更多功能,包括双因素身份验证、必填字段和恢复模式。

最后,在一切都完成之后,我们的 API 已经在 AWS Amplify 上发布,并准备好进行开发,具有可以用作开发环境的 URL。

另请参阅

将 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 驱动程序:

  1. 要安装使用 GraphQL 客户端所需的软件包,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install --save graphql aws-amplify graphql-tag aws-appsync
  1. boot文件夹中创建一个名为amplify.js的新文件,并打开它。

  2. 在这个文件中,我们将导入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 () => {}; 
  1. root文件夹中的quasar.conf.js文件中,我们需要向webpack捆绑器添加新规则。要做到这一点,找到extendWebpack函数。在函数的第一行之后,创建两个新规则给捆绑器,第一个规则将添加graphql-loaderwebpack 加载程序,第二个规则将允许捆绑器理解.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...
  1. 现在,在src/driver文件夹中创建一个名为graphql.js的新文件,并打开它。

  2. 在这个文件中,我们需要从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,
  }, }); 
  1. 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。

另请参阅

为您的应用程序创建 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 云基础设施上保存文件:

  1. 首先,我们需要向项目添加 AWS 存储。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹并执行以下命令:
> amplify add storage
  1. 现在我们需要选择将上传什么内容。我们需要选择内容(图片、音频、视频等)
? Please select from one of the below mentioned services: (Use arrow 
  keys) 
❯ Content (Images, audio, video, etc.)  NoSQL Database
  1. 我们需要为资源添加一个名称。我们将其称为bucket
? Please provide a friendly name for your resource that will be used 
  to label this category in the project: bucket
  1. 现在我们需要提供一个 AWS S3 存储桶名称。我们将其称为chatappbucket
? Please provide bucket name: chatappbucket 
  1. 然后我们需要选择谁可以操作存储桶文件。由于应用程序将仅基于授权,我们需要选择仅授权用户
? Who should have access: (Use arrow keys) 
❯ Auth users only  Auth and guest users
  1. 现在您需要选择用户在存储桶中的访问级别:
? What kind of access do you want for Authenticated users? 
  create/update
  read
❯ delete
  1. 当被问及创建自定义 Lambda 触发器时,选择n
? Do you want to add a Lambda Trigger for you S3 Bucket: n
  1. 最后,我们需要将更改推送到云端。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify push
  1. 当您完成所有操作后,我们需要将配置发布到 AWS Amplify。为此,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),进入项目文件夹,并执行以下命令:
> amplify publish

创建 Amplify Storage 驱动程序

在这部分,我们将创建与 Amplify Storage 通信的驱动程序。该驱动程序将处理我们应用程序中的文件上传:

  1. src/driver文件夹中创建一个名为bucket.js的新文件并打开它。

  2. aws-amplify包中导入Storage类,从quasar中导入uid函数,以及AwsExports

import { Storage } from 'aws-amplify'; import { uid } from 'quasar'; import AwsExports from '../aws-exports'; 
  1. 创建一个名为uploadFile的异步函数,它接收三个参数:filenametypename参数的默认值为uid()type参数的默认值为'image/png'。在这个函数中,我们将调用Storage.put函数,传递namefile作为参数,作为第三个参数,我们将传递一个 JavaScript 对象,其中contentType属性定义为接收到的type,并且accept属性定义为'**/**'。上传完成后,我们将返回一个具有bucketregionuploadedFile属性的 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);
  } }
  1. 创建一个名为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);
  } } 
  1. 最后,导出一个默认的 JavaScript 对象,并将创建的函数uploadFilegetFile作为属性添加进去:
export default {
  uploadFile,
  getFile, };  

创建 Amplify Auth 驱动程序

现在我们将创建认证驱动程序。该驱动程序负责处理应用程序中的所有认证请求并获取用户信息:

  1. src/driver文件夹中创建一个名为auth.js的新文件并打开它。

  2. 在新创建的文件中,从aws-amplify包中导入Auth类:

import { Auth } from 'aws-amplify';
  1. 创建一个名为signIn的新异步函数。它将接收emailpassword作为参数,并且该函数将返回Auth.signIn函数,传递emailpassword作为参数:
export async function signIn(email = '', password = '') {
  try {
  return Auth.signIn({
  username: email,
  password,
  });
  } catch (err) {
  return Promise.reject(err);
  } }
  1. 创建一个名为signUp的新异步函数,该函数将接收emailpassword作为参数。该函数将返回Auth.signUp函数,传递一个带有这些属性的 JavaScript 对象作为参数:usernamepasswordattributesvalidationData

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);
  } }
  1. 创建一个名为validateUser的新异步函数,该函数将接收usernamecode作为参数。该函数等待Auth.confirmSignUp函数的响应,将usernamecode作为参数传递给该函数,并在完成时返回true
export async function validateUser(username = '', code = '') {
  try {
  await Auth.confirmSignUp(username, `${code}`);    return Promise.resolve(true);
  } catch (err) {
  return Promise.reject(err);
  } }
  1. 创建一个名为resendValidationCode的新异步函数,该函数将接收username作为参数。该函数返回Auth.resendSignUp函数,将username作为参数:
export async function resendValidationCode(username = '') {
  try {
  return Auth.resendSignUp(username);
  } catch (err) {
  return Promise.reject(err);
  } } 
  1. 创建一个名为signOut的新异步函数,该函数返回Auth.signOut函数:
export async function signOut() {
  try {
  return Auth.signOut();
  } catch (err) {
  return Promise.reject(err);
  } }
  1. 创建一个名为changePassword的新异步函数,该函数将接收oldPasswordnewPassword作为参数。该函数等待获取当前经过身份验证的用户,并返回Auth.changePassword函数,将获取的useroldPasswordnewPassword作为参数:
export async function changePassword(oldPassword = '', newPassword = '') {
  try {
  const user = await Auth.currentAuthenticatedUser();
  return Auth.changePassword(user, `${oldPassword}`, `${newPassword}`);
  } catch (err) {
  return Promise.reject(err);
  } }
  1. 创建一个名为getCurrentAuthUser的新异步函数;该函数将获取当前经过身份验证的用户,并返回一个带有idemailusername属性的 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 实例,其中包含用户身份验证信息:

  1. src/driver文件夹中创建一个名为appsync.js的新文件并打开它。

  2. 在新创建的文件中,从aws-amplify包中导入AuthAPI,从@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';
  1. 通过执行API.configure函数从aws-amplify包中配置 API,传递一个 JavaScript 对象作为参数,其中包含urlregionauth的属性。

url属性中,传递 GraphQL 端点 URL 的配置。

region属性中,传递当前正在使用的 AWS 区域的配置。

auth属性中,我们需要传递一个具有两个属性typejwtToken的 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(),
  }, });
  1. 最后,我们将API导出为名为AuthAPI的常量:
export const AuthAPI = API;

工作原理...

在这个示例中,我们学习了如何将应用程序的责任分离为可以在多个领域重复使用而无需重写整个代码的驱动程序。通过这个过程,我们能够创建一个用于 Amplify 存储的驱动程序,可以异步发送文件,并且这些文件被保存在 AWS S3 服务器上的存储桶中。

在我们对 Auth 驱动程序的工作中,我们能够创建一个可以管理 Amplify 身份验证 SDK 并在需要时提供信息并封装特殊功能以使在我们的应用程序中执行任务更容易的驱动程序。

最后,在 Amplify AppSync API 中,我们成功实例化了 API 连接器,并使用了所有需要的身份验证标头,以便应用程序可以在没有任何问题的情况下执行,并且用户可以在请求时访问所有信息。

另请参阅

第四章:创建自定义应用程序组件和布局

要开始我们应用程序的开发,我们需要创建整个应用程序将使用的自定义组件和输入。这些组件将采用无状态的方法创建。

我们将开发UsernameInput组件,PasswordInput组件,EmailInput组件和AvatarInput组件。我们还将开发应用程序页面和聊天布局的基本布局,它将包装聊天页面。

在本章中,我们将涵盖以下示例:

  • 为应用程序创建自定义输入

  • 创建应用程序布局

技术要求

在本章中,我们将使用Node.jsQuasar Framework

注意,Windows 用户!您需要安装一个名为windows-build-toolsnpm包,以便能够安装所需的包。要做到这一点,以管理员身份打开 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>部分:

  1. src/components文件夹中创建一个名为UsernameInput.vue的新文件,并打开它。

  2. 创建一个带有nameprops属性的默认导出的 JavaScript 对象:

export default {
  name: '',
  props: {}, };
  1. 对于name属性,将其定义为"UsernameInput"
name: 'UsernameInput',
  1. 对于props属性,将其定义为一个 JavaScript 对象,并添加一个名为value的新属性,它也将是一个具有typedefaultrequired属性的 JavaScript 对象。type属性需要定义为Stringdefault''requiredfalse
props: {
  value: {
    type: String,
    default: '',
    required: false,
  },
},

单文件组件<template>部分

在这里,我们将创建UsernameInput组件的<template>部分:

  1. <template>部分,创建一个QInput组件。创建两个动态属性,valuerules。现在,value将绑定到value属性,rules属性将接收一个数组。数组的第一项是一个函数,用于验证输入,第二项是出现错误时的消息。

  2. outlinedlazy-rules属性设置为true,并将label属性定义为"Your Username"

  3. 最后,通过创建一个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>部分:

  1. components文件夹中创建一个名为PasswordInput.vue的新文件,并打开它。

  2. 创建一个默认导出的 JavaScript 对象,具有三个属性,namepropsdata

export default {
  name: '',
  props: {},
  data: () => (), };
  1. 对于name属性,将值定义为"PasswordInput"
name: 'PasswordInput',
  1. 对于props属性,添加两个属性,valuelabel,都是 JavaScript 对象。每个对象内部应该有三个属性:typedefaultrequired。将value.type设置为Stringvalue.default设置为''value.required设置为false。然后,将label.type设置为Stringlabel.default设置为'Your Password'label.required设置为false
props: {
  value: {
  type: String,
  default: '',
  required: false,
  },
  label: {
  type: String,
  default: 'Your password',
  required: false,
  }, }, 
  1. 最后,在data属性中,添加一个 JavaScript 对象作为返回值,其中isPwd值设置为true
data: () => ({
  isPwd: true, }),

单文件组件

posted @ 2024-05-16 12:10  绝不原创的飞龙  阅读(51)  评论(0编辑  收藏  举报