The introduction

This year, for students engaged in front-end development, one thing is looking forward to is the release of Vue3.0. However, the release of Vue3.0 is still some time away, and the official release does not mean we are ready for business development immediately. It also needs to perfect the corresponding ecological tools. But it’s one thing to use it formally, it’s another thing to play it by ourselves.

Vue3.0 has prepared a pilot project for you to experience some of the upcoming Vue3.0 apis, such as setup, Reactive, toRefs, Readonly, etc., along with the address of the Composition API documentation. If you haven’t seen it yet, Get it before it’s released. (Dumb birds fly first, smart birds fly first.)

Likewise, I’ve been playing around and getting excited about the Reactive API. So today we’re going to look at what a Reactive API is. How to implement (source code implementation)?

I. Definition and advantages

1.1 define

Reactive apis are defined to pass in an object and return a reactive Proxy based on the original object, that is, a Proxy, equivalent to Vue2x’s vue.Observer.

First of all, we need to know that Vue3.0 completely scrapped the Options API in favor of the Composition API. The simplified version of the Composition API will look something like this:

  setup() {

    const state = reactive({

      count0.

      double: computed((a)= > state.count * 2)

    })



    function increment({

      state.count++

    }



    return {

      state,

      increment

    }

  }

Copy the code

As you can see, there is no familiar data, computed, methods, and so on. Seemingly a bit React style, this proposal did spark a lot of discussion in the community at the time that Vue was becoming more like React…. A lot of people don’t like it, but you can read the RFC for details.

1.2 the advantages

Getting back to the focus of this article, it is clear that reactive apis are the standard DATA option, so what are the advantages of the DATA option compared to the reactive API?

First, reactive processing of data in Vue 2X is based on Object.defineProperty(), but it only listens for attributes of objects, not objects. So, when adding object attributes, you usually need to:

    // vue2x adds attributes

    Vue.$set(object, 'name', wjc)

Copy the code

Reactive API is based on ES2015 Proxy to implement the responsive processing of data objects. That is, in Vue3.0, attributes can be added to objects, and these attributes also have responsive effects, such as:

    // Add attributes in Vue3.0

    object.name = 'wjc'

Copy the code

1.3 pay attention to the point

The important thing about using reactive apis is that when you return from setup, you need to do it in the form of an object, such as:

    export default {

      setup() {

          const pos = reactive({

            x0.

            y0

          })



          return {

             pos: useMousePosition()

          }

      }

    }

Copy the code

Alternatively, wrap the export with the toRefs API, in which case we can use expansion operators or destructions, for example:

    export default {

      setup() {

          let state = reactive({

            x0.

            y0

          })

        

          state = toRefs(state)

          return {

. state

          }

      }

    } 

Copy the code

What exactly does toRefs() do that I’ll talk about with Reactive next

Two, source code implementation

First, as I’m sure you’ve all heard, Vue3.0 is refactored in TypeScript. So you might expect to see a bunch of TypeScript types this time around. For various considerations, this time I just explain the compiler, into JS source code implementation (no thresholds, we rest assured hh).

2.1 reactive

1. Reactive function implementation

function reactive(target{

    // if trying to observe a readonly proxy, return the readonly version.

    if (readonlyToRaw.has(target)) {

        return target;

    }

    // target is explicitly marked as readonly by user

    if (readonlyValues.has(target)) {

        return readonly(target);

    }

    if (isRef(target)) {

        return target;

    }

    return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers);

}

Copy the code

Readonly, readonlyValues, isRef, isRef, isRef, isRef, isRef, isRef Leaving the logic aside, usually we define Reactive to pass in an object directly. So the last logical createReactiveObject() is hit.

2. CreateReactiveObject () :

function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers{

    if(! isObject(target)) {

        if((process.env.NODE_ENV ! = ='production')) {

            console.warn(`value cannot be made reactive: The ${String(target)}`);

        }

        return target;

    }

    // target already has corresponding Proxy

    let observed = toProxy.get(target);

    if(observed ! = =void 0) {

        return observed;

    }

    // target is already a Proxy

    if (toRaw.has(target)) {

        return target;

    }

    // only a whitelist of value types can be observed.

    if(! canObserve(target)) {

        return target;

    }

    const handlers = collectionTypes.has(target.constructor)

        ? collectionHandlers

        : baseHandlers;

    observed = new Proxy(target, handlers);

    toProxy.set(target, observed);

    toRaw.set(observed, target);

    return observed;

}

Copy the code

CreateReactiveObject () passes in four parameters, each playing a role:

  • targetThat’s what we definereactiveIs passed to the object
  • toProxyIt’s an empty oneWeakSet.
  • toProxyIt’s an empty oneWeakSet.
  • baseHandlersIs one that’s already definedgetsetObject, it would look like this:
    const baseHandlers = {

        get(target, key, receiver) {},

        set(target, key, value, receiver) {},

        deleteProxy: (target, key) {},

        has: (target, key) {},

        ownKey: (target) {}

    };

Copy the code
  • collectionHandlersIs an object that contains only get.

Then, go to createReactiveObject(), and again, some branching logic that we won’t examine this time.

Look at the source need to keep a normal heart, first look at the main logic

So, we hit the final logic, which is:

    const handlers = collectionTypes.has(target.constructor)

        ? collectionHandlers

        : baseHandlers;

    observed = new Proxy(target, handlers);

    toProxy.set(target, observed);

    toRaw.set(observed, target);

Copy the code

It first determines whether collectionTypes will contain the constructor of target we passed in. CollectionTypes is a Set, which mainly contains the constructors of Set, Map, WeakMap and WeakSet.

If collectionTypes contain its constructor, handlers are assigned to the Get-only collectionHandlers object; otherwise, baseHandlers are assigned to it.

The difference between the two is that the former only has GET, which is obviously reserved for variable definitions that don’t need to be updated, such as the familiar props, which only implements GET.

Target and Handlers are then passed to the Proxy as parameters to instantiate a Proxy object. This is where we’ve seen some articles about Vue3.0 replacing Object.defineProperty with ES2015 Proxy.

The last two logics, which are also very important, toProxy() inserts the target and the corresponding observed of the defined Proxy object into the WeakMap of toProxy as the key value pair. If the target requiring reactive has the same reference next time, it will match the previous branch logic and return the previously defined observed, that is:

    // Target already has corresponding Proxy target

    let observed = toProxy.get(target);

    if(observed ! = =void 0) {

        return observed;

    }

Copy the code

ToRaw () is the opposite of toProxy and is used to return the target the next time the passed target is already a Proxy object, i.e. :

    // Target is already a Proxy object

    if (toRaw.has(target)) {

        return target;

    }

Copy the code

2.2 toRefs

In addition, toRefs makes it easy to use the deconstruction and expansion operators. In fact, the recent ISSUE of Vue3.0 has also explained this aspect. When it’s really needed to use toRefs in order to retain reactivity of Reactive Value

I was also a part of the fun, as shown below:


As you can see, toRefs returns a normal Proxy object with a GET and set. This solves the problem of Proxy objects losing references when they encounter deconstruction and expansion operators.

conclusion

Well, the definition and rough source code implementation of the Reactive API is described in the article above. The logic of branches, we can go through different cases to read. Of course, it is necessary to say that the source code is only the early version, does not rule out the formal will do a lot of optimization, but the main body must remain the same.