Vuex 是什么?
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。訪問官網,點擊這里
每一個 Vuex 應用的核心就是 store(倉庫)。“store”基本上就是一個容器,它包含着你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有以下兩點不同:
-
Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
-
你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
vuex中,有默認的五種基本的對象:
-
state:存儲狀態(變量)
-
getters:對數據獲取之前的再次編譯,可以理解為state的計算屬性。我們在組件中使用 $sotre.getters.fun()
-
mutations:修改狀態,並且是同步的。在組件中使用$store.commit('',params)。這個和我們組件中的自定義事件類似。
-
actions:異步操作。在組件中使用是$store.dispath('')
-
modules:store的子模塊,為了開發大型項目,方便狀態管理而使用的。這里我們就不解釋了,用起來和上面的一樣。
0、在 main.js里面,引入vuex相關的js文件
import store from './store'
// store.js import Vue from 'vue' import Vuex from 'vuex' import user from './module/user' import app from './module/app' Vue.use(Vuex) export default new Vuex.Store({ state: { // number: 0 }, mutations: { }, getters: { }, actions: { }, modules: { } })
1、在 Vue 組件中獲得 Vuex 狀態(state)
(1)直接在組件father.vue中調用:
<div style="border: 2px solid #007606;padding: 5px;"> VUEX的數據【number】: <span style="font-size: 30px;font-weight: bolder;"> {{this.$store.state.number}} </span> </div>
(2)或者在計算屬性中處理后使用
<div style="border: 2px solid #007606;padding: 5px;"> VUEX的數據【number】: <span style="font-size: 30px;font-weight: bolder;"> {{count}} </span> </div>
computed: {
count () {
return '==這是VUEX中定義的Number==:'+this.$store.state.number
}
},
2、Getter
有時候我們需要從 store 中的 state 中派生出一些狀態,
如果有多個組件需要用到此屬性,我們要么復制這個函數,或者抽取到一個共享函數然后在多處導入它——無論哪種方式都不是很理想。
Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。
(1)實例:孫子級組件F中,處理【number】的值,*2
<div style="border: 2px solid #2700ff;padding: 5px;"> Vuex的數值【number*2】: <span style="font-size: 30px;font-weight: bolder;"> {{$store.getters.doneTodosCount}} </span> </div>
(2)store.js代碼:
getters: {
doneTodosCount: (state, getters) => {
return state.number * 2
}
},
3、Mutation(Mutation 必須是同步函數)
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受 state 作為第一個參數
(1)實例:祖父級組件parent,點擊按鈕,對number進行++或者--的操作
<div style="border: 2px solid #007606;padding: 5px;"> VUEX的數據【number】: <span style="font-size: 30px;font-weight: bolder;"> {{count}} </span> <br> <el-button @click="vuexClick1">點擊++</el-button> <el-button @click="vuexClick2">點擊--</el-button> </div>
methods: {
vuexClick1 () {
this.$store.commit('vuexClick1')
},
vuexClick2 () {
this.$store.commit('vuexClick2')
}
},
(2)store.js代碼:
mutations: {
//
vuexClick1 (state) {
return state.number++
},
vuexClick2 (state) {
return state.number--
}
},
4、Ation
Action 類似於 mutation,不同在於:
-
Action 提交的是 mutation,而不是直接變更狀態。
-
Action 可以包含任意異步操作。
(1)實例:祖父級組件parent,點擊按鈕"number延時3s加10",對number進行延時3s加10的操作
<el-button @click="vuexClick3">number延時3s加10</el-button>
vuexClick3 () {
this.$store.dispatch('vuexClick3')
}
(2)store.js代碼:
actions: {
//
vuexClick3 (state) {
setTimeout(() => {
return state.commit('NumberChange3')
}, 3000)
}
},
NumberChange3 (state) {
return state.number += 10
}
5、Module
由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常復雜時,store 對象就有可能變得相當臃腫。
為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割:
modules: {
user,
app
}
命名空間
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。
如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true
的方式使其成為帶命名空間的模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名。
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模塊內容(module assets)
state: { ... }, // 模塊內的狀態已經是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模塊
modules: {
// 繼承父模塊的命名空間
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 進一步嵌套命名空間
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}})
【官網實例】
完整代碼:
1.store.js
import Vue from 'vue' import Vuex from 'vuex' import user from './module/user' import app from './module/app' Vue.use(Vuex) export default new Vuex.Store({ state: { // number: 0 }, mutations: { // vuexClick1 (state) { return state.number++ }, vuexClick2 (state) { return state.number-- }, NumberChange3 (state) { return state.number += 10 } }, getters: { doneTodosCount: (state, getters) => { return state.number * 2 } }, actions: { // vuexClick3 (state) { setTimeout(() => { return state.commit('NumberChange3') }, 3000) } }, modules: { user, app } })
2.father.vue
<!-- 文件描述:祖父級組件 創建時間:2019/12/28 15:30 --> <template> <div class="" style="width: 900px;height: 700px;background-color: #d6e7ff;padding: 20px;"> <span style="font-size: 20px;font-weight: bold;">祖父級組件:father</span> <div style="border: 2px solid #007606;padding: 5px;"> VUEX的數據【number】: <span style="font-size: 30px;font-weight: bolder;"> {{count}} </span> <br> <el-button @click="vuexClick1">點擊++</el-button> <el-button @click="vuexClick2">點擊--</el-button> <el-button @click="vuexClick3">number延時3s加10</el-button> </div> <br> <div> <childrenA></childrenA> <childrenB></childrenB> <childrenC></childrenC> </div> </div> </template> <script> // 這里可以導入其他文件(比如:組件,工具js,第三方插件js,json文件,圖片文件等等) // 例如:import 《組件名稱》 from '《組件路徑》'; import childrenA from './childrenA' import childrenB from './childrenB' import childrenC from './childrenC' export default { name: 'father', // import引入的組件需要注入到對象中才能使用 components: { childrenA, childrenB, childrenC }, computed: { count () { return this.$store.state.number } }, methods: { vuexClick1 () { this.$store.commit('vuexClick1') }, vuexClick2 () { this.$store.commit('vuexClick2') }, vuexClick3 () { this.$store.dispatch('vuexClick3') } }, data () { // 這里存放數據 return { } } } </script>
3.childrenG.vue
<!--文件描述:孫子級組件G 創建時間:2019/12/28 15:37--> <template> <div class="" style="float: left;margin: 10px;width: 200px;height: 200px;background-color: #ff00de;padding: 20px;color:#fff;"> <span style="font-size: 14px;font-weight: bold;color:#fff;">孫子級組件G:childrenG</span> <div style="border: 2px solid #00ff09;padding: 5px;"> VUEX的數據【number】: <span style="font-size: 30px;font-weight: bolder;"> {{this.$store.state.number}} </span> </div> </div> </template> <script> export default { name: 'childrenA', // import引入的組件需要注入到對象中才能使用 components: {}, data () { // 這里存放數據 return { } } } </script>
4.childrenF.vue
<!-- 文件描述:孫子級組件F 創建時間:2019/12/28 15:37 創建人: --> <template> <div class="" style="float: left;margin: 10px;width: 200px;height: 200px;background-color: #aaff1a;padding: 20px;"> <span style="font-size: 14px;font-weight: bold;color:#000000;">孫子級組件F:childrenF</span> <div style="border: 2px solid #2700ff;padding: 5px;"> Vuex的數值【number*2】: <span style="font-size: 30px;font-weight: bolder;"> {{$store.getters.doneTodosCount}} </span> </div> </div> </template> <script> // 這里可以導入其他文件(比如:組件,工具js,第三方插件js,json文件,圖片文件等等) // 例如:import 《組件名稱》 from '《組件路徑》'; // 例如:import uploadFile from '@/components/uploadFile/uploadFile' export default { name: 'childrenA', // import引入的組件需要注入到對象中才能使用 components: {}, data () { // 這里存放數據 return { } }, // 監聽屬性 類似於data概念 computed: {}, // 方法集合 methods: {}, // 監控data中的數據變化 watch: {}, // 生命周期 - 創建完成(可以訪問當前this實例) created () { }, // 生命周期 - 掛載完成(可以訪問DOM元素) mounted () { }, beforeCreate () { }, // 生命周期 - 創建之前 beforeMount () { }, // 生命周期 - 掛載之前 beforeUpdate () { }, // 生命周期 - 更新之前 updated () { }, // 生命周期 - 更新之后 beforeDestroy () { }, // 生命周期 - 銷毀之前 destroyed () { }, // 生命周期 - 銷毀完成 activated () { } // 如果頁面有keep-alive緩存功能,這個函數會觸發 } </script> <style scoped lang="scss"> //@import url(); 引入公共css類 </style>
5.user.js
import {
login,
logout,
getUserInfo,
getMessage,
getContentByMsgId,
hasRead,
removeReaded,
restoreTrash,
getUnreadCount
} from '@/api/user'
import { setToken, getToken } from '@/libs/util'
export default {
state: {
userName: '',
userId: '',
avatorImgPath: '',
token: getToken(),
access: '',
hasGetInfo: false,
unreadCount: 0,
messageUnreadList: [],
messageReadedList: [],
messageTrashList: [],
messageContentStore: {}
},
mutations: {
setAvator (state, avatorPath) {
state.avatorImgPath = avatorPath
},
setUserId (state, id) {
state.userId = id
},
setUserName (state, name) {
state.userName = name
},
setAccess (state, access) {
state.access = access
},
setToken (state, token) {
state.token = token
setToken(token)
},
setHasGetInfo (state, status) {
state.hasGetInfo = status
},
setMessageCount (state, count) {
state.unreadCount = count
},
setMessageUnreadList (state, list) {
state.messageUnreadList = list
},
setMessageReadedList (state, list) {
state.messageReadedList = list
},
setMessageTrashList (state, list) {
state.messageTrashList = list
},
updateMessageContentStore (state, { msg_id, content }) {
state.messageContentStore[msg_id] = content
},
moveMsg (state, { from, to, msg_id }) {
const index = state[from].findIndex(_ => _.msg_id === msg_id)
const msgItem = state[from].splice(index, 1)[0]
msgItem.loading = false
state[to].unshift(msgItem)
}
},
getters: {
messageUnreadCount: state => state.messageUnreadList.length,
messageReadedCount: state => state.messageReadedList.length,
messageTrashCount: state => state.messageTrashList.length
},
actions: {
// 登錄
handleLogin ({ commit }, { userName, password }) {
userName = userName.trim()
return new Promise((resolve, reject) => {
login({
userName,
password
}).then(res => {
const data = res.data
commit('setToken', data.token)
resolve()
}).catch(err => {
reject(err)
})
})
},
// 退出登錄
handleLogOut ({ state, commit }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('setToken', '')
commit('setAccess', [])
resolve()
}).catch(err => {
reject(err)
})
// 如果你的退出登錄無需請求接口,則可以直接使用下面三行代碼而無需使用logout調用接口
// commit('setToken', '')
// commit('setAccess', [])
// resolve()
})
},
// 獲取用戶相關信息
getUserInfo ({ state, commit }) {
return new Promise((resolve, reject) => {
try {
getUserInfo(state.token).then(res => {
const data = res.data
commit('setAvator', data.avator)
commit('setUserName', data.name)
commit('setUserId', data.user_id)
commit('setAccess', data.access)
commit('setHasGetInfo', true)
resolve(data)
}).catch(err => {
reject(err)
})
} catch (error) {
reject(error)
}
})
},
// 此方法用來獲取未讀消息條數,接口只返回數值,不返回消息列表
getUnreadMessageCount ({ state, commit }) {
getUnreadCount().then(res => {
const { data } = res
commit('setMessageCount', data)
})
},
// 獲取消息列表,其中包含未讀、已讀、回收站三個列表
getMessageList ({ state, commit }) {
return new Promise((resolve, reject) => {
getMessage().then(res => {
const { unread, readed, trash } = res.data
commit('setMessageUnreadList', unread.sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
commit('setMessageReadedList', readed.map(_ => {
_.loading = false
return _
}).sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
commit('setMessageTrashList', trash.map(_ => {
_.loading = false
return _
}).sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
resolve()
}).catch(error => {
reject(error)
})
})
},
// 根據當前點擊的消息的id獲取內容
getContentByMsgId ({ state, commit }, { msg_id }) {
return new Promise((resolve, reject) => {
let contentItem = state.messageContentStore[msg_id]
if (contentItem) {
resolve(contentItem)
} else {
getContentByMsgId(msg_id).then(res => {
const content = res.data
commit('updateMessageContentStore', { msg_id, content })
resolve(content)
})
}
})
},
// 把一個未讀消息標記為已讀
hasRead ({ state, commit }, { msg_id }) {
return new Promise((resolve, reject) => {
hasRead(msg_id).then(() => {
commit('moveMsg', {
from: 'messageUnreadList',
to: 'messageReadedList',
msg_id
})
commit('setMessageCount', state.unreadCount - 1)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 刪除一個已讀消息到回收站
removeReaded ({ commit }, { msg_id }) {
return new Promise((resolve, reject) => {
removeReaded(msg_id).then(() => {
commit('moveMsg', {
from: 'messageReadedList',
to: 'messageTrashList',
msg_id
})
resolve()
}).catch(error => {
reject(error)
})
})
},
// 還原一個已刪除消息到已讀消息
restoreTrash ({ commit }, { msg_id }) {
return new Promise((resolve, reject) => {
restoreTrash(msg_id).then(() => {
commit('moveMsg', {
from: 'messageTrashList',
to: 'messageReadedList',
msg_id
})
resolve()
}).catch(error => {
reject(error)
})
})
}
}
}