Vue 3尚未正式发布,可是维护者已经发布了beta版本,以供咱们的用户尝试并提供反馈
html
若是您想知道Vue 3的主要特性和主要变化,我将在本文中经过使用Vue 3 beta 9建立一个简单的应用程序来强调它们vue
我将介绍尽量多的新内容,包括片断、传送、复合API和其余一些模糊的更改。我也会尽我所能来解释这个特性或变动的基本原理webpack
咱们将构建一个带有模态窗口功能的简单应用程序。我选择这个是由于它方便地容许我展现一些Vue 3的更改。git
下面是这款应用在打开和关闭状态下的样子,这样你就能够在脑海中想象出咱们正在作的事情:github
与其直接安装Vue 3,不如克隆Vue -next- Webpack -preview项目,它将为咱们提供包括Vue 3在内的最小的Webpack设置。web
$ git clone https://github.com/vuejs/vue-next-webpack-preview.git vue3-experiment
$ cd vue3-experiment
$ npm i
一旦克隆并安装了NPM模块,咱们所须要作的就是删除样板文件并建立一个新的main.js文件,这样咱们就能够从头建立Vue 3应用程序了。npm
$ rm -rf src/*
$ touch src/main.js
如今咱们将运行开发服务器:数组
立刻,咱们启动一个新的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
})
});
使用对象而不是工厂函数的优势是,首先,它在语法上更简单,其次,你能够在多个根实例之间共享顶层状态,例如:
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>
如今让咱们为根组件建立模板。咱们将建立一个按钮来打开模态,它将触发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的特性,它再也不强制拥有单个根元素!
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,由于它们是在模板呈现时传递给模板的值。
请记住,组合API不是一个更改,由于它彻底是可选的。主要动机是考虑更好的代码组织和组件之间的代码重用(由于mixin本质上是一种反模式)
若是您认为在本例中重构应用程序组件以使用复合API是没必要要的,那么您是正确的。可是,若是这是一个更大的组件,或者咱们须要与其余组件共享它的特性,那么您就会看到它的有用性。
提供更深刻的示例超出了本文的范围,因此若是您有兴趣了解更多关于新API的使用,请参阅个人另外一篇文章,了解什么时候使用新Vue复合API(以及什么时候不使用)。
若是您之前建立过模态特性,您就会知道它一般被放置在关闭的标记以前。
<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>
想象一下,打开别人编写的组件文件,并查看显式声明的组件的道具和事件。立刻,您就会理解这个组件的接口,即它要发送和接收什么。
除了提供自我记录的代码以外,您还可使用事件声明来验证事件负载,尽管在本例中我找不到这样作的理由。
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-deep
and::v-global
which you can learn more about here:Scoped Styles RFC
好了,这就是我能够在一个简单的例子中介绍的全部新特性。我获得了大部分主要的,但这里有一些我认为重要到足以在结束文章以前提到,你能够本身研究:
Added:
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!