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

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,911