iT邦幫忙

0

[鼠年全馬] W38 - 使用Vuex管理資料狀態(上)


這週要來介紹一個很好用的套件 - Vuex

看到名稱應該馬上可以理解他就是Vue專案在使用的套件吧xD

有看過我之前的專案開發的文章的人,應該會發現我從來沒有用過 Vuex
我也是最近這兩週才開始接觸它的~

趁著記憶猶新時趕快把它寫起來~
如果有使用不當或是有誤的地方再麻煩多指教/images/emoticon/emoticon41.gif


#Vuex是什麼?

萬用起手式來了~
所謂的Vuex是什麼呢? 他可以做到哪些事呢?

官方是這麼介紹它的:

Vuex是一個專為Vue.js應用程序開發的狀態管理模式。
它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
Vuex也集成到Vue的官方調試工具devtools extension,提供了諸如零配置的time-travel調試、狀態快照導入導出等高級調試功能。

blablabla...
當時看完這段介紹之後我還是不知道Vuex是在做什麼xD

沒關係我們繼續往下看:

每一個Vuex應用的核心就是store(倉庫)。
“store”基本上就是一個容器,它包含著你的應用中大部分的狀態(state)。

到這邊應該稍微可以理解原來它的用途是「管理資料狀態的容器」


#為什麼需要Vuex?

原本的做法就可以在每個元件中存放資料了,那為什麼還要用Vuex呢?

通常我們的資料流向會是父傳子,上層傳至下層,保持數據單向流通
遇到需要從子層改變父層資料時可以使用 $emit() 呼叫父層方法進而改變數據
遇到兄弟同層之間的元件要互相影響時就會有困難了,我自己的做法是將資料放到父層再用父傳子的方式處理
但是不論父傳子還是兄弟互傳的做法都非常的麻煩且繁瑣

這時候Vuex就出現了!!

我們可以將需要用到傳遞的資料通通丟進Vuex管理
當元件需要使用資料時,可以使用 $store 輕鬆取得數據
且透過套件提供的 dispatch 方法,可以從任何元件去改變資料數據

那傳遞呢? 當然用不到囉~
透過Vuex我們可以把傳遞的方式取代掉,並且還是保持著資料數據單向流通


#Vuex內容

剛才有看到官方文件說「Vuex應用的核心就是store」
Store 就是存放資料狀態的容器

那這個容器內包含著哪些東西呢?

  • state: 存放資料狀態的物件,類似Vue元件中的 data
  • actions: 存放方法的物件,類似Vue元件中的 methods
    除了改變資料狀態以外的工作,都會在這邊處理
  • mutations: 負責做改變資料狀態的動作
  • getters: 取得資料狀態的方法,在取得之前也可以在這先做一些資料處理,類似Vue元件中的 computed

以上四個項目會是基本常使用到的,其他還有像是

  • modules: 模組化的Vuex

#Vuex數據流向


在數據流向的部分
前端元件(component)會透過 ditpatch 呼叫 actions 方法
actions 再透過提交(commit)的方式呼叫 mutations
mutations 再做更新 state 中的資料狀態的動作
state 更新後再將數據渲染到元件中

透過這個流程達到單向數據流的操作


#安裝Vuex

基本介紹之後,就先來安裝它吧~
這裡以使用 Vue Cli 專案為主來說明

使用npm安裝指令:

npm install vuex --save

官方還有提供其他方式如:

  • Yarn
yarn add vuex
  • script引用
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>

#建立基本的Store

安裝完成之後來建立一個最簡單的Store囉~

#Step 1

在專案下新增一個 /store 資料夾,資料夾內再新增一個 index.js 檔:

src
└── store
    └── index.js

#Step 2

開啟剛剛建立的 index.js 加入基本的程式碼:

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        msg: "Hello Vuex!!"
    },
});

Step 3

main.js 中引用:

import store from './store'

store 要加進vue喔!!

new Vue({
  ...,
  store,
}).$mount('#app')

#Step 4

上面步驟都完成之後可以來到元件中去抓看看 msg 囉~
在元件的 computed 中使用 $store.state 來取得資料數據,可以在 App.vue 中試試看是否能抓到並渲染在畫面上:

//App.vue
<template>
  <div>
    {{ myMsg }}
  </div>
</template>
<script>
export default {
  name: "App",
  computed: {
    myMsg() {
      return this.$store.state.msg;
    },
  },
};

基本的 Store 就建立完成囉!!


#建立TodoList

接著我會用不包含任何功能的TodoList來介紹後面的操作

#Step 1

像剛才 msg 一樣,我們在 Store 中建一個TodoList:

