by Gerard Sans |  @gerardsans

Vuex like nobody is looking

Vuex like nobody is looking

SANS

GERARD

Google Developer Expert

Google Developer Expert

International Speaker

Spoken at 105 events in 27 countries

🎉 100 🎉

🎉   🎉

Blogger

Blogger

Community Leader

900

1.5K

Trainer

Master of Ceremonies

Master of Ceremonies

Vuex

  • v3.0.1 (Feb 2016)
  • 225 contributors
  • CLI integration
  • 17K stars
  • v6.1.0 (Dec 2015)
  • 137 contributors
  • CLI integration
  • 3K stars
  • v4.0.1 (June 2015)
  • 631 contributors
  • Inspired by Flux
  • 44K stars

Vuex

Overview

  • State Management for Vue
  • Inspired by Redux
  • Composable using Modules
  • Vue DevTools integration

Components

Actions

Mutations

commit

dispatch

mutate

State

update

Vuex one-way data flow

Actions

(async)

Mutations

(sync)

Actions vs Mutations

DevTools

Backend

commit

Packages

vue devtools

vuex

vuex-router-sync

Features

Utilities

Counter

Increment

Decrement

Reset

Total

import Vue from "vue";
import App from "./App";

import store from "./store";

new Vue({
  el: "#app",
  store,
  components: { App },
  template: "<App/>"
});
src/app.component.ts

Store Setup

src/main.js

Components

Actions

Mutations

commit

dispatch

mutate

State

update

import Vue from "vue";
import App from "./App";
import store from "./store";

new Vue({
  el: "#app",
  store,                    // this.$store.state
  components: { App },
  template: "<App/>"
});
src/app.component.ts

Store Setup

src/main.js
import actions from "./actions";
import mutations from "./mutations";

export default new Vuex.Store({
  actions, 
  mutations, 
  state, 
});
src/app.component.ts

State

src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
  counter: 0
};

export default new Vuex.Store({
  state, 
});
import mutations from "./mutations";
import actions from "./actions";

export default new Vuex.Store({
  state,
  mutations,
  actions,
});

Components

Actions

Mutations

commit

dispatch

mutate

State

update

export default {
  increment(context) {
    context.commit("INCREMENT");
  },
  decrement({ commit }) {
    commit("DECREMENT");
  },
  reset({ commit, dispatch, state, getters }) {
    context.commit("RESET");
  }
};
src/app.component.ts

Counter Actions

src/store/actions.js

Components

Actions

Mutations

commit

dispatch

mutate

State

update

export default {
  INCREMENT(state) {
    state.counter += 1;
  },
  DECREMENT(state) {
    state.counter -= 1;
  },
  RESET(state) {
    state.counter = 0;
  }
};
src/app.component.ts

Counter Mutations

src/store/mutations.js

Components

Actions

Mutations

commit

dispatch

mutate

State

update

<Counter
  @increment="increment" @decrement="decrement" @reset="reset"
/>

export default {
  methods: {
    increment() {
      this.$store.dispatch("increment");
    },
    reset() {
      this.$store.dispatch("reset", { value: 0 });
    }
  },
}
src/app.component.ts

Dispatching Actions

src/App.vue

We pass events up to container

<Counter :total="total" 
  @increment="increment" @decrement="decrement" @reset="reset"
/>

export default {
  computed: {
    total() {
      return this.$store.state.counter;
    }
  }
}
src/app.component.ts

Updating Component

src/App.vue

We will improve this part later

<div>
  <div class="total">{{total}}</div>
  <button @click="$emit('increment')">+</button>
  <button @click="$emit('decrement')">-</button>
  <button @click="$emit('reset', { value:0 })">C</button>
</div>

export default {
  name: "Counter",
  props: ["total"]
};
src/app.component.ts

Counter

src/components/Counter.vue
// src/store/actions.js
reset({ commit }, payload) {
  commit("RESET", payload);
}

// src/store/mutations.js
RESET(state, { value }) {
  state.counter = value;
}
src/app.component.ts

Adding payload

Getters

State

Property

getters

Computed

getters

s

g

g

Vuex Getters

  • Helpers to access Store

  • Avoid Components tight coupling with Store

  • Memoized for performance

import getters from "./getters";

export default new Vuex.Store({
  actions, 
  mutations, 
  getters,
  state, 
});
src/app.component.ts

Vuex Store

src/store/index.js
src/app.component.ts

Getters

src/store/getters.js
export default {
  total: state => state.counter,

  overflow: state => {
    return state.counter > state.maximum;
  }
};
this.$store.state.counter;
this.$store.state.pagination.page;

Tight coupling

Components

State

Getters: loose coupling

this.$store.getters.total;
this.$store.getters.page;

Components

getters

State

A small change  won't break all Components now

