After vue-next became publicly available, inspired by LinusBorg composition-api-demos, I started building a utility composition-api library vue-composable with a goal of learning and understanding the composition-api.
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 or other caveats of using typescript with vue.
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 composable:
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 and useFetch
- usePagination allows manipulating pages based on items, it's generic enough to allow to adapt any pagination implementation.
- useFetch 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:
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
When building 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, disables the auto unwrapping of the object (more info), making the usage of ref
a bit more verbose
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 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:
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.
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 work. Asides from that, I think is a pretty neat way to overcome .value
👍
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 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:
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
Top comments (0)