前言
Day05 也只有一個主題,那就是Vuex!跟昨天的元件不同~他要分兩篇rrrrr
開學之後沒什麼時間能夠寫長篇大論的文章了 இдஇ
廢話不多說趕緊進入主題!!
Vuex
之前在做Blog練習時,登入後的使用者和Token等資料我都存到localStorage
裡,
但在發文、詳細文章或個人檔案……等頁面,都需向localStorage
要資料,
醬子每個頁面都為了那個狀態寫重複的code,實在太累了吧。
(ps. 大多時間都是做個人練習啦,所以才存在這><)
這時Vuex
就是好幫手了!用最最最簡單的方式去理解他的話,
他就像一個倉庫(官方也這麼翻譯XD),負責管理每個元件共用的狀態。
需要的時候向他拿取狀態,也可以進行更改。
當然事情沒那麼簡單只是我懶惰而已
💡 如果專案非大型結構,基本上event bus、cookie或session等技術就夠用了!
Vuex的架構流程,圖源自官網
簡單粗暴的介紹一下:
- 一開始有初始
state
渲染到元件上。 - 元件Dispatch
actions
,像是按了登入按鈕 → call API → 等待response action
commit 給mutations
資料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
}
}
}
在根實例中註冊store
和components
選項,會注入到所有子元件中供他們使用。
子元件能用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?
- 在每個元件中複製貼上n次計算屬性?
- 把他抽出來當共用函數一個一個導入使用?
Noooo! 雖然你想這麼做也不是不行,但如果程式跟阿嬤的裹腳布又臭又長
這樣做實在太沒效率了!而且後續維護要一個一個改嗎?太累了吧 ><
Vuex可以讓我們在store
中定義getter
(可以視為store
的computed
)
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天,加油!(๑•̀ㅂ•́)و✧