export default {
  total: state => {
    return state.counter;
  },
  page: state => {
    return state.pagination.page;
  }
};
src/app.component.ts

Property Getters

src/store/getters.js

State

Property

getters

Computed getters

s

g

g

Computed Getters

  • Computed values, can use other getters

  • Memoised for performance

  • Default one-slot memoisation

// State
{ 
  todos: [{ id: 1, text: 'Learn Vuex', complete: false }],
  currentFilter: "SHOW_ALL"
}

// Visible Todos
[{ id: 1, text: 'Learn Vuex', complete: false }]
src/app.component.ts

Example: visibleTodos Getter

// State
{ 
  todos: [{ id: 1, text: 'Learn Vuex', complete: false }],
  currentFilter: "SHOW_COMPLETED"
}

// Visible Todos
[]
const getters = {
  todos: state => state.todos,
  currentFilter: state => state.currentFilter,
};
const getters = {
  todos: state => state.todos,
  currentFilter: state => state.currentFilter,
};

Todos Getters

src/store/getters.js

Getter: Visible Todos

const getters = {
  visibleTodos: function(state, getters) {
    var todos = getters.todos.slice().reverse();
    switch (getters.currentFilter) {
      case "SHOW_ACTIVE":
        return todos.filter(t => !t.done);
      case "SHOW_COMPLETED":
        return todos.filter(t => t.done);
      case "SHOW_ALL":
      default:
        return todos;
    }
  }
};
src/store/getters.js
<todo-list 
  :todos="visibleTodos" :currentFilter="currentFilter"
/>
export default {
  computed: {
    visibleTodos() {
      return this.$store.getters.visibleTodos;
    },
    currentFilter() {
      return this.$store.getters.currentFilter;
    }
  }
};
src/app.component.ts

Using visibleTodos

src/App.vue
<todo-list 
  :todos="visibleTodos" :currentFilter="currentFilter"
/>
export default {
  computed: {
    ...mapGetters(['visibleTodos', 'currentFilter'])
  }
};

ComponentHelpers

import { mapState } from 'vuex';
export default {
  data: () => ({ 
   factor: 12 
  }),
  computed: {
    ...mapState(['total']),
    ...mapState({ counter: 'total' }),
    formula(state) { 
      return state.total*this.factor;
    }
  }
}
src/app.component.ts

Mapping Helpers: mapState

import { mapActions, mapMutations, mapGetters } from 'vuex';

export default {
  methods: {
    ...mapActions(['increment', 'decrement']),
    ...mapActions({ clear: 'reset' }),  // alias
  computed: {
    ...mapGetters({ total: 'total' })
  }
}
src/app.component.ts

Other mapping helpers

Modules

Namespaced Module

Extension

Module

Root

Module

Modules

import cart from './modules/cart'
import products from './modules/products'

export default new Vuex.Store({
  modules: {
    cart,
    products
  },
})
src/app.component.ts

Store Setup

src/store/index.js
export default {
  namespaced: true,
  state: {
    items: [],
    checkoutStatus: null
  },
  actions,
  mutations
  getters,
}
src/app.component.ts

Cart Module

src/store/modules/cart.js
export default {
  namespaced: true,
  state: {
    all: [],
  },
  actions,
  mutations
  getters,
}
src/app.component.ts

Products Module

src/store/modules/products.js
<Product v-for="product in products"
  @click="addProductToCart(product)" 
/>

export default {
  computed: mapState({
    products: state => state.products.all
  }),
  methods: mapActions('cart', [
    'addProductToCart'
  ]),
  created () {
    this.$store.dispatch('products/getAllProducts')
  }
}
src/app.component.ts

ProductList Component

Reusing stores

{ 
  counter: 7
}

Single Counter

dispatch("increment");

State

Action

User clicks

{ 
  counter: 8
}

Mutation

commit("increment");

Multiple Counters

{ 
  "a": { counter: 7 },
  "b": { counter: 3 },
}

a

b

State

User clicks

{ 
  "a": { counter: 7 },
  "b": { counter: 2 },
}
dispatch('b/decrement');

Action

Mutation in Module B

commit("decrement");
import counter1 from "./modules/counter";
import counter2 from "./modules/counter";

export default new Vuex.Store({
  modules: {
    a: counter1,
    b: counter2
  }
});
src/app.component.ts

Store Setup

src/store/index.js
export default {
  namespaced: true,
  state: () => ({
    counter: 0
  }),
  // same actions, mutations and getters
};
src/app.component.ts

Store Setup

src/store/modules/counter.js

More

@sarah_edo

Blake Newman

Sarah Drasner

@blakenewman

Vuex like nobody is looking

By Gerard Sans

Vuex like nobody is looking

State Management is key to build modern Web Apps

  • 2,730