写给 vue2.0 开发者的 vue3.0 教程

Vue 3还没有正式发布,但是维护者已经发布了beta版本,以供我们的用户尝试并提供反馈

如果您想知道Vue 3的主要特性和主要变化,我将在本文中通过使用Vue 3 beta 9创建一个简单的应用程序来强调它们

我将介绍尽可能多的新内容,包括片段、传送、复合API和其他一些模糊的更改。我也会尽我所能来解释这个特性或变更的基本原理

如何构建
我们将构建一个带有模态窗口功能的简单应用程序。我选择这个是因为它方便地允许我展示一些Vue 3的更改。

下面是这款应用在打开和关闭状态下的样子,这样你就可以在脑海中想象出我们正在做的事情:

Vue3.0的安装与启动

与其直接安装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应用程序了。

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

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

创建一个新的 vue3.0 app

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

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

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

import { createApp } from "vue";

const app = createApp({
  // root instance definition
});

app.mount("#app");

改变的原因

在旧的API中,我们添加的任何全局配置(插件、混合、原型属性等)都会永久地改变全局状态。例如:

// Affects both instances
Vue.mixin({ ... })

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

这在单元测试中确实是一个问题,因为它使确保每个测试与上一个测试隔离变得很棘手。

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

Learn more:Global API change RFC.

添加状态属性

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

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

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

这是您必须为Vue组件做的事情,但是现在它也对Vue应用程序实例强制执行

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

Reason for change

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

const state = {
  sharedVal: 0
};

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

// Affects both instances
app1._data.sharedVal = 1;

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

Learn more:Data object declaration removed RFC

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

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

使用根组件
如果您现在转到浏览器并检查控制台,您将看到警告“组件缺少呈现函数”,因为我们还没有为根实例定义模板。

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

我们在这里也做一下。

touch src/App.vue

现在我们可以获得根实例来呈现该组件。不同之处在于,在Vue 2中,我们通常会使用渲染函数来完成以下操作:

import App from "./App.vue";

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

app.mount("#app");

我们仍然可以这样做,但是Vue 3有一个更简单的方法——让应用程序成为一个根组件。为此,我们可以删除根实例定义并传递App组件。

import App from "./App.vue";

const app = createApp(App);

app.mount("#app");

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

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

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

现在移动到根组件,让我们重新添加状态和方法到这个组件:

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

让我们也为模态特性创建一个新组件:

touch src/Modal.vue

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

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

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

我们还将使用刚刚创建的模态组件,它将根据modalState的值呈现。我们还可以在内容槽中插入一段文本。

<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中,由于一个称为fragment的特性,它不再强制拥有单个根元素!

使用复合API重构
Vue 3的旗舰特性是复合API。这个新的API允许您使用setup函数定义组件功能,而不是使用添加到组件定义对象的属性。

现在,让我们重构应用程序组件,以使用复合API。

在我解释代码之前,要清楚我们所做的一切都是重构——组件的功能是相同的。还要注意,模板没有改变,因为复合API只影响我们定义组件功能的方式,而不是我们呈现它的方式。

<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 method

首先,请注意我们导入了ref函数,该函数允许我们定义一个反应变量modalState。这个变量等价于This . modalstate。

toggleModalState方法只是一个普通的JavaScript函数。但是,请注意,要更改方法体中的modalState的值,我们需要更改它的子属性值。这是因为使用ref创建的反应变量被包装在一个对象中。这对于保持它们在传递过程中的活性是必要的。

如果您想详细了解refs的工作方式,最好查阅Vue Composition API文档。

最后,我们从setup方法返回modalState和toggleModalState,因为它们是在模板呈现时传递给模板的值。

Reason for change

请记住,组合API不是一个更改,因为它完全是可选的。主要动机是考虑更好的代码组织和组件之间的代码重用(因为mixin本质上是一种反模式)

