前言

Day05 也只有一個主題,那就是Vuex!跟昨天的元件不同~他要分兩篇rrrrr
開學之後沒什麼時間能夠寫長篇大論的文章了 இдஇ

廢話不多說趕緊進入主題!!


Vuex

之前在做Blog練習時,登入後的使用者和Token等資料我都存到localStorage裡,
但在發文、詳細文章或個人檔案……等頁面,都需向localStorage要資料,
醬子每個頁面都為了那個狀態寫重複的code,實在太累了吧。
(ps. 大多時間都是做個人練習啦,所以才存在這><)

這時Vuex就是好幫手了!用最最最簡單的方式去理解他的話,
他就像一個倉庫(官方也這麼翻譯XD),負責管理每個元件共用的狀態。
需要的時候向他拿取狀態,也可以進行更改。

當然事情沒那麼簡單只是我懶惰而已

💡 如果專案非大型結構,基本上event bus、cookie或session等技術就夠用了!


Vuex的架構流程,圖源自官網

簡單粗暴的介紹一下:

  1. 一開始有初始state渲染到元件上。
  2. 元件Dispatch actions,像是按了登入按鈕 → call API → 等待response
  3. action commit 給mutations資料
  4. mutations 中改變state並回傳。

⚠️ mutation只能執行同步事件!絕對不可以用錯😥

如果對前幾天的內容有些概念,那麼大概可以這麼理解:

比較 資料 計算 事件 事件
Vuex state getter action mutation
Vue data computed methods methods



State

Vuex中的store就是倉庫本身,他負責儲存state(狀態)。
當Vue元件從store讀取狀態,元件會得到狀態的值;
如果store中的狀態更新,元件中的狀態也會更新。

store中不能直接更改狀態!!
必須透過commit mutation才能改變狀態。

從最簡單的讀取store中的狀態開始,我們先在store寫入狀態
💡 記得先引入Vuex喔!

const store = new Vuex.Store({
    state: {
        count: 0
    },
})

再來做一個元件,利用計算屬性取得state

const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count() {
            return this.$store.state.count
        }
    }
}

在根實例中註冊storecomponents選項,會注入到所有子元件中供他們使用。
子元件能用this.$store存取store中的狀態。

new Vue({
    el: '#app',
    store,
    components: {
        Counter
    },

})

View部分就放<counter/>

最後在畫面上就可以看到當前count的值了!

💡 this.$store放在computed是為了可以即時響應更新。


Getter

有時候多個元件可能會有相同的功能,以表單來說可能會需要轉換一些東西。
這裡我們舉例名字轉換大寫:

computed: {
  upperName () {
    return this.$store.state.name.toUpperCase()
  }
}


🤔 如果今天有n個元件都使用相同功能,想想看我們要怎麼辦呢QQ?

  1. 在每個元件中複製貼上n次計算屬性?
  2. 把他抽出來當共用函數一個一個導入使用?

Noooo! 雖然你想這麼做也不是不行,但如果程式跟阿嬤的裹腳布又臭又長
這樣做實在太沒效率了!而且後續維護要一個一個改嗎?太累了吧 ><

