5 Getting Started Chapter 2
Getting Started Chapter II
Welcome to the sequel of Getting Started, Chapter II - The Final problem. Alright, here we go. We'll be setting up Todo in the default route setup Homepage and an about page showing the purpose of the app. Let's take account of how many components needed for creating the app.
App.vue
:- A header component with title 'TodoList' and the Navbar,
Header
- A header component with title 'TodoList' and the Navbar,
Home.vue
:- A component which will contain all the pre-recorded tasks fetched from the vuex store,
Todos.vue
- A component template for each tasks,
TodoItem.vue
.
- A component template for each tasks,
- A component which have an input bar and Submit button to add new todo task,
AddTodo.vue
- A component which will contain all the pre-recorded tasks fetched from the vuex store,
About.vue
App├─ Home│ ├─ AddTodo│ └─ Todos│ └─ TodoItem└─ About
Let's start by setting up Vuex. Modify the store index.js
as follows:
/src/store/index.js:
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state:{todos:[{title:'Complete Vue.js Intro',completed:true},{title:'Hello Vue World',completed:true},{title:'Understand router',completed:true},{title:'Understand store',completed:false},{title:'Become a Vue.js pro',completed:false},]},mutations:{addTask: function(state,{title,completed}){state.todos = [...state.todos,{title,completed,id:state.id++}]},deleteTask: function(state,id){state.todos = state.todos.filter(todo=> todo.id !== id)},markDone: function(state,id) {const index = state.todos.findIndex(todo=>todo.id===id)state.todos[index].completed=!state.todos[index].completed}},actions:{addTodo: function( context /*An Obj having all props. of store*/ ,newTodo){context.commit('addTask',newTodo) //calling a mutation},deleteTodo: function( {commit} /*Taking only the commit prop. of context*/ , id){commit('deleteTask',id)},markComplete: function({commit},id){commit('markDone',id)}}})
There are 3 properties defined here for the vuex store
object. 5 default todos are defined inside the state property of vuex store
, along with an id variable, to ID the future todos. The other properties are actions
and mutations
which are functions that'll modify the state objects when called. We shouldn't directly mutate or change state objects, cause the change won't be observed. We call actions
by sending changes. The actions
then commit the changes by calling mutations
. An action
is called by the method store.dispatch('actionName',payload)
and mutation
is done by calling store.commit('mutationName',payload)
. Here 3 actions
are declared, one for adding todo, one fore deleting and one for marking a todo as completed or not. There're also 3 corresponding mutations
.
actions
will have two arguments, context
and payload
. context
is an object which is received from vuex
, not function call, and has all the properties of the vuex store
object. So you can access every actions, mutations and state with context object. If you look closely, in one of the methods of actions
, the first argument is context
, and in others, it's {commit}
. What's happening in the second situation is that it's taking only the commit
property of the context
object. payload
is the argument containing the data for mutation/change.
mutations
also have two arguments, one being payload
and the other being state
, which is the state
object of vuex store
. mutations
are the methods which actually modifies the state upon calling actions
.
You'll be wondering why we can't call commit
directly to mutate and avoid actions
, aren't you? Well, you CAN call commit directly. Then why actions
? The answer is simple. mutations
are meant to be synchronous transactions. The changes should be done synchronously, inorder to get the correct output. That's where actions
come into the action. actions
can handle asynchronous operations and keep mutations
synchronous.
If you want to learn more about vuex
, check this out.
Now let's modify our App.vue
.
/src/App.vue:
<template><div id="app"><Header /> <!-- Hey! A new component here.. Let's see what's this next. --><router-view/></div></template><script>import Header from './components/layout/Header';export default {name:"app",components: {Header}}</script><style>#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;}#nav {padding: 30px;}#nav a {font-weight: bold;}#nav a.router-link-exact-active {color: #42b983;}* {box-sizing: border-box;margin: 0;padding: 0;}.btn {display: inline-block;border: none;background: #555;color: #fff;padding: 7px 20px;cursor: pointer;}.btn:hover {background: #666;}</style>
/src/components/layout/Header.vue:
<template><header class="header"><h1>TodoList</h1><div id="nav"><router-link to="/">Home</router-link> |<router-link to="/about">About</router-link></div></header></template><script>export default {name: "Header"}</script><style scoped>.header {background: #333;color: #fff;text-align: center;padding: 10px;}.header a {color: #fff;text-decoration: none;}</style>
So, Header.vue
is imported inside App.vue
. There aren't many changes in there. Just some stylings applied to the previous Navbar, combined with a header and coupled it into a component just to show you that anything can be made a component and reused instead of boring Ctrl+c Ctrl+v. Vue CLI's hot reloading feature just refreshes and recompiles everytime your code changes on the disk(When you hit save). If your program is running, just go to the browser window and take a look at that beautiful Header. Beautiful isn't it? Okay, Let's edit the contents of Home.vue
page next.
/src/views/Home.vue:
<template><div class="home"><AddTodo v-on:add-todo="addTodo" /> //New one<Todos :todos="todos" @del-todo="deleteTodo" /> //Another</div></template><script>import Todos from '../components/Todos';import AddTodo from '../components/AddTodo';import { mapActions } from "vuex"; //Hmm..export default {name: 'Home',components: {Todos,AddTodo},methods:{ //functions used in a component are defined here....mapActions(['addTodo','deleteTodo'])//Another way of accessing store},computed:{todos: function(){return this.$store.state.todos //One way of accessing store},}}</script><style>* {box-sizing: border-box;margin: 0;padding: 0;}.home{padding-top: 15px;}body {font-family: Arial, Helvetica, sans-serif;line-height: 1.4;}.btn {display: inline-block;border: none;background: #555;color: #fff;padding: 7px 20px;cursor: pointer;}.btn:hover {background: #666;}</style>
The Homepage we planned has two things, an input bar and a submit button to add new task, and currently active todos. Our Home.vue
has two components exactly for these purposes. AddTodo
and Todos
. You can see v-bind
being used in Todos
to bind todos
from Home Component. Hmm.. there's another thing which is new, v-on:add-todo
and @del-todo
. The v-on
is the vue alternative for listening to DOM events and run some javaScript when they're triggered. Here, a function is called on both v-on
instances. The part coming after the colon :
will be the event v-on
listens to. For eg: v-on:click
will listen to the mouse click event. @del-Todo
is a shorter syntax for v-on:del-Todo
. Now, what's add-Todo and del-Todo? They are custom events. Ringing any bells? The child component communicates with parent component by triggering custom events. The parent component listens to that event where the child is embedded. That's what happening here. When a new todo is submitted in the AddTodo
component, it triggers the event add-Todo
. Similarly, When one of the todos are deleted in the Todos
component, it triggers del-todo
event. The parent, i.e., Home
component, adds and deletes todos whenever events are triggered. Simple as that. These events can take a payload
along with them, which contains the data to processed on event occurence.
Now, check out the script
part. The methods
property is an Object which contains the functions of the component. The computed
property is an object which holds some functions which returns some value after doing operations on a data.
What's the difference between computed properties and functions? Functions are invoked whenever it's called. But computed
properties are invoked/updated every time the data used in it changes. Let's make this explanation simple with an example:
<template>...<span>{{message.split('').reverse().join('')}}</span><!--Ugly isn't it?--><span>{{reversedMessage}}</span><!--Neat-->...</template><script>...computed:{reversedMessage(){return this.message.split('').reverse().join('')//reversing string}}...</script>
Check this out. That's what computed properties does. Also whenever the data message
changes, the computed property reversedMessage
updates. That makes computed properties really useful.
This part is a bit important. Inside the script
part, the properties of a Vue instance are accessed by using this
keyword. For eg: this.message = 'Hello'
. This is really important because you tend to forget to use this
. this
used inside a vue instance represents the vm
object which contains all the properties like data, methods, computed properties etc of that Vue instance and other properties like $parent
,$store
,$router
,$route
etc.
You can see a computed property todos
which returns this.$store.state.todos
. This todos
is passed to the child component Todos
as props. You see the vuex store
is accessed using the this.$store
here. This is one way of accessing store inside components. If you looks inside the methods
property, you can see ...mapGetters(['addTodo','deleteTodo'])
. Also the mapActions
being imported from vuex: import { mapActions } from 'vuex'
. What does ...mapActions()
do? It maps the actions
provided as argument of the method as properties of the component. I'll explain the case here.
methods:{...mapActions(['addTodo','deleteTodo'])}
The actions addTodo
and deleteTodo
will be defined as the methods(mapActions
is used inside methods) of the Home
component. So you can access the actions like you invoke a method of the component. This is another way of accessing the store. there's also mapState
,mapMutations
, and mapGetters
to access state
, mutations
and getters
of the store respectively. The method of usage is same. Let's see how the computed property todos can be rewritten in the second method.
From this:
todos: function(){return this.$store.state.todos}
To this:
import { mapActions , mapState } from 'vuex'...computed:{...mapState(['todos'])}
Remember, don't forget to import the map functions before you use them :wink:
Aw, crap that's lengthy. Let's head to part 2. See you there!