A Full Day of Vue.js

04. State Management & Vuex

 What we've learned so far...

  • The Vue Instance is the starting point of all Vue applications, and is mounted to a DOM element.

  • Vue applications are data-driven.

  • Mustache Syntax can be used to bind data on to text content.

  • Directives tell DOM elements to act in a certain way.

  • v-bind:
  • v-on:
  • v-for:
  • v-model:
  • v-if/v-show:

bind data to HTML attributes

listen and act on DOM events

render a list based on a data source

two-way data binding with form inputs

conditionally render/show a DOM element

 What we've learned so far...

  • Methods are evaluated when explicitly called. 

  • Computed properties are used to perform more complex calculations/manipulation to data values and are cached.

  • Watchers allow us to react to data changes.

  • Lifecycle hooks are functions that run through the lifecycle of a Vue instance.

  • beforeCreate()/created()
  • beforeMount()/mounted()
  • beforeUpdate()/updated()
  • beforeDestroyed()/destroyed()

 What we've learned so far...

  • Vue Components are self-contained modules that can group markup, logic, and even styles.

Global Components:
Component Variables:
Single File Components
Vue.component('name-of-component', {});
const nameOfComponentObject = {};
  • Single File Components allow us to define HTML/CSS and JS of a component within a single .vue file

<template> section - HTML
<script> section - JS
<style> section - CSS
  • Props (data) can only be passed downwards in a single direction (parent - child - etc.).

  • vue-cli can help facilitate the rapid building and developing of Vue applications.

npm install -g @vue/cli
vue create name_of_project

 What we've learned so far...

Components are the building blocks of Vue applications

1

Whether Global or Single-File components

 Every Vue component contains a data() function

2

data() prepares the component to be reactive

 What we've learned so far...

Data (i.e. props) can only be passed downwards

3

Downwards = Uni-directional (parent -> child -> grandchild -> etc.)

 What we've learned so far...

Parent Component

Child Component

props

 What we've learned so far...

<template>
  <div>
    <ChildComponent :numbers="numbers" />
  </div>
</template>

<script>
import ChildComponent from "./ChildComponent";
export default {
  name: "ParentComponent",
  data() {
    return {
      numbers: [1, 2, 3]
    };
  },
  components: {
    "ChildComponent": ChildComponent
  }
};
</script>
<template>
  <div>
    <h2>{{ numbers }}</h2>
  </div>
</template>

<script>
export default {
  name: "ChildComponent",
  props: ['numbers']
};
</script>
numbers
ParentComponent.vue
ChildComponent.vue

What if we needed to communicate in the opposite direction?

🤔

e.g. Have ChildComponent introduce a new number in ParentComponent's numbers array

Custom Events can allow us to have child components notify/communicate with parent components.

 Custom Events

 Custom Events

this.$emit('name-of-event', {});

Reference to Vue Instance

Data

Vue Custom Events behave very similar to native JavaScript Custom Events

 Custom Events

<template>
  <div>
    <ChildComponent
      :numbers="numbers"
      @number-added="numbers.push($event)"
    />
  </div>
</template>

<script>
import ChildComponent from "./ChildComponent";
export default {
  name: "ParentComponent",
  data() {
    return {
      numbers: [1, 2, 3]
    };
  },
  components: {
    "ChildComponent": ChildComponent
  }
};
</script>
<template>
  <div>
    <h2>{{ numbers }}</h2>
    <input v-model="number" type="number" />
    <button @click="$emit('number-added', Number(number))">
     Add new number
    </button>
  </div>
</template>

<script>
export default {
  name: "ChildComponent",
  props: {
    numbers: Array
  },
  data() {
    return {
      number: 0
    };
  }
};
</script>
ParentComponent.vue
ChildComponent.vue

 Custom Events

Parent Component

Child Component

props
custom events

 Custom Events

Parent Component

Child Component #1

Child Component #2

?

 Custom Events

this.$emit('name-of-event', {});
<child-component
  @name-of-event="triggerMethod">
</child-component>

Child Component

Parent Component

With standard custom events; the custom event listener needs to be declared where the child component is being rendered in the parent.

 Custom Events

3 ways to managing data (i.e. state) in a Vue application.

Using a global EventBus

Using simple state management

Using Vuex

1.

2.

3.

 EventBus

