In this series we have explored various official and DIY store solutions. At the end of this series, we’ll look at a solution for the family volume that makes up the API using Vue 3.

Before we continue, if you haven’t read the first few articles in this series, I encourage you to go back and at least look at article 1. What is the Store and 2.Vuex, the Official Vue.js Store, because we won’t talk too much about the principles in this article, but rather focus on the implementation.

Installation and Setup

Unlike Vuex or Pinea, the composite API ships with Vue 3, so there are no external packages to install and set up. If you use Vue 2, you can install the composition-API Vue plugin.

Definition of a store

There are many different ways to define a store using the Composition API. Since it’s a family-volume solution, it’s really up to you to decide exactly what structure you want, but the important part is that your state is passed on to reactive methods so that your state is reactive. In fact, Reactive is the Vue 3 renamed equivalent of vue.Observable to avoid confusion with RxJS observation variables.

As an alternative to Reactive, you can use ref, but I prefer Reactive in this case. Ref is typically used for raw values, such as strings or booleans, while Reactive is used for objects. Because we’re used to thinking of state as a single entity, it’s nice to reference all the states in the storage space under a state object, so I prefer Reactive. Using Reactive also means THAT I don’t have to use.value when I refer to any of the states, which feels more intuitive to me.

Now, we can just export an anonymous function that returns an empty object. Later, we will specify which states and actions we export from the module.

// store/loggedInUser.js
import {reactive} from 'vue'
// state
const state = reactive({})
export default ()=>({})
Copy the code

Then, accessing the store in the component is now as simple as importing from the store and calling the exported function.

//AppComponent.vue
<script>
import useLoggedInUser from "./store/loggedInUser.js";
export default{
  setup(){
    const loggedInUser = useLoggedInUser()
  }
}
</script>
Copy the code

state

Now we’ve defined our store, but it’s kind of sad because it hasn’t kept up with any state. Without state, a store is nothing, so let’s add some. We can do this by adding properties to objects passed to Reactive. And just like that, our state is reflected!

// store/loggedInUser.js
import {reactive} from 'vue'
const state = reactive({
  name: 'John Doe',
  email: '[email protected]', 
  username: 'jd123',
})
Copy the code

Now we will pass the state we want to expose from the loggedInUser store to the output object. We can do this with a state property.

// store/loggedInUser.js
export default ()=>({
  state // shorthand for state:state
})
Copy the code

However, this model, despite its simplicity, has two drawbacks.

  1. This means that we can only access the state under the state property of the consuming component.
  2. It does not prevent the state from being changed directly from the consuming component.

Instead, we can create computed properties for each property in the state we want to expose.

// store/loggedInUser.js
import {reactive, computed} from 'vue'

const state = reactive({
  name: 'John Doe',
  email: '[email protected]', 
  username: 'jd123',
})

export default () => ({
  name: computed(()=> state.name),
  email: computed(()=> state.email), 
  username: computed(()=> state.username),
  posts: computed(()=> state.posts),
})
Copy the code

This approach is verbose, but it has three advantages.

  1. It allows us to directly access the state in the consumption component without needing to be nested in state properties.
  2. It prevents the consuming component from changing state directly.
  3. It even allows us to choose which state to expose.

Alternatively, if you don’t care which states you choose to expose, you can create a helper function that loops through all your states and automatically creates computed properties from them.

// helper.js
import {computed} from 'vue'
export const withState =  (target, state)=>{
  Object.keys(state).forEach(prop =>{
    target[prop] = computed(()=> state[prop])
  })
  return target
}
Copy the code
// store/loggedInUser.js import {withState} from '.. /helper' const state = reactive({ name: 'John Doe', email: '[email protected]', username: 'jd123', }) export default () => withState({}, state)Copy the code

Finally, we can access the state in the template after returning loggedInUser from the setup function.

// AppComponent.vue
<template>
  <h1>Hello, my name is {{name}}</h1>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
  setup(){
    const loggedInUser = useLoggedInUser()
    return loggedInUser
  }
}
</script>
Copy the code

We can even use the spread operator on loggedInUser when it is returned from the setup function, if we need to expose any other variables to the template. You might be hesitant to use the propagation operator here because we use the Reactive method to define the state. Why is that? Because propagating a Reactive object breaks its reactivity. Note, however, that we are not propagating the state, but rather the object returned from the loggedinUser.js module, where the state is defined as a computed item. It means we can spread everything we want

