Vue 3.0 RFC: Function-based Component API plugin Vue 3.0 RFC: Function-based Component API Plugin Vue 2.0 Vue-function-api, which can be used to experience the function-based Component API of VUE 3.0. For the purpose of learning, the author tried vue-function-API in the project in advance.

The author plans to write two articles, and this is the first one, which mainly focuses on the learning experience of Vue Function API. In the second part, I plan to read the core code principles of VUe-function-API, including Setup, Observable and Lifecycle.

This article should take about 15 to 20 minutes to read.

An overview of the

The organization of higher-order components in Vue 2.x and before it is more or less problematic, especially in projects that deal with repetitive logic. If the developer does not organize the project structure well, the component code can be criticized as “glue code.” In Vue 2.x and earlier versions, the solution to this problem is roughly as follows:

  • mixin
  • Functional component
  • slots

The project I maintain also deals with a lot of reuse logic, and I have been trying to reuse components using mixins until now. Developers and maintainers are often confused by issues such as a component mixin multiple components at the same time, making it difficult to tell which mixin the corresponding property or method is written in. Second, namespace conflicts in mixins can also cause problems. It is difficult to guarantee that different mixins do not have the same property name. To this end, the official team proposed a request for comments in a functional form, the RFC: Function-based Component API. Using functional writing, can achieve more flexible reuse of components, developers in the organization of higher-order components, do not have to consider reuse in the component organization, can better focus on the development of the function itself.

Note: This article is an early experience of the Vue Function API using vue-function-API. This API is only the RFC of VUE 3.0, not the final Vue 3.x API. There may be inconsistencies after release.

Used in Vue 2.x

In order to experience Vue Function API in Vue 2.x in advance, it is necessary to introduce vue-function-API. The basic introduction method is as follows:

import Vue from 'vue';
import { plugin as VueFunctionApiPlugin } from 'vue-function-api';

Vue.use(VueFunctionApiPlugin);
Copy the code

Basic Component Examples

Let’s start with a basic example:

<template>
    <div>
        <span>count is {{ count }}</span>
        <span>plusOne is {{ plusOne }}</span>
        <button @click="increment">count++</button>
    </div>
</template>

<script>
import Vue from 'vue';
import { value, computed, watch, onMounted } from 'vue-function-api';

export default {
    setup(props, context) {
        // reactive state
        const count = value(0);
        // computed state
        const plusOne = computed((a)= > count.value + 1);
        // method
        const increment = (a)= > {
            count.value++;
        };
        // watch
        watch(
            (a)= > count.value * 2,
            val => {
                console.log(`count * 2 is ${val}`); });// lifecycle
        onMounted((a)= > {
            console.log(`mounted`);
        });
        // expose bindings on render context
        return{ count, plusOne, increment, }; }};</script>
Copy the code

Break down

setup

The setup Function is the main logic of the functional writing built by the Vue Function API. It is called when a component is created. The Function takes two arguments: props passed by the parent component and the context of the current component. If you look at the following example, you can get the following property values in context:

