一、理解 Vue 3 和创建组件

Vue 3为开发人员带来了许多新功能和变化,所有这些都是为了帮助开发和提高框架的整体稳定性、速度和可维护性。Vue 核心团队以其他框架和库为灵感,成功地实现了 API 的高度抽象,任何人现在都可以使用 Vue,无论他们是前端开发人员还是后端开发人员。

在本章中,我们将学习如何将我们的 Vue 项目升级到新版本,以及更多关于一些新 Vue 功能的信息,例如多个根元素、新的属性继承引擎、如何在另一个应用中使用 Vue 外部公开的 API,以及如何使用新的组合 API 创建组件。

在本章中,您将学习以下内容:

  • Vue 3 有什么新功能
  • 将视图 2 应用升级到视图 3
  • 创建具有多个根元素的组件
  • 使用属性继承创建组件
  • 在 Vue 范围外使用反应性和可观察 API
  • 使用 CompositionAPI 创建组件

Vue 3 有什么新功能

您可能想知道,新版本的框架如何在互联网上引起如此大的宣传?想象一下,在高速公路上驾驶一辆汽车,完成 360 度的翻滚,然后继续全速朝同一方向行驶。这会引起戏剧性的场面,这是描述 Vue 如何从版本 2 发展到版本 3 的完美方式。

在本章的第一部分中,我将向您介绍 Vue 的改进、添加到框架中的内容、发生的变化以及它将如何影响编写 Vue 应用的方式。

对框架的改进

在这个新版本中,Vue 框架有许多改进;他们都致力于尽一切可能使框架变得更好。下面是一些可以影响用户和开发人员日常开发和使用框架的改进。

幕后

外壳看起来和旧的一样,但发动机是一件艺术品。在新版本中,Vue 2 没有遗留代码。核心团队使用 TypeScript 从头开始构建框架,并重写所有面向框架最大性能的内容。

选择 TypeScript 是为了为 Vue 核心团队和开源社区创建一个更易于维护的代码库,并改进自动完成功能,如 IDE 和代码编辑器提供的IntelliSensetypeahead,而无需特殊插件和扩展。

渲染引擎

对于 Vue 3,使用阴影 DOM 的新算法开发了一个新的渲染引擎。默认情况下,此新渲染完全由框架的核心公开,而无需由框架执行。这使得全新渲染功能的新实现成为可能,该功能可以注入框架并替换原始渲染引擎。

在这个新版本的 Vue 中,新的模板编译器是从头开始编写的。这个新的编译器使用了一种新的缓存操作技术和管理渲染元素,并将一种新的提升方法应用于 VNode 的创建。

对于缓存操作,应用了一种新方法来控制元素的位置,其中元素可以是具有计算数据的动态元素,也可以是对可以变异的函数的响应。

Vue 核心团队制作了一个浏览器,在那里可以看到新模板编译器如何呈现最终的render函数。可在查看 https://vue-next-template-explorer.netlify.app/

公开 API

通过所有这些修改,可以呈现 Vue 应用范围之外的文件中公开使用的所有 Vue API。可以在 React 应用中使用 Vue 或阴影 DOM,而无需在 React 应用中呈现 Vue 应用。这种可扩展性是将 Vue 转换为更通用的框架的一种方式,它可以在任何地方使用,而不仅仅是在前端开发中。

新的自定义组件

Vue 3 引入了三个新的自定义组件,开发人员可以使用它们来解决旧问题。这些组件出现在 Vue 2 上,但作为第三方插件和扩展。现在,它们由 Vue 核心团队制作并添加到 Vue 核心框架中。

碎片

在 Vue 2 中,我们总是需要一个父节点将组件包装在单个文件组件中。这是由 Vue 2 的渲染引擎的构造方式造成的,每个节点上都需要一个根元素。

在 VUE2 中,我们需要一个包装器元素,封装将要呈现的元素。在本例中,我们有一个divHTML 元素,包含两个pHTML 子元素,因此我们可以在页面上实现多个元素:

<template>
  <div>
    <p>This is two</p>
    <p>children elements</p>
  </div>
</template>

现在,在 Vue 3 中,可以在单个文件组件上声明任意数量的根元素,而不需要使用新的片段 API(将处理多个根元素)的特殊插件。这有助于为用户维护更清晰的最终代码,而不需要仅包装元素的空壳:

<template>
  <p>This is two</p>
  <p>root elements</p>
</template>

正如我们在 VUE3 代码中看到的,我们能够拥有两个根[T0]HTML 元素,而不需要包装器元素。

传送

Teleport组件,顾名思义,也称为门户组件,是一个可以使元素从一个组件转到另一个组件的组件。这在第一种情况下可能看起来很奇怪,但它有很多应用,包括对话框、自定义菜单、警报、徽章和许多其他需要在特殊位置显示的自定义 UI。