const somethingElse: ref('') return {... loggedInUse, somethingElse}Copy the code

getter

Not only can the composite API handle our reaction state, but we can also combine it with the computed functions of the composite API to easily create accessors.


// store/loggedInUser.js
const state = reactive({
  //...
  posts: ['post 1', 'post 2', 'post 3', 'post 4']
})

// getters
const postsCount = computed(() => state.posts.length)
Copy the code

After defining the calculated item, we can expose it from the store by adding it to the exported object.

// store/loggedInUser.js
export default () => withState({
  postsCount
}, state)
Copy the code

Since we have returned loggedInUser from the setup function, we can now access postCount directly in the template.

// AppComponent.vue
<template>
  <h1>Hello, my name is {{name}}</h1>
  <p>I have {{ postsCount }} posts available.</p>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
  setup(){
    const loggedInUser = useLoggedInUser()
    return loggedInUser
  }
}
</script>
Copy the code

action

With the composition API, you will of course want to define actions to change the state of your store. There are many ways you can define actions, but it is important that you provide these actions to provide a controlled interface for changing the state of the store.

With composition apis, there are absolutely no new concepts to learn when defining actions. They’re just functions. They do not expect any special parameters and can define any required parameters to make an action in an intuitive interface. In addition, accessing a state is as simple as referring to a state variable.

// store/loggedInUser.js
const state = reactive({
  //...
  posts: ['post 1', 'post 2', 'post 3', 'post 4']
})
//...
const insertPost = (post) => state.posts.push(post)
Copy the code

To expose the action, we simply add it to the exported object.

// store/loggedInUser.js
export default () => withState({
  //..
  insertPost
}, state)
Copy the code

Then in your component, the action can be used as a method on the loggedInUser, or directly in the template, since we’ve returned the loggedInUser from the setup method.

// AppComponent.vue
<template>
  <!-- ... -->
  <input v-model="post" type="text" />
  <button @click="insertPost(post)">Save</button>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
  setup(){
    const loggedInUser = useLoggedInUser()
    const post = ref('')
    return {...loggedInUser, post}
  }
}
</script>
Copy the code

Just reading the code above, you probably won’t notice any difference, but when you actually put the composite API storage solution into practice, the action feels more natural because they’re just normal Javascript functions. Nothing needs to be scheduled or committed.

Organize in modules

With the current direction, we can treat our stores like Pinia: modular by default. We can create new domain-specific files for each store or module and import them only as needed. Alternatively, you could make a single entry point for your store and treat it like Vuex does, but I don’t think there’s any real benefit to doing that, so we won’t go into that further in this article.

Vue development tools

As far as DevTools is concerned, the approach that makes up the API doesn’t offer any special solutions. There is no time travel available and no dedicated panel to check the status of your store. However, despite this, you can still see the state of the component under “Setup” in Vue DevTools, as long as you return the whole thing from the setup function. As a bonus, you can even see what actions are available.

Notable features

To summarize the composite API as a store solution, let’s quickly review its most noteworthy features to help you decide which store to implement is best for you and your project.

  • No additional libraries are required
  • The Composition API plug-in is available in Vue 3 or Vue 2
  • You can customize it to your liking
  • Offers the opportunity to learn more about the store and pushes the boundaries of the store.
  • There is no need to deal with mutations
  • IDE support for actions because they are just plain Javascript.

On the other hand, since the Vue composite API is home-based, the disadvantages of using it are basically the same as using vue.Observable (see the last article in this series). However, due to the influence of the Composition API on official solutions like Pinea and even the upcoming version of Vuex, you’ll do yourself a favor by at least trying.

conclusion

The Composition API is a powerful tool for not only fully sharing logic between components, but also a powerful solution for sharing state. If you want to learn more about launching your own store using the Composition API, Filip Rakowski has also written an excellent article on the subject.

This concludes our series of articles on store solutions in vue.js. If you missed any articles, feel free to come back and read them

  • What is a store in vue.js?
  • Vuex, the official vuex.js store
  • Pinia, an alternative vue.js store
  • Use Vue. Obsable (Vue 2) home page scrolling store
  • Use the Composition API’s home page scroll shop