DAY28 | 跟 Vue.js 認識的30天 - Vue 套餐之圖書館 Vuex (中)

上一篇筆記寫到如何安裝,接下來就來開始使用 Vuex 做資料管理。
之後的紀錄也都是用 CDN 做安裝喔!

開始第一個 Vuex

1
2
<div id="vm"></div>
<script src="https://unpkg.com/vuex@3.6.2/dist/vuex.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const store = new Vuex.Store({
state: {
// 資料
// 類似 Vue 中 data 的作用
},
getters: {
// 對 state 中某個屬性進行一些邏輯操作
// 類似 Vue 中 computed 的作用
},
mutations: {
// 更改 state 中屬性的值
// 同步函數
// 類似 Vue 中 methods 的作用
},
actions: {
// 無法直接更改 state 中屬性的值,必須透過調用 mutation 才可進行更改
// 異步函數
// 類似 Vue 中 methods 的作用
}
})

const vm = new Vue({
el: '#vm',
store,
})

state 作用及使用方法

state 類似於 Vue 中 data 的作用,就是用來儲存數據的地方,所以這些在多個模組中都會使用到的資料,就放在 state 裡,以便各模組取用。

用法

創建 Vuex 後,將資料放入 state 中。

1
2
3
4
5
6
7
8
9
const store = new Vuex.Store({
state: {
user: {
firstName: 'Celeste',
lastName: 'KUO',
gender: 'female'
}
}
})

在 Vue 中註冊 store,讓 Vue 的所有模組都可以透過 this.$state 調用 store

1
2
3
4
5
const vm = new Vue({
el: 'vm',
// 在 Vue 實例中註冊 store ,並且該 store 會被掛載到 Vue 實例的 $store 中
store,
})

透過在模組中 computed 中取得 Vuex store 中的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const vm = new Vue({
el: 'vm',
store,
computed: {
vmUser(){
// store會被掛載到 Vue 實例(this)的 $store 中
return this.$store.state.user.firstName
}
},
})

// store.state.user.firstName 發生變化時,是否會作用到 Vue 實例上?
setTimeout(()=>{
store.state.user.firstName = 'Mark'
},1000)

為什麼是使用 computed ? 因為 computed 的作用可以創建一種依賴關係,所以當 store.state.user.name 發生變化時,依賴於該值的 vmUser 也會發生變化。

getters 作用及使用方法

類似於 Vue 實例中 computed 的功能,透過在 Vuex getters 中先對 state 中某些屬性的值形成依賴,如果這些屬性值發生改變,那麼有依賴關係的 getters 的屬性也會發生變化。
如此一來,就不需要在每個模組中在對欲使用的 state 寫下邏輯操作了,使用 Vuex getters 只需要寫 1 次邏輯操作就可供多個模組使用。

在 Vuex getters 中對 state 某些值形成依賴。
須注意 getters 的每個方法要固定將 state 作為其第一參數。

1
2
3
4
5
6
7
8
9
10
11
12
const store = new Vuex.Store({
state: {
user: {
firstName: 'Celeste',
lastName: 'KUO',
gender: 'female'
}
},
getters:{
fullName: state => `${state.user.firstName} ${state.user.lastName}`
}
})

透過在模組中 computed 中取得 Vuex getters 中的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const vm = new Vue({
el: "#vm",
store,
computed: {
vmUser(){
// store會被掛載到 Vue 實例的 $store 中
return this.$store.state.user.firstName
},
vmFullName(){
// 透過 getters 取值
return this.$store.getters.fullName
}
},
});

// store.state.user.firstName 發生變化時, Vuex 實例中的 getters 是否會發生變化並作用到 Vue 實例上?
setTimeout(()=>{
store.state.user.firstName = 'Mark'
},1000)

Vuex getters 的每個方法也可將 getters 作為其第二參數,並可透過 getters 可與其他的 getters 方法的返回值建立依賴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "shopping", done: true },
{ id: 2, text: "washing", done: false },
{ id: 3, text: "walking", done: false }
]
},
getters: {
// 取得未完成的 todo 項目
undones: (state) => state.todos.filter((item) => !item.done),
// 透過第二參數 getters 取得 getters.undones 的返回值
// 並與 getters.undones 建立依賴
// 因此當 state.todos 發生變化,undones 會跟著發生變化,undonesLen也會跟著 undones發生改變
undonesLen: (state, getters) => getters.undones.length
}
});

const vm = new Vue({
el: "#vm",
store,
computed: {
undones() {
return this.$store.getters.undones;
},
undonesLen() {
return this.$store.getters.undonesLen;
}
}
});

// store.state.todos 發生變化時, Vuex 實例中的以 getters 作為第二參數的 undonesLen 是否會跟著發生變化並作用到 Vue 實例上?
setTimeout(() => {
store.state.todos = [
...store.state.todos,
{ id: 4, text: "swimming", done: false }
];
}, 1000);

可以讓 getters 方法返回一個函數,之後即可調用該函數取得返回值(函數柯理化)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "shopping", done: true },
{ id: 2, text: "washing", done: false },
{ id: 3, text: "walking", done: false }
]
},
getters: {
// 使用柯理化函數
// 調用 getTodoById 時,返回的是一個函數(id) => state.todos.find(item => item.id === id)
getTodoById: (state) => (id) => state.todos.find(item => item.id === id)
}
});

const vm = new Vue({
el: "#vm",
store,
methods: {
getTodoById(id){
return this.$store.getters.getTodoById(id)
}
}
});

mutations 作用及使用方法

