Vue 3 has not been officially released yet, but the maintainers have released a Beta version for our participants to try out and provide feedback.

If you want to know what the main features and changes of Vue 3 are, I will focus on them in this article, showing you how to create a simple application using Vue 3 Beta 9.

I’ll cover as many new things as possible, including fragments, Teleport, Composition APIS, and other obscure changes. I will try to explain the rationale for this feature or change.


Vue3 Related articles:

  • How does Vue3 Composition API replace Vue Mixins
  • Extract and reuse logic in the Vue3 Composition API
  • How do I build the same components in Vue2 and Vue3
  • A preliminary study on Vue Router in Vue3

What will we build

We will build a simple application with modal window functionality. I chose it because it is a handy way to show off the many changes to Vue 3.

Here’s what the app looks like when it’s on and off, so you can picture in your mind what we’re doing:

Vue 3 Installation and Setup

Instead of installing Vue 3 directly, clone a project called VUe-next-Webpack-Preview, which will give us a minimal webpack setup including Vue 3.

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

Once the clone is done and the NPM module is installed, all we need to do is remove the boilerplate and create a new main.js file so we can create our Vue 3 app from scratch.

$ rm -rf src/*
$ touch src/main.js
Copy the code

Now we will run the development server:

$ npm run dev
Copy the code

Create a new Vue 3 app

The way we launch a new Vue application has changed, and we now need to import the new createApp method instead of using the new Vue().

We call this method, pass our Vue instance definition object, and assign the return object to a variable app.

Next, we’ll call the mount method on our app and pass a CSS selector to indicate our mount element, just as we did with the $mount instance method in Vue 2.

// src/main.js

import { createApp } from "vue";

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

app.mount("#app");
Copy the code

Reasons for change

As with the old API, any global configurations we add (plugins, mixins, stereotype properties, etc.) will permanently change the global state. Such as:

// src/main.js

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

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

In unit tests, this can be a real problem because it can be tricky to ensure that each test is isolated from the last.

Under the new API, calling createApp returns a new app instance that will not be tainted by any global configuration applied to other instances.

Learn more: Global API Change RFC.

Add the State attribute

Our modal window can be in one of two states — open or closed. Let’s manage it with a Boolean state property, modalOpen, which we’ll give an initial value of false.

Under Vue 2, we can declare the modalOpen property by creating a data property on our application instance and assigning an object to it, for example:

// src/main.js

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

This is no longer allowed. Instead, the data must be assigned a factory function that returns a status object.

// src/main.js

const app = createApp({
  data: (a)= > ({
    modalOpen: false})});Copy the code

Reasons for change

The advantages of using objects instead of factory functions to store data are that, first, it is syntactically simpler; Second, you can share top-level state between multiple root instances, for example:

// src/main.js

const state = {
  sharedVal: 0
};

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

// Affects two instances
app1._data.sharedVal = 1;
Copy the code

This use case is rare and can be used. Because there are two types of declarations that are not suitable for beginners, I decided to remove this feature.

Learn more: Data Object Declaration Removed RFC

Before continuing, we also add a method to toggle the modalOpen value. This is no different from Vue 2.

// src/main.js

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

Use a root component

If you go into your browser now and check the console, you’ll see the warning “Component is missing Render function” because we haven’t defined the template for the root instance yet.

The best practice for Vue 2 is to create a minimal template for the root instance and create an app component in which the main App tag will be declared.

Let’s do the same here.

$ touch src/App.vue
Copy the code

Now we can get the root instance to render the component. The difference is that with Vue 2, we usually use the render function to do this:

// src/main.js

import App from "./App.vue";

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

app.mount("#app");
Copy the code

We can still do this, but Vue 3 has an easier way to make the App the root component. To do this, we can remove the root instance definition and pass the App component instead.

// src/main.js

import App from "./App.vue";

const app = createApp(App);

app.mount("#app");
Copy the code

This means that the App component is not only rendered by the root instance, but also by the root instance.

In doing so, we simplify the syntax by removing the app variable:

// src/main.js

createApp(App).mount("#app");
Copy the code

Now moving to the root component, let’s readd state and methods to this component:

// src/App.vue

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

We also create a new component for modal functionality:

$ touch src/Modal.vue
Copy the code

Now, we’ll provide a minimal template that includes content slots. This ensures that our modes are reusable. We’ll add more to this component later.

// src/Modal.vue

<template>
  <div class="modal">
    <slot></slot>
  </div>
</template>
Copy the code

Dogan template

Now let’s create a template for our root component. We will create a button to open the mode, which will trigger the toggleModalState method.

We will also use the Modal component we just created, which will be rendered based on the value of modalState. Let’s also insert a paragraph of text in the slot as content.

// 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>
Copy the code

Notice anything strange about this template? Watch it again.

That’s right — there are two roots. In Vue 3, thanks to a feature called fragments, it no longer mandates a single root element!

Use Composition API for refactoring

Vue 3’s flagship feature is the Composition API. This new API allows you to define component functionality using setup functionality rather than using properties added to component definition objects.

Now, let’s refactor the App component to use the Composition API.

Before explaining the code, be clear that all we’re doing is refactoring — the components will function the same. Also note that the template does not change, because the Composition API only affects how we define a component’s functionality, not how we render it.

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>Copy the code

The setup method

First, notice that we imported the ref function, which allows us to define the reactive variable modalState. This variable is equivalent to this.modalState.

The toggleModalState method is just a normal JavaScript function. Note, however, that to change the modalState value in the method body, we need to change the subproperty value. This is because reactive variables created using ref are encapsulated in an object. This is necessary to preserve their responsivity as they are passed along.

Finally, we return modalState and toggleModalState from the setup method, because these are the values passed to the template when it is rendered.

Reasons for change

Keep in mind that the Composition API is not a change, as it is purely optional. The primary motivation is to allow better code organization and code reuse between components (since mixins are inherently anti-patterns).

If you don’t think it’s necessary to refactor the App component to use the Composition API in this example, you’re right. However, if this is a larger component, or if we need to share its functionality with other components, then you may find it useful.

Teleporting content

If you’ve ever created a modal feature, you know that it is usually placed before the closed tag.

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

This is done because schemas usually have a background that overwrites the page, and to do this with CSS, you don’t need to deal with parent location and z-index stack context, so the simplest solution is to put the schema at the bottom of the DOM.

But this creates a problem in vue.js, which assumes that the UI will be built as a single component tree. A new Teleport component was added in Vue 3 to allow moving fragments of the tree to other locations in the DOM.

To use Teleport, we first add an element to the page, and we move the modal content to the page. We’ll go to index.html and place the div with the MODal-Wrapper ID next to the Vue installation element.

index.html

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

Now, back to app.vue, we wrap the modal content in a Teleport component. We also need to specify a TO attribute to which we assign a query selector to identify the target element, in this case # 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>
Copy the code

That’s it, anything in the teleport will be rendered in the target element.

Emitting and event

Now, let’s add a button in Modal so that it can be turned off. To do this, we add a button element to the Modal template and a click handler that emits a close event.

src/Modal.vue

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

The event is then caught by the parent component and switches the value of modalState, logically setting it to false and causing the window to close.

src/App.vue

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

So far, this functionality is the same as in Vue 2. However, now in Vue 3, it is recommended that you explicitly declare component events using the new emits component option. Just like props, you can simply create an array of strings to name each event that the component will emit.

src/Modal.vue

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

Reasons for change

Imagine opening a file of a component written by someone else and seeing its prop and Event plaintext declarations. In a moment, you will understand the interface of this component, that is, what it sends and receives.

In addition to providing self-explanatory code, you can also use event declarations to validate your event payloads, although I see no reason to do so in this example.

Learn more: Emits Option RFC

Style slot content

To make the modes reusable, we provide a content slot. Let’s start styling the content by adding a style tag to the component.

It is good practice to use scoped CSS in our component to ensure that the rules we provide do not have an unintended effect on the rest of the page.

Let’s italicize any paragraph text that goes into the slot. To do this, we will create a new CSS rule using the P selector.

src/Modal.vue

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

If you try, you’ll find that this doesn’t work. The problem is that Scoped STYLING is determined at compile time while the slot contents still belong to the parent.

The solution provided in Vue 3 is to provide a pseudo-selector :: V-slotted () that allows you to target slot content with scoping rules in components that provide slots.

Here’s how we use it:

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

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.

Other changes

Well, that’s all the new features I can cover in a simple example. I have almost all the major ones, but here are a few that I think are important enough to look into on my own before summarizing the article.

Add:

  • Global API treeshaking

Remove:

  • Filters
  • Inline templates
  • Event Interface for Components (No longer Event Bus)

Changes:

  • Async component API
  • Custom directive API
  • Render function syntax

There have also been various changes to the Vue Router, but I will devote an article to those changes!


This article is first published in the public number “front-end full stack developers”, after paying attention to the private letter reply: gift package, send a network of high-quality video courses network disk information, can save you a lot of money!