This article focuses on the common new features of Vue3. For details, see the Vue3.0 official website tutorial

How is Vue3 better than Vue2?

  • Vue3 has significant performance improvements (smaller package size, faster initial rendering, faster updates, reduced memory usage, etc.)
  • Vue3’s Composition API solves the problem of component fragmentation, making components more logical for later maintenance.
  • Vue3 adds some nice new features like fragments, tree-shaking, and Teleport.

Fragment (Fragment)

In Vue 2.x, because multiple node components are not supported, a warning is issued when developers accidentally create one (The Template root requires exactly one element.).

In Vue 3.x, a component can contain multiple root nodes!

<! -- Layout.vue --><template>
  <header>.</header>
  <main v-bind="$attrs">.</main>
  <footer>.</footer>
</template>
Copy the code

A new global API: createApp

In Vue 2.x, new is used to create a Vue instance, and in Vue 3.x, vue. createApp is used to create a Vue instance.

const app = Vue.createApp({
  / * option * /
})
Copy the code

The following is a table of Vue2 global APIS corresponding to Vue3 global apis

2. X global API 3. X Instance API (app)
Vue.config app.config
Vue.config.productionTip remove
Vue.config.ignoredElements app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

Tree-shaking

Tree-shaking is all about eliminating useless JS code. Dead code elimination (DCE) elimination occurs in many traditional programming language compilers. The compiler determines that certain code does not affect the output at all and eliminates that code. Tree-shaking is a new implementation of DCE. Javascript is different from traditional programming languages in that Javascript is mostly loaded over the network and then executed. The smaller the file size is loaded, the shorter the overall execution time is. It makes more sense for javascript.

In Vue 3.x, both the global and internal apis have been refactored with tree-shaking support in mind. As a result, the global API can now only be accessed as named exports of ES module builds. Such as:

// vue 2
import Vue from 'vue'

Vue.nextTick(() = > {
  // Something DOM related
})

// vue 3
import { nextTick } from 'vue'

nextTick(() = > {
  // Something DOM related
})
Copy the code
//vue 2
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'

test('an async feature'.async() = > {const wrapper = shallowMount(MyComponent)

  // Perform some DOM related tasks

  await wrapper.vm.$nextTick()

  // Run your assertion
})

//vue 3
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature'.async() = > {const wrapper = shallowMount(MyComponent)

  // Perform some DOM related tasks

  await nextTick()

  // Run your assertion
})

Copy the code

With this change, unused global apis in Vue applications will be excluded from the final package for optimal file sizes if the module packaging tool supports tree-shake.

These global apis in Vue 2.x are affected by this change:

  • Vue.nextTick
  • Ue. Observable (replaced by ue. Reactive)
  • Vue.version
  • Vue.compile (full build only)
  • Vue.set (build only)
  • Vue.delete (build only)

The life cycle

Comparison of Vue2 and Vue3 life cycles

emits option (New)

Similar to Prop, events that can be triggered by a component can be defined with the emits option

