Thought on Vue 3 Composition API - `reactive()` considered harmful
Tags: vue, javascript, webdev
Published: 2019-10-19 12:42:49

Vue.js stands out from other frameworks for its intuitive reactivity. Vue 3 composition api is going to removing some limitations in Vue 2 and provide a more explicit api.

## Quick Intro to the Composition API

There are two ways to create reactive "things":
1. `reactive()`
2. `ref()` / `computed()`

### Introducing `reactive()`

`reactive(obj)` will return a new object that looks exactly the same as `obj`, but any mutation to the new object will be tracked.

For example:
```js
// template: {{ state.a }} - {{ state.b }}
const state = reactive({ a: 3 })
// renders: 3 - undefined

state.a = 5
state.b = 'bye'
// renders: 5 - bye
```

This works exactly like `data` in Vue 2. Except we can now add new properties to them as reactivity is implemented with proxies in Vue 3.

### Introducing `Ref`

Vue composition API introduced `Ref` which is simply an object with 1 property `.value`. We can express this using Typescript:

```ts
interface Ref<A> {
  value: A
}
```

There are two ways of creating refs:
1. `ref()`
  - `.value` can be get/set.
2. `computed()`
  - `.value` is readonly unless a setter is provided.

For Example:
```js
const countRef = ref(0) // { value: 0 }
const countPlusOneRef = computed(() => countRef.value + 1) // { value: 1 }
countRef.value = 5

/*
 * countRef is { value: 5 }
 * countPlusOneRef is { value: 6 } (readonly)
 */
```


## `reactive()` is bad; `Ref` is good.

This section of the article is purely my tentative opinion on the composition api after building a few projects with it. Do try it yourself and let me know if you agree.

Before using the composition api, I thought `reactive()` would be the api that everyone will end up using as it doesn't require the need to do `.value`. Surprisingly, after building a few projects with the composition api, not once have I used `reactive()` so far!

Here are 3 reasons why:
1. Convenience - `ref()` allow declaration of new reactive variable on the fly.
2. Flexibility - `ref()` allow complete replacement of an object
3. Explicitness - `.value` forces you to be aware of what you are doing

### 1. Convenience

The composition api is proposed to provide a way to group code with accordance to their feature in the component instead of their function in Vue. The options api groups code into `data`, `computed`, `methods`, lifecycles etc. This make it almost impossible to group code by feature. See the following image:

![](https://user-images.githubusercontent.com/499550/62783026-810e6180-ba89-11e9-8774-e7771c8095d6.png)

Consider the following examples:

```js
const state = reactive({
  count: 0,
  errorMessage: null,
})
setTimeout(() => state.count++, 1000)
watch(state.count, count => {
  if (count > 10) {
    state.errorMessage = 'Larger than 10.'
  }
})
```

If we use `reactive()` to store multiple properties. It is easy to fall back into the trap of grouping things by functions, not feature. You will likely be jumping around the code base to modify that reactive object. This makes the development process less smooth.

```js
const count = ref(0)
setTimeout(() => count.value++, 1000)

const errorMessage = ref(null)
watch(count, count => {
  if (count > 10) {
    errorMessage.value = 'Larger than 10.'
  }
})
```

On the other hand, `ref()` allow us to introduce new variables on the fly. From the example above, I only introduce variables as I need them. This makes the development process much smoother and intuitive. 

### 2. Flexibility

I initially thought the sole purpose of `ref()` was to enable primitive values to be reactive. But it can become extremely handy too when using `ref()` with objects.

Consider:

```js
const blogPosts = ref([])
blogPosts.value = await fetchBlogPosts()
```

If we wish to do the same with `reactive`, we need to mutate the array instead.

```js
const blogPosts = reactive([])
for (const post of (await fetchBlogPosts())) {
  blogPosts.push(post)
}
```

or with our "beloved" `Array.prototype.splice()`
```js
const blogPosts = reactive([])
blogPosts.splice(0, 0, ...(await fetchBlogPosts()))
```

As illustrated, `ref()` is simpler to work with in this case as you can just replace the whole array with a new one. If that doesn't convince you, imagine if the `blogPosts` needs to be paginated:

```js
watch(page, page => {
  // remove everything from `blogPosts`
  while (blogPosts.length > 0) {
    blogPosts.pop()
  }
  
  // add everything from new page
  for (const post of (await fetchBlogPostsOnPage(page))) {
    blogPosts.push(post)
  }
})
```

or with our best friend `splice`
```js
watch(page, page => {
  blogPosts.splice(0, blogPosts.length, ...(await fetchBlogPostsOnPage(page)))
})
```

But if we use `ref()`

```js
watch(page, page => {
  blogPosts.value = await fetchBlogPostsOnPage(page)
})
```

It is much flexible to work with.

### 3. Explicitness

`reactive()` returns an object that we will interact with the same we interact with other non-reactive object. This is cool, but can become confusing in practise if we have deal with other non-reactive objects.

```js
watch(() => {
  if (human.name === 'Jason') {
    if (!partner.age) {
      partner.age = 30 
    }
  }
})
```

We cannot really tell if `human` or `partner` is reactive. But if we ditch using `reactive()` and consistently utilise `ref()`, we won't have the same problem.


`.value` may seem wordy at first; but it helps reminding us that we are dealing with reactivity.

```js
watch(() => {
  if (human.value.name === 'Jason') {
    if (!partner.age) {
      partner.age = 30 
    }
  }
})
```

It becomes obvious now that `human` is reactive but not `partner`.

## Conclusion

The above observations and opinions are totally tentative. What do you think? Do you agree `ref()` is going to dominate in Vue 3? Or do you think `reactive()` will be preferred?

Let me know in the comments! I would love to hear more thoughts!