Vue 3教程(适用于Vue 2用户)

Vue 3尚未正式发布,但是维护者已经发布了Beta版本,供我们的参与者尝试并提供反馈。

如果你想知道Vue 3的主要功能和主要变化是什么,那么我将在这篇文章中重点介绍一下,告诉你使用Vue 3 beta 9创建一个简单的应用程序。

我将介绍尽可能多的新内容,包括fragments,teleport,Composition API以及其他一些晦涩的更改。我将尽力解释该功能或更改的原理。

目录

  • 我们将建立什么

  • Vue 3安装和setup

  • 创建一个新的Vue 3 app

    • 变化的原因

  • 添加state属性

    • 变化的原因

  • 使用一个根组件

  • 多根模板

  • 使用Composition API进行重构

    • setup 方法

    • 变化的原因

  • Teleporting content

  • Emitting 和 event

    • 变化的原因

  • 样式插槽内容

  • 其他改变

我们将建立什么

我们将构建一个带有模式窗口功能的简单应用。我之所以选择它,是因为它可以方便地展示Vue 3的许多变化。

这是该应用在打开和关闭状态下的外观,因此你可以在脑海中描绘出我们正在做什么:

Vue 3安装和setup

与其直接安装Vue 3,不如克隆一个项目 vue-next-webpack-preview ,这将为我们提供一个包括Vue 3在内的最小的Webpack设置。

$ git clone https://github.com/vuejs/vue-next-webpack-preview.git vue3-experiment
$ cd vue3-experiment
$ npm i

一旦克隆好了,安装好了NPM模块,我们需要做的就是删除样板文件,然后创建一个新的 main.js 文件,这样我们就可以从头开始创建我们的Vue 3 app了。

