React has come out with a solution to being able to compose state: react hooks. The idea is that sometimes you want to create a mixin-like entity that is basically only state. It used to be that you’d have to write a Higher Order Component or do some sort of render prop.
No more! You can create nice, transportable things that can be shared between components! And you don’t have to subscribe to redux.
Vue does something similar…
Vue has a very similar thing. It’s called an observable.
The idea is that sometimes you want to create a small bundle of state that many components could reference, but you don’t want to use vuex. I’ve run into this in many apps where maybe we just want to run little Vue “widgets” in separate parts of the page, but they need to be able to look at the same data.
More importantly, they need to re-render and react to that data!
Here’s a way that you can basically make “Vue hooks.” You can do this in a plain ol’ javascript file in your project:
const someData = Vue.observable({
prop1: "value1",
prop2: "value2"
});
export default someData;
Now if you import it in a Vue Single File Component you can access it through a computed function. If you modify it, Vue reacts. Spectacular!
<template>
<p>{{ someData.prop1 }}</p>
</template>
<script>
import someData from './someDataFile.js';
export default {
name: "MyComponent",
computed: {
myData() {
return someData.prop1;
},
},
};
</script>
What makes this composable state is that I can import this “observable” data object in as many places as I want, and I can depend on Vue reacting.
Extending abilities with observable
You can also return functions that would act on your data. I like to think of this as a getter from vuex. Maybe we could create a service, something along the lines of…
const tags = Vue.observable({
tags: [...someBigArrayOfTags],
});
export {
tags,
startsWith(str) {
return tags.filter(el => el.startsWith(str);
}
}
I mean, you could just filter over the tags you get back in the component itself, but in this way we’re packaging up some functionality that you can use elsewhere. And, probably, there’s going to be other more interesting real cases where you need getters… because you need them in Vuex.
Caveats
So, because we’re working in Vue 2.x, the observable()
function needs to be passed an initial object. Under the hood, Vue is using Object.defineProperty
to make each one of those properties on that initial object reactive.
If you don’t pass an initial object, it won’t be reactive. It won’t have any properties in that object to target.
There are some other reactivity caveats that go along with this, but really, these are not new. Vue.js tries to outline these in their basic Vue guide.
This will not be the case in Vue 3.0 when they’ll use Proxies!
One last example
You can do some interesting things by using this observable object. Here is a more advanced example where I’m creating a contrived blog post service. The service actually creates a placeholder for a post, but the post data is not loaded until the post is being accessed.
It’s not something I’d say is ship-worthy or necessarily a good idea, but it is an example of some really cool ways we can compose state.