<template>
  <div>
    <p>{{ text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text'].emits: ['accepted']}</script>
Copy the code

This option can also accept an object that allows the developer to define validators for passed event parameters, similar to the validators in the PROPS definition.

Teleport

Teleport is A technology that allows us to move our templates to A location in the DOM other than the Vue app, A bit like Doraemon’s “Any door”. Here’s an example 🌰 :

//index.html

  <div id="app"></div>
+  <div id="teleport-target"></div>
  <script type="module" src="/src/main.js"></script>
Copy the code
/ / SRC/components/HelloWorld. Vue, add the following, pay attention to the to attribute to keep up with the id selector

  <button @click="showToast" class="btn"</button> <! The to attribute is the target position --><teleport to="#teleport-target">
    <div v-if="visible" class="toast-wrap">
      <div class="toast-msg">I am a Toast writer</div>
    </div>
  </teleport>
  
<script>
  import { ref } from 'vue';
  export default {
  setup() {
    // Toast encapsulation
    const visible = ref(false);
    let timer;
    const showToast = () = > {
      visible.value = true;
      clearTimeout(timer);
      timer = setTimeout(() = > {
        visible.value = false;
      }, 2000);
    }
    return {
      visible,
      showToast
    }
  }
}
</script>
Copy the code

For renderings and a more detailed understanding, see this article (the article is clear and easy to understand)

Remove $children

In Vue2. X, the developer can use this.$children to directly access the child components of the current instance:

<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png">
    <my-button>Change logo</my-button>
  </div>
</template>

<script>
import MyButton from './MyButton'

export default {
  components: {
    MyButton
  },
  mounted() {
    console.log(this.$children) // [VueComponent]}}</script>
Copy the code

In Vue3. X, the $childrens usage has been removed and is no longer supported.

Remove filter

In Vue2. X you can use filters to handle generic text formats.

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountBalance | currencyUSD }}</p>
</template>

<script>
  export default {
    props: {
      accountBalance: {
        type: Number.required: true}},filters: {
      currencyUSD(value) {
        return '$' + value
      }
    }
  }
</script>
Copy the code

In Vue3. X, filters have been removed and are no longer supported. Instead, Vue officially recommends replacing them with method calls or computed properties.

Remove the.sync modifier

In Vue2.x, we sometimes need to “bidirectional bind” a prop. This can be done.

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
/ / short
<ChildComponent :title.sync="pageTitle" />
Copy the code

Equivalent to replacing the. Sync modifier with the V-model modifier in Vue3. X.

<ChildComponent v-model:title="pageTitle" />
Copy the code

Remove the V-on. native modifier

In Vue2. X, to add a native DOM listener to the root element of a child component, use the.native modifier

<my-component
  v-on:close="handleComponentEvent"
  v-on:click.native="handleNativeClickEvent"
/>
Copy the code

In Vue3. X, the.native modifier for V-on has been removed. Also, the new emits option allows child components to define events that will actually be fired.

<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>
Copy the code
// MyComponent.vue
<script>
  export default {
    emits: ['close']
  }
</script>
Copy the code

Remove $listeners

The $Listeners object has been removed from Vue 3. You can see the official documents for details

Remove $(on, off, once)

In Vue3, the $(on, off, once) instance method has been removed and the application instance no longer implements the event-triggering interface.

<script>
	created() {
        console.log(this.$on, this.$once, this.$off) // undefined undefined undefined
	}
</script>

Copy the code

!!!!!!!!! The Composition API is one of the most important features in Vue3!!

Composition API

What is the Composition API?

In order to better understand the Composition API, I found four animation pictures of The Master Monkey about the Composition API. The pictures are from a night of animation, so that you can better understand Vue3’s Composition API

Let’s take a look at what the Options API looks like in Vue 2:

When a project has a new requirement, it is often necessary to add the following function code to the Options API:

As our components start to get bigger, the list of logical concerns grows. This can lead to components that are hard to read and understand, especially for those who didn’t write them in the first place. So the Vue3 Composition API is designed to solve this problem. When we use Vue3’s Composition API, we can collect code related to the same logical concern together, which increases readability:

Now that we know why, we can know how. To get started with the composite API, we first need a place where we can actually use it. In Vue components, we call this location setup

The new setup option is executed before the component is created and acts as an entry point to the composite API once props is resolved. The setup option is a function that accepts the props and context.

(⚠️ Note: You should avoid using this in Setup because it won’t find the component instance. Setup calls occur before data Property, computed Property, or methods are resolved, so they cannot be retrieved in SETUP.)

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String.required: true}},setup(props) {
    console.log(props) // { user: '' }

    return {} // Anything returned here can be used for the rest of the component
  }
  // The "rest" of the component
}
Copy the code

Reactive variable with ref

In Vue 3.0, we can make any reactive variable work anywhere with a new ref function, as follows:

import { ref } from 'vue'

const counter = ref(0)
Copy the code

Ref takes the parameter and returns it wrapped in an object with a value property, which can then be used to access or change the value of a reactive variable:

import { ref } from 'vue'

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) / / 0

counter.value++
console.log(counter.value) / / 1
Copy the code

Encapsulating values in an object may seem unnecessary, but it is necessary in order to keep the behavior of different data types consistent in JavaScript. This is because in JavaScript, primitive types such as Number or String are passed by value rather than by reference, and there is a wrapper object around any value so that we can safely pass it across the application without worrying about losing its responsiveness somewhere.

Register the lifecycle hooks in setup

To make the composite API as functional as the optional API, we also need a way to register the lifecycle hooks in setup.

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// In our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // In 'Mounted', call 'getUserRepositories'

  return {
    repositories,
    getUserRepositories
  }
}
Copy the code

Watch changes in response

