by Gerard Sans | @gerardsans
SANS
GERARD
Spoken at 101 events in 27 countries
900
1.5K
Components
Actions
Mutations
commit
dispatch
mutate
State
update
Actions
(async)
Mutations
(sync)
DevTools
Backend
commit
vue devtools
vuex
vuex-router-sync
Features
Utilities
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
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
src/main.js
import actions from "./actions";
import mutations from "./mutations";
export default new Vuex.Store({
actions,
mutations,
state,
});
src/app.component.ts
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
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
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
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
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
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
State
Property
Getters
Computed
Getters
s
g
g
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
src/store/index.js
src/app.component.ts
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;
Components
State
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
src/store/getters.js
State
Property
Getters
Computed
Getters
s
s
s
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
// 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,
};
src/store/getters.js
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
src/App.vue
<todo-list
:todos="visibleTodos" :currentFilter="currentFilter"
/>
export default {
computed: {
...mapGetters(['visibleTodos', 'currentFilter'])
}
};
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
import { mapActions, mapMutations, mapGetters } from 'vuex';
export default {
methods: {
...mapActions(['increment', 'decrement']),
...mapActions({ clear: 'reset' }), // alias
computed: {
...mapGetters({ total: 'total' })
}
}
src/app.component.ts
Namespaced Module
Extension
Module
Root
Module
import cart from './modules/cart'
import products from './modules/products'
export default new Vuex.Store({
modules: {
cart,
products
},
})
src/app.component.ts
src/store/index.js
export default {
namespaced: true,
state: {
items: [],
checkoutStatus: null
},
actions,
mutations
getters,
}
src/app.component.ts
src/store/modules/cart.js
export default {
namespaced: true,
state: {
all: [],
},
actions,
mutations
getters,
}
src/app.component.ts
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
{
counter: 7
}
dispatch("increment");
State
Action
User clicks
{
counter: 8
}
Mutation
commit("increment");
{
"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
src/store/index.js
export default {
namespaced: true,
state: () => ({
counter: 0
}),
// same actions, mutations and getters
};
src/app.component.ts
src/store/modules/counter.js
import { sync } from 'vuex-router-sync'
import store from './vuex/store'
import router from './router'
// to register router module
const unsync = sync(store, router)
// to unregister router module
unsync()
src/app.component.ts
Store
Route
sync()
unsync()
state.registerModule('route', store)
state.unregisterModule('route')
Store
my_module
register
unregister
state.registerModule(path, store)
state.unregisterModule(path)
1
path = ['1', 'my_module']
vuex-router-sync/src/index.js
exports.sync = function (store, router, options) {
store.registerModule('route', {
namespaced: true,
state: cloneRoute(router.currentRoute),
mutations: {
'ROUTE_CHANGED' (state, t) {
store.state['route'] = cloneRoute(t.to, t.from)
}
}
})
...
}
vuex-router-sync/src/index.js
exports.sync = function (store, router, options) {
// watch state changes
const storeUnwatch = store.watch(
state => state['route'],
route => router.push(route),
{ sync: true }
)
// update state after navigations
const afterEachUnHook = router.afterEach((to, from) => {
store.commit('route/ROUTE_CHANGED', { to, from })
})
return function unsync() {}
}
exports.sync = function (store, router, options) {
return function unsync () {
storeUnwatch()
afterEachUnHook()
store.unregisterModule('route')
}
}
vuex-router-sync/src/index.js
@sarah_edo
@blakenewman