An Event Bus is a Vue instance that is used to enable isolated components to subscribe and publish custom events between one another.

 EventBus

EventBus = new Vue();
window.EventBus
import Vue from 'vue';
export const EventBus = new Vue();

An EventBus can have isolated components communicate by being made global.

 EventBus

 EventBus

import Vue from 'vue';
export const EventBus = new Vue();
<template>
  <div>
    <input v-model="number" type="number" />
    <button @click="addNumber">
     Add new number
    </button>
  </div>
</template>

<script>
import { EventBus } from "../event-bus.js";
export default {
  name: "NumberSubmit",
  data() {
    return {
      number: 0
    };
  },
  methods: {
    addNumber() {
      EventBus.$emit("number-added", Number(this.number));
    }
  }
};
</script>
<template>
  <div>
    <h2>{{ numbers }}</h2>
  </div>
</template>

<script>
import { EventBus } from "../event-bus.js";
export default {
  name: "NumberDisplay",
  data() {
    return {
      numbers: [1, 2, 3]
    };
  },
  created() {
    EventBus.$on("number-added", number => {
      this.numbers.push(number);
    });
  }
};
</script>
NumberSubmit.vue
NumberDisplay.vue
event-bus.js

 EventBus

Parent Component

Child Component #1

Child Component #2

EventBus

EventBus

 EventBus

Parent

Component #1

Component #2

Component #3

Component #2.1

Component #1.2

Component #1.1

😭

 EventBus

 EventBus

An Event Bus (or using this.$root) is not the recommended way of managing application wide state.

 Simple State Management

State Management is the management of application-level data

 Simple State Management

Parent Component

Child Component

What we've seen so far...

Could we have all child components notify the parent to make changes, without using events?

 Simple State Management

Instead of having everything centralized in the parent component - we can externalize this to a store

store = {
  state: {
    numbers: [1, 2, 3]
  },



}
  addNumber(number) {
    this.state.numbers.push(number);
  }

 Simple State Management

export const store = {
  state: {
    numbers: [1, 2, 3]
  },
  addNumber(newNumber) {
    this.state.numbers.push(newNumber);
  }
};
<template>
  <div>
    <h2>{{ storeState.numbers }}</h2>
  </div>
</template>

<script>
import { store } from "../store.js";
export default {
  name: "NumberDisplay",
  data() {
    return {
      storeState: store.state
    };
  }
};
</script>
<template>
  <div>
    <input v-model="number" type="number" />
    <button @click="addNumber">
     Add new number
    </button>
  </div>
</template>

<script>
import { store } from "../store.js";
export default {
  name: "NumberSubmit",
  data() {
    return {
      number: 0
    };
  },
  methods: {
    addNumber() {
      store.addNumber(Number(this.number));
    }
  }
};
</script>
store.js
NumberDisplay.vue
NumberSubmit.vue

 Simple State Management

Store

NumberSubmit

NumberDisplay

Dispatches store.addNumber()

Triggers updates to view

 Simple State Management

Store state is mutated directly.

store = {
  state: {
    numbers: [1, 2, 3]
  },
  addNumber(number) {
    this.state.numbers.push(number);
  }
}

 Simple State Management

We want to keep state mutations within the store.

store = {
  state: {
    numbers: [1, 2, 3]
  },
  addNumber(number) {
    this.state.numbers.push(number);
  },
  removeLastNumber() {
    this.state.numbers.pop();
  },
  reverseNumbers() {
    this.state.numbers.reverse();
  }
}

 Simple State Management

Parent

Component #1

Component #2

Component #3

Component #2.1

Component #1.2

Component #1.1

😭

Store

 Simple State Management

export const store = {
  state: {
    numbers: [1, 2, 3]
  },
  addNumber(newNumber) {
    this.state.numbers.push(newNumber);
  }
};
<template>
  <div>
    <h2>{{ storeState.numbers }}</h2>
  </div>
</template>

<script>
import { store } from "../store.js";
export default {
  name: "NumberDisplay",
  data() {
    return {
      storeState: store.state
    };
  }
};
</script>
<template>
  <div>
    <input v-model="numberInput" type="number" />
    <button @click="addNumber">
     Add new number
    </button>
  </div>
</template>