Vuex可以讓我們在store中定義getter(可以視為storecomputed
getter的返回值會根據他的依賴被緩存起來,且只有依賴值發生了改變才會被重新計算。
這聽起來是不是方便許多了?讓我們來試試看,一樣是把名字轉換:

const store = new Vuex.Store({
    state: {
        name: 'chihuahua is cuteeeee doge.'
    },
    getters: {
        // 當state有更新才重新計算屬性
       upperName(state) {
            return state.name.toUpperCase()
        }
    }
})

新增transCase元件:

const transCase = {
    template: `<div>{{ upperName }}</div>`,
    computed: {
        upperName() {
            return this.$store.getters.upperName;
        },
    }
}

看到畫面中name變成大寫就成功囉!!

我這個例子舉的不好,讓程式看起來變比較長惹...
不過因此清楚了getter的用法及使用時機(๑•́ ₃ •̀๑)


Mutations

上面我們有說到,在store中不能直接更改狀態
必須透過commit mutations才能改變狀態。

⚠️ 要更改state只能透過mutations!!不是action

每個mutations都有一個事件類型(type)和一個回調函數(handler)。
這個回調函數就是實際進行狀態更改的地方,會接受state作為第一個參數。

以計數器為例,我要做一個按下+1的按鈕,畫面上count就會跟著+1
先把store寫好:

const store = new Vuex.Store({
    state: {
        count: 0,
    },

    mutations: {
        add(state) {
            state.count++
        }
    }
})

💡 記得是mutations,不要少加一個s!! who cares

下一步是創立我們的Counter:

const Counter = {
    template: `
        <div>
            count:{{ count }}
            <button class="btn btn-info mx-2" @click="add"> +1 </button>
        </div>
    `,

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

    methods: {
        add() {
            store.commit('add')
        }
    }
}

做好之後成果應該是這樣的:

以他們的關係來看,我是這樣理解的:

畫圖看比較快理解他們在做什麼 d(`・∀・)b

如果一次 +1 太少了,想要一次 +10 有沒有辦法?
當然可以!這邊跟function一樣後面可以帶參數進去:

store.commit('add',10)

記得mutations也要更改:

add(state,num) {
        state.count += num
    }


mutation必須是同步函數

⚠️ mutations絕對不可以非同步函數!!!!!
⚠️ mutations絕對不可以非同步函數!!!!!
⚠️ mutations絕對不可以非同步函數!!!!!

~ 超 ~ 級 ~ 重 ~ 要 ~ 很 ~ 重 ~ 要 ~

從架構圖看到mutations會與Devtools搭配記錄state的變化,這可以幫助我們debug,用同步函數以方便調試。

如果在mutations中如果用非同步會發生什麼事?

📢 請參考 Jacky大大的 vue & vuex 10 - 什麼是 vuex?

這篇文章非常好理解Vuex的流程!


Action

action的函數可以是同步或非同步的,但我們在上面再三的強調:
⚠️ 更改state只能透過mutations

action這個理解了好久QQ,果然只有實際操作才能真正懂... 😢😢😢
修改上面做的Counter按鈕元件:按下後 → 間隔一秒 → +10 → 更改state。

修改Counter元件的計算屬性和事件,要透過store.dispatch調用action

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

methods: {
    add() {
        // 以下請擇一使用!

        // 方法一:使用payload(傳入的參數)形式進行分發
        store.dispatch('addAsync', {payload: 10})


        // 方法二:使用物件形式進行分發
        store.dispatch({
          type: 'addAsync',
          payload: 10
            })
        }
    }

由於這裡 "隔一秒" 是 非同步事件(取名為addAsync),所以要在action中寫:

actions: {
    addAsync({commit}, payload) {
        setTimeout( () => {
            commit('add', payload)
        }, 1000)
    }
}

修改mutations

mutations: {
        add(state, { payload }) {
            console.log(`payload是:${payload}`)
            state.count += payload
            console.log(`state.count是${state.count}`)
        }
    },


詳解action

在學Vuex時對action比較不理解,函數內的參數是什麼?

actions: {
    addAsync({commit}, payload) {
        // 省略...
    }
}

ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。像是上面的{commit}
等等會解釋{commit}是什麼,在這之前先來聊聊解構!(這裡指提物件的)

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

↑ 因為物件屬性沒有次序,變數必須與屬性同名,才能取到正確的值。
記住,這個是縮寫!展開請參考第三個範例。

let {foo} = {bar: 'baz'};
foo // undefined

↑ 等號右邊的物件沒有foo屬性,變數foo取不到值,所以等於undefined

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

↑ foo是匹配的模式,baz才是變數。真正被賦值的是變數baz,而不是模式foo。

以上翻譯修改自阮一峰大大 ECMAScript 6 入門


知道這些之後我們再來看看剛才提到的{commit},他原本寫法應該是這樣的:

actions: {
    addAsync(context,payload) {
      context.commit("add",payload);
    }
  }

處理函數總是接受context作為第一個參數,payload作為第二個參數(可選)。

問題來了,context又是什麼東西呢?
他是個物件,包含了這些屬性:

{
  state,      //  `store.state`,若在模組中則為局部狀態
  rootState,  //  `store.state`,只存在於模組中
  commit,     //  `store.commit`
  dispatch,   //  `store.dispatch`
  getters,    //  `store.getters`
  rootGetters //  `store.getters`,只存在於模組中
}

哇~剛好上面講解的解構現在就可以拿來用了!

{commit} = context

是不是方便很多了~


小小心得

若觀念或內容有誤請不吝指教,謝謝您( ᐛ)パァ

複習了Vuex,把基本核心功能都牢記了!
今天醬子的進度算寫得頗多,已經開學了...課滿滿DER很難騰出時間來寫 o(>_<)o
Vue-router部分就先暫緩了。

明天會繼續寫Vuex的部分,還有好多東西沒說完QQ
像是mapState等和module,都還不是很熟的部分,
畢竟現在沒做項目也用不太到這些功能的說:(

快結束了,希望剩下的範例、vue-router和Axios能夠順利的寫完!
還剩2天,加油!(๑•̀ㅂ•́)و✧


📢 參考文章

#javascript #Vue #Web #前端







你可能感興趣的文章

DAY11:Disemvowel Trolls

DAY11:Disemvowel Trolls

Command Line 基本指令

Command Line 基本指令

FB v.s. IG: 簡單的AB testing(Paired T-test)

FB v.s. IG: 簡單的AB testing(Paired T-test)






留言討論