Giới thiệu

Hiện nay, Vuejs là một trong những framework JavaScript tốt nhất và nhiều người cho rằng Vue sẽ dần thay thế cho Angular và React trong tương lai. Xét vòng đời của mình, Vuejs không mới hơn hay phổ biến hơn so với những frameworks khác nhưng vẫn sở hữu những yếu tố tạo nên sự khác biệt.

FluxRedux là 2 cái tên không xa lạ gì với cộng đồng React, chúng được sử dụng trong hầu hết các app JS. Và khi nhắc đến Vuejs người ta nghĩ ngay đến Vuex. Theo dõi bài viết và cùng nhau trả lời các câu hỏi dưới đây nhé ❤

Mục Lục

  • Vuex là gì?
  • Khi nào nên sử dụng vuex?
  • Ví dụ đơn giản với vuex

Vuex là gì?

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue's official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / import.

Chỉ cần hiểu rằng: "Vuex là thư viện giúp quản lý trạng thái các component trong Vue.js, nó là nơi lưu trữ tập trung cho tất cả các component trong một ứng dụng, với nguyên tắc trạng thái chỉ có thể được thay đổi theo kiểu có thể dự đoán."

Vuex hoạt động theo mô hình "Luồng dữ liệu một chiều" với các thành phần sau:

  • State: Trạng thái, là nơi khởi nguồn để thực hiện ứng dụng.
  • View: Khung nhìn, là các khai báo ánh xạ với trạng thái.
  • Action: Hành động, là những cách thức làm trạng thái thay đổi phản ứng lại các nhập liệu của người dùng từ View.

Vuex nhìn thấy tại sao không đưa các trạng thái được chia sẻ của các component ra và quản lý chúng trong một bộ máy toàn cục, và đó chính là lý do cho sự ra đời của Vuex. Trong đó, các component trở thành các view và các component có thể truy xuất trạng thái hoặc trigger các hành động. Với cách thức này, mã nguồn có cấu trúc và dễ dàng duy trì.

Khi nào nên sử dụng vuex?

Vuex giúp cho việc quản lý dự án của chúng ta trở nên hiệu quả. Nó cân bằng giữa tốc độ và hiệu năng của dự án. Nếu chưa phân tích kỹ yêu cầu của dự án mà lao ngay vào code vuex, có lẽ chúng ta sẽ cảm thấy code vuex khá là dài dòng, phức tạp và đương nhiên không mang lại hiệu quả gì. Khi có ý định code một SPA (Single-Page App) tầm trung đến lớn, vuex có lẽ là sự lựa chọn hoàn toàn sáng suốt cho chúng ta. Chả cần phải chần chừ suy nghĩ, vuex là lựa chọn tốt nhất rồi đó =))

Ví dụ đơn giản với vuex

Link github: https://github.com/vanquynguyen/my-task-vuex

Link demo: https://vanquynguyen.github.io/my-task-vuex/

Và chỉ còn vài ngày nữa là đến tết nguyên đán Mậu Tuất 2018 rồi, sau một năm làm việc vất vả, chắc ai cũng bận rộn chuẩn bị mọi thứ, chuẩn bị đủ đầy để đón tết. Chắc chắn có rất nhiều việc để làm nên có lẽ vì thể chúng ta không thể nhớ hết những việc cần làm. Tự tạo tasks cho mình để không quên việc nào nhé.

Bắt tay vào code thôi =))

Kiểm tra cài đặt nodejs và npm, nếu chưa có truy cập https://nodejs.org/en/

$ nodejs -v
v9.4.0
$ npm -v
5.6.0

Cài đặt VueCLI:

$ npm install -g vue-cli

Khởi tạo project:

$ vue init webpack my-task-vuex

Cài đặt package vuex:

$ npm install --save vuex

Oke vậy là xong phần khởi tạo :like

Vuex có 5 Core Concepts:

  • State
  • Getters
  • Mutations
  • Actions
  • Modules

Ví dụ này mình sẽ chỉ sử dụng 4 Core Concepts là State, Getters, Mutations và Actions.

Trong src tạo folder có tên store

+   |- /store
+      |- store.js
+      |- getters.js
+      |- mutations.js
+      |- actions.js

Xây dựng store.js theo cấu trúc:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import getters from './getters'
import mutations from './mutations'
import actions from './actions'

export default new Vuex.Store({
    state: {
        tasks: [],
        newTask: ''
    },
    getters,
    mutations,
    actions
})

Nên tách getters, mutations, actions thành file js riêng để dễ quản lý

getters.js

export default {
    newTask: state => state.newTask,
    tasks: state => state.tasks.filter((task) => {return !task.completed}),
    completedTask: state => state.tasks.filter((task) => {return task.completed})
}

mutations.js

export default {
    getTask(state, task) {
        state.newTask = task
    },
    addTask(state){
        state.tasks.push({
            body: state.newTask,
            completed: false
        })
    },
    editTask(state, task) {
        var tasks = state.tasks
        tasks.splice(tasks.indexOf(task), 1)
        state.tasks = tasks
        state.newTask = task.body
    },
    removeTask(state, task) 
        var tasks = state.tasks
        tasks.splice(tasks.indexOf(task), 1)
        
    },
    completeTask(state, task) {
        task.completed = !task.completed
    },
    clearTask(state) {
        state.newTask = ''
    }
}

actions.js

export default {
    getTask({commit}, task) {
        commit('getTask', task)
    },
    addTask({commit}) {
        commit('addTask')
    },
    editTask({commit}, task) {
        commit('editTask', task)
    },
    removeTask({commit}, task) {
        commit('removeTask', task)
    },
    completeTask({commit}, task) {
        commit('completeTask', task)
    },
    clearTask({commit}) {
        commit('clearTask')
    }
}

Đừng quên import store trong file main.js

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

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

Tương tự trong src tạo folder có tên components

+   |- /components
+      |- CompletedTask.vue
+      |- GetTask.vue
+      |- ListTask.vue

GetTask.vue

<template>
    <div id="get-tasks">
        <form @submit.prevent="addTask">
            <input 
                class="form-control"
                :value="newTask"
                @change="getTask"
                placeholder="I need to...">
            <br>
            <button type="submit" class="btn btn-primary"><i class="fa fa-plus"></i> New Task</button>
        </form> 
    </div>
</template>

<script>
    import Vuex from 'vuex'
   
    export default{
        methods: {
            getTask(e) {
                this.$store.dispatch('getTask', e.target.value)
            },
            addTask(e) {
                e.preventDefault();
                this.$store.dispatch('addTask')
                this.$store.dispatch('clearTask')
            }
        },
        computed: {
          newTask() {
            	return this.$store.getters.newTask
            }
        }
    }
</script>

ListTask.vue

<template>
    <div id="list-tasks">
        <div class="panel panel-default">
            <div class="panel-heading my-task">
                  <h3 class="panel-title">My Tasks<span class="task-length" v-if="tasks.length > 0">{{tasks.length}}</span></h3>
            </div>
            <div class="panel-body">
                <ul class="list-group" 
                    name="custom-classes-transition"
                    enter-active-class="animated tada"
                    leave-active-class="animated bounceOutRight"
                    is="transition-group"
                >
                    <li class="list-group-item" v-for="(task, index) in tasks" v-bind:key="index">
                        <img src="http://lazi.vn/files/large/5a64450874df215" alt="" width="53">
                        {{task.body}}
                        <div class="btn-group">
                            <button type="button" @click="edit(task)" class="btn btn-default btn-sm" href="#edit" role="button" data-toggle="modal">
                            <span class="glyphicon glyphicon-edit"></span> Edit
                            </button>
                            <button type="button" @click="complete(task)" class="btn btn-success btn-sm">
                            <span class="glyphicon glyphicon-ok-circle"></span> Complete
                            </button>
                            <button type="button" @click="remove(task)" class="btn btn-danger btn-sm">
                            <span class="glyphicon glyphicon-remove-circle"></span> Remove
                            </button>
                        </div>
                    </li>
                </ul>
            </div>
        </div>    
    </div>
</template>
<script>
    export default{
        methods: {
            edit(task) {
                this.$store.dispatch('editTask', task)
            },
            complete(task) {
                this.$store.dispatch('completeTask', task)
            },
            remove(task) {
                this.$store.dispatch('removeTask', task)
            }
        },
        computed: {
            tasks() {
                return this.$store.getters.tasks
            },
            newTask() {
            	return this.$store.getters.newTask
            }
        }
    }
</script>
<style>
    .btn-group{
        float: right;
    }
</style>

CompletedTask.vue

<template>
    <div id="completed-tasks">
        <div class="panel panel-default">
            <div class="panel-heading my-completed">
                <h3 class="panel-title">Completed<span class="completed-length" v-if="completed.length > 0">{{completed.length}}</span></h3>
            </div>
            <div class="panel-body">
                <ul class="list-group"
                name="slide-fade"
                is="transition-group"
                >
                    <li class="list-group-item complete-list" v-for="(task, index) in completed" v-bind:key="index">
                        <img src="http://lazi.vn/files/large/5a64450874df215" alt="" width="53">
                        {{task.body}}
                        <button type="button" @click="remove(task)" class="btn btn-danger btn-sm remove-completed">
                        <span class="glyphicon glyphicon-remove-circle"></span> Remove
                        </button>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
    export default{
        methods: {
            remove(task){
                this.$store.dispatch('removeTask', task)
            }
        },
        computed: {
            completed(){
                return this.$store.getters.completedTask
            }
        }
    }
</script>

Import các components vào App.vue cho ứng dụng:

<template>
    <div id="app" class="container">
        <img src="http://lazi.vn/files/large/5a64450874df215" alt="" width="100"><span class="logo-title">Tết Tết Tết đến rồi!!!</span>
        <br>
        <div class="row">
            <div class="col-md-6"><ListTask></ListTask></div>
            <div class="col-md-6"><CompletedTask></CompletedTask></div>
        </div>
        <GetTask></GetTask>
    </div>
</template>

<script>
    import GetTask from './components/GetTask.vue'
    import ListTask from './components/ListTask.vue'
    import CompletedTask from './components/CompletedTask.vue'

    export default {
        components: {
            GetTask,
            ListTask,
            CompletedTask
        }
    
    }
</script>

<style>
  body {
    font-family: Helvetica, sans-serif;
  }

  .logo-title {
    font-weight: bold;
  }

  .my-task {
    color: #f4f7f9!important;
    background-color: #656c7a!important;
  }
  
  .my-completed {
    color: #f4f7f9!important;
    background-color: #656c7a!important;
  }
  .task-length {
    float: right!important;
  }

  .completed-length {
    float: right!important;
  }

  .list-group-item {
    background-color: wheat;
  }

  .complete-list {
    background-color: #26bf0a!important;
  }

  .remove-completed {
    float: right!important;
  }

  .slide-fade-enter-active {
    transition: all .3s ease;
  }

  .slide-fade-leave-active {
    transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
  }
  
  .slide-fade-enter, .slide-fade-leave-to
  /* .slide-fade-leave-active below version 2.1.8 */ {
    transform: translateX(10px);
    opacity: 0;
  }
</style>

Thêm chút link cdn trong index.html =))

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>My Task</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" />
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <!-- jQuery library -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <!-- Latest compiled JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

Done! Mở terminal ngay và luôn

$ npm run dev

và xem kết quả

Đừng quên expect để kiểm tra nhé

Lời kết

Hy vọng các bạn đã có một ứng dụng hữu ích cho tết năm nay. Qua bài viết chắc rằng chúng ta cũng có thêm những hiểu viết về Vuex và áp dụng hiệu quả cho các dự án của mình.Theo dõi Theo dõi series 2018 - Cùng nhau học VueJS còn nhiều điều lý thú ở phần sau đấy 😜😜😜