<script>
import { store } from "../store.js";
export default {
  name: "NumberSubmit",
  data() {
    return {
      number: 0
    };
  },
  methods: {
    addNumber() {
      store.addNumber(Number(this.number));
    }
  }
};
</script>
store.js
NumberDisplay.vue
NumberSubmit.vue

Action

Mutation

Getter

 Simple State Management

This brings us very close to the Flux-like architecture of managing client-side state.

 Vuex

Vuex is a flux-like, state management library built solely for use with Vue

 Vuex

Dispatcher

Action

Store

View

 Vuex

npm install vuex

 Vuex

Vue.use(Vuex);
import Vue from 'vue';
import Vuex from 'vuex';

 Vuex

const store = new Vuex.Store({
  state,
  mutations,
  actions,
  getters
});
new Vue({
  el: '#app',
  store,
  render: h => h(App)
});

 Vuex

const state = {
  numbers: [1, 2, 3]
}

 Vuex

const mutations = {
  ADD_NUMBER(state, payload) {
    state.numbers.push(payload);
  }
}
const mutations = {
  ADD_NUMBER(state, payload) {
    state.numbers.push(payload);
  }
}
state
payload
const mutations = {
  ADD_NUMBER(state, payload) {
    state.numbers.push(payload);
  }
}

 Vuex

const actions = {
  addNumber(context, number) {
    context.commit('ADD_NUMBER', number);
  }
}
const actions = {
  addNumber(context, number) {
    context.commit('ADD_NUMBER', number);
  }
}
context
context.state
context.getters
context.commit
const actions = {
  addNumber(context, number) {
    context.commit('ADD_NUMBER', number);
  }
}

 Vuex

const getters = {
  getNumbers(state) {
    return state.numbers;
  }
}
state
const getters = {
  getNumbers(state) {
    return state.numbers;
  }
}
const getters = {
  getNumbers(state) {
    return state.numbers;
  }
}

 Vuex

With a Vuex Store; Components often do one of two things:

GET State Data

Dispatch actions

GETTERS
STATE

 Vuex

In Components, store can be accessed with:

this.$store
this.$store.state
this.$store.dispatch('nameOfAction')
this.$store.getters
this.$store.commit('nameOfMutation')

 Vuex

<template>
  <div>
    <h2>{{ getNumbers }}</h2>
  </div>
</template>

<script>
export default {
  name: "NumberDisplay",
  computed: {
    getNumbers() {
      return this.$store.getters.getNumbers;
    }
  }
};
</script>
<template>
  <div>
    <input v-model="number" type="number" />
    <button @click="addNumber">
     Add new number
    </button>
  </div>
</template>

<script>
export default {
  name: "NumberSubmit",
  data() {
    return {
      number: 0
    };
  },
  methods: {
    addNumber() {
      this.$store.dispatch("addNumber", Number(this.number));
    }
  }
};
</script>
NumberDisplay.vue
NumberSubmit.vue

 Vuex

const state = {
  numbers: [1, 2, 3]
}

 Vuex

methods: {
  addNumber() {
    this.$store.dispatch('addNumber', Number(this.number));
  }
}
NumberSubmit.vue

 Vuex

store.js
const actions = {
  addNumber(context, number) {
    context.commit('ADD_NUMBER', number);
  }
}
5

 Vuex

store.js
const mutations = {
  ADD_NUMBER(state, payload) {
    state.numbers.push(payload);
  }
}
5
[1,2,3]

 Vuex

 Vuex

Vuex > simple state management

1. By introducing explicitly defined getters, mutations, and actions

2. Integrating with Vue devtools for time travel debugging

 Vuex

What are ways we can scale up a Vuex store?

 Vuex - MapState, MapGetters, MapActions

computed: {
  numbers() {
    return this.$store.state.numbers
  }
}

State

computed: {
  numbers() {
    return this.$store.getters.numbers
  }
}

Getters

Actions

methods: {
  addNumber() {
    this.$store.dispatch('addNumber', Number(this.number));
  }
}

Without Helpers

 Vuex - MapState, MapGetters, MapActions

computed: mapState({
 'numbers'
})

State

Getters

Actions

methods: mapActions({
 'addNumber'
})

With Helpers

computed: mapGetters({
 'numbers'
})

 Vuex - MapState, MapGetters, MapActions

computed: mapState({
 computedNumber: 'numbers'
})

