Reactivity in Depth

    This term comes up in programming quite a bit these days, but what do people mean when they say it? Reactivity is a programming paradigm that allows us to adjust to changes in a declarative manner. The canonical example that people usually show, because it’s a great one, is an excel spreadsheet.

    Your browser does not support the video tag.

    If you put the number two in the first cell, and the number 3 in the second and asked for the SUM, the spreadsheet would give it to you. No surprises there. But if you update that first number, the SUM automagically updates too.

    JavaScript doesn’t usually work like this — If we were to write something comparable in JavaScript:

    If we update the first value, the sum is not adjusted.

    So how would we do this in JavaScript?

    • Detect when there’s a change in one of the values
    • Track the function that changes it
    • Trigger the function so it can update the final value

    When you pass a plain JavaScript object to a Vue instance as its option, Vue will walk through all of its properties and convert them to Proxies using a handler with getters and setters. This is an ES6-only feature, but we offer a version of Vue 3 that uses the older Object.defineProperty to support IE browsers. Both have the same surface API, but the Proxy version is slimmer and offers improved performance.

    See the Pen by Sarah Drasner (@sdras) on .

    We use it like this: new Proxy(target, handler)

    1. const dinner = {
    2. meal: 'tacos'
    3. }
    4. const handler = {
    5. get(target, prop) {
    6. return target[prop]
    7. }
    8. }
    9. const proxy = new Proxy(dinner, handler)
    10. console.log(proxy.meal)
    11. // tacos

    Ok, so far, we’re just wrapping that object and returning it. Cool, but not that useful yet. But watch this, we can also intercept this object while we wrap it in the Proxy. This interception is called a trap.

    1. const dinner = {
    2. meal: 'tacos'
    3. }
    4. const handler = {
    5. console.log(‘intercepted!’)
    6. return target[prop]
    7. }
    8. }
    9. const proxy = new Proxy(dinner, handler)
    10. // intercepted!
    11. // tacos

    Beyond a console log, we could do anything here we wish. We could even not return the real value if we wanted to. This is what makes Proxies so powerful for creating APIs.

    Furthermore, there’s another feature Proxies offer us. Rather than just returning the value like this: target[prop], we could take this a step further and use a feature called Reflect, which allows us to do proper this binding. It looks like this:

    We mentioned before that in order to have an API that updates a final value when something changes, we’re going to have to set new values when something changes. We do this in the handler, in a function called track, where pass in the target and key.

    1. const dinner = {
    2. meal: 'tacos'
    3. }
    4. const handler = {
    5. get(target, prop, receiver) {
    6. track(target, prop)
    7. return Reflect.get(...arguments)
    8. }
    9. }
    10. const proxy = new Proxy(dinner, handler)
    11. console.log(proxy.meal)
    12. // intercepted!
    13. // tacos

    Finally, we also set new values when something changes. For this, we’re going to set the changes on our new proxy, by triggering those changes:

    1. const dinner = {
    2. }
    3. const handler = {
    4. get(target, prop, receiver) {
    5. track(target, prop)
    6. return Reflect.get(...arguments)
    7. },
    8. set(target, key, value, receiver) {
    9. trigger(target, key)
    10. return Reflect.set(...arguments)
    11. }
    12. }
    13. const proxy = new Proxy(dinner, handler)
    14. console.log(proxy.meal)
    15. // intercepted!
    16. // tacos

    Remember this list from a few paragraphs ago? Now we have some answers to how Vue handles these changes:

    • Detect when there’s a change in one of the values: we no longer have to do this, as Proxies allow us to intercept it
    • Track the function that changes it: We do this in a getter within the proxy, called effect
    • Trigger the function so it can update the final value: We do in a setter within the proxy, called trigger

    The proxied object is invisible to the user, but under the hood they enable Vue to perform dependency-tracking and change-notification when properties are accessed or modified. As of Vue 3, our reactivity is now available in a separate packageReactivity in Depth - 图3. One caveat is that browser consoles format differently when converted data objects are logged, so you may want to install for a more inspection-friendly interface.

    When a nested object is accessed from a reactive proxy, that object is also converted into a proxy before being returned:

    Proxy vs. original identity

    The use of Proxy does introduce a new caveat to be aware with: the proxied object is not equal to the original object in terms of identity comparison (===). For example:

    1. const obj = {}
    2. const wrapped = new Proxy(obj, handlers)
    3. console.log(obj === wrapped) // false

    The original and the wrapped version will behave the same in most cases, but be aware that they will fail operations that rely on strong identity comparisons, such as .filter() or .map(). This caveat is unlikely to come up when using the options API, because all reactive state is accessed from this and guaranteed to already be proxies.

    However, when using the composition API to explicitly create reactive objects, the best practice is to never hold a reference to the original raw object and only work with the reactive version:

    1. const obj = reactive({
    2. count: 0

    Every component instance has a corresponding watcher instance, which records any properties “touched” during the component’s render as dependencies. Later on when a dependency’s setter is triggered, it notifies the watcher, which in turn causes the component to re-render.

    See the Pen by Sarah Drasner (@sdras) on .

    When you pass an object to a Vue instance as data, Vue converts it to a proxy. This proxy enables Vue to perform dependency-tracking and change-notification when properties are accessed or modified. Each property is considered a dependency.

    After the first render, a component would have tracked a list of dependencies — the properties it accessed during the render. Conversely, the component becomes a subscriber to each of these properties. When a proxy intercepts a set operation, the property will notify all of its subscribed components to re-render.