In this article, I’ll talk about why Vue’s Composition API is better than the previous Options API and how it works.

What’s wrong with the Options API

First of all, this is not to abandon the Options API. If you think the Options API is good and you are used to it, keep using it. But I hope you can see why the Composition API is a better choice.

When I first started with Vue, I liked the way it built components with anonymous objects. Easy to learn and natural. But the longer you use it, the more strange problems you encounter. Take this code for example:

export default {
  mounted: async function () {
    this.load().then(function () {
      this.name = 'Foo'}})},Copy the code

The second “problem here I think many people have encountered. It’s hard to know why the two This’s are different without debugging them. All we know is that to solve this problem, we use the arrow function anyway, for example:

mounted: async function () {
  this.load().then(() = > {
    this.name = 'Foo';
  });
}
Copy the code

In addition, after returning members from the data function, Vue wraps them (usually using Proxy) so that it can listen for changes to the object. However, if you try to replace an object directly (such as an array), you may not be able to listen for object changes. Such as:

export default {
  data: () = > {
    return {
      items: [].}},mounted: async function () {
    this.load().then((result) = > {
      this.items = result.data
    })
  },
  methods: {
    doSomeChange() {
      this.items[0].foo = 'foo'}},}Copy the code

This is one of the most common problems with Vue. While these problems can be easily solved, they pose a barrier to learning how Vue works.

Finally, the Options API makes it difficult to share functionality between components. To address this, Vue uses the concept of mixins. For example, you can create a mixin to extend the component as follows:

export default {
  data: function () {
    return { items: []}; },methods: {... }}Copy the code

Mixins look a lot like the Options API. It simply merges objects to allow you to add them to specific components:

