Thoughts on building composition-api functions
Tags: vue, javascript, typescript
Published: 2020-02-25 12:43:35

After [vue-next](https://github.com/vuejs/vue-next) became publicly available, inspired by [LinusBorg composition-api-demos](https://github.com/LinusBorg/composition-api-demos), I started building a utility composition-api library [vue-composable](https://pikax.me/vue-composable) with a goal of learning and understanding the [composition-api](https://vue-composition-api-rfc.netlify.com/). 


# Typescript

Because of my background on C#, I'm really keen to have `intellisense`, I always went the extra mile to get types on my vue apps, even when it required heavily modify and adapt [vuex typings](https://github.com/vuejs/vuex/pull/1121) or [other caveats of using typescript with vue](https://github.com/ffxsam/vue-typescript-cookbook).

I must admit using typescript within `setup()` has been really pleasant, it feels like plain typescript (similar to react in a way), without `sleight of hands`.


# composition-api

IMHO `composition-api` shines when composing multiple functions to get the desire result.

Let's try implementing [SWAPI](https://swapi.co/) composable:

```ts
import { usePagination, useFetch, wrap } from "vue-composable";
import { ref, watch, isRef } from "@vue/composition-api";

type SWAPI_RESOURCE =
  | "planets"
  | "spaceships"
  | "vehicles"
  | "people"
  | "films"
  | "Species";

interface SWAPIList<T = any> {
  count: number;
  next: string;
  previous: string;
  results: Array<T>;
}

function useSWAPI<T = any>(r: SWAPI_RESOURCE) {
  const resource = wrap(r);
  const ENDPOINT = `https://swapi.co/api/`;

  const items = ref<T[]>([]);
  const { json, loading, exec, status } = useFetch<SWAPIList>();

  const pagination = usePagination({
    currentPage: 1,
    pageSize: 10, // default size
    total: 0
  });

  watch(
    json,
    json => {
      if (json) {
        pagination.total.value = json.count;
        items.value = json.results;
      } else {
        pagination.total.value = 0;
        items.value = [];
      }
    },
    {
      lazy: true
    }
  );

  watch([pagination.currentPage, resource], () => {
    exec(`${ENDPOINT}${resource.value}/?page=` + pagination.currentPage.value);
  });

  return {
    ...pagination,
    exec,
    items,
    loading,
    status
  };
}

