Finally understand Vuex

What is Vuex?

This is a new video on my ongoing Vuex series on YouTube explaining the core concepts.

I know, I know, I already posted two videos about Vuex before but I felt like I dove a little but to fast into the code and kinda missed to explain the basics about the framework.

So, here is a new “episode 0” to the Vuex series, a prequel if you want. If you haven’t seen the other episodes yet, just watch the full playlist below:

PGDay EU Conference 2017
Vue.js logo

Understanding Vue.js Reactivity in Depth with Object.defineProperty()

I’m coming from a Java background, so when I started with JavaScript many years ago it kinda felt weird to not have getters and setters but somehow I got used to it and as time went by I began to like it because it lead to much cleaner code compared to thousands of getter/setter lines in java. For example, check out the following Java Code:

class Person{
  String firstName;
  String lastName;
 
  // constructor omitted for this demo ;-)
 
  public void setFirstName(firstName) {
    this.firstName = firstName;
  }
 
  public String getFirstName() {
    return firstName;
  }
 
  public void setLastName(lastName) {
    this.lastName = lastName;
  }
 
  public String getLastName() {
    return lastName;
  }
}
 
// Create instance
Person bradPitt = new Person();
bradPitt.setFirstName("Brad");
bradPitt.setLastName("Pitt");

A JavaScript developer would never go though the hassle of doing something like this but instead:

var Person = function () {
};
 
var bradPitt = new Person();
bradPitt.firstName = 'Brad';
bradPitt.lastName = 'Pitt';

It’s much simpler. And simpler is usually better, isn’t it?

Sure it is, but sometimes though, I felt like it would be useful if object properties could be modified when they’re accessed without the caller knowing anything about it. For example, let’s expand our Java code with a new method getFullName():

class Person{
  private String firstName;
  private String lastName;
 
  // constructor omitted for this demo ;-)
 
  public void setFirstName(firstName) {
    this.firstName = firstName;
  }
 
  public String getFirstName() {
    return firstName;
  }
 
  public void setLastName(lastName) {
    this.lastName = lastName;
  }
 
  public String getLastName() {
    return lastName;
  }
 
  public String getFullName() {
    return firstName + " " + lastName;
  }
}
 
Person bradPitt = new Person();
bradPitt.setFirstName("Brad");
bradPitt.setLastName("Pitt");
 
// Prints 'Brad Pitt'
System.out.println(bradPitt.getFullName());

fullName is a computed property in this case. It doesn’t really exist as a private attribute but it will always return the correct full name.

C# and implicit getter/setters

Looking at languages like C# there is a feature of implicit getters/setters which I really liked. Here, you can define getters/setters if you want, but you don’t have to, but if you decide to do so the caller won’t have to call a function. Instead, all he/she needs to do is keep accessing the property directly and the getter/setter will be called automagically under the hood:

public class Foo
{
    public string FirstName {get; set;}
    public string LastName {get; set;}
    public string FullName {get { return firstName + " " + lastName }; private set;}
}

… which is pretty cool I think.

Now, if I wanted to achieve something similar in JavaScript, I most of the time ended up with something like this:

var person0 = {
  firstName: 'Bruce',
  lastName: 'Willis',
  fullName: 'Bruce Willis',
  setFirstName: function (firstName) {
    this.firstName = firstName;
    this.fullName = `${this.firstName} ${this.lastName}`;
  },
  setLastname: function (lastName) {
    this.lastName = lastName;
    this.fullName = `${this.firstName} ${this.lastName}`;
  },
};
console.log(person0.fullName);
person0.setFirstName('Peter');
console.log(person0.fullName);

This will print:

"Bruce Willis"
"Peter Willis"

It works but it’s not very “javascripty” to use method names like setXXX(value).

So, the following approach solves this problem:

var person1 = {
  firstName: 'Brad',
  lastName: 'Pitt',
  getFullName: function () {
    return `${this.firstName} ${this.lastName}`;
  },
};
console.log(person1.getFullName()); // Prints Brad Pitt

Here, we’re back to a computed getter. You can set first and last name by simply assigning the values:

person1.firstName = 'Peter'
person1.getFullName(); // returns "Peter Pitt"

So, that’s better but I still don’t like it because we have a method that starts with “getXXX()” which is not very javascripty as well and for many, many years I thought that this is how I’d have to live with JavaScript.

And then there was Vue

After posting quite a lot of Vue.js related video tutorials on my YouTube channel I got used to the ease of use regarding reactivity or as we called it in good old Angular1 times: Data binding. It seems so easy. All you have to do is define something in the data() section of a Vue instance and then bind it to the HTML:

var vm = new Vue({
  data() {
    return {
      greeting: 'Hello world!',
    };
  }
})
<div>{greeting}</div>

This will print “Hello world!‘ to the UI obviously.

Now, if you changed the value of “greeting” the Vue engine will react to this and update the view accordingly.

methods: {
  onSomethingClicked() {
    this.greeting = "What's up";
  },
}

So, for quite some time I’ve been wondering how this works? Is there some kind of event that triggers when an object’s property changes? Or does Vue constantly fire setInterval to check for changes?

Looking into the Vue documentation I understood that changing an object’s property will implicitly call its getter/setters which again notify a watcher which then triggers a re-render as explained in this illustration from the official Vue.js docs:

Vue.js reactivity explained

Vue.js reactivity explained – Source https://vuejs.org

But I still wondered:

  • How do the getter/setters get there in the first place?
  • How are these implicitly called?

Well, the first one is quite easy: Vue does that for us. Whenever you add new data, Vue will walk through its properties and add setter/getters there. But how are these called if all you do is foo.bar = 3?

The answer to this question appeared to me after a short conversation with SVG & Vue expert Sarah Drasner on Twitter:

Twitter conversation with Sarah Drasner about Vue reactivity

Twitter conversation with Sarah Drasner about Vue reactivity

So, obviously she also referenced the docs which I’d read before, so I started to dig into Vue’s sources on GitHub to better understand what’s going on. It wasn’t easy since I didn’t know the code and also didn’t really know what to look for. But after a while I remembered that the docs stated something about a method called Object.defineProperty(), so I searched for that and voilà:

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
 
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
 
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
 
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

So, the answer was there all the time in the docs:

“When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters using Object.defineProperty. This is an ES5-only and un-shimmable feature, which is why Vue doesn’t support IE8 and below.”

I was simply to stupid to understand what Object.defineProperty() actually does, so let me break it down to you with a simple example:

var person2 = {
  firstName: 'George',
  lastName: 'Clooney',
};
Object.defineProperty(person2, 'fullName', {
  get: function () {
    return `${this.firstName} ${this.lastName}`;
  },
});
console.log(person2.fullName); // Prints "George Clooney"

Remember the implicit getters from C# at the beginning of this article? This is pretty much the same but for JavaScript and it seems it has been around since ES5. All you have to do is define those on an existing object using Object.defineProperty() and when ever you access the property, the getter will be called automagically – and that’s exactly what Vue does internally when you add new data.

Could Object.defineProperty() simplify Vuex?

Having learned all this I’ve been wondering if this couldn’t be something that could be used to simplify Vuex? I mean there are so many new terms in there which are not really necessary and make things overly complicated and hard to understand for beginners in my opinion (same goes for Redux btw):

  • Mutator – Or do you mean (implicit) setter?
  • Getters – Why not use Object.defineProperty() to set an (implicit) getter instead?
  • store.commit() – Why not simply call foo = bar; instead of store.commit("setFoo", bar);?

What do you think? Does Vuex have to be as complicated as it is or could it be simplified with Object.defineProperty()?

How to use Vuex with Framework7