Vuex 就是前端為了方便數(shù)據(jù)的操作而建立的一個” 前端數(shù)據(jù)庫“伸头。模塊間是不共享作用域的匾效,那么B 模塊想要拿到 A 模塊的數(shù)據(jù),我們會怎么做熊锭?我們會定義一個全局變量弧轧,叫aaa 吧,就是window.aaa碗殷。然后把A 模塊要共享的數(shù)據(jù)作為屬性掛到 B 模塊上。這樣我們在 B 模塊中通過window.aaa 就可以拿到這個數(shù)據(jù)了速缨。但是問題來了锌妻,B 模塊拿到了共享的數(shù)據(jù),就叫他xxx 吧旬牲?得了仿粹,名字太混亂了搁吓,咱先給它們都改下名字。那個全局變量既然是存東西的吭历,就叫store 吧堕仔,共享的數(shù)據(jù)就叫state 吧,B 模塊拿到了 A 模塊的數(shù)據(jù)state晌区,但是這個數(shù)據(jù)不是一成不變的呀摩骨,A 要操作這個數(shù)據(jù)的。那么我們是不是要在這個數(shù)據(jù)——state 改變的時候通知一下 B朗若?那寫個自定義事件吧恼五。首先,你得能取吧哭懈?那么得有一套取數(shù)據(jù)的 API灾馒,我們給他們集中起個名字吧?既然是獲取遣总,那就叫g(shù)etter 吧睬罗。我們還得存數(shù)據(jù)呀,是吧旭斥。存數(shù)據(jù)就是對數(shù)據(jù)庫的修改容达,這些 API,我們也得給它起個名字琉预,就叫mutation董饰。vuex 生成的倉庫也就這么出來了,所以我說 vuex 就是” 前端的數(shù)據(jù)庫“圆米。State 就是數(shù)據(jù)庫卒暂。Mutations 就是我們把數(shù)據(jù)存入數(shù)據(jù)庫的 API,用來修改state 的娄帖。getters 是我們從數(shù)據(jù)庫里取數(shù)據(jù)的 API也祠,既然是取,那么你肯定不能把數(shù)據(jù)庫給改了吧近速?所以getters 得是一個”純函數(shù)“诈嘿,就是不會對原數(shù)據(jù)造成影響的函數(shù)。拿到了數(shù)據(jù)削葱,總要做個處理吧奖亚,處理完了再存到數(shù)據(jù)庫中。其實這就是action的過程析砸。當然你也可以不做處理昔字,直接丟到數(shù)據(jù)庫。來看看 vuex 的數(shù)據(jù)流首繁,通過action處理數(shù)據(jù)作郭,然后通過mutation 把處理后的數(shù)據(jù)放入數(shù)據(jù)庫(state)中陨囊,誰要用就通過getter從數(shù)據(jù)庫(state)中取。
安裝
npm install --save vuex
引入
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
實例化生成store的過程是
//store為實例化生成的
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
const mutations = {...};
const actions = {...};
const state = {...};
Vuex.Store({
state,
actions,
mutation
});
State
存儲整個應(yīng)用的狀態(tài)數(shù)據(jù).
想要獲取對應(yīng)的狀態(tài)你就可以直接使用this.$store.state
獲取夹攒,也可以利用vuex
提供的mapState
輔助函數(shù)將state
映射到計算屬性中去
//我是組件
import {mapState} from 'vuex'
export default {
computed: mapState({
count: state => state.count
})
}
Mutations
更改狀態(tài)蜘醋,本質(zhì)就是用來處理數(shù)據(jù)的函數(shù), 其接收唯一參數(shù)值state
。
store.commit(mutationName)
是用來觸發(fā)一個mutation
的方法。需要記住的是,定義的mutation
必須是同步函數(shù)
const mutations = {
mutationName(state) {
//在這里改變state中的數(shù)據(jù)
}
}
在組件中觸發(fā):
//我是一個組件
export default {
methods: {
handleClick() {
this.$store.commit('mutationName')
}
}
}
或者使用輔助函數(shù)mapMutations
直接將觸發(fā)函數(shù)映射到methods
上参淹,這樣就能在元素事件綁定上直接使用了
import {mapMutations} from 'vuex'
//我是一個組件
export default {
methods: mapMutations([
'mutationName'
])
}
Actions
Actions
也可以用于改變狀態(tài)苍碟,不過是通過觸發(fā)mutation
實現(xiàn)的,重要的是可以包含異步操作。其輔助函數(shù)是mapActions
與mapMutations
類似,也是綁定在組件的methods
上的。如果選擇直接觸發(fā)的話斥季,使用this.$store.dispatch(actionName)
方法。
//定義Actions
const actions = {
actionName({ commit }) {
//dosomething
commit('mutationName')
}
}
在組件中使用
import {mapActions} from 'vuex'
//我是一個組件
export default {
methods: mapActions([
'actionName',
])
}
Getters
有些狀態(tài)需要做二次處理累驮,就可以使用getters
酣倾。通過this.$store.getters.valueName
對派生出來的狀態(tài)進行訪問“ǎ或者直接使用輔助函數(shù)mapGetters
將其映射到本地計算屬性中去躁锡。
const getters = {
strLength: state => state.aString.length
}
//上面的代碼根據(jù)aString狀態(tài)派生出了一個strLength狀態(tài)
在組件中使用
import {mapGetters} from 'vuex'
//我是一個組件
export default {
computed: mapGetters([
'strLength'
])
}
Plugins
插件就是一個鉤子函數(shù),在初始化store
的時候引入即可置侍。比較常用的是內(nèi)置的logger插件映之,用于作為調(diào)試使用。
import createLogger from 'vuex/dist/logger'
const store = Vuex.Store({
...
plugins: [createLogger()]
})
Example
npm install --save vuex
該步驟完成之后蜡坊,我們需要在 src 目錄下創(chuàng)建名為 store 的目錄來存放狀態(tài)管理相關(guān)代碼杠输,首先創(chuàng)建 index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
},
actions: {
},
mutations: {
},
getters: {
}
})
export default store
然后在 main.js 文件中我們需要將該 Store 實例添加到構(gòu)造的 Vue 實例中:
import store from './store'
/* eslint-disable no-new */
new Vue({
template: `
<div>
<navbar />
<section class="section">
<div class="container is-fluid">
<router-view></router-view>
</div>
</section>
</div>
`,
router,
store,
components: {
navbar
}
}).$mount('#app')
然后,我們需要去完善 Store 定義:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
projects: []
},
actions: {
LOAD_PROJECT_LIST: function ({ commit }) {
axios.get('/secured/projects').then((response) => {
commit('SET_PROJECT_LIST', { list: response.data })
}, (err) => {
console.log(err)
})
}
},
mutations: {
SET_PROJECT_LIST: (state, { list }) => {
state.projects = list
}
},
getters: {
openProjects: state => {
return state.projects.filter(project => !project.completed)
}
}
})
export default store
在本項目中秕衙,我們將原本存放在組件內(nèi)的項目數(shù)組移動到 Store 中蠢甲,并且將所有關(guān)于狀態(tài)的改變都通過 Action 進行而不是直接修改:
// /src/components/projectList.vue
<template lang="html">
<div class="">
<table class="table">
<thead>
<tr>
<th>Project Name</th>
<th>Assigned To</th>
<th>Priority</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
<tr v-for="item in projects">
<td>{{item.name}}</td>
<td>{{item.assignedTo}}</td>
<td>{{item.priority}}</td>
<td><i v-if="item.completed" class="fa fa-check"></i></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'projectList',
computed: mapState([
'projects'
])
}
</script>
<style lang="css">
</style>
這個模板還是十分直觀,我們通過computed對象來訪問 Store 中的狀態(tài)信息据忘。值得一提的是這里的mapState函數(shù)鹦牛,這里用的是簡寫,完整的話可以直接訪問 Store 對象:
computed: {
projects () {
return this.$store.state.projects
}
}
mapState 是 Vuex 提供的簡化數(shù)據(jù)訪問的輔助函數(shù)勇吊。我們視線回到 project.vue 容器組件曼追,在該組件中調(diào)用this.$store.dispatch('LOAD_PROJECT_LIST)來觸發(fā)從服務(wù)端中加載項目列表:
<template lang="html">
<div id="projects">
<div class="columns">
<div class="column is-half">
<div class="notification">
Project List
</div>
<project-list />
</div>
</div>
</div>
</template>
<script>
import projectList from '../components/projectList'
export default {
name: 'projects',
components: {
projectList
},
mounted: function () {
this.$store.dispatch('LOAD_PROJECT_LIST')
}
}
</script>
當我們啟動應(yīng)用時,Vuex 狀態(tài)管理容器會自動在數(shù)據(jù)獲取之后渲染整個項目列表『汗妫現(xiàn)在我們需要添加新的 Action 與 Mutation 來創(chuàng)建新的項目:
// under actions:
ADD_NEW_PROJECT: function ({ commit }) {
axios.post('/secured/projects').then((response) => {
commit('ADD_PROJECT', { project: response.data })
}, (err) => {
console.log(err)
})
}
// under mutations:
ADD_PROJECT: (state, { project }) => {
state.projects.push(project)
}
然后我們創(chuàng)建一個簡單的用于添加新的項目的組件 addProject.vue:
<template lang="html">
<button type="button" class="button" @click="addProject()">Add New Project</button>
</template>
<script>
export default {
name: 'addProject',
methods: {
addProject () {
this.$store.dispatch('ADD_NEW_PROJECT')
}
}
}
</script>
該組件會派發(fā)某個 Action 來添加組件拉鹃,我們需要將該組件引入到 projects.vue 中:
<template lang="html">
<div id="projects">
<div class="columns">
<div class="column is-half">
<div class="notification">
Project List
</div>
<project-list />
<add-project />
</div>
</div>
</div>
</template>
<script>
import projectList from '../components/projectList'
import addProject from '../components/addProject'
export default {
name: 'projects',
components: {
projectList,
addProject
},
mounted: function () {
this.$store.dispatch('LOAD_PROJECT_LIST')
}
}
</script>
重新運行下該應(yīng)用會看到服務(wù)端返回的創(chuàng)建成功的提示,現(xiàn)在我們添加另一個功能鲫忍,就是允許用戶將某個項目設(shè)置為已完成膏燕。我們現(xiàn)在添加新的組件 completeToggle.vue:
<template lang="html">
<button type="button" class="button" @click="toggle(item)">
<i class="fa fa-undo" v-if="item.completed"></i>
<i class="fa fa-check-circle" v-else></i>
</button>
</template>
<script>
export default {
name: 'completeToggle',
props: ['item'],
methods: {
toggle (item) {
this.$store.dispatch('TOGGLE_COMPLETED', { item: item })
}
}
}
</script>
該組件會展示一個用于切換項目是否完成的按鈕,我們通過 Props 傳入具體的項目信息然后通過觸發(fā) TOGGLE_COMPLETED Action 來使服務(wù)端進行相對應(yīng)的更新與相應(yīng):
// actions
TOGGLE_COMPLETED: function ({ commit, state }, { item }) {
axios.put('/secured/projects/' + item.id, item).then((response) => {
commit('UPDATE_PROJECT', { item: response.data })
}, (err) => {
console.log(err)
})
}
// mutations
UPDATE_PROJECT: (state, { item }) => {
let idx = state.projects.map(p => p.id).indexOf(item.id)
state.projects.splice(idx, 1, item)
}
UPDATE_PROJECT 會觸發(fā)項目列表移除對應(yīng)的項目并且將服務(wù)端返回的數(shù)據(jù)重新添加到數(shù)組中:
app.put('/secured/projects/:id', function (req, res) {
let project = data.filter(function (p) { return p.id == req.params.id })
if (project.length > 0) {
project[0].completed = !project[0].completed
res.status(201).json(project[0])
} else {
res.sendStatus(404)
}
})
最后一步就是將 completeToggle 組件引入到 projectList 組件中悟民,然后將其添加到列表中:
// new column in table
<td><complete-toggle :item="item" /></td>
// be sure import and add to the components object
整理
基本用途:
- 將某些data變成組件間公用的狀態(tài)坝辫,組件隨時都可以進行訪問和響應(yīng),解決了
props
傳值的鏈式響應(yīng)的代碼冗余 - 給狀態(tài)配以公用方法射亏,將狀態(tài)的變更及時響應(yīng)并處理
基本用法:
/store/store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
sideBarOpened: false
//放置公用狀態(tài)
},
getters: {
changeState: state => {
//相當于vue實例中的methods,用于處理公用data
//自vuex 面向組件的數(shù)據(jù)處理
}
},
mutations: {
//寫法與getters相類似
//組件想要對于vuex 中的數(shù)據(jù)進行的處理
//組件中采用this.$store.commit('方法名') 的方式調(diào)用近忙,實現(xiàn)充分解耦
//內(nèi)部操作必須在此刻完成(同步)
},
actions: {
//使得mutations能夠?qū)崿F(xiàn)異步調(diào)用,實現(xiàn)例如延遲調(diào)用
increment ({ commit }, payload) {
commit('突變方法名')
//payload應(yīng)該是一個對象智润,可通過模板方法調(diào)用傳入對象的方式將數(shù)據(jù)從組件傳入vuex
},
asyncIncrement({commit}) => {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
modules: {
//引入某一個state的以上集合的模塊及舍,會自動分別填充到上面,使得結(jié)構(gòu)更加清晰
}
});
main.js
import { store } from './store/store'
*
*
new Vue({
el: '#app',
store, //注入根組件
...
})
訪問vuex中的數(shù)據(jù)和方法
this.$store.state.數(shù)據(jù)名
this.$store.getters.方法名
受影響組件局部定義計算屬性響應(yīng)變化數(shù)據(jù)
computed: {
open () {
return this.$store.state.sideBarOpened
}
}
將 store 中的 getters/mutations 映射到局部(計算屬性/方法)使用mapGetters/mapMutations
輔助函數(shù)
import { mapGetters } from 'vuex'
computed: {
// 使用對象展開運算符將 getters 混入 computed 對象中
...mapGetters([
//映射 this.doneTodosCount 為 store.getters.doneTodosCount
'doneTodosCount',
//'getter名稱',
// 映射 this.doneCount 為 store.getters.doneTodosCount
doneCount: 'doneTodosCount'
// 三個點表示將內(nèi)部拿出生成鍵值對窟绷,這樣使得組件本身的計算屬性不受影響
// 此語法依賴babel-preset-stage-2
])
}
注意事項:
mutation 必須是同步函數(shù) — devtool要保存快照锯玛,方便追蹤狀態(tài)變化
使用 v-model 綁定 vuex 計算屬性的時候要設(shè)置get 和 set 才能雙向綁定
computed: {
value: {
get () {
return this.$store.getters.value;
},
set (event) {
this.$store.dispatch('updateValue', event.target.value);
}
}
}
注意:
- 當 Vue 組件從 store 中讀取狀態(tài)的時候,若 store中的狀態(tài)發(fā)生變化兼蜈,那么相應(yīng)的組件也會相應(yīng)地得到高效更新攘残。
- 不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交mutations
// 如果在模塊化構(gòu)建系統(tǒng)中为狸,請確保在開頭調(diào)用了 Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
現(xiàn)在歼郭,你可以通過 store.state 來獲取狀態(tài)對象,以及通過 store.commit 方法觸發(fā)狀態(tài)變更:
store.commit('increment')
console.log(store.state.count) // -> 1
每次對數(shù)據(jù)對象進行操作的時候辐棒,都需要進行commit
state
Vuex 通過 store 選項病曾,提供了一種機制將狀態(tài)從根組件『注入』到每一個子組件中(需調(diào)用 Vue.use(Vuex)):
const app = new Vue({
el: '#app',
// 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通過在根實例中注冊 store 選項漾根,該 store 實例會注入到根組件下的所有子組件中泰涂,且子組件能通過 this.$store訪問到。讓我們更新下 Counter 的實現(xiàn):
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState 輔助函數(shù)
輔助函數(shù)幫助我們生成計算屬性
mapState函數(shù)返回的是一個對象立叛。我們?nèi)绾螌⑺c局部計算屬性混合使用呢负敏?通常,我們需要使用一個工具函數(shù)將多個對象合并為一個秘蛇,以使我們可以將最終對象傳給 computed 屬性其做。但是自從有了對象展開運算符(現(xiàn)處于 ECMASCript 提案 stage-3 階段),我們可以極大地簡化寫法赁还。
Getters
可以認為是 store 的計算屬性
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
會暴露為 store.getters 對象:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
也可以接受其他 getters 作為第二個參數(shù)
mapGetters 輔助函數(shù)
將 store 中的 getters 映射到局部計算屬性
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getters 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
你想將一個 getter 屬性另取一個名字妖泄,使用對象形式:
mapGetters({
// 映射 this.doneCount 為 store.getters.doneTodosCount
doneCount: 'doneTodosCount'
})