Vue3 New Features & Major Changes (part)

Reference: v3.vuejs.org/guide/migra…

1. The new features

1.1. Composition API

In Vue2, developers use the Vue Option API to distribute component logic among component options (Data, computed, Methods, Watch, etc.). As components get bigger and have more responsibilities, we have to split the code for these responsibilities into component options. If you want to understand a particular responsibility, you need to “jump” between the options when reading the code. This severely reduces the readability and maintainability of the code.

Vue3 brings a Composition API to address this problem, allowing developers to organize code according to code responsibilities.

1.1.1.Setup component options

With the Setup component option, we can declare a function that will be executed before the component is created, which is the entry point to the Composition API.

Setup takes two arguments:

  • props
  • context
    • A normal JS object
    • Three component properties are exposed
// MyBook.vue

export default {
  setup(props, context) {
    // Attributes (Non-reactive object)
    console.log(context.attrs)

    // Slots (Non-reactive object)
    console.log(context.slots)

    // Emit Events (Method)
    console.log(context.emit)
  }
}
Copy the code

Setup returns an object that can be accessed by components

The instance

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

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

  return {
    repositories, // The returned reactive variables can be accessed directly from the template and other component options
    getUserRepositories // Return a function equivalent to methods}}Copy the code

This cannot be accessed because the component has not been created at the time setup is executed. None of the component options are accessible except props.

1.1.2. Declare reactive variables using ref

In setup, we can declare reactive variables via ref:

The instance

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

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

  return {
    repositories,
    getUserRepositories
  }
}
Copy the code

When we call getUserRepositories, repositories will be changed and the template will be updated accordingly.

* The value attribute is used to read/modify reactive variables internally.

Register lifecycle hooks in 1.1.3. setup

Vue provides an interface to register lifecycle hooks. Name on + hook

* No need for beforeCreate and Created lifecycle hooks! Because setup executes at a similar time to these two hooks, you don’t need to declare them specifically.

The instance

// 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) // on `mounted` call `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}
Copy the code

Declare Watcher in 1.1.4. setup

Vue also provides an interface to register watcher.

The instance

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

Watch accepts three parameters:

  • A reactive reference or a getter function
// watching a getter
const state = reactive({ count: 0 })
watch(
  () = > state.count,
  (count, prevCount) = > {
  	/ *... * /})// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) = > {
	/ *... * /
})
Copy the code
  • The callback
  • (Optional) Configuring objects
    • deep
    • immediate
    • flush

Declare a computed function in setup

Computed returns a read-only responsive reference:

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

1.2. Teleport

Use to add component templates anywhere in the DOM tree.

The most common example is full-screen modal. When there is a full-screen Modal in a deeply nested child component, the modal has to locate the parent component based on its nearest position that is not static, whereas we expect Modal to locate based on the body. Vue2’s solution is to manually insert modal corresponding DOM elements into the body in the Mounted hook, but Vue3 allows us to elegantly add the specified template anywhere in the DOM tree using the Teleport feature.

app.component('modal-button', {
  template: `  
       
        
       `.data() {
    return {
      modalOpen: false}}})Copy the code

Using the teleport tag, tell Vue to “pass” the DOM element compiled from the following template under the body element.

The result is that Modal is going to be a child of the body.

1.2.1. Teleport a Vue component

When a Teleport contains a Vue component, the component remains a logical parent-child component to the current component, even though it is “transported” to another location in the DOM tree!

1.2.2. Multiple Teleports point to the same target

The passed elements are rendered to the target elements in the order declared by teleport:

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<! -- result-->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
Copy the code

1.2.3. API

Teleport tags accept 2 props:

// Must be a valid Query selector
to: {
  type: String.required: true,}// Used to disable the transfer function, wrapped elements are rendered in place. Allows us to initiate teleportation by determining a condition
disabled: {
  type: Boolean.required: false,}Copy the code

1.3. Fragments could

In Vue2, components can only declare one root node, otherwise an error will be reported. Normally, we use an extra div to wrap all child components, whereas in Vue3, components support multiple root nodes!

1.3.1. Vue2 syntax

<! -- Layout.vue -->
<template>
  <div>
    <header>.</header>
    	<main>.</main>
    <footer>.</footer>
  </div>
</template>
Copy the code

1.3.2. Vue3 syntax

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

1.4. Emits Component Option