類似於 Vue 實例中 methods 的功能,並且更改 Vuex 的 store 中的值的唯一方法是提交(commit) mutation

mutation 中的方法內容必須是同步函數,以便我們透過 Vue devtools 去做觀察 state 的變化。

建立 mutations 中的方法,並且該方法將 state 作為第一參數。
建立的方法名稱為 type,而事件內容稱為 handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
user: {
firstName: 'Celeste',
lastName: 'KUO',
gender: 'female'
}
},
mutations: {
// type(state){handler}
changeUserName(state) {
state.user.firstName = 'Vita';
},
}
});

要觸發 mutations ,就必須透過 store.commit('type') 來調用該 mutations 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const vm = new Vue({
el: "#vm",
store,
computed: {
vmUser() {
// store 會被掛載到 Vue 實例的 $store 中
return this.$store.state.user.firstName;
},
vmFullName() {
return this.$store.getters.fullName;
}
},
methods: {
changeUserName(){
// store 會被掛載到 Vue 實例的 $store 中
// 所以 $store 中也有 commit 方法
this.$store.commit('changeUserName')
},
}
});

mutations 可接受其他數值(通常會是物件)做為第二參數(payload)喔!
透過 store.commit('type', 'payload') 調用該 mutations 時,將引數帶入 payload 的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const store = new Vuex.Store({
state: {
user: {
firstName: 'Celeste',
lastName: 'KUO',
gender: 'female'
}
},
mutations: {
// type(state, payload){handler}
changeUserName(state, payload) {
state.user.firstName = payload;
},
}
});

const vm = new Vue({
el: "#vm",
store,
computed: {
vmUser() {
// store 會被掛載到 Vue 實例的 $store 中
return this.$store.state.user.firstName;
},
vmFullName() {
return this.$store.getters.fullName;
}
},
methods: {
changeUserName(){
// store 會被掛載到 Vue 實例的 $store 中
// 所以 $store 中也有 commit 方法
this.$store.commit('changeUserName', 'Vita')
},
}
});

mutations 中必須特別注意的事項:

  • 提前在你的 store.state 中初始化好所有所需屬性(如同 Vue data 一樣)。

  • 透過展開運算符添加對象(個人偏好)。

  • 可以使用常量作為方法名(type)。

actions 作用及使用方法

類似於 Vue 實例中 methods 的功能,但不同於 mutations
mutations 的差別:

  • 透過提交(commit) mutations 去更改 state 的值。

  • 方法內容可以是包含異步函數。

建立一個 actions 方法。
該方法接受 context 作為第一參數, context 具有 store 相同的屬性及方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "shopping", done: true },
{ id: 2, text: "washing", done: false },
{ id: 3, text: "walking", done: false }
]
},
mutations: {
changeTodos(state, payload){
state.todos = payload
},
},
actions: {
getNewTodos(context) {
fetch('https://jsonplaceholder.typicode.com/todos/5')
.then(response => response.json())
.then(json => {
console.log(json);
})
},
}
});

之後透過提交(commit) mutations 去更改 state 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "shopping", done: true },
{ id: 2, text: "washing", done: false },
{ id: 3, text: "walking", done: false }
]
},
mutations: {
changeTodos(state, payload){
state.todos = [payload]
},
},
actions: {
// `context` 具有 `store` 相同的屬性及方法
getNewTodos(context) {
fetch('https://jsonplaceholder.typicode.com/todos/5')
.then(response => response.json())
.then(json => {
// 透過 context.commit('mutation type', payload) 更改 state
context.commit('changeTodos', json)
})
},
}
});

相信一定有很多人跟我有相同疑問,為什麼一定要透過提交 mutation 去更改 state 的值?
在網路上找到許多回答,其實在 actions 中變更 state 的值是可行的,但是為了好用 Vue devtools 追蹤狀態修改,所以才建議在 actions 中使用提交 mutation 去更改 state 的值。

參考文件:vuex为什么不建议在action中修改state

透過 store.dispatch('方法名') 觸發 actions 中指定的方法。

1
2
3
4
5
6
7
8
9
const vm = new Vue({
el: "#vm",
store,
methods: {
changeTodos() {
this.$store.dispatch('getNewTodos');
}
}
});

actions 的第一參數可以透過 ES6 中的參數解構來獲得需要使用到的屬性或方法,如此一來程式碼也可以變得更簡潔喔!
actions 可接受其他數值做為第二參數(payload)喔!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "shopping", done: true },
{ id: 2, text: "washing", done: false },
{ id: 3, text: "walking", done: false }
]
},
mutations: {
changeTodos(state, payload){
state.todos = [payload]
},
},
actions: {
// 第一參數使用參數解構,因此會自動去取的 context 中的 commit 方法
getNewTodos({ commit }, payload) {
fetch(payload)
.then(response => response.json())
.then(json => {
// 透過 context.commit('mutation type', payload) 更改 state
context.commit('changeTodos', json)
})
},
}
});

const vm = new Vue({
el: "#vm",
store,
methods: {
changeTodos() {
// 觸發 Vuex actions 中 getNewTodos 的方法,並且將 "https://jsonplaceholder.typicode.com/todos/5" 做為第二引數帶入 getNewTodos 的 payload
this.$store.dispatch("getNewTodos","https://jsonplaceholder.typicode.com/todos/5");
}
}
});

Demo

See the Pen DAY28 | 跟 Vue.js 認識的30天 - Vue 套餐之圖書館 Vuex (中) by Celeste6666 (@celeste6666) on CodePen.

參考資料

开始 → 核心概念