import dataService from "./dataServiceMixin";
export default {
  mixins: [dataService],
  ...
Copy the code

The biggest problem with mixins is name conflicts.

Given the shortcomings of the Options API, the Composition API was born.

Understand the Composition API

Now let’s introduce the Composition API. Vue 2 can also use the Composition API. First, you need to introduce the Composition API library:

> npm i -s @vue/composition-api
Copy the code

To enable the Composition API, just register in main.js/ts:

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
Copy the code

Let’s write a Composition API component. First, components are still anonymous objects:

export default {
  setup() {
    return{}}},Copy the code

The key to the Composition API is this setup method, from which all the required data is returned. This is very similar to the Data method of the Options API:

export default {
  setup() {
    const name = 'Foo'
    return { name }
  },
}
Copy the code

Instead of just assigning values in return, create them with a local variable in setup. Why do you do that? Because of JavaScript closures. Let’s expand and write another function.

export default {
  setup() {
    const name = 'Foo'

    function save() {
      alert(`Name: ${name}`)}return { name, save }
  },
}
Copy the code

This function can access name because they are in the same scope. That’s the magic of the Composition API. No magic objects, just JavaScript. This pattern can support more complex scenarios, more flexibility, all based on closures and nothing more.

In this case the name will never change. In order for Vue to be able to handle the binding, it needs to know about the name change. In this example, if you change the code in save() to change the name, it will not be reflected in the UI:

export default {
  setup() {
    const name = 'Foo'

    function save() {
      name = name + ' saved'
      alert(`Name: ${name}`)}return { name, save }
  },
}
Copy the code

responsive

To make objects responsive, Vue provides two approaches: ref and Reactive wrappers.

You can wrap name as a ref object:

import { ref } from '@vue/composition-api'
export default {
  setup() {
    const name = ref('Foo')

    function save() {
      name.value = name.value + ' saved'
      alert(`Name: ${name.value}`)}return { name, save }
  },
}
Copy the code

This is a simple response where the value attribute of the REF object is the actual value.

This works well with simple objects, but for objects that have their own state (such as classes or arrays), changes to the value are not sufficient. What you really need is to use a proxy function to listen for any changes that happen inside the object.

import { ref, reactive } from '@vue/composition-api'
export default {
  setup() {
    const name = ref('Foo')
    const items = reactive([])

    function save() {
      // Change Array
      items.splice(0, items.length)
      name.value = name.value + ' saved'
      alert(`Name: ${name.value}`)}return { name, save, items }
  },
}
Copy the code

In this example, the SPLice change array is used, and Vue needs to be aware of this change. You can do this by wrapping the object (in this case, an array) with another wrapper called Reactive.

The Reactive wrapper in Composition API is the same as Vue2’s Vue.Observable wrapper.

The difference between REF and React is that Reactive uses a proxy to wrap objects, while REF is a simple value wrapper.

As a result, you don’t need to rewrap the returned Reactive object and access its value through the value attribute, as ref objects do.

Once you have ref and Reactive objects, you can observe changes. There are two ways to do this: Watch and watchEffect. Watch allows you to listen for changes to a single object:

 setup() {
    const name = ref("Shawn");
    watch(() = > name, (before, after) = > {
        console.log("name changes");
    });
    return { name };
},
Copy the code

The watch function takes two arguments: the callback to return data to watch and the callback to call when changes occur. It provides two arguments, the pre-modified value before and the modified value after.

Alternatively, you can use watchEffect to listen for changes to reactive objects. It only needs one callback:

watchEffect(() = > {
  console.log('Ch.. ch... ch... changes.')})Copy the code

Combination of components

Sometimes you want to combine the functionality of one existing component into another for code reuse and data interaction. When composing components, the Composition API simply uses scopes and closures to solve problems. For example, you could create a simple service:

import axios from 'axios'

export let items = []

export async function load() {
  let result = await axios.get(API_URL)
  items.splice(0, items.length, ... result.data) }export function removeItem(item) {
  let index = items.indexOf(item)
  if (index > -1) {
    items.splice(index, 1)}}Copy the code

You can then import the desired functionality (objects or functions) into your component as needed:

import { ref, reactive, onMounted } from '@vue/composition-api'

import { load, items, removeItem } from './dataService'

export default {
  setup() {
    const name = ref('Foo')

    function save() {
      alert(`Name: The ${this.name}`)}return {
      load, // from import
      removeItem, // from import
      name,
      save,
      items, // from import}}},Copy the code

You can use the Composition API to compose your components more explicitly. Let’s talk about the use of components.

The use of Props

Define Props in the Composition API in the same way as the Options API:

export default {
  name: 'WaitCursor'.props: {
    message: {
      type: String.required: false,},isBusy: {
      type: Boolean.required: false,}},setup(){},}Copy the code

You can access Props by adding an optional parameter to setup:

setup(props) {
    watch(() = > props.isBusy, (b,a) = > {
        console.log(`isBusy Changed`);
    });
}
Copy the code

In addition, you can add a second argument that accesses the emit, slots, and attrs objects that correspond to the this pointer on the Options API:

setup(props, context) {
    watch(() = > props.isBusy, (b,a) = > context.emit("busy-changed", a));
}
Copy the code

Using the component

There are two ways to use components in Vue. You can register components globally (as generic components) so that they can be used anywhere:

import WaitCursor from './components/WaitCursor'
Vue.component('wait-cursor', WaitCursor)
Copy the code

In general, you can add components to specific components that are being used. In the Composition API, the same as the Options API:

import WaitCursor from './components/waitCursor'
import store from './store'
import { computed } from '@vue/composition-api'

export default {
  components: {
    WaitCursor, // Use Component
  },
  setup() {
    const isBusy = computed(() = > store.state.isBusy)
    return { isBusy }
  },
}
Copy the code

Once you have specified a component in the Composition API, you can use it:

<div>
  <WaitCursor message="Loading..." :isBusy="isBusy"></WaitCursor>
  <div class="row">
    <div class="col">
      <App></App>
    </div>
  </div>
</div>
Copy the code

The way components are used is no different from the way they are used in the Options API.

Use the Composition API in Vue 3

If you are using Vue 3, there is no need to refer to the Composition API separately. Vue 3 is already integrated. The @vue/ composition-API library is only for vue 2 projects. The real change in Vue 3 is that when you need to import the Composition API, you just need to get them directly from Vue:

import {
  ref,
  reactive,
  onMounted,
  watch,
  watchEffect,
  //from "@vue/composition-api";
} from 'vue'
Copy the code

Everything else is the same. Just import from “vue”. In Vue 3, using the Composition API is simply simpler because it is the default behavior.

One of the main goals of Vue 3 is to improve the TypeScript experience, with a type library for everything. But to increase type safety, you have to make some small changes to use TypeScript. When creating a component, use defineComponent:

import { defineComponent, reactive, onMounted, ref } from "vue";
import Customer from "@/models/Customer";

export default defineComponent({
    name: "App".setup() {
      const customers = reactive([] as Array<Customer>);
      const customer = ref(new Customer());
      const isBusy = ref(false);
      const message = ref("");
      return {
        customers, customer, isBusy, message
      }
    }
});
Copy the code

In these examples, variables are inferred to be type instances (for example, the Customers object is an instance of type Reactive

). In addition, using strong typing reduces the chance of incorrect data being passed. Of course, if you use TypeScript (especially in Visual Studio or VS Code), there are very friendly intelligent hints.
[]>