Modular API

Let’s start with a simple example of VUe2

<template lang="pug"> div div {{number}} button(@click="handleDoubleNumber") multiply number div {{string}} Button (@click="handleDoubleString") double string </template> <script> export default {data() {return {number: 1, string: 'hi', } }, methods: { handleDoubleNumber() { this.number = this.number * 2 }, handleDoubleString() { this.string += this.string }, }, }Copy the code

As we continued to add features, the code blocks grew, making writing and reading difficult. I believe you have encountered in the company to modify the predecessor very smelly long code, obviously only need to change a little logic, but have to clear the whole code, make people really anxious. In addition, if multiple pages need to use some functions, we generally need to separate them and mix them with mixins. However, mixins have disadvantages such as naming conflicts, unknown source of variables, difficult code to understand and maintain. This fragmentation makes it difficult to understand and maintain complex components, and the separation of options masks potential logic problems. In addition, when dealing with a single logical concern, we must constantly “jump” to the option blocks of the related code.

It is better to be able to configure code related to the same logical concern together, which is what composite apis enable us to do. Let’s rewrite the above code using the composite API.

Create a usedoublenumber.js file

import { ref } from 'vue'
export default function useDoubleNumber() {
    const number = ref(1)

    const handleDoubleNumber = () => {
        number.value = number.value * 2
    }

    return {
        number,
        handleDoubleNumber,
    }
}
Copy the code

Create a new usedoubleString.js

import { ref } from 'vue'
export default function useDoubleString() {
    const string = ref('hi')

    const handleDoubleString = () => {
        string.value += string.value
    }

    return {
        string,
        handleDoubleString,
    }
}
Copy the code

Page use

<template lang="pug"> div div {{number}} button(@click="handleDoubleNumber") multiply number div {{string}} Button (@click="handleDoubleString") double string </template> <script> import {defineComponent} from 'vue' import useDoubleNumber from './useDoubleNumber' import useDoubleString from './useDoubleString' export default defineComponent({ setup() { const { number, handleDoubleNumber } = useDoubleNumber() const { string, handleDoubleString } = useDoubleString() return { number, handleDoubleNumber, string, handleDoubleString, } }, }) </script>Copy the code

Through the above transformation, we can find that the logic of the page is very clear, function reuse is also very convenient. This greatly improves the readability and maintainability of the code.

Setup component options

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 component option is executed before the component is created, once the props is resolved, and acts as an entry point to the composition API. Because the component instance has not been created when setup is executed, there is no this in the Setup option.

The setup option should be a function that accepts props and context. In addition, everything we return from Setup is exposed to the rest of the component (computed properties, methods, lifecycle hooks, and so on) as well as the component’s template.

Props

The first parameter in setup is props. The props in the setup function is responsive and will be updated when a new prop is passed in. Because the props are responsive, we can’t deconstruct it, which would eliminate its responsiveness.

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

What do we do if we want to deconstruct the props and keep it responsive? We’ll describe it in the following sections.

context

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

export default { setup(props, Context) {// Attribute (non-responsive object), equivalent to this.$attrs console.log(context.attrs) // slot (non-responsive object), equivalent to this.$slots Console. log(context.slots) // Trigger event (method) corresponding to this.$emits console.log(context.emit)}}Copy the code

We said setup doesn’t have this, so how do we get an instance of a component in setup?

We can use the getCurrentInstance method, which can only be used in setup or lifecycle hooks.

<template>
    <p @click="handleClick">{{ num }}</p>
</template>
<script>
import { ref, getCurrentInstance } from 'vue'
export default {
    setup() {
        const num = ref(3)
        const instance = getCurrentInstance() // works
        console.log(instance)

        const handleClick = () => {
            getCurrentInstance() // doesn't work
        }
        return { num, handleClick }
    },
}
</script>
Copy the code

When printed out, you will find two properties CTX and proxy as follows (only some properties are captured) :

This is the content of the instance we want to get, the only difference being that the proxy property is reactive.

Ref, Reactive, toRefs

In VUe2, data is defined in data. How does VUe3 define reactive variables?

We saw in the example above that we introduced a ref function from vUE to declare a variable, and our view will update as the variable changes. In VUe3, we can make any reactive variable work anywhere through a new ref function. 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, directly accessed in template.

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

Refs are used to define reactive data of a primitive type (and can also be used to define reference types). Reactive data of a primitive type (and cannot be used to define a primitive type) is used to define reactive data of a primitive type.

Reactive returns a reactive copy of the object. Reactive conversion is “deep” – it affects all nested properties.

import { reactive } from 'vue'

const state = reactive({ counter: 0 })
console.log(state.counter) // 0

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

Originally we mentioned that we could not deconstruct props otherwise it would lose its responsiveness. At this point we can use toRefs to convert a reactive object into a normal object, where each property of the resulting object is a REF to the corresponding property of the original object.

import { reactive } from 'vue'

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)

state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
Copy the code

Ref = ref (); ref = ref (); ref = ref (); ref ();

<template> <div> <div ref="el">div element </div> </template> <script> import {ref, OnMounted} from 'vue' export default {setup() {// create a DOM reference, OnMounted (() => {el.value.innerhtml = 'content modified'}) // Create a reference to the element Return {el}}} </script>Copy the code

Lifecycle hook

To make the composite API more complete than the optional API, we also need a way to register the lifecycle hooks in setup. The lifecycle hooks on the composite API have the same name as the optional API, but are prefixed with ON, as follows:

Option type API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

These functions accept a callback function that will be executed when the hook is called by the component:

import { onMounted } from 'vue' export default { setup() { // mounted onMounted(() => { console.log('Component is mounted! ')})}}Copy the code

Watch changes in response

In VUe2, when we want to monitor the change of some data to perform some operations, we usually use Watch.

export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    }
  }
}
Copy the code

In VUe3, we can do the same with the Watch function imported from Vue. It takes three arguments:

  • A reactive reference or getter function that we want to listen for
  • A callback
  • Optional configuration options
Import {ref, reactive, watch} from 'vue' const state = reactive({count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... Const count = ref(0) watch(count, (count, prevCount) => {/* prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount, prevCount) * /})Copy the code

It can also listen on multiple sources simultaneously using arrays:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
Copy the code

In VUe3, there is also a listener, watchEffect, which differs from Watch in that it executes immediately, tracing its dependencies responsively, and rerunking the function when a dependency changes. However, watchEffect does not access values prior to the dependency change.

import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)
Copy the code

Both Watch and watchEffect link listeners to the component’s lifecycle when its setup() function or lifecycle hook is called and stop automatically when the component is uninstalled. You can also display the return value of the call to stop listening, if desired.

const stop = watchEffect(() => {
  /* ... */
})

// later
stop()
Copy the code

Independent computed attributes

In VUE, we generally deal with the states that depend on other states by calculating attributes. In VUe2, the calculation attributes are used as follows:

export default {
  data() {
    return {
      counter: 0
    }
  },
  computed: {
    doubleCounter() {
       return this.counter * 2
    }
  }
}
Copy the code

In VUe3, the calculated attributes are used as follows:

import { ref, watchEffect } from 'vue'

const count = ref(1)
const plusOne = computed(() => count.value++)

console.log(plusOne.value) // 2

plusOne.value++ // error
Copy the code

Alternatively, it can use an object with get and set functions to create a writable ref object

import { ref, watchEffect } from 'vue'

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
Copy the code

Teleport

The word Teleport means “transfer,” and with it we can insert child components or DOM nodes wherever we want.

</teleport> </div> </div> </template>Copy the code

Let’s go to the console and see

You can see that our element is running under the element id app.

fragment

In VUe2, multiple components are not supported, and a warning is issued when users accidentally create multiple components, so to fix this error, many components are wrapped in a

. In VUe3, however, components can have multiple root nodes.
<template> <header>... </header> <main>... </main> <footer>... </footer> </template>Copy the code

Incompatible changes

Global API

Vue2 has a number of global apis and configurations that can change the behavior of Vue globally. The application we define is just the root Vue instance created with new Vue(). From the same. Each root instance created by a Vue constructor shares the same global configuration. This makes it difficult for multiple “apps” on the same page to share copies of the same Vue.

// This affects both root instances Vue. Mixin ({/*... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })Copy the code

To avoid these problems, vue3 has a new global API: createApp, which returns an application instance.

import { createApp } from 'vue'

const app = createApp({})
Copy the code

The application instance exposes a subset of the current API, and any API that globally changes Vue behavior is moved under the application instance:

2. X global API 3. X instance API (app)
Vue.config app.config
Vue.config.productionTip removed
Vue.config.ignoredElements app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
const app = createApp(MyApp) app.component('button-counter', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) app.directive('focus', { mounted: El => el.focus()}) // Now all application instances are mounted, together with their component tree, will have the same "button-counter" component and "focus" command without contamecting the global environment app.mount('#app')Copy the code

All other global apis that do not globally change behavior can only be accessed as named exports of ES module builds, for example:

Import {nextTick} from 'vue' nextTick(() => {// something DOM related})Copy the code

If multiple applications need to share configuration, one way to do this is to create a factory function like this:

import { createApp } from 'vue' import Foo from './Foo.vue' import Bar from './Bar.vue' const createMyApp = options => {  const app = createApp(options) app.directive('focus' /* ... */) return app } createMyApp(Foo).mount('#foo') createMyApp(Bar).mount('#bar')Copy the code

Focus directives can now be used in both Foo and Bar instances and their descendants.

v-model

In VUe2, using v-Models on components is equivalent to binding ValueProp and input time:

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

If you want to change the property or event name to something else, add the Model option to ChildComponent (you can also use v-bind.sync) :

<! -- ParentComponent.vue --> <ChildComponent v-model="pageTitle" />Copy the code
// ChildComponent.vue export default { model: { prop: 'title', event: 'change' }, props: {// This will allow the 'value' attribute to be used for other purposes value: String, // Use 'title' instead of 'value' as prop title for model: {type: String, default: 'Default title' } } }Copy the code

So, in this example, v-model is abbreviated as follows:

<ChildComponent :title="pageTitle" @change="pageTitle = $event" />
Copy the code

In VUE3, a V-Model on a custom component is equivalent to passing modelValueprop and receiving an Update :modelValue event thrown:

<ChildComponent v-model="pageTitle" /> <! <ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />Copy the code

If we need to change the Model name instead of changing the Model option within the component, we can now pass an argument to Model:

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

This can also be used as an alternative to the.sync modifier and allows us to use multiple V-Models on custom components.

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" /> <! -- shorthand:  --> <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" :content="pageContent" @update:content="pageContent = $event" />Copy the code

Key usage has changed on <template v-for> and non-V-for nodes

Special key attributes are used to prompt Vue’s virtual DOM algorithm to keep track of node identity. This allows Vue to know when existing nodes can be reused and patched, and when they need to be reordered or recreated.

Vue2 recommends using keys in branches of V-if/V-else/V-else -if. If a key is set, Vue3 still works properly. However, we no longer recommend continuing to use key attributes in v-if/ V-else/V-else if branches, because a unique key is automatically generated when no key is provided for the conditional branch.

In Vue 2, the

Comparison of v-IF and V-for priorities

When v-if and V-for are used on the same element in vue2, v-for takes precedence. In VUe3, V-IF always takes precedence over V-for.

Slot Indicates the syntax of the named slot

In VUE2, slots are used as follows

// Subcomponent <slot name="content" :data="data"></slot> export default {data(){return{data:[" happy sheep "," lazy sheep "," beautiful sheep "]}}}Copy the code
// The parent component uses <template slot="content" slot-scope="scoped"> <div v-for="item in scoped. Data ">{{item}}</div> <template>Copy the code

In VUe3 the use of the parent component changes as follows:

<template v-slot:content="scoped"> <div v-for="item in scoped. Data ">{{item}}</div> </template>  <template #content="{data}"> <div v-for="item in data">{{item}}</div> </template>Copy the code

Note that V-slot can only be added to

<todo-list v-slot:default="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>
</todo-list>
Copy the code

You can also write:

<todo-list v-slot="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>
</todo-list>
Copy the code

V-slot without parameters is assumed to correspond to the default slot. The default slot abbreviation syntax cannot be mixed with named slots.

Syntax removed

1. Vue3 no longer supports numbers (key codes) as v-ON modifiers, for example:

<! <input V-on :keyup.13="submit" /> <! <input V-on :keyup. Enter ="submit" />Copy the code

The $ON, $OFF, and $once instance methods have been removed. The application instance no longer implements the event-triggering interface.

In VUe2, we often use $bus for component communication, using $on to register events, $emit to trigger events, $OFF to remove events, and $once events to execute only once. Only $emit is retained in Vue3 because it is used to trigger event handlers that have been declared attached by the parent component, everything else is removed.

3. The filter has been deleted

In Vue2 we often use filters to format the data we want to present. In Vue3, filters are removed and method calls or computed property replacements are recommended.

If a filter is registered globally in the application, it is not convenient to replace it with computed properties or methods in each component, so we can use global properties instead.

// main.js
const app = createApp(App)

app.config.globalProperties.$filters = {
  currencyUSD(value) {
    return '$' + value
  }
}
Copy the code

You can then modify all the templates through the $filters object like this:

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>
Copy the code

Reference documentation

1. Vue3 Chinese document – vuejs