DEV Community

Sadick
Sadick

Posted on • Originally published at Medium on

4

Build Vuex from scratch.

I took Vuex an stripped it down to its simplest form

If you are a front end developer or have been playing with vuejs then you have probably come across vuex. If not then here is what it is.

What is Vuex? · Vuex

I always find it annoying when you watch a tutorial and then the instructor goes like. “You need to npm intsall vue, vuex, vue-router, axios ”and other 10 modules. I think before you introduce a library to your project you should really understand what that library does.

Libraries will come and go. [insert your cool tool here] might be hot today but trust me something better (or worse) will replace it, if not today then tomorrow. If you are the type of developer that follows hype please take time to understand the core concepts before adopting a library.

Getting an understanding on the underlying concepts will help you a lot. For this reason I would like us to look at a minimal implementation of Vuex.

How you typically use Vuex

  • You always dispatch an action from your component’s methods
export default {
name: 'some-component',
data () {
return {
name: 'sadick'
}
},
methods: {
getUsername () {
this.$store.dispatch('getUsernameFromAPI')
}
}
}
view raw component.js hosted with ❤ by GitHub
  • And then in your actions.js you commit the mutation to the store
export const getUsernameFromAPI = ({commit}) => {
fetch('https://someapi.com/username').then(resp => resp.json()).then(data => {
commit('SET_USERNAME', data)
})
}
view raw actions.js hosted with ❤ by GitHub
  • Lastly mutation to the state is done in your mutations.js
export default {
['SET_USERNAME'] (state, username) {
state.person.username = username
}
}
view raw mutations.js hosted with ❤ by GitHub
  • How Vuex is tied up with the Vue ecosystem.
Vue.use(Vuex)
const store = new Vuex.Store({
state,
actions,
mutations
})
new Vue({
el: '#app'
store
})
view raw init.js hosted with ❤ by GitHub

Minimal Vuex Implementation.

Vuex makes use of three things. Actions, Mutations and State. Lets start by making the structure of our implementation.

export default class Store {
constructor(options = {}) {
this._state = Object.assign({}, options.state)
this._actions = options._actions
this._mutations = options.mutations
}
dispatch() {}
commit() {}
}
view raw store.js hosted with ❤ by GitHub

What goes in the dispatch method?

Dispatch is the central hub of data flow in an application that uses Vuex. It is used to dispatch actions to the store and responsible of calling the correct action.

export default
//... *some code above*
dispatch(action, payload) {
if (!this._actions[action]) {
throw new Error(`[Error] action ${action} is undefined`)
}
this._actions[action].call()
}
}
view raw dispatch.js hosted with ❤ by GitHub

There is one important thing we have to do in line 8 of the above code. We would like to call the action and pass our commit function and the current state of the store. Lets modify the line

this._actions[action].call(
null,
{
commit: this.commit.bind(this),
state: this._clone(this._state)
},
payload
)
view raw dispatch.js hosted with ❤ by GitHub
_clone(obj) {
return JSON.parse(JSON.stringify(obj))
}
view raw clone.js hosted with ❤ by GitHub

What goes in the commit method?

Through the commit method we can mutate the state of the store. We cannot mutate the store in any part of our implementation except through the commit method.

commit(type, payload) {
if (!this._mutations[type]) {
throw new Error(`[Error] mutation ${type} is undefined`)
}
this._mutations[type].call(null, this._state, payload)
}
view raw commit.js hosted with ❤ by GitHub

Are we done?

Believe it or not we are done with the implementation. The last thing we have to do is hook it up with the Vue ecosystem. We will do that via a mixin.

function install(Vue, options) {
Vue.$store = Vue.mixin({
created() {
if (this.$options.store) {
this.$store = this.$options.store
} else if (this.$options.parent && this.$options.parent.$store) {
this.$store = this.$options.parent.$store
Vue.util.defineReactive(this, 'state', this.$store)
}
}
})
}
view raw install.js hosted with ❤ by GitHub