// usage
setup(){
  return useSWAPI('people');
}
```

> We can improve the return types from the request based on `SWAPI_RESOURCE`.

In this example we use two composables [usePagination](https://pikax.me/vue-composable/composable/pagination/pagination.html) and [useFetch](https://pikax.me/vue-composable/composable/web/fetch.html)

- [usePagination](https://pikax.me/vue-composable/composable/pagination/pagination.html) allows manipulating pages based on items, it's generic enough to allow to adapt any pagination implementation.
- [useFetch](https://pikax.me/vue-composable/composable/web/fetch.html) just a fetch wrapper


## Re-usability 

You might be thinking `"Isn't that what mixins are used for?"` and you be correct, but using mixins you need to be careful with naming collisions, handling variable names, methods, etc.

Using composition-api becomes trivial to expose multiple api calls on the setup: 

```ts
setup(){
  const people = useSWAPI('people');
  const planets = useSWAPI('planets');

  return {
    people,
    planets
  }
}
```

## `Ref` vs `Reactive`

I recommend having a look on this [Thought on Vue 3 Composition API - `reactive()` considered harmful](https://dev.to/ycmjason/thought-on-vue-3-composition-api-reactive-considered-harmful-j8c)

When building [vue-composable](https://github.com/pikax/vue-composable) 98% of the cases I will return a object with `ref`, the reason being it allows you to deconstruct your object and vue will `unwrap` it on the render.

One common practice I use on my composables is accept both `Ref<T>|T`, this allows the flow in the `setup()` to be much cleaner (without `.value` everywhere) and also allowing the composable to `watch` changes on the argument. 

### Template unwrapping

One of the arguments of using `ref` is the `auto-unwrapping` on the template(no need to use `.value` in the render), ~~but the commit [refactor: remove implicit reactive() call on renderContext](https://github.com/vuejs/vue-next/commit/6b10f0cd1da942c1d96746672b5f595df7d125b5), disables the auto unwrapping of the object ([more info](https://github.com/vuejs/vue-next/pull/738)), making the usage of `ref` a bit more verbose~~

```js
export default {
  // before 
  template: `<div> {{ awesomeObject.items }} {{ awesomeObject.selected }} </div>`,
  // after
  template:  `<div> {{ awesomeObject.items.value }} {{ awesomeObject.selected.value }} </div>`,
  // after with auto unwrap
  template:  `<div> {{ autoUnwrap.items }} {{ autoUnwrap.selected }} </div>`,
  setup() {
    const awesomeObject = {
      items: ref([]),
      selected: ref({}),
    };

    return {
      awesomeObject,
      // auto unwrapping, it need to be a ref, cannot return plain object with nested ref
      autoUnwrap: ref(awesomeObject) // or reactive(awesomeObject)
    };
  }
};
```

This is a breaking change and as far as I know, the [@vue/composition-api](https://github.com/vuejs/composition-api) it not updated yet.

This change makes the usage of ref less appealing, but not sure how in the real world environment what changes it will make.


### Not everything needs to be `ref` or `reactive`

This might be a bit controversial, I don't believe your `use*` should always return `ref`, when you are returning something you know it will **not** change, you might be better off not wrapping it on a `ref`/`reactive`, eg:

```ts
export function useOnline() {
  const supported = "onLine" in navigator;

  // not sure how to test this :/
  if (!supported) {
    online = ref(false);
  }

  if (!online) {
    online = ref(navigator.onLine);
    // ... listen for changes
  }

  return {
    supported,
    online
  };
}
```

`supported` won't change, so the usage of a `ref` is not necessary, I don't think consistency is a good argument in this particular case.

### using `reactive(state)` and then return `toRefs()`

I've seen code that uses an `reactive state` and then return `toRefs(state)`.

I like how clean this is, you just need to understand why you need to return `toRefs(state)` and that's basically the complexity of this approach.

```ts
const state = reactive({
  supported: "onLine" in navigator,
  online: navigator.onLine
})
window.addEventListener("online", () => state.online = true));

return toRefs(state);
```

Although as a library creator, having to call `toRefs` will have a **theoretically** more objects created (just an opinion, I might be wrong, you can prove me wrong), thus more [GC](https://javascript.info/garbage-collection) work. Asides from that, I think is a pretty neat way to overcome `.value` :thumbsup:


## Moving to composition-api

IMO you don't need to port your `object-api` code to `composition-api`, I would go as far as don't recommend doing it without a good reason.

object-api only has a few issues when dealing with huge components, you can even make an argument that you should refactor your component better, I would only use composition-api if maintaining your component if getting out-of-hand and composition-api would allow to make the component easier to maintain. 


### You can use `composition-api` return values on your `object-api`

When using [@vue/composition-api](https://github.com/vuejs/composition-api) plugin, it is built on top of the object-api and when using `vue-next` the options-api is built using composition-api, making it trivial to share functionality between them.

For example, our `useSWAPI` example, if you want to use it on your vue component without using `setup()`, you can use: 

```ts
export default {
  data(){ 
   return reactive(useSWAPI('people')), 
   // you need to use `reactive` or `ref` to unwrap the object,
   // otherwise you need to use `.value` on the template
  }
}
```

# Final thoughts

I really like the extensibility of the `composition-api`, I'm looking forward for what the community will build once `vue3` is out!

I recommend being open-minded and use the right tool for the job, some components don't required extra complexity or you don't want to migrate your huge project to composition-api, my answer to that is: You don't need it. 

You can take advantage of the community libraries for `composition-api` within your `object-api` component, to be fair that's one thing I like VueJS it provides an standard way to do things but also giving you the tool to allow modifying and tweak it for your needs.

I know the composition-api was quite controversial in the beginning, let me know if you still think is unnecessary or if you are interested in learning or if you're using it already or what's your thoughts on this matter?



You can check some of my choices and implementations: https://github.com/pikax/vue-composable


### EDIT 
_2020-03-01_

Striking the text that mentions the commit to prevent auto-unwrapping on template, because the auto-unwrapping was re-added 
[refactor(runtime-core): revert setup() result reactive conversion](https://github.com/vuejs/vue-next/commit/e67f655b2687042fcc74dc0993581405abed56de)