State

Getters

Actions

methods: mapActions({
 methodAddNumber: 'addNumber'
})

With Helpers

computed: mapGetters({
 computedNumbers:'numbers'
})
app/
components/
cart/
product/
store/
index.js
🛒

 Vuex - Modules

const state = {
  // ...
}
const mutations = {
  // ...
}
const actions = {
  // ...
}
const getters = {
  // ...
}
export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters
});
store/index.js

 Vuex - Modules

 Vuex - Modules

Vuex Modules allows us to break a store into more manageable fragments

 Vuex - Modules

const cartModule = {
  state,
  mutations,
  actions,
  getters
}
const productModule = {
  state,
  mutations,
  actions,
  getters
}

 Vuex - Modules

export default new Vuex.Store({
  modules: {
    cartModule,
    productModule
  }
});

 Vuex - Modules

By default - actions, mutations, and getters are all registered under the global namespace

this.$store.state.cartModule
this.$store.state.productModule
this.$store.dispatch('name-of-any-action')
this.$store.getters['name-of-any-getter']

namespaced

Not namespaced

 Vuex - Modules

namespaced: true,
const cartModule = {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}
const cartModule = {





}
const productModule = {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}
namespaced: true,
const productModule = {
   
   
   
   
   
}

 Vuex - Modules

this.$store.state.cartModule

namespaced modules

this.$store.dispatch('cartModule/name-of-action')
this.$store.getters['cartModule/name-of-getter']
...mapGetters([
  'cartModule/name-of-getter'
]);
...mapActions([
  'cartModule/name-of-action'
]);

 Vuex - Modules

store/
index.js
cart/
index.js
product/
index.js
index.js

 Vuex - Modules

store/
cart/
index.js
index.js
actions.js
getters.js
mutations.js
product/
index.js
actions.js
getters.js
mutations.js

 Vuex - Modules

store

cart

product

state

mutations

actions

getters

state

mutations

actions

getters

 Vuex - Modules

store/
cart/
index.js
product/
user/
actions.js
getters.js
index.js
mutations.js

 Vuex - Modules

store/
cart/
index.js
product/
user/
shared/

 Vuex

EventBus | Simple Store | Vuex

What's the best approach?

 Vuex

EventBus

Pro:

Incredibly easy to set-up.

Con:

Unable to track changes as they occur.

Simple Store

Pro:

Relatively easy to establish.

Con:

State and possible state changes aren't explicitly defined.

Vuex

Pro:

The most robust way to manage application level state and integrates with Vue devtools.

Con:

Additional boilerpate and requires Flux-like understanding.

Avoid!

 Vuex

- State Management/Vuex Documentation

- Managing State in Vue.js

- The Importance of State Management in Vue: Spotlight Vue

 State Management and Vuex - Chapter Summary

Props can be used to pass data from parent components down to child components.

Custom Events allow child components to notify/communicate with parent components.

this.$emit('name-of-event', {});

An Event Bus can enable isolated components to subscribe and publish custom events between one another.

Event Bus is a new Vue instance that's made global:
const EventBus = new Vue();

Simple state management can be performed by creating a store pattern that involves sharing a data store between components.

Store manages application state AND methods responsible in mutating state

Vuex is the flux-like, state management library built solely for use with Vue

Introduces explicitly defined getters, mutations, and actions
Integrates with Vue Devtools for time-travel debugging

 State Management and Vuex - Chapter Exercise 💪!

 State Management and Vuex - Chapter Exercise 💪!

App

InputComponent

NoteCountComponent

 State Management and Vuex - Chapter Exercise 💪!

App

InputComponent

NoteCountComponent

 State Management and Vuex - Chapter Exercise 💪!

Custom Events

Have App contain notes array.

InputComponent can submit a custom event upwards to App, to manipulate App data array.

App can pass data array to NoteCountComponent.

Simple Store

Create a simple store.js file that contains state.notes array and possible mutation.

InputComponent can call a method that acts on store mutation (action).

NoteCountComponent proxies state.notes value to a local data value (getter).

Vuex

Create a Vuex store.js file that contains state, mutations, actions, and (maybe) getters.

InputComponent will dispatch an action.

NoteCountComponent will return a getter/or state value.

Solution 👀

Solution 👀

Solution 👀

🎉

Made with Slides.com