Wrapping Up.

Here is the complete code.

class Store {
constructor(options = {}) {
this._state = Object.assign({}, options.state)
this._actions = options.actions
this._mutations = options.mutations
}
dispatch(action, payload) {
if (!this._actions[action]) {
throw new Error(`[Error] action ${action} is undefined`)
}
this._actions[action](
{
commit: this.commit.bind(this),
state: this._clone(this._state)
},
payload
)
}
commit(type, payload) {
if (!this._mutations[type]) {
throw new Error(`[Error] mutation ${type} is undefined`)
}
this._mutations[type].call(null, this._state, payload)
}
_clone(obj) {
return JSON.parse(JSON.stringify(obj))
}
}
function install(Vue, options) {
Vue.$store = Vue.mixin({
created() {
if (this.$options.store) {
this.$store = this.$options.store
} else if (this.$options.parent && this.$options.parent.$store) {
this.$store = this.$options.parent.$store
Vue.util.defineReactive(this, 'state', this.$store)
}
}
})
}
export default {
install,
Store
}
view raw Suex.Store.js hosted with ❤ by GitHub

Please bear in mind, this is for learning purposes to understand what happens when you use Vuex. This is a minimal implementation. Vuex has tones of features i.e (getters, modules, e.t.c) which I have not touched on.

If you found this post helpful, please share it so that others can find it. You can follow me on GitHub and LinkedIn. If you have any ideas and improvements feel free to share them with me.

_Happy coding! _💪

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (5)

Collapse
 
benavern profile image
Benjamin CARADEUC

Hi!

I am very interested in this little implementation but can't get it to work.

I maybe did something wrong...

here is my modified implementation:

class Store {
  constructor (options = {}) {
    this._state = { ...options.state }
    this._actions = { ...options.actions }
    this._mutations = { ...options.mutations }
  }

  dispatch (action, payload) {
    if (!this._actions[action]) {
      throw new Error(`[Error] action ${action} is undefined`)
    }

    return this._actions[action](
      {
        commit: this.commit.bind(this),
        state: { ...this._state }
      },
      payload
    )
  }

  commit (type, payload) {
    if (!this._mutations[type]) {
      throw new Error(`[Error] mutation ${type} is undefined`)
    }

    return this._mutations[type].call(null, this._state, payload)
  }
}

function install (Vue) {
  Vue.$store = Vue.mixin({
    created () {
      if (this.$options.store) {
        this.$store = this.$options.store
      } else if (this.$options.parent && this.$options.parent.$store) {
        this.$store = this.$options.parent.$store
        Vue.util.defineReactive(this, 'state', this.$store)
      }
    }
  })
}

export default {
  install,
  Store
}

I can dispatch the actions, commit the mutations and I can see my state has been updated but I don't understant how to get the state value in my vue file :(

For example after mutating a users array in the state, I try to use this :

  • this.$store.state.users
  • this.$store.users
  • this.state.users

but neither of these are working...

Could you tell me what I'm doing wrong ?

Thanks!

Collapse
 
sadick profile image
Sadick

What do you get when you console.log(Vue.$store)?

Collapse
 
benavern profile image
Benjamin CARADEUC

I just modified this line and it worked Vue.util.defineReactive(this, 'state', this.$store._state)

Thread Thread
 
sadick profile image
Sadick • Edited

Nice to know that it worked.

Collapse
 
bobdotjs profile image
Bob Bass

I've been working on building my own version of Vuex but for react because I strongly prefer it over Redux.

I've been hitting so many strange edge cases that were so hard to work through. I really wanted to avoid digging through the Vuex code because I don't want to steal it exactly, I want to build my own version using patterns that I enjoy working with, while keeping the same general syntax.

Good for you for taking this project on, I'm certainly growing as a developer from my efforts to do the same but your code is clean and readable. This was cool to read.

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay