Exploring Vue 3’s reactivity: ref and watchEffect
Tags: vue, javascript, webdev, programming
Published: 2022-10-06 13:28:05

In this second part of the series, let's look into how Vue 3's reactivity works with `ref` and `watchEffect`.

## Vue 3's reactivity

Within the scope of this article, we'll only explore `ref` and `watchEffect` for now. The other reactivity functions will be explored in the next part of the series.

We're talking about reactivity a lot here, so what is it really? It's actually not a new paradigm. The typical example is an Excel spreadsheet:

![Excel spreadsheet example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9rin9w5kevey2si73zq0.png)

In the example above, cell B1 is defined as `= A1 + A2`. When you update A1 or A2, B1 will also be reactively updated.

However, in JavaScript, variables don't work that way:

```js
let A1 = 1
let A2 = 4
let B1 = A1 + A2

console.log(B1) // 5

A1 = 3

console.log(B1) // still 5
```

If our purpose is only to log the sum of A1 and A2 when either A1 or A2 changes, we can write something like this in Vue 3:

```js
const A1 = ref(1)
const A2 = ref(4)

watchEffect(() => {
  console.log('B1 =', A1.value + A2.value)
})
```

But how does it work? How does `watchEffect` _magically_ know when A1 or A2 changes? To answer this question, let's build `ref` from scratch. It begins with an object with only 1 property - `value` - with its getter and setter:

```js
export function ref(value) {
  return new RefImpl(value)
}

class RefImpl {
  private _value

  constructor(value) {
    this._value = value
  }

  get value() {
    return this._value
  }

  set value(newVal) {
    this._value = newVal
  }
}
```

Nothing happens here yet. The magic trick we're going to use here is when the `value` property of the `ref` object is read (the getter method is called), we will automatically add the caller as the **subscriber** of the `ref` object. We also call this subscriber an **effect** (short for **side effect**). The `ref` object now becomes a **dependency** of the effect.

```js
export function ref(value) {
  return new RefImpl(value)
}

class RefImpl {
  private _value
  public dep = undefined // Ironically, in the Vue's codebase, the subscribers/effects of a ref object seem to also be called its dependencies.

  constructor(value) {
    this._value = value
  }

  get value() {
    trackRefValue(this)

    return this._value
  }

  set value(newVal) {
    this._value = newVal;
  }
}

export function trackRefValue(ref) {
  if (!activeEffect) return;

  if (!ref.dep) ref.dep = new Set() // We must use Set here to avoid duplication

  ref.dep.add(activeEffect) // Add the active (currently running) effect as one of the ref object's subscribers
}
```

Now there's 2 problems left:

1. Where does the value of `activeEffect` come from? In other words, how do we know which effect is currently running?
2. We need to trigger/inform all of a `ref` object's subscribers/dependencies when its `value` property changes.

Let's deal with the second problem first because it's quite straightforward:

```js
export function ref(value) {
  return new RefImpl(value)
}

class RefImpl {
  private _value
  public dep = undefined // Ironically, in the Vue's codebase, the subscribers/effects of a ref object seem to also be called its dependencies.

  constructor(value) {
    this._value = value
  }

  get value() {
    trackRefValue(this)

    return this._value
  }

  set value(newVal) {
    this._value = newVal

    triggerRefValue(this)
  }
}

export function trackRefValue(ref) {
  if (!activeEffect) return;

  if (!ref.dep) ref.dep = new Set() // We must use Set here to avoid duplication

  ref.dep.add(activeEffect) // Add the active (currently running) effect as one of the ref object's subscribers
}

export function triggerRefValue(ref) {
  if (!ref.dep) return;

  for (const effect of ref.dep) {
    effect() // run the effect
  }
}
```

Now the most important question left is: Where does the value of `activeEffect` come from? Let's take a look at the rawest implementation of `watchEffect`:

```js
export let activeEffect = undefined

export function watchEffect(effectHandler) {
  const effect = () => {
    activeEffect = effect

    effectHandler()
    // The above function call will read the value property of any ref object inside it
    // and trigger the getter method of the value property,
    // which in turn adds this effect as one of the subscribers of that ref object.

    activeEffect = undefined
  }

  effect() // watchEffect triggers the effect (handler) immediately
}
```

Because `watchEffect` always runs immediately, the first time it runs will always trigger the getter methods of the `value` property of `ref` objects inside it, and registers the effect as a subscriber of these `ref` objects. This is why you don't need to explicitly specify the dependencies for `watchEffect`.

Here's a working code example of `ref` and `watchEffect`:

{% codesandbox stupid-vue-reactivity-reproduction-1-ivvknd %}

For now, we have a roughly working version of `ref` and `watchEffect`. It is nowhere near usable because we've left out too many cases where it might fail, plus there's no batching, and the flow of control is a little bit messed up here. But it serves its purpose as an oversimplified example of what's happening behind the scenes, hopefully.

## Conclusion

In this part of the series, we've tapped into the gist of Vue 3's `ref` and `watchEffect`. In the next part, we'll continue to improve the oversimplified version of `ref` and `watchEffect` to match the real implementation more closely, and we'll also explore `reactive`, `computed`, and `watch`.

I hope that you find this useful somehow. If you have any suggestion or advice, please don't hesitate to reach out to me in the comment section.