如果您认为在本例中重构应用程序组件以使用复合API是不必要的,那么您是正确的。但是,如果这是一个更大的组件,或者我们需要与其他组件共享它的特性,那么您就会看到它的有用性。

子由风:vue3.0 Composition API 翻译版本(周末研究版)

zhuanlan.zhihu.com
图标
提供更深入的示例超出了本文的范围,所以如果您有兴趣了解更多关于新API的使用,请参阅我的另一篇文章,了解何时使用新Vue复合API(以及何时不使用)。

When To Use The New Vue Composition API (And When Not To)

vuejsdevelopers.com
图标
传送内容
如果您以前创建过模态特性,您就会知道它通常被放置在关闭的标记之前。

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

这样做是因为情态动词通常有一个页面覆盖的背景(如果你不明白我的意思,请参阅开头的图片)。要使用CSS实现这一点,您不需要处理父元素定位和z-index叠加上下文,因此最简单的解决方案是将模态放在DOM的最底部。

这就与Vue产生了问题。不过,它假设UI将被构建为一个组件树。为了允许树的片段移动到DOM中的其他位置,Vue 3中添加了一个新的传送组件

要使用传送,让我们首先向页面添加一个元素,我们希望将模态内容移动到该页面。我们将转到index.html,并在Vue的挂载元素旁边放置一个带ID modal-wrapper的div。

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

现在,回到App.vue,我们将把模态内容包装在传送组件中。我们还需要指定一个to属性,它将被分配一个用于标识目标元素的查询选择器,在本例中是#modal-wrapper。

<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>

就是这样。传送中的任何内容都将在目标元素中呈现。然而,它仍然会像它在层级中的最初位置一样工作(关于道具,事件等)。

因此,在您保存代码之后,重新加载页面,在开发工具中检查DOM,您会感到惊讶!

Learn more:Teleport RFC

发出一个事件
现在让我们在模态中添加一个按钮来关闭它。为此,我们将向modal tempate添加一个按钮元素,并使用一个发出事件close的click处理程序。

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

然后父组件将捕捉此事件,并切换modalState的值,使其在逻辑上为假,并导致窗口关闭。

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

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

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

Reason for change

想象一下,打开别人编写的组件文件,并查看显式声明的组件的道具和事件。马上,您就会理解这个组件的接口,即它要发送和接收什么。

除了提供自我记录的代码之外,您还可以使用事件声明来验证事件负载,尽管在本例中我找不到这样做的理由。

Learn more:Emits Option RFC

样式槽内容

为了使我们的模式可重用,我们为内容提供了一个插槽。让我们通过向组件添加样式标签来开始对该内容进行样式化。

在我们的组件中使用限定范围的CSS是一个很好的实践,以确保我们提供的规则不会对页面中的其他内容产生意外的影响

让我们把任何段落文本放到槽里都改成斜体。为此,我们将使用p选择器创建一个新的CSS规则。

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

如果你试一下,你会发现它不起作用。问题是,当槽内容仍然属于父内容时,在编译时确定了作用域样式。

Vue 3提供的解决方案是提供一个伪选择器::v- sloated(),允许您使用提供插槽的组件中的作用域规则来锁定插槽内容。

Here’s how we use it:

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

Vue 3 also includes some other new scoped styling selectors::v-deepand::v-globalwhich you can learn more about here:Scoped Styles RFC

Other changes
好了,这就是我可以在一个简单的例子中介绍的所有新特性。我得到了大部分主要的,但这里有一些我认为重要到足以在结束文章之前提到,你可以自己研究:

Added:

Global API treeshaking

github.com
Removed:

Filters
Inline templates
Event interface for components (no more event bus!)
Changed:

Async component API
Custom directive API
Render function syntax
There are also various changes regarding Vue Router but I’ll be dedicating a separate article to those!

展开阅读全文

05-08 1065

v8worker

05-06 1175

学习秘籍

没有更多推荐了,返回首页