设想一个头部组件,您希望在该组件上有一个自定义插槽,以便可以放置组件:

<template>
  <header>
    <div id="blue-portal" />
  </header>
</header>  

然后,您希望在此标题上显示自定义按钮,但希望从页面调用此按钮。您只需要执行以下代码:

<template>
  <page>
   <Teleport to="blue-portal">
     <button class="orange-portal">Cake</button>
   </Teleport>
  </page>
</template>

现在,您的按钮将显示在标题上,但代码将在页面上执行,从而访问页面范围。

担心

当等待数据的时间比您希望的时间长时,为用户显示自定义加载程序如何?现在不需要自定义代码就可以实现这一点;Vue 将为您处理此问题。Suspense组件将管理此过程,加载数据时使用默认视图,加载数据时使用回退视图。

您可以编写如下特殊包装:

<template>
  <Suspense>
    <template #default>
      <data-table />
    </template>
    <template #fallback>
      <loading-gears />
    </template>
  </Suspense>
</template>

新的 Vue composition API 将了解组件的当前状态,因此它将能够区分组件是正在加载还是准备显示。

API 更改

Vue 3 中进行了一些必要的 API 更改,以清理 Vue API 并简化开发。其中一些是中断更改,另一些是添加。但别担心;Vue 2 对象开发尚未删除,它仍然存在,并将继续使用。这种声明方法是许多开发人员选择 Vue 而不是其他框架的原因之一。

Vue 3 中会发生一些中断更改,这些更改对于了解更多信息非常重要。我们将讨论 Vue 3 中引入的最重要的中断更改,以及如何处理这些更改。

在 Vue 3 中,引入了一种创建组件的新方法—composition API。此方法将使代码的可维护性更好,并为您提供更可靠的代码,您将拥有 TypeScript 的全部功能。

一些小的改变

Vue 3 中存在一些小的中断更改,需要提及。这些更改与我们以前编写代码时使用的一种方法有关,现在在使用 Vue 3 时已被替换。这不是一项艰巨的工作,但你需要了解他们。

再见过滤器,你好过滤器!Vue 过滤器 API

我们在 Vue 2 上使用的filters方式不再可用。Vue 筛选器已从 API 中删除。进行此更改是为了简化渲染过程并加快渲染速度。最后,所有过滤器都是接收字符串并返回字符串的函数。

在 Vue 2 中,我们以前使用的filters是这样的:

{{ textString | filter }}

现在,在 Vue 3 中,我们只需要通过一个function来操纵string

{{ filter(textString) }}

公共汽车刚离开车站!事件总线 API

在 Vue 2 中,我们能够使用全局 Vue 对象的强大功能创建一个新的 Vue 实例,并将此实例用作事件总线,可以在组件和功能之间轻松传输消息。我们只需要发布和订阅事件总线,一切都很完美。

这是在组件之间传输数据的好方法,但对于 Vue 框架和组件来说是一种反模式的方法。在 Vue 中的组件之间传输数据的正确方法是通过父子通信或状态管理(也称为状态驱动体系结构)。