export default new Vuex.Store({
    state: {
        todolist: [
            { id: 1, todo: "交女朋友", done: false },
            { id: 2, todo: "財務自由", done: false },
            { id: 3, todo: "當碼農", done: true },
        ],
    },
});

#Step 2

App.vue 中抓值並渲染:

<template>
  <div>
    <ul>
      <li v-for="item in myTodo" :key="item.id">
        {{ item.todo }} - {{ item.done ? "已完成" : "未完成" }}
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  name: "App",
  computed: {
    myTodo() {
      return this.$store.state.todolist;
    },
  },
};
</script>

出來的畫面長這樣:
https://ithelp.ithome.com.tw/upload/images/20201123/20118686uvaNgUU6mi.jpg

#Step 3

前置作業完成了,再來我要加上click事件來切換 [已完成] 及 [未完成] 的狀態:

<li
  ...
  @click="todoDone(item.id, !item.done)"
>
  ...
</li>

對應的 todoDone() 方法:

methods: {
  //id: 要修改狀態的資料唯一碼
  //done: 要修改的值
  todoDone(id, done) {
  
  },
},

#Step 4

再來很重要(應該從頭到尾都很重要xD)
我們要使用 dispatch 來呼叫 actions
第一個參數設定要呼叫的 actions 名稱
第二個參數設定要傳入的值,有多個值的話就用物件的方式帶入

假設要呼叫的 actions 名稱也叫 todoDone,並且將 id & done 傳進去:

todoDone(id, done) {
  this.$store.dispatch("todoDone", { id, done });
},

#Step 5

回到 /store/index.js 加入 todoDone():

actions: {
    todoDone(context, { id, done }) {
    
    },
},

說明一下 actions 的參數結構,每個 actions 會有帶兩個參數: myActions(context, payload)

  • 第一個參數 context 是個與store實例具有相同方法和屬性的對象

Action函數接受一個與store實例具有相同方法和屬性的context對象,因此你可以調用context.commit提交一個mutation,或者通過context.state和context.getters來獲取state和getters。

我們在 context 中可以使用這些屬性:

{ 
    state, // 等同於`store.state`,若在模塊中則為局部狀態
    rootState, // 等同於`store.state`,只存在於模塊中
    commit, // 等同於`store.commit` 
    dispatch , // 等同於`store.dispatch` 
    getters, // 等同於`store.getters` 
    rootGetters // 等同於`store.getters`,只存在於模塊中 
}
  • 第二個參數 payload 是帶入的參數內容(可不帶)

#Step 6

接著我們可以在 actions 中使用 commit 將要修改的數據提交給 mutations 來更新:

todoDone(context, { id, done }) {
    context.commit('TODOLIST', { id, done });
},

第一個參數設定要提交給哪個 mutations 的名稱
第二個參數設定要傳入的值,有多個值的話就用物件的方式帶入

通常actions會有一些資料面的邏輯處理,例如呼叫ajax等等,邏輯處理結束後才會提交到mutations
但範例中因為沒有這層,所以看起來像是多做了一個沒有用的動作

#Step 7

接著可以加入 mutations:

mutations: {
    TODOLIST(state, { id, done }) {
    
    },
}

這邊使用了常量替代Mutation 事件類型:

使用常量替代mutation 事件類型在各種Flux 實現中是很常見的模式。這樣可以使linter 之類的工具發揮作用,同時把這些常量放在單獨的文件中可以讓你的代碼合作者對整個app 包含的mutation 一目了然
用不用常量取決於你——在需要多人協作的大型項目中,這會很有幫助。但如果你不喜歡,你完全可以不這樣做。

說明一下 mutations 的參數結構,每個 mutations 會有帶兩個參數: myMutations(state, payload)

  • 第一個參數 state 就是指存放資料狀態的 state
  • 第二個參數 payload 是帶入的參數內容(可不帶)

#Step 8

最後就是更新資料狀態
先抓到 state 中的 todolist:

let todolist = state.todolist;

使用陣列的 find() 方法抓到要改的那筆:

let todo = todolist.find((item) => {
    return item.id === id;
});

更新 done 值:

todo.done = done;

整段會是這樣:

TODOLIST(state, { id, done }) {
    let todolist = state.todolist;
    let todo = todolist.find((item) => {
        return item.id === id;
    });
    todo.done = done;
},

也可以合併成這樣:

TODOLIST(state, { id, done }) {
    state.todolist.find((item) => {
        return item.id === id
    }).done = done;
},

#結果

到這裡就完成更新資料狀態囉!!
來看看結果吧~
gif已死QQ


先到這囉~
下週繼續來探討模組化以及其他的功能!!
/images/emoticon/emoticon29.gif/images/emoticon/emoticon29.gif/images/emoticon/emoticon29.gif


尚未有邦友留言

立即登入留言