const MyComponent = {
    props: {
        name: String
    },
    setup(props, context) {
        console.log(props.name);
        // context.attrs
        // context.slots
        // context.refs
        // context.emit
        // context.parent
        // context.root}}Copy the code

value & state

The value function creates a wrapper object that contains a reactive property value:

So why use value, because in JavaScript, basic types and no reference, in order to ensure that property is responsive, can only be object to realize the aid of the packing, the advantage is component state will be saved in the form of reference, which can be invoked in the setup of different modules of function in the form of parameters, It can reuse logic and realize responsiveness easily.

Getting the value of the wrapped object directly must use.value, but if the wrapped object is a property of another reactive object, the wrapped object is automatically expanded internally by proxy. It is also automatically expanded in the context of template rendering.

import { state, value } from 'vue-function-api';
const MyComponent = {
    setup() {
        const count = value(0);
        const obj = state({
            count,
        });
        console.log(obj.count) // As a property of another responsive object, it is automatically expanded

        obj.count++ // As a property of another responsive object, it is automatically expanded
        count.value++ // To get a responsive object directly, use.value

        return {
            count,
        };
    },
    template: `<button @click="count++">{{ count }}</button>`};Copy the code

If a state does not need to be modified responsively in different functions, state can be used to create a reactive object, which is not a wrapper object and does not require a. Value value.

watch & computed

The basic concepts of Watch and computed are the same as watch and computed in Vue 2.x, where watch can be used to track state changes to perform some subsequent operations, and computed for properties to be recalculated depending on property changes.

Computed returns a read-only wrapper object that can be returned by the setup function just as a normal wrapper object, so that computed properties can be used in the template context. You can take two arguments, the first of which returns the current computed property value, and computed is writable when you pass the second argument.

import { value, computed } from 'vue-function-api';

const count = value(0);
const countPlusOne = computed((a)= > count.value + 1);

console.log(countPlusOne.value); / / 1

count.value++;
console.log(countPlusOne.value); / / 2

// Writable computed attribute values
const writableComputed = computed(
    // read
    () => count.value + 1.// write
    val => {
        count.value = val - 1; });Copy the code

The first parameter of watch, like computed, returns the value of the listened wrapper object property, but you need to pass two additional parameters: the second is a callback function that is triggered when the data source changes, and the third is options. The default behavior is different from Vue 2.x:

  • Lazy: Whether the callback function is called once the component is created. In contrast to Vue 2.x, lazy defaults to false and is called once when the component is created.
  • Deep: the same as deep in Vue 2.x
  • Flush: Three optional values are ‘post’ (call callback after rendering, i.e., nextTick), ‘pre’ (call callback before rendering, i.e., nextTick), and ‘sync’ (trigger synchronization). The default is ‘post’.
// Double is a computational wrapper object
const double = computed((a)= > count.value * 2);

watch(double, value => {
    console.log('double the count is: ', value);
}); // -> double the count is: 0

count.value++; // -> double the count is: 2
Copy the code

When watch has multiple wrapped object attributes, the parameters can be passed as an array, and, like Vue 2.x’s vm.$watch, watch returns an unlisten function:

const stop = watch(
    [valueA, () => valueB.value],
    ([a, b], [prevA, prevB]) => {
        console.log(`a is: ${a}`);
        console.log(`b is: ${b}`); }); stop();Copy the code

Note: in the first draft of the RFC: function-based component API, there was a mention of effect-cleanup, which was used to cleanup side effects in special cases, and has been removed from the proposal.

The life cycle

All existing hook cycles have corresponding hook functions created in the form of onXXX. However, the deStoryed hook function needs to be unmounted instead:

import { onMounted, onUpdated, onUnmounted } from 'vue-function-api';

const MyComponent = {
    setup() {
        onMounted((a)= > {
            console.log('mounted! ');
        });
        onUpdated((a)= > {
            console.log('updated! ');
        });
        // Adjust to unmounted
        onUnmounted((a)= > {
            console.log('unmounted! '); }); }};Copy the code

Some think

The above description mainly extracts the common parts of Vue Function API, not the RFC: The function-based Component API includes all the advantages of dependency injection and TypeScript type derivation. For more information about the function-based Component API, check out the RFC: Function-based Component API. I also saw some more comments on the function-based Component API discussion board:

  • Due to the underlying design, setup cannot get the component instance this. I also encountered this problem when I tried to experience it. I expect the official release of Vue 3.x can improve this problem.

  • The issue of having to use wrapped objects for values of primitive types is the most hotly debated in the RFC discussion section, where.value must be used for wrapped properties to preserve TypeScript type inference, reusability, and Vue data listening

  • The proposed Amendment Proposal to Function-based Component API has been discussed in terms of unclear naming of the value and state methods of the wrapped object, which may lead to misleading developers:

setup() {
    const state = reactive({
        count: 0});const double = computed((a)= > state.count * 2);

    function increment() {
        state.count++;
    }

    return {
        ...toBindings(state), // retains reactivity on mutations made to `state`
        double,
        increment,
    };
}
Copy the code
  • The introduction ofreactiveAPI andbindingAPI,reactiveThe API is similar tostateAPI,bindingThe API is similar tovalueAPI.
  • The name of the method used previouslystateCould be used as a component state object in Vue 2.x, leading to variable namespace conflicts, which the team believes willstateAPI was renamedreactiveMore elegant. Developers can writeconst state = ...And then throughstate.xxxxThis is also a relatively natural way to retrieve component state.
  • valueThe lack of elegance does occur when methods are used to encapsulate primitive types.valueDevelopers may forget to use it when evaluating the wrapped object directly.value, the amendment proposedreactiveAPI, which means to create a responsive object, initialize the statestateUse thereactiveCreated to preserve each attributegetterandsetterThis allows both type derivation and reactive references to be shared across different modules.
  • butreactiveMay cause the following problems and need to be introducedbindingAPI. Solve, such as usingreactiveCreates a reactive object on which the extended operator is used.Object will be lostgetterandsetterTo providetoBindingsMethod can preserve a reactive form of state.

In the next article, I will read the core code principles of VUe-function-API, including Setup, Observable, Lifecycle, etc., and explore the changes that the Vue Function API may bring to us from the inside.

Of course, the Vue Function API is still in the discussion stage, and Vue 3.0 is still under development. Let’s look forward to the release of the first version of Vue 3.0 in the second half of the year, hopefully to bring us more surprises.