Similar to props option, Vue3 adds emits option, which is used to declare events triggered by a component. It has two main purposes:

1. It is convenient for developers to know the events supported by the current component at a glance

* When native events are declared in emits option, component-triggered events will be used instead of native events

app.component('custom-form', {
  emits: ['inFocus'.'submit']})Copy the code

2. Event parameters can be verified

app.component('custom-form', {
  emits: {
    // No validation
    click: null.// Validate submit event
    submit: ({ email, password }) = > {
      if (email && password) {
      	return true
      } else {
      	console.warn('Invalid submit event payload! ')
        return false}}},methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})
Copy the code

1.5. The SFC style

1.5.1. Depth pseudo-elements

The depth selectors (>>> > and /deep/) in Vue2 were deprecated.

<style scoped>
/* deep selectors */: :v-deep(.foo) {}
/* shorthand */
:deep(.foo) {}
</style>
Copy the code

1.5.2. Slot pseudo-elements

Elements passed by slot are not affected by the style of the child element by default, but if the child element wishes to modify the slot element, it can use the new :: V-slotted pseudo-element:

<style scoped>
/* targeting slot content */: :v-slotted(.foo) {}
/* shorthand */
:slotted(.foo) {}
</style>
Copy the code

1.5.3. Global pseudo-elements

We can declare global styles in the tag of any component with the new :: V-global () pseudo-element:

<style scoped>
/* one-off global rule */: :v-global(.foo) {}
/* shorthand */
:global(.foo) {}
</style>
Copy the code

2. Major changes (part)

2.1. Template instructions

2.1.1. V – model

2.1.1.1. Changes

We know that V-Model is a grammatical sugar, and the transformation relation in Vue2 is as follows:

<ChildComponent v-model="pageTitle" />

<! -- would be shorthand for: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
Copy the code

In Vue3, the default props name was modified to read:

<ChildComponent v-model="pageTitle" />

<! -- would be shorthand for: -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>
Copy the code
2.1.1.2. Changes

In Vue2 we can use the.sync modifier to allow a child component to notify the parent component to modify its props:

/ / child component
this.$emit('update:title', newValue)
Copy the code
<! -- Parent component -->
<ChildComponent :title.sync="pageTitle" />
<! -- would be shorthand for: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
Copy the code

Vue3 did not use the. Sync modifier, but did allow us to pass a parameter to the V-Model and convert it to: * Title = prop Name, pageTitle = data to bind * When we declare the parameter, the effect is the same as.sync

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

<! -- would be shorthand for: -->

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

Vue2 provides several V-Model modifiers, such as trim and Number, while Vue3 supports custom modifiers

2.2. The key

When v-for is declared on the template, the key is also declared on the template (not on the child elements)

2.3. Use of V-if and V-for on the same element

In Vue2, v-for has a higher priority when v-if is used on the same element as V-for

In Vue3, v-if has a higher priority when used on the same element as V-for

** It is best to avoid using v-if and v-for on the same element

2.4. V-bind = priority of “object”

In Vue2, separately bound prop has higher priority:

<! -- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<! -- result -->
<div id="red"></div>
Copy the code

In Vue3, the order in which the props declarations are declared affects the result of the props merge:

<! -- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<! -- result -->
<div id="blue"></div>

<! -- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<! -- result -->
<div id="red"></div>
Copy the code

2.5. V-on: Event. Native Modifier was removed

In Vue2, the. Native Modifier is used to listen for browser native events, not events emitted by components via emit:

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

In Vue3, because the component added emit Option, when we do not declare an event in emit Option, the event listener defaults to listening for the native event:

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

2.6. ref in v-for

In Vue2, when the ref attribute is present in conjunction with the V-for instruction, the corresponding this.$refs.elementList attribute is an array containing multiple refs:

<div v-for="item in list" ref="elementList"></div>
Copy the code

In Vue3, when the ref attribute is present with the V-for instruction, vUE no longer automatically generates an array of multiple Refs, but we can pass a function to the ref attribute to achieve the same effect:

<div v-for="item in list" :ref="setItemRef"></div>
Copy the code
export default {
  data() {
    return {
      itemRefs: []}},methods: {
    setItemRef(el) {
      if (el) {
      	this.itemRefs.push(el)
      }
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}
Copy the code

*itemRefs need not be arrays, but can be other data types