在 VUE3 中,删除了$on$off$once实例方法。现在要使用事件总线策略,建议使用第三方插件或框架,如 mitt(https://github.com/developit/mitt )。

不再使用 global Vue–安装 API

在 Vue 2 中,我们习惯于导入 Vue,在安装应用之前,使用全局 Vue 实例添加pluginsfilterscomponentsrouterstore。这是一种很好的技术,我们可以向 Vue 实例添加任何内容,而无需将任何内容直接附加到已装载的应用。它是这样工作的:

import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';

Vue.use(Vuex);
const store = new Vuex.store({});

new Vue({
  store,
  render: (h) => h(App),
}).$mount('#app');

现在,在 VUE3 中,这已经不可能了。我们需要将每个componentpluginstorerouter直接连接到挂载实例:

import { createApp } from 'vue';
import { createStore } from 'vuex';
import App from './App.vue';

const store = createStore({});

createApp(App)
  .use(store)
  .mount('#app');

使用此方法,我们可以在同一个全局应用中创建不同的 Vue 应用,而不会使应用的pluginsstorerouter相互干扰。

v 型,v 型,v 型-多个 v 型

在开发单个文件组件时,我们只能使用一个[T0]指令和一个[T1]选项进行第二次更新更改。这意味着我们使用大量自定义事件发射器和巨大的对象有效负载来处理组件内部的数据。

In this breaking change, a collateral break change was introduced that resulted in the model property (https://vuejs.org/v2/api/#model) being removed from the Vue API. This property is used in custom components that used to do the same thing that the new v-model directive now does.

使用[T0]指令的新方法将改变 sugar 语法的工作方式。在 Vue 2 中,要使用v-model指令,我们必须创建一个组件,希望将props作为"value"接收,当发生更改时,我们需要发出'input'事件,如以下代码:

<template>
  <input 
    :value="value" 
    @input="$emit('input', $event)" 
  />
</template>
<script>
export default {
  props: {
    value: String,
  },
}
</script>

在 Vue 3 中,为了使语法糖起作用,组件将接收的props属性和事件发射器将发生变化。现在,组件需要一个名为modelValueprops,并发出一个事件'update:modelValue',如下代码:

<template>
  <input 
    :modelValue="modelValue" 
    v-on:['update:modelValue']="$emit('update:modelValue', $event)" 
  />
</template>
<script>
export default {
  props: {
    modelValue: String,
  },
}
</script>

但是多个v-model指令呢?理解v-model中断变化是了解多重v-model新方法如何工作的第一步。

要创建多个v-model组件,我们需要使用我们想要的 model 指令的名称创建各种props,并发出'update:value'事件,其中值是 model 指令的名称:

<script>
export default {
  props: {
    name: String,
    email: String,
  },
  methods: {
   updateUser(name, email) {
    this.$emit('update:name', name);
    this.$emit('update:email', email);
   }
  }
}
</script>

在我们想要使用多个v-model指令的组件中,使用以下代码:

<template>
  <custom-component
    v-model:name="name"
    v-model:email="email"
  />
</template>

该组件将具有每个v-model指令,该指令与子组件发出的事件相关联。在这种情况下,子组件发出'update:email'(父组件),以便能够使用带有电子邮件修饰符的v-model指令。例如,您可以使用v-model:email在组件和数据之间创建双向数据绑定。

合成原料药

这是 Vue 3 最值得期待的功能之一。CompositionAPI 是创建 Vue 组件的一种新方法,具有编写代码的优化方式,并在组件中提供完整的 TypeScript 类型检查支持。此方法以更简单、更有效的方式组织代码。

在这种声明 Vue 组件的新方式中,您只需要有一个将被执行的setup属性,它将返回组件执行所需的所有内容,如以下示例所示:

<template>
  <p @click="increaseCounter">{{ state.count }}</p>
</template>
<script>
import { reactive, ref } from 'vue';

export default {
  setup(){
    const state = reactive({
      count: ref(0)
    });

    const increaseCounter = () => {
      state.count += 1;
    }

    return { state, increaseCounter }
  }
}
</script>

您将从 Vue 核心导入reactivityAPI,以在对象类型数据属性中启用它,在本例中为staterefAPI 启用基本类型值中的反应性,如count,这是一个数字。

最后,这些函数可以在setup函数中声明并传递给返回的对象。然后,<template>部分中的所有内容都可以访问。

现在,让我们继续看一些食谱。

技术要求

在本章中,我们将使用Node.jsVue CLI。

Attention Windows users! You need to install an NPM package called windows-build-tools to be able to install the following requisite packages. To do this, open Power Shell as an administrator and execute the following command: > npm install -g windows-build-tools

要安装 Vue CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> npm install -g @vue/cli @vue/cli-service-global

创建基文件

在本章的所有配方中,我们将使用我们现在创建的基础模板。在配方中开始示例之前,请确保按照以下步骤创建文件:

  1. 在任何文件夹中创建一个新的.html文件并打开它。
  2. 创建一个html标记并添加一个headHTML 元素作为子元素。在headHTML 元素中,添加一个scriptHTML 元素,其src属性定义为http://unpkg.com/vue@next
<html>
  <head>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
</html>
  1. 作为headHTML 元素的同级,创建一个bodyHTML 元素。在bodyHTML 元素中,添加一个divHTML 元素,其属性id定义为"app"
<body>
  <div id="app">
  </div>
</body>
  1. 最后,作为divHTML 元素的同级,创建一个内容为空的scriptHTML 元素。这将是我们放置配方代码的地方:
<script></script>

将视图 2 应用升级到视图 3

将项目从 Vue 2 升级到 Vue 3 有时可以自动完成,但在其他情况下,这需要手动完成。这取决于应用使用 Vue API 的深度。

与使用自定义框架包装器 CLI 的项目相比,使用 Vue CLI 制作和管理的项目将无缝地完成此过程,并且具有更直接的方法。

在本教程中,您将学习如何使用 Vue CLI 升级应用,以及如何手动升级项目和依赖项。

准备

此配方的先决条件如下:

  • Node.js 12+

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

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

为了将 Vue 2 项目升级到 Vue 3,您必须将升级分为不同的部分。我们有框架本身的升级,然后我们有生态系统组件,比如vue-routervuex,最后是最终连接所有东西的捆绑机。

The framework upgrade comes with break changes. There are some break changes that are presented in this book in the What is new in Vue 3 section of this chapter, and others that may occur in a more advanced API schema. You have to manually update and check whether your components are valid for the upgrade on the framework.

使用 Vue CLI 升级项目

使用最新版本的 Vue CLI,您将能够在项目中开箱即用地使用 Vue 3,并且可以将当前项目更新为 Vue 3。

要将 Vue CLI 更新至最新版本,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> npm install @vue/cli-service@latest

手动升级项目

要手动升级项目,必须首先将项目依赖项升级到其最新版本。不能将旧版本的 Vue 生态系统插件与 Vue 3 一起使用。为此,请执行以下步骤:

  1. 我们需要升级 Vue 框架、ESLint 插件(Vue 依赖于它)和捆绑机的vue-loader。要升级它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install vue@next eslint-plugin-vue@next vue-loader@next
  1. 我们需要将新的 Vue 单文件组件编译器作为依赖项添加到项目中。要安装它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install @vue/compiler-sfc@latest
  1. 如果您在项目中使用单元测试和@vue/test-utils包,您还需要升级此依赖项。要升级它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install @vue/test-utils@next @vue/server-test-utils@latest
  1. 对于 Vue 生态系统插件,如果您使用的是vue-router,您也需要对其进行升级。要升级它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install vue-router@next
  1. 如果您的应用使用vuex作为默认状态管理,那么您也需要升级它。要升级它,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> npm install vuex@next

更改起始文件

有了新版本的软件包,我们需要更改起始文件。在使用 Vue CLI 初学者工具包创建的 Vue 项目中,您将找到一个名为main.jsmain.ts的文件。如果您使用的是 TypeScript,则此文件位于src文件夹中。现在按照以下说明操作:

  1. 打开项目的src文件夹中的main.js文件。在导入包的文件顶部,您将看到以下代码:
import Vue from 'vue';

我们需要将其更改为新的 Vue 暴露 API 方法。为此,我们需要从 Vue 包中导入createApp,如下所示:

import { createApp } from 'vue';
  1. 从代码中删除Vue.config.productionTip的全局 Vue 静态属性定义。
  2. 需要更改应用的安装功能。旧 API 将如下所示:
new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');

旧 API 应改为新的createAppAPI,如下所示:

createApp(App)
  .use(router)
  .use(store)
  .mount('#app')
  1. 打开您的vuex门店实例化文件(通常该文件位于src/store中,名称为store.jsindex.js
  2. 将存储的创建从新的vuex类实例化更改为新的createStoreAPI。vuex v3 类实例化可能如下所示:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: { /* ... */ },
  mutations: { /* ... */ },
  actions: { /* ... */ },
  getters: { /* ... */ },
  modules: { /* ... */ },
});

您需要将其内容替换为createStoreAPI,该 API 可能如下所示,例如:

import { createStore } from 'vuex';

export default createStore({
  state: { /* ... */ },
  mutations: { /* ... */ },
  actions: { /* ... */ },
  getters: { /* ... */ },
  modules: { /* ... */ },
});
  1. vue-router生态系统中,我们需要用新的 API 替换路由创建中的旧 API。为此,打开路由创建文件(在src/router文件夹中,通常命名为router.jsindex.js
  2. 最后,在创建文件中,用新的createRouterAPI 替换旧的vue-router类实例化。vue-routerv3 类实例化可能如下所示:
import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

export default new VueRouter({
  routes: [{
    path: '/',
    name: 'HomePage',
    component: () => import('pages/home'),
  }]
});

您还需要将new VueRouter实例化替换为新的createRoutercreateWebHistoryAPI,如本例所示:

import {
  createRouter,
  createWebHistory,
} from 'vue-router';

Vue.use(VueRouter);

export default createRouter({
  history: createWebHistory(),
  routes: [{
    path: '/',
    name: 'HomePage',
    component: () => import('pages/home'),
  }]
});

它是如何工作的。。。

在升级过程中,Vue 为我们提供了两种更新项目的方法。第一种方法是使用 Vue CLI 插件,它尝试自动化升级所需的几乎所有过程和更改。

第二种方法是手动升级项目。此方法要求开发人员将所有依赖项升级到最新版本,安装新的单文件组件编译器@vue/compiler-sfc,并将 Vue 应用、路由和存储的入口文件更改为新 API。

从 Vue 到 Vue,从 Vue 到 Vue,从 Vue 到 Vue,从 Vue 到 Vue,从 Vue 到 Vue,从 Vue 到 Vue 到 Vue,从 Vue 到 Vue,从 Vue 到 Vue,从 Vue 到 Vue 到 Vue,从 Vue 到 Vue 到 Vue,从 Vue 到 Vue 到 Vue,从。

创建具有多个根元素的组件

在 Vue 3 中,可以创建具有多个根元素的组件,而不需要包装元素。此选项也称为片段。

在 React 中,这已经有很长一段时间了,但在 Vue 中,您需要使用自定义的第三方插件,例如vue-fragmenthttps://github.com/Thunberg087/vue-fragment 使用此功能。

在本配方中,您将学习如何创建具有多个根元素的组件,以及如何将其与<template>节和render函数一起使用。

怎么做。。。

在这个配方中,我们将创建两个多根元素组件的示例,一个具有<template>结构,另一个具有render函数。为此,本配方将分为两部分。

创建具有

为了在我们的示例中使用<template>结构,我们将使用 Vue 对象的template属性,其中我们可以传递字符串或模板字符串作为值,该值将由 Vue 脚本插值并在屏幕上呈现:

  1. 使用“创建基本文件”部分中的基本示例,创建一个名为template.html的新文件并打开它。
  2. 在空的<script>HTML 元素中,通过对Vue全局常量进行对象分解来创建常量defineComponentcreateApp
const {
  defineComponent,
  createApp,
} = Vue;
  1. 创建一个名为component的常量,定义为defineComponent方法,将 JavaScript 对象作为参数传递,该参数具有三个属性:datamethodstemplate
const component = defineComponent({
 data: () => ({}),
 methods: {},
 template: ``
});
  1. data属性中,将其定义为单例函数,返回一个 JavaScript 对象,属性名为count,默认值为0
data: () => ({
  count: 0
}),
  1. methods属性中,创建一个名为addOne的属性,该属性是一个将count的值增加1的函数:
methods: {
  addOne() {
    this.count += 1;
  },
},
  1. template属性的模板字符串中,创建一个带有标题的h1HTML 元素。然后,作为同级,创建一个带有绑定到click事件的事件侦听器的buttonHTML 元素,在执行时触发addOne函数:
template: `
  <h1>
    This is a Vue 3 Root Element!
  </h1>
  <button @click="addOne">
    Pressed {{ count }} times.
  </button>
`
  1. 最后,调用createApp函数,将component常量作为参数传递。然后,原型链接mount函数,并作为函数的参数传递divHTML 元素id属性("#app")
createApp(component)
  .mount('#app');

使用“渲染”功能创建组件

为了在我们的示例中使用<template>结构,我们将使用 Vue 对象的template属性,其中我们可以传递字符串或模板字符串作为值,该值将由 Vue 脚本插值并在屏幕上呈现:

  1. 使用“创建基本文件”部分中的基本示例,创建一个名为render.html的新文件并打开它。

  2. 在空的<script>HTML 元素中,创建将使用对象分解方法使用的函数的常量,从Vue全局常量调用defineComponenthcreateApp方法:

const {
  defineComponent,
  h,
  createApp,
} = Vue;
  1. 创建一个名为component的常量,定义为defineComponent方法,将 JavaScript 对象作为参数传递,该参数具有三个属性:datamethodsrender
const component = defineComponent({
  data: () => ({}),
  methods: {},
  render() {},
});
  1. data属性中,将其定义为单例函数,返回一个 JavaScript 对象,该对象的属性名为count,默认值为0
data: () => ({
  count: 0
}),
  1. methods属性中,创建一个名为addOne的属性,该属性是一个将count的值增加1的函数:
methods: {
  addOne() {
    this.count += 1;
  },
},
  1. render属性中,执行以下步骤:
    • 创建一个名为h1的常量,并将其定义为h函数,将'h1'作为第一个参数传递,将标题作为第二个参数传递。
    • 创建一个名为button的常量,它将是h函数,传递"button"作为第一个参数,传递一个属性为onClick的 JavaScript 对象,属性值为this.addOne作为第二个参数,内容为button作为第三个参数。
    • 返回一个数组,第一个值为h1常量,第二个值为button常量:
render() {
  const h1 = h('h1', 'This is a Vue 3 Root Element!');
  const button = h('button', {
      onClick: this.addOne,
    }, `Pressed ${this.count} times.`);

  return [
    h1,
    button,
  ];
},
  1. 最后,调用createApp函数,将component常量作为参数传递,原型链接mount函数,并将divHTML 元素id属性("#app")作为函数的参数传递:
createApp(component)
  .mount('#app');

它是如何工作的。。。

新的 Vue 组件创建 API 需要由函数[T0]执行,并且作为参数传递的 JavaScript 对象保持与 Vue 2 中的旧结构几乎相同的结构。在示例中,我们使用了相同的属性,datarendermethodstemplate,它们都存在于 Vue 2 中。

在具有<template>结构的示例中,我们不必创建包装器元素来封装应用组件的内容,并且能够在组件上直接包含两个根元素。

render函数示例中,出现了相同的行为,但最后一个示例使用了新公开的hAPI,它不再是render函数的参数。示例中出现了突破性变化;在创建按钮时,我们必须在 data JavaScript 对象中使用onClick属性,而不是on属性,并使用click方法。这是因为 Vue 3 的 VNode 具有新的数据结构。

使用属性继承创建组件

自 Vue 2 以来,可以在组件上使用属性继承,但在 Vue 3 中,属性继承做得更好,并且在组件中使用了更可靠的 API。

组件中的属性继承是一种模式,它基于 HTML 元素(如自定义输入、按钮、文本包装器或链接)提供更快的自定义组件开发。

在此配方中,我们将创建一个自定义输入组件,其属性继承直接应用于[T0]HTML 元素。

怎么做。。。

在这里,我们将创建一个组件,该组件将在 DOM 树上的选定元素上具有完整的属性继承:

  1. 使用创建基础文件部分中的基础示例,创建一个名为component.html的新文件并打开它。
  2. 在空的<script>HTML 元素中,创建将使用对象分解方法使用的函数的常量,从Vue全局常量调用defineComponentcreateApp方法:
const {
  defineComponent,
  createApp,
} = Vue;
  1. 创建一个名为nameInput的常量,定义为defineComponent方法,将 JavaScript 对象作为参数传递,该参数具有四个属性:namepropstemplateinheritAttrs。然后,我们将inheritAttrs的值定义为false
const nameInput = defineComponent({
  name: 'NameInput',
  props: {},
  inheritAttrs: false,
  template: ``
});
  1. props属性中,添加一个名为modelValue的属性,并将其定义为String
props: {
  modelValue: String,
},
  1. 在 template 属性的模板字符串中,我们需要执行以下操作:
    • 创建一个labelHTML 元素,并将一个inputHTML 元素作为子元素添加。
    • inputHTML 元素中,将v-bind指令定义为一个 JavaScript 对象,其析构函数值为this.$attrs
    • 将变量属性value定义为接收到的道具的modelValue
    • input属性type设置为"text"
    • change事件侦听器中,添加一个匿名函数,接收一个event作为参数,然后emit一个名为"update:modeValue"的事件,有效负载为event.target.value
template: `
<label>
  <input
    v-bind="{
      ...$attrs,
    }"
    :value="modelValue"
    type="text"
    @change="(event) => $emit('update:modelValue', 
                             event.target.value)"
  />
</label>`
  1. 创建一个名为appComponent的常量,定义为defineComponent方法,将 JavaScript 对象作为参数传递,该参数具有两个属性datatemplate
const component = defineComponent({
  data: () => ({}),
  template: ``,
});
  1. data属性中,将其定义为单例函数,返回一个名为name属性的 JavaScript 对象,默认值为''
data: () => ({
  name: ''
}),
  1. 在 template 属性的模板字符串中,我们需要执行以下操作:
    • 创建一个NameInput组件,其v-model指令绑定到name数据属性。
    • 创建一个值为"border:0; border-bottom: 2px solid red;"style属性。
    • 创建一个值为"name-input"data-test属性:
template: `
<name-input
  v-model="name"
  style="border:0; border-bottom: 2px solid red;"
  data-test="name-input"
/>`
  1. 创建一个名为app的常量,并将其定义为createApp函数,将component常量作为参数传递。然后,调用app.component函数,将要注册的组件的名称作为第一个参数传递,将组件作为第二个参数传递。最后调用app.mount函数,传递"#app"作为参数:
const app = createApp(component);
app.component('NameInput', nameInput);
app.mount('#app');

它是如何工作的。。。

在 VUE3 中,为了创建组件,我们需要执行[T0]函数,将 JavaScript 对象作为参数传递。此对象保持与 Vue 2 几乎相同的组件声明结构。在示例中,我们使用了相同的属性,datamethodspropstemplate,所有这些属性都存在于 V2 中。

我们使用inheritAttrs属性来阻止自动将属性应用到组件上的所有元素,只将它们应用到具有v-bind指令且解构了this.$attrs对象的元素。

为了在 Vue 应用中注册组件,我们首先使用createAppAPI 创建应用,然后在呈现应用之前,执行app.component函数在应用上全局注册组件。

在 Vue 范围外使用反应性和可观察 API

在 VUE3 中,通过公开的 API,我们可以使用 Vue 反应性和反应性变量,而无需创建 Vue 应用。这使后端和前端开发人员能够充分利用其应用中的 VuereactivityAPI。

在此配方中,我们将使用reactivitywatchAPI 创建一个简单的 JavaScript 动画。

怎么做。。。

在这里,我们将使用 Vue exposedreactivityAPI 创建一个应用,在屏幕上渲染动画:

  1. 使用“创建基本文件”部分中的基本示例,创建一个名为reactivity.html的新文件并打开它。
  2. <head>标签中,添加一个新的<meta>标签,其属性chartset定义为"utf-8"
<meta charset="utf-8"/>
  1. <body>标记中,移除div#appHTML 元素,创建一个divHTML 元素,其中id定义为marathon,而style属性定义为"font-size: 50px;"
<div
  id="marathon"
  style="font-size: 50px;"
>
</div>
  1. 在空的<script>HTML 元素中,创建将使用对象分解方法使用的函数的常量,从Vue全局常量调用reactivitywatch方法:
const {
  reactive,
  watch,
} = Vue;
  1. 创建一个名为mod的常量,定义为函数,它接收两个参数ab。然后返回一个算术运算,ab
const mod = (a, b) => (a % b);
  1. 创建一个名为[T0]的常量,其值为[T1]。然后,创建一个名为competitor的常量,其值为reactivity函数,传递一个 JavaScript 对象作为参数,position属性定义为0,而speed属性定义为1
const maxRoadLength = 50;
const competitor = reactive({
  position: 0,
  speed: 1,
});
  1. 创建一个watch函数,传递一个匿名函数作为参数。在函数内部,执行以下操作:
    • 创建一个名为street的常量,将其定义为一个Array,大小为maxRoadLength,并填入*'_'*
    • 创建一个名为marathonEl的常量,并将其定义为 HTML DOM 节点#marathon
    • competitor.position的数组索引中选择street上的元素,如果competitor.position为偶数,则定义为*"![](img/c8b07311-36a4-4df3-98fd-3b68200deed3.png)"*,如果competitor.position为奇数,则定义为*"![](img/562ed724-a630-4193-a9c6-4e143a9690e2.png)"*
    • marathonEl.innertHTML定义为*""*street.reverse().join('')

The emojis used in this recipe are Person Running and Person Walking. The emoji image may vary depending on your OS. The images presented in this recipe are the emojis for the Apple OS.

watch(() => {
  const street = Array(maxRoadLength).fill('_');
  const marathonEl = document.getElementById('marathon');
  street[competitor.position] = (competitor.position % 2 === 1)
    ? ''
    : '';

  marathonEl.innerHTML = '';
  marathonEl.innerHTML = street.reverse().join('');
});
  1. 创建一个setInterval函数,传递一个匿名函数作为参数。在函数内部,将competitor.position定义为mod函数,将competitor.positioncompetitor.speed作为第一个参数,将maxRoadLength作为第二个参数:
setInterval(() => {
  competitor.position = mod(competitor.position +competitor.speed, 
    maxRoadLength)
}, 100);

它是如何工作的。。。

使用 Vue 中公开的reactivewatchAPI,我们能够创建一个具有 Vue 框架中存在的反应性的应用,但不使用 Vue 应用。

首先,我们创建了一个反应对象competitor,其工作方式与 Vuedata属性相同。然后,我们创建了一个watch函数,其工作方式与watch属性相同,但用作匿名函数。在watch功能中,我们为参赛者开辟了道路,并创建了一个简单的动画,使用两种不同的表情,根据道路上的位置进行更改,以便模仿屏幕上的动画。

最后,我们在屏幕上打印了当前跑步者,并创建了每个100mssetInterval功能来改变参赛者在道路上的位置:

使用 CompositionAPI 创建组件

组合 API 是一种新的编写 Vue 组件的方法,它基于使用函数来组合组件,使代码的组织性和可重用性更好。

此方法受 React 钩子的启发,并引入了创建特殊函数的技术,以组成应用,由于使用了公开的 Vue API,因此无需位于 Vue 应用内部即可共享这些应用。

在这个配方中,我们将学习如何创建一个外部函数来获取用户的地理位置,并使用 CompositionAPI 在屏幕上显示该数据。

怎么做。。。

在这里,我们将使用 composition API 创建一个组件,该组件将获取用户 GPS 位置并在屏幕上显示该信息:

  1. 使用“创建基本文件”部分中的基本示例,创建一个名为component.html的新文件并打开它。
  2. 在空的<script>HTML 元素中,创建将使用对象分解方法使用的函数的常量,从Vue全局常量调用createAppdefineComponentsetuprefonMountedonUnmounted方法:
const {
  createApp,
  defineComponent,
  setup,
  ref,
  onMounted,
  onUnmounted,
} = Vue;
  1. 创建一个fetchLocation函数,并在其中创建一个名为watcherlet变量。然后,创建一个名为geoLocation的常量,并将其定义为navigator.geolocation。接下来,创建一个名为gpsTime的常量,并将其定义为ref函数,将Date.now()函数作为参数传递。最后,创建一个名为coordinates的常量,将其定义为ref函数,传递一个 JavaScript 对象作为参数,属性accuracylatitudelongitudealtitudealtitudeAccuracyheadingspeed定义为0
function fetchLocation() {
  let watcher;
  const geoLocation = navigator.geolocation;
  const gpsTime = ref(Date.now());
  const coordinates = ref({
    accuracy: 0,
    latitude: 0,
    longitude: 0,
    altitude: 0,
    altitudeAccuracy: 0,
    heading: 0,
    speed: 0,
  });
}
  1. 然后,在fetchLocation函数内部,在创建常量之后,创建一个名为setPosition的函数,该函数的参数名为payload。在函数内部,将gpsTime.value定义为payload.timestamp参数,将coordinates.value定义为payload.coords参数:
function setPosition(payload) {
  gpsTime.value = payload.timestamp
  coordinates.value = payload.coords
} 
  1. 创建setPosition函数后,调用onMounted函数,传递一个匿名函数作为参数。在函数内部,检查浏览器是否有geoLocationAPI 可用,并将watcher定义为geoLocation.watchPostion函数,将setPosition函数作为参数传递:
onMounted(() => {
  if (geoLocation) watcher = geoLocation.watchPosition(setPosition);
});
  1. 调用onMounted函数后,创建一个onUnmounted函数,传递一个匿名函数作为参数。在函数内部,检查watcher是否定义,然后执行geoLocation.clearWatch函数,传递watcher作为参数:
onUnmounted(() => {
  if (watcher) geoLocation.clearWatch(watcher);
});
  1. 最后,在fetchLocation函数中,返回一个 JavaScript 对象,并在属性/值定义时传递coordinatesgpsTime常量:
return {
  coordinates,
  gpsTime,
};
  1. 创建一个名为appComponent的常量,并将其定义为defineComponent函数,传递一个属性为setuptemplate的 JavaScript 对象作为参数:
const appComponent = defineComponent({
  setup() {},
  template: ``
});
  1. setup函数中,创建一个常数,该常数是一个具有fetchLocation函数的coordinatesgpsTime属性的对象解构:
setup() {
  const {
    coordinates,
    gpsTime,
  } = fetchLocation();
}
  1. setup函数中,创建另一个名为formatOptions的常量,并将其定义为属性为yearmonthdayhourminute'numeric'的 JavaScript 对象。然后,将属性hour12定义为true
const formatOptions = {
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    hour12: true,
  };
  1. 创建formatOptions常量后,创建一个名为formatDate的常量,并将其定义为一个函数,该函数接收一个名为date的参数。然后,返回一个新的Intl.DateTimeFormat函数,传递navigator.language作为第一个参数,传递formatOption常量作为第二个参数。然后,原型链接format功能,传递date参数:
const formatDate = (date) => (new 
  Intl.DateTimeFormat(navigator.language, 
     formatOptions).format(date));
  1. 最后,在setup函数的末尾,返回一个 JavaScript 对象,其属性定义为coordinatesgpsTimeformatDate常量:
return {
  coordinates,
  gpsTime,
  formatDate
};
  1. template属性中,执行以下操作:
    • 使用文本"My Geo Position at {{ formatDate(new Date(gpsTime) }}"创建一个h1HTML 元素。
    • 创建一个ulHTML 元素并添加三个liHTML 元素作为子元素。
    • 在第一个子元素中,添加文本"Latitude: {{ coordinates.latitude }}"
    • 在第二个子元素中,添加文本"Longitude: {{ coordinates.longitude }}"
    • 在第三个子元素中,添加文本"Altitude: {{ coordinates.altitude }}"
template: `
  <h1>My Geo Position at {{formatDate(new 
                          Date(gpsTime))}}</h1>
  <ul>
    <li>Latitude: {{ coordinates.latitude }}</li>
    <li>Longitude: {{ coordinates.longitude }}</li>
    <li>Altitude: {{ coordinates.altitude  }}</li>
  </ul>
`
  1. 最后,调用createApp函数,将appComponent常量作为参数传递。然后,原型链接mount函数,并作为函数的参数传递divHTML 元素id属性("#app")
createApp(appComponent)
  .mount('#app');

它是如何工作的。。。

在这个配方中,首先,我们导入了暴露的 API-createAppdefineComponentsetuprefonMountedonUnmounted,-作为常量,我们将使用这些常量创建组件。然后,我们创建了fetchLocation函数,该函数负责获取用户的地理位置数据,并将其作为反应数据返回,当用户更改位置时,该数据可以自动更新。

获取用户 GPS 位置的能力是可能的,因为现代浏览器上存在的navigator.geolocationAPI 能够获取用户当前的 GPS 位置。通过使用浏览器提供的这些数据,我们能够使用它定义使用 VuerefAPI 创建的变量。

我们使用 Vue 对象声明的setup函数创建了组件,因此渲染知道我们正在使用新的 composition API 作为组件创建方法。在setup函数中,我们导入了fetchLocation函数的动态变量,并创建了一个方法,用于格式化日期,以用作模板上的过滤器。

然后,我们返回导入的变量和过滤器,以便可以在模板部分使用它们。在模板部分,我们创建了一个标题,添加了最后一个 GPS 位置的时间,使用过滤器对其进行格式化,并创建了一个用户纬度、经度和海拔的列表。

最后,我们使用createApp公开的 API 创建了应用,并安装了 Vue 应用。

另见

有关Navigator.geolocation的更多信息,请访问https://developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation

有关Intl.DateTimeFormat的更多信息,请访问https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat