Nuxt.js Fundamental ตอนที่ 7 - การใช้งานร่วมกับ Vuex Store

Published on

เขียนวันที่ : Aug 23, 2020

LastModified on

(อัพเดท : Mar 19, 2022)

Discord

มาถึงเรื่องการเก็บข้อมูลลง Store โดยใช้ Vuex กันบ้างครับ ตัว Nuxt.js ก็รองรับการใช้งาน Vuex เลย (default จะ disable) หากเราต้องการใช้งาน ก็แค่เพิ่มไฟล์ index.js ใน folder store ก็ได้แล้ว

โดย Vuex รองรับทั้งแบบ 1. ธรรมดา และ 2. แบบ modules โดยหลักการทำงานของ Nuxt Vuex คือ

  1. Nuxt.js จะมองหาไฟล์ใน store ถ้ามี
  2. จะทำการ import 'vuex' ให้อัตโนมัติ
  3. เพิ่ม store ลงใน Vue instance.

state ควรจะเป็น function นะครับ เพื่อหลีกเลี่ยงปัญหา share state กับ server

ตัวอย่างเช่นไฟล์ store/index.js

export const state = () => ({
  counter: 0
});

export const mutations = {
  increment(state) {
    state.counter++;
  }
};

เรามี state เก็บค่า counter และมี mutation ชื่อ increment สำหรับเพิ่มค่า counter ใน state

ตัวอย่าง vuex เวลาส่องใน Vue DevTools (ปกติใครเขียน Vue.js ก็ควรมีติด browser ไว้นะครับ)

Vuex in DevTools

สำหรับใครยังไม่เคยใช้ Vue DevTools สามารถดาวน์โหลดได้จาก Vue Devtools

Modules Mode

ปกติตัว Nuxt จะทำเป็น modules mode ให้เราอยู่แล้ว ตามชื่อไฟล์ หรือ folder เป็น namespaced ให้เรา เช่น เราสร้างไฟล์ store/todos.js

store/todos.js
export const state = () => ({
  list: []
});

export const mutations = {
  add(state, text) {
    state.list.push({
      text,
      done: false
    });
  },
  remove(state, { todo }) {
    state.list.splice(state.list.indexOf(todo), 1);
  },
  toggle(state, todo) {
    todo.done = !todo.done;
  }
};

จะได้ store เทียบเท่ากับการใช้ Vuex ปกติคือ

new Vuex.Store({
  state: () => ({
    counter: 0
  }),
  mutations: {
    increment(state) {
      state.counter++;
    }
  },
  modules: {
    todos: {
      namespaced: true,
      state: () => ({
        list: []
      }),
      mutations: {
        add(state, { text }) {
          state.list.push({
            text,
            done: false,
            id: Date.now()
          });
        },
        remove(state, { todo }) {
          state.list = state.list.filter((item) => item.id !== todo.id);
        },
        toggle(state, { todo }) {
          todo.done = !todo.done;
        }
      }
    }
  }
});

และไฟล์ pages/todos.vue แบบนี้

pages/todos.vue
<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <input :checked="todo.done" @change="toggle(todo)" type="checkbox" />
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">remove</button>
    </li>
    <li>
      <input @keyup.enter="addTodo" placeholder="What needs to be done?" />
    </li>
  </ul>
</template>

<script>
  import { mapMutations } from 'vuex'

  export default {
    computed: {
      todos() {
        return this.$store.state.todos.list
      }
    },
    methods: {
      addTodo(e) {
        this.$store.commit('todos/add', e.target.value)
        e.target.value = ''
      },
      ...mapMutations({
        toggle: 'todos/toggle'
      }),
      removeTodo(todo) {
        this.$store.commit('todos/remove', todo)
      }
    }
  }
</script>

<style>
  .done {
    text-decoration: line-through;
  }
</style>

Source Code จาก Nuxt Vuex Store

  • จากตัวอย่างโค๊ด จะเห็นว่า เราสามารถใช้ Vuex ได้แบบปกติใน Nuxt.js เลยไม่ต้องทำอะไรเลย เพียงแค่สร้างไฟล์ใน store ปกติครับ
  • ตัวอย่าง mapMutations คือการ map function ใน mutations ให้เราเรียกผ่าน this.toggle(value) ได้เลย

และสำหรับใครที่อยากรู้เรื่อง Vuex เพิ่มเติม แนะนำให้อ่าน Vuex Doc เพิ่มเติมนะครับ

ตัวอย่างการสร้างหลายๆ Modules

ตัวอย่างเช่น ผมมี vues 3 modules คือ

  1. auth
  2. users
  3. posts

ทีนี้ ผมจะแยก เป็น folder และใน folder ผมก็จะแยก state.js, mutations.js, actions.js และมี index.js เพื่อรวมเป็น module อีกที แบบนี้

store/
  -- auth/
    -- mutations.js
    -- actions.js
    -- state.js
    -- getters.js
  -- users/
    -- mutations.js
    -- actions.js
    -- state.js
  -- posts/
    -- mutations.js
    -- actions.js
    -- state.js

และตัวอย่างแต่ละไฟล์ ประมาณนี้ครับ เช่น

ไฟล์ store/auth/state.js

store/auto/state.js
export default () => ({
  isAuthenticated: false,
  currentUser: null
});

ไฟล์ store/auth/mutations.js

store/auth/mutations.js
export default {
  setCurrentUser(state, payload) {
    state.currentUser = payload;
    state.isAuthenticated = true;
  }
};

ไฟล์ store/auth/actions.js

store/auth/actions.js
export default {
  login({ commit }) {
    // POST /auth/login , axios.post(url, payload).then()
    // if success then set response.data
    commit('setCurrentUser', response.data);
  }
};

หรืออย่างไฟล์ store/auth/getters.js หากเราอยากใช้ getters

store/auth/getters.js
export default {
  isAuthenticated: (state) => state.isAuthenticated
};

และเวลาเราใช้งาน แต่ละ modules ในหน้า pages เราใช้ mapActions และ mapGetters ก็จะได้หน้าตาประมาณนี้

<script>
import { mapActions, mapGetters } from 'vuex'

export default {
  computed: mapGetters({
    isAuthenticated: 'auth/isAuthenticated'
  }),
  methods: {
    ...mapActions({
      login: 'auth/login'
    })
  }
}
</script>

Hints & Questions?

เพื่อนๆ ลองนำ Vuex ไปประยุกต์กับเรื่องการ Fetch API จากตอนที่แล้วกันดูนะครับ โดย

  1. สร้าง state สำหรับเก็บข้อมูล photos ที่ได้จาก API
  2. เมื่อได้ข้อมูลก็ใช้ให้ actions หรือ mutation อัพเดทค่าใน state
  3. ใช้ mapState, mapActions ของ Vuex Helpers มาช่วย
Buy Me A Coffee
Discord