Just as we used the Watch option in the component and set the listener on the User property, we can do the same with the watch function imported from Vue. It takes three arguments:

  • A reactive reference or getter that you want to listen for
  • A callback
  • Optional configuration options
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) = > {
  console.log('The new counter value is: ' + counter.value)
})
Copy the code

Whenever counter is modified, such as counter. Value =5, The listener triggers and executes The callback (The second argument), which in this case logs ‘The new Counter value is:5’ to The console.

Independent computed attributes

Like REF and Watch, computed functions imported from Vue can also be used to create computed properties outside the Vue component. Let’s go back to the counter example:

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() = > counter.value * 2)

counter.value++
console.log(counter.value) / / 1
console.log(twiceTheCounter.value) / / 2
Copy the code

Here we pass the first parameter to the computed function, which is a getter-like callback function that outputs a read-only responsive reference. To access the value of the newly created computed variable, we need to use.value property like ref.

Let’s look at two pieces of code that convert the Options API to Composition API:

// Options API

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { 
      type: String.required: true
    }
  },
  data () {
    return {
      repositories: []./ / 1
      filters: {... },/ / 3
      searchQuery: ' ' / / 2}},computed: {
    filteredRepositories () { ... }, / / 3
    repositoriesMatchingSearchQuery () { ... }, / / 2
  },
  watch: {
    user: 'getUserRepositories' / / 1
  },
  methods: {
    getUserRepositories () {
      // Use 'this.user' to get the user repository
    }, / / 1
    updateFilters () { ... }, / / 3
  },
  mounted () {
    this.getUserRepositories() / / 1}}Copy the code
// src/composables/useRepositoryNameSearch.js

import { ref, computed } from 'vue'

export default function useRepositoryNameSearch(repositories) {
  const searchQuery = ref(' ')
  const repositoriesMatchingSearchQuery = computed(() = > {
    return repositories.value.filter(repository= > {
      return repository.name.includes(searchQuery.value)
    })
  })

  return {
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}


// Composition API
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String.required: true}},setup(props) {
    const { user } = toRefs(props)

    const { repositories, getUserRepositories } = useUserRepositories(user)

    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)

    const {
      filters,
      updateFilters,
      filteredRepositories
    } = useRepositoryFilters(repositoriesMatchingSearchQuery)

    return {
      // Because we don't care about unfiltered warehouses
      // We can expose the filtered results under the name 'Repositories'
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}
Copy the code

setup

When you use the setup function, it takes two arguments: 1, props 2, and context

Props

The first argument in the setup function is props. As expected in a standard component, the props in the setup function are reactive and will be updated when a new prop is passed in.

// MyBook.vue

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}
Copy the code

However, because props are reactive, you can’t use ES6 deconstruction, which eliminates the responsiveness of prop. If you need to deconstruct a prop, you can do this using the toRefs function in the setup function:

// MyBook.vue

import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}
Copy the code

If title is an optional prop, there may be no title in the props passed in. In this case, toRefs will not create a ref for the title. You need to use toRef instead:

// MyBook.vue
import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}
Copy the code

Context

The second argument passed to the setup function is context. Context is a plain JavaScript object that exposes the component’s three properties:

// MyBook.vue

export default {
  setup(props, context) {
    // Attribute (non-responsive object)
    console.log(context.attrs)

    // slot (non-responsive object)
    console.log(context.slots)

    // Trigger event (method)
    console.log(context.emit)
  }
}
Copy the code

Context is a normal JavaScript object, that is, it is not reactive, which means you can safely use ES6 deconstruction of the context.

// MyBook.vue
export default {
  setup(props, { attrs, slots, emit }){... }}Copy the code

WatchEffect

This method takes a function and executes it immediately, re-executing the function when the variables in the function change. This method does not get the original value, only the changed value.

watchEffect(() = > {
	console.log(nameObj.name) 
})
Copy the code

Cancel to monitor

const stop = watchEffect(() = > {
	console.log(nameObj.name) 
    setTimeout(() = > {
    	stop()
    }, 5000)})Copy the code

To summarize the differences between watch and watchEffect:

Watch: 1. Lazy will not be executed when the page is displayed for the first time. Lazy will be executed only when the data changes. Parameter can get the current value and the original value 3. Can listen for multiple data changes, use a listener to load

WatchEffect: 1. Execute immediately, without inertia, the first load of the page will execute. 2. Only the current value can be obtained

For more information on responsiveness apis, see the official documentation on responsiveness apis