$ rm -rf src/*
$ touch src/main.js

现在,我们将运行开发服务器:

$ npm run dev

创建一个新的Vue 3 app

我们启动一个新的Vue应用程序的方式改变了,我们现在需要导入新的 createApp 方法,而不是使用新的 Vue()

我们调用这个方法,传递我们的Vue实例定义对象,并将返回对象分配给一个变量 app

接下来,我们将在 app 上调用 mount 方法,并传递一个CSS选择器来指示我们的mount元素,就像在Vue 2中使用 $mount 实例方法一样。

// src/main.js

import { createApp } from "vue";

const app = createApp({
  // 根实例定义
});

app.mount("#app");

变化的原因

与旧的API一样,我们添加的任何全局配置(plugins,mixins,原型属性等)都将永久更改全局状态。例如:

// src/main.js

// 影响两个实例
Vue.mixin({ ... })

const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })

在单元测试中,这确实是一个问题,因为要确保将每个测试都与上一个测试隔离是很棘手的。

在新的API下,调用 createApp 将返回一个新的app实例,该实例不会被应用于其他实例的任何全局配置污染。

了解更多: Global API change RFC [1]

添加state属性

我们的模态窗口可以处于两种状态之一——打开或关闭。让我们用一个布尔状态属性 modalOpen 来管理它,我们将给它一个初始值 false

在Vue 2下,我们可以通过在我们的应用实例上创建一个 data 属性并将一个对象分配给该对象来声明 modalOpen 属性,例如:

// src/main.js

const app = createApp({
  data: {
    modalOpen: false
  }
});

不再允许这样做。相反,必须为数据分配一个返回状态对象的工厂函数。

// src/main.js

const app = createApp({
  data: () => ({
    modalOpen: false
  })
});

变化的原因

使用对象而不是工厂函数来存储数据的优点是,首先,它在语法上更简单;其次,你可以在多个根实例之间共享顶级状态,例如:

// src/main.js

const state = {
  sharedVal: 0
};

const app1 = new Vue({ state });
const app2 = new Vue({ state });

// 影响两个实例
app1._data.sharedVal = 1;

这种用例很少,可以使用。因为有两种类型的声明是不适合初学者的,所以决定删除这个特性。

了解更多: Data object declaration removed RFC [2]

在继续之前,我们还添加一个方法来切换 modalOpen 值。这与Vue 2没什么不同。

// src/main.js

const app = createApp({
  data: () => ({
    modalOpen: true  
  }),
  methods: {
    toggleModalState() {
      this.modalOpen = !this.modalOpen;
    }
  }
});

使用一个根组件

如果你现在进入浏览器并检查控制台,则会看到警告“Component is missing render function”,因为我们尚未为根实例定义模板。

Vue 2的最佳实践是为根实例创建一个最小的模板,并创建一个app组件,其中将声明主app标记。

让我们在这里也这样做。

$ touch src/App.vue

现在我们可以获取根实例来渲染该组件。区别在于,对于Vue 2,我们通常会使用render函数来执行此操作:

// src/main.js

import App from "./App.vue";

const app = createApp({
  ...
  render: h => h(App)
});

app.mount("#app");

我们仍然可以做到这一点,但是Vue 3有一个更简单的方法——使 App 成为根组件。为此,我们可以删除根实例定义,而是传递 App 组件。

// src/main.js

import App from "./App.vue";

const app = createApp(App);

app.mount("#app");

这意味着 App 组件不仅由根实例渲染,而且是根实例。

在此过程中,我们通过删除 app 变量来简化语法:

// src/main.js

createApp(App).mount("#app");

现在移至根组件,让我们向该组件重新添加状态和方法:

// src/App.vue

<script>
export default {
  data: () => ({
    modalOpen: true  
  }),
  methods: {
    toggleModalState() {
      this.modalOpen = !this.modalOpen;
    }
  }
};
</script>

我们还为模态功能创建一个新组件:

$ touch src/Modal.vue

现在,我们将提供一个最小的模板,其中包括内容插槽。这确保了我们的模态是可重用的。稍后我们将向此组件添加更多内容。

// src/Modal.vue

<template>
  <div class="modal">
    <slot></slot>
  </div>
</template>

多根模板

现在让我们为我们的根组件创建模板。我们将创建一个按钮来打开模态,它将触发 toggleModalState 方法。

我们还将使用我们刚刚创建的modal组件,它将根据 modalState 的值来渲染。让我们也在槽中插入一段文字作为内容。

// src/App.vue

<template>
  <button @click="toggleModalState">Open modal</button>
  <modal v-if="modalOpen">
    <p>Hello, I'm a modal window.</p>
  </modal>
</template>
<script>
import Modal from "./Modal.vue";
export default {
  components: {
    Modal
  },
  ...
}
</script>

注意这个模板有什么奇怪的地方吗?再看一遍。

没错——有两个根元素。在Vue 3中,由于有了一个叫做片段(fragments)的功能,它不再强制要求有一个单一的根元素! 使用Composition API进行重构

Vue 3的旗舰功能是Composition API。这个新的API允许你使用 setup 功能而不是使用添加到组件定义对象的属性来定义组件功能。

现在,让我们重构App组件以使用Composition API。

在解释代码之前,请清楚我们所做的只是重构——组件的功能将相同。还要注意,模板没有更改,因为Composition API仅影响我们定义组件功能的方式,而不影响我们渲染它的方式。

src/App.vue

<template>
  <button @click="toggleModalState">Open modal</button>
  <modal v-if="modalOpen">
    <p>Hello, I'm a modal window.</p>
  </modal>
</template>
<script>
import Modal from "./Modal.vue";
import { ref } from "vue";
export default {
  setup () {
    const modalState = ref(false);
    const toggleModalState = () => {
      modalState.value = !modalState.value;
    };
    return {
      modalState,
      toggleModalState
    }
  }
};
</script>

setup 方法

首先,请注意,我们导入了 ref 函数,该函数允许我们定义响应式变量 modalState 。此变量等效于 this.modalState

toggleModalState 方法只是一个普通的JavaScript函数。但是,请注意,要更改方法主体中的 modalState 值,我们需要更改其子属性 value 。这是因为使用 ref 创建的响应式变量被封装在一个对象中。这对于保留它们的响应式是非常必要的,因为它们在被传递的过程中会被保留下来。

最后,我们从 setup 方法返回 modalStatetoggleModalState ,因为这些是在呈现模板时传递给模板的值。

变化的原因

请记住,Composition API并不是更改,因为它纯粹是可选的。主要动机是允许更好的代码组织和组件之间的代码重用(因为mixin本质上是一种反模式)。

如果你认为在这个例子中重构App组件以使用Composition API是没有必要的,那你的想法是正确的。但是,如果这是一个更大的组件,或者我们需要与其他组件共享其功能,那么你就会发现它的用处。

Teleporting content

如果你曾经创建过模态功能,你会知道它通常被放置在关闭的 </body> 标签之前。

<body>
  <div>
    <!--main page content here-->
  </div>
  <!--modal here-->
</body>

这样做是因为模式通常具有覆盖页面的背景,要使用CSS来实现,您不需要处理父元素定位和z-index堆栈上下文,因此最简单的解决方案是将模式放在DOM的最底部。

但这在Vue.js中产生了一个问题,它假定UI将作为一个单一的组件树来构建。为了允许将树的片段移动到DOM中的其他位置,在Vue 3中添加了一个新的 teleport 组件。

要使用teleport,首先要在页面上添加一个元素,我们要将模态内容移动到该页面。我们将转到 index.html ,并将ID为 modal-wrapperdiv 放在Vue的安装元素旁边。

index.html

<body>
  ...
  <div id="app"></div><!--Vue mounting element-->
  <div id="modal-wrapper">
    <!--modal should get moved here-->
  </div>
</body>

现在,回到 App.vue ,我们将模态内容包装在 teleport 组件中。我们还需要指定一个 to 属性,为该属性分配一个查询选择器,以标识目标元素,在本例中为 #modal-wrapper

src/App.vue

<template>
  <button @click="toggleModalState">Open modal</button>
  <teleport to="#modal-wrapper">
    <modal v-if="modalOpen">
      <p>Hello, I'm a modal window.</p>
    </modal>
  </teleport>
</template>

就是这样, teleport 中的任何内容都将渲染在目标元素中。

Emitting 和 event

现在,让我们在modal中添加一个按钮,让它可以被关闭。要做到这一点,我们要在modal 模板中添加一个按钮元素,并添加一个点击处理程序,该处理程序会发出一个 close 事件。

src/Modal.vue

<template>
  <div class="modal">
    <slot></slot>
    <button @click="$emit('close')">Dismiss</button>
  </div>
</template>

然后,该事件将由父组件捕获,并将切换 modalState 的值,从逻辑上将其设置为 false 并导致窗口关闭。

src/App.vue

<template>
  ...
    <modal 
      v-if="modalOpen" 
      @click="toggleModalState"
    >
      <p>Hello, I'm a modal window.</p>
    </modal>
  </teleport>
</template>

到目前为止,此功能与Vue 2中的功能相同。但是,现在在Vue 3中,建议您使用新的 emits 组件选项显式声明组件的事件。就像props一样,你可以简单地创建一个字符串数组来命名组件将发出的每个事件。

src/Modal.vue

<template>...</template>
<script>
export default {
  emits: [ "close" ]
}
</script>

变化的原因

想象一下,打开别人写的组件的文件,看到它的prop和event明文声明。马上,你就会明白这个组件的界面,也就是它要发送和接收什么。

除了提供自说明代码外,你还可以使用事件声明来验证你的事件有效载荷,虽然我在这个例子中找不到理由来验证。

了解更多: Emits Option RFC [3]

样式插槽内容

为了使模态可重用,我们提供了一个内容插槽。让我们开始通过为组件添加 style 标签来为内容设置样式。

在我们的组件中使用 scoped CSS是一种很好的做法,以确保我们提供的规则不会对页面中的其他内容产生意外影响。

让我们把任何被放入插槽中的段落文字变成斜体。要做到这一点,我们将使用 p 选择器创建一个新的CSS规则。

src/Modal.vue

<template>...</template>
<script>...</script>
<style scoped>
  p {
    font-style: italic;
  }
</style>

如果你尝试一下,你会发现这一点并不奏效。问题是,在编译时,当插槽内容仍属于父对象时,Scoped styling是在编译时确定的。

Vue 3提供的解决方案是提供一个伪选择器 ::v-slotted() ,允许你在提供插槽的组件中使用范围化规则来针对插槽内容。

这是我们的用法:

<style scoped>
  ::v-slotted(p) {
    font-style: italic;
  }
</style>

Vue 3还包含了其他一些新的Scoped Styling选择器: ::v-deep::v-global ,你可以在这里了解更多: Scoped Styles RFC [4]

其他改变

好吧,这就是我可以在一个简单示例中涵盖的所有新功能。主要的我基本都有了,但这里有一些我认为很重要的,在总结文章之前,我觉得足够重要,可以自己研究一下。

添加的:

  • Global API treeshaking

移出的:

  • Filters

  • Inline templates

  • Event interface for components(不再有event bus)

更改的:

  • Async component API

  • Custom directive API

  • Render function syntax

关于Vue Router也有各种变化,但我将专门用一篇文章来介绍这些变化!

支付宝红包口令:94066657

参考资料

[1]

Global API change RFC: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0009-global-api-change.md

[2]

Data object declaration removed RFC: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0019-remove-data-object-declaration.md

[3]

Emits Option RFC: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md

[4]
Scoped Styles RFC:

https://github.com/vuejs/rfcs/blob/master/active-rfcs/0023-scoped-styles-changes.md

近期文章

JavaScript vs Dart  两者之间的比较

HTML a标签打开新标签页避免出现安全漏洞,请使用“noopener”

实战 | 使用TypeScript和ES模块发布Node模块

避免在单页应用程序中使用CORS,如何以及为什么?

使用CSS文字阴影创建有趣的效果

测量JavaScript函数的性能的简单方法及与其他方式对比

不要过度使用React.useCallback()

什么是纯函数,它是函数式编程 的基础

HTML页面生成器:使用JavaScript和Node创建CLI

React.js和Vue.js的语法并列比较

温故知新 | Vue.js进阶必会,编写你的第一个Vue.js插件

从零开始使用JavaScript制作自己的命令行(CLI工具)

Vue.js中编写更好的v-for循环的6种技巧

如果对你有帮助,还可以 在看、留言、 转发 ,这是对作者最大的帮助。

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章