前言
我們經常用element-ui做后台管理系統,經常會遇到父組件給子組件傳遞數據,下面一個簡單的例子,點擊按鈕,把彈框顯示變量數據通過子組件的props屬性傳遞,子組件通過$emit事件監聽把數據回傳給父組件。
父組件代碼:
<template> <div> <a href="javascript:;" @click="dialogshow = true">點擊</a> <common-dialog :show.sync="dialogshow"></common-dialog> 彈框是否顯示:{{dialogshow}} </div> </template> <script> import commondialog from '@/components/dialog' export default { name: 'parent', components:{ 'common-dialog':commondialog }, data () { return { dialogshow:false } }, methods:{ } } </script>
子組件代碼:
<template> <el-dialog :visible.sync="elDialogShow" title="提示"> <span>這是一段彈框信息</span> <span slot="footer" class="dialog-footer"> <el-button @click="elDialogShow = false">取 消</el-button> <el-button type="primary" @click="elDialogShow = false">確 定</el-button> </span> </el-dialog> </template> <script> export default { name:'children', props:['show'], computed:{ elDialogShow:{ get(){ return this.show }, set(value){ this.$emit('update:show',value) } } }, data() { return { }; } } </script>
感覺這樣挺麻煩,父組件通過設置子組件的屬性(props)來向子組件傳遞數據,而父組件想獲得子組件的數據,得向子組件注冊事件,在子組件觸發這個事件再把數據傳遞出來。一句話總結起來就是,props 向下傳遞數據,事件向上傳遞數據。
如果使用vuex,像下面這種方式就很簡單:
<!--父組件--> <template> <div> <a href="javascript:;" @click="$store.state.show = true">點擊</a> <common-dialog></common-dialog> 彈框是否顯示:{{$store.state.show}} </div> </template> <!--子組件--> <el-dialog :visible.sync="$store.state.show" title="提示"> <!--其它代碼省略--> </el-dialog>
當然,在這兒舉這個例子似乎不恰當,只是為了說明vuex使用方便。
安裝使用vuex
安裝:
npm install vuex --save
在main.js添加配置:
import vuex from 'vuex' Vue.use(vuex) var store = new vuex.Store({ state:{ show:false } }) new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
然后子組件代碼就能有效運行了。
<template> <el-dialog :visible.sync="$store.state.show" title="提示"> <span>這是一段彈框信息</span> <span slot="footer" class="dialog-footer"> <el-button @click="$store.state.show = false">取 消</el-button> <el-button type="primary" @click="$store.state.show = false">確 定</el-button> </span> </el-dialog> </template> <script> export default { name:'children', data() { return { }; } } </script>
modules
前面我們把store對象寫到了main.js中,為了項目的好管理,我們可以新建一個store文件夾放於src文件夾下,在store文件夾下新建index.js,代碼如下:
import Vue from 'vue' import vuex from 'vuex' Vue.use(vuex); export default new vuex.Store({ state:{ show:false } })
那么對應的main.js得改成如下:
//vuex import store from './store' new Vue({ el: '#app', router, store,//使用store template: '<App/>', components: { App } })
這樣雖然結構看着清晰了,但是如果多個模塊,不同類型(比如用戶基本信息,用戶購物車等)放到同一個state下面不好管理,這就用modules。
那么store文件夾下的index.js就變成了這樣,假如我們有app與user模塊:
import Vue from 'vue' import Vuex from 'vuex' import app from './modules/app' //app模塊數據 import user from './modules/user' //用戶模塊數據 import getters from './getters' Vue.use(Vuex) const store = new Vuex.Store({ modules: { app, user }, getters }) export default store
getters
這兒說明下,上面我在Store實例對象中引入了getters,是為了后面頁面可以直接調用經常用到的狀態信息,也就相當於vue實例里面的computed計算屬性,通過$store.getters.show方式得到相關數據,即通過上面的getters引入知道,是直接在store文件夾下創建了一個getters.js,代碼如下:
const getters = { show: state => state.app.show } export default getters
當然這個getters也可以放到具體的模塊中,比如放到app模塊中,官網的這塊代碼結構如下:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的狀態 store.state.b // -> moduleB 的狀態
我們從index.js中看到,我們在store文件夾下創建了一個modules文件夾,也就是在該文件夾下放各個需要區分的狀態模塊,比如user.js,app.js等。
結構如下圖:

上面我們通過$store.getters.show方式(即相當於通過計算屬性)獲得show的值,當然也可以通過$store.state.app.show獲得。
app.js代碼:
export default { state:{ show:false } }
mutations
在我們點擊按鈕出現彈框或者點擊彈框中的關閉按鈕,需要修改app模塊中的show的狀態,這時候就需要通過mutations進行修改數據(同步操作)。
我們需要在app.js模塊代碼:
export default { state:{ show:false }, mutations:{ SET_DIALOG_STATE:(state,val) => { //改變彈框是否顯示的狀態 state.show = val } } }
父組件:
<template> <div> <a href="javascript:;" @click="$store.commit('SET_DIALOG_STATE',true)">點擊</a> <common-dialog></common-dialog> 彈框是否顯示:{{$store.state.app.show}} </div> </template> <script> import commondialog from '@/components/dialog' export default { name: 'parent', components:{ 'common-dialog':commondialog }, data () { return { } }, methods:{ } } </script>
子組件:
<template> <el-dialog :visible.sync="$store.getters.show" title="提示"> <span>這是一段彈框信息</span> <span slot="footer" class="dialog-footer"> <el-button @click="$store.commit('SET_DIALOG_STATE',false)">取 消</el-button> <el-button type="primary" @click="$store.commit('SET_DIALOG_STATE',false)">確 定</el-button> </span> </el-dialog> </template>
這兒就是用到了$store.commit('SET_DIALOG_STATE',false)來觸發mutations中的SET_DIALOG_STATE方法來改變狀態值。
需要注意的是:
- mutations中的方法是不分模塊的,比如你在app.js中定義了SET_DIALOG_STATE這個方法,也在user.js中定義了SET_DIALOG_STATE這個方法,那么在任一組件中調用$store.commit('SET_DIALOG_STATE',false),會執行所有的SET_DIALOG_STATE方法。
- mutations里的操作必須是同步的。
如果在mutations 里執行異步操作會發生什么事情 , 實際上並不會發生什么奇怪的事情 , 只是官方推薦 , 不要在 mutations 里執行異步操作而已。
actions
我們在上面的app.js中通過mutations改變了一個狀態,那么如果需要改變多個狀態的值呢,需要執行mutations中定義的多個方法(也就是說需要調用多次$store.commit()方法),那么就需要actions
那么app.js代碼如需要改成如下:
export default { state:{ show:false }, getters:{ showState(state){ return state.show } }, mutations:{ SET_DIALOG_STATE:(state,val) => { //改變彈框是否顯示的狀態 state.show = val } }, actions:{ set_dialog_state({commit,state},dialogVal){ //對象解構 commit('SET_DIALOG_STATE',dialogVal) //commit('mutations其它方法','其它方法需要改變的值') } //等價於下面的: /* set_dialog_state(context,dialogVal){ context.commit('SET_DIALOG_STATE',dialogVal) context.commit('mutations其它方法','其它方法需要改變的值') } */ } }
那么父組件的調用方式就需要用$store.dispatch()方法,父組件代碼如下:
<template>
<div>
<a href="javascript:;" @click="$store.dispatch('set_dialog_state',true)">點擊</a>
<common-dialog></common-dialog>
彈框是否顯示:{{$store.state.app.show}}
</div>
</template>
<script>
import commondialog from '@/components/dialog'
export default {
name: 'parent',
components:{
'common-dialog':commondialog
},
data () {
return {
}
},
methods:{
}
}
</script>
子組件的代碼:
<template>
<el-dialog :visible.sync="$store.getters.show" title="提示">
<span>這是一段彈框信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="$store.dispatch('set_dialog_state',false)">取 消</el-button>
<el-button type="primary" @click="$store.dispatch('set_dialog_state',false)">確 定</el-button>
</span>
</el-dialog>
</template>
這兒就使用$store.dispatch('set_dialog_state',true)來觸發actions中的set_dialog_state方法。官方推薦 , 將異步操作放在 action 中。
mapGetters、mapState、mapMutations、mapActions
很多時候 , $store.state.app.show 、$store.dispatch('set_dialog_state',true) 這種寫法又長又臭 , 很不方便 , 我們沒使用 vuex 的時候 , 獲取一個狀態只需要 this.show , 執行一個方法只需要 this.set_dialog_state就行了 , 使用 vuex 使寫法變復雜了 ?
使用 mapState、mapGetters、mapActions 就變得簡單了。
mapGetters
比如子組件原來是這樣:
<template>
<el-dialog :visible.sync="$store.getters.show" title="提示">
<span>這是一段彈框信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="$store.dispatch('set_dialog_state',false)">取 消</el-button>
<el-button type="primary" @click="$store.dispatch('set_dialog_state',false)">確 定</el-button>
</span>
</el-dialog>
</template>
通過$store.getter.show得到狀態值,我們也可以這樣:
<template>
<el-dialog :visible.sync="show" title="提示">
<span>這是一段彈框信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="$store.dispatch('set_dialog_state',false)">取 消</el-button>
<el-button type="primary" @click="$store.dispatch('set_dialog_state',false)">確 定</el-button>
</span>
</el-dialog>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name:'children',
data() {
return {
};
},
computed:{
...mapGetters([
'show'
])
}
}
</script>
mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性。
當然我們也可以給getters里面的狀態show換一個名字,比如叫dialogShow,那么子組件就需要改成如下:
<template>
<el-dialog :visible.sync="dialogShow" title="提示">
<span>這是一段彈框信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="$store.dispatch('set_dialog_state',false)">取 消</el-button>
<el-button type="primary" @click="$store.dispatch('set_dialog_state',false)">確 定</el-button>
</span>
</el-dialog>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name:'children',
data() {
return {
};
},
computed:{
...mapGetters({
dialogShow:'show'
})
}
}
</script>
mapState
上面我們通過$store.getters.show拿到狀態值,我們也可以通過$store.state.app.show拿到值,那么怎樣使用mapState呢?
子組件寫法:
<template> <el-dialog :visible.sync="show" title="提示"> <span>這是一段彈框信息</span> <span slot="footer" class="dialog-footer"> <el-button @click="$store.dispatch('set_dialog_state',false)">取 消</el-button> <el-button type="primary" @click="$store.dispatch('set_dialog_state',false)">確 定</el-button> </span> </el-dialog> </template> <script> import { mapState } from 'vuex' export default { name:'children', data() { return { }; }, computed:{ ...mapState({ show:state => state.app.show }) } } </script>
上面使用的是箭頭函數,也可以使用常規函數,如下:
<template> <el-dialog :visible.sync="showState" title="提示"> <span>這是一段彈框信息</span> <span slot="footer" class="dialog-footer"> <el-button @click="$store.dispatch('set_dialog_state',false)">取 消</el-button> <el-button type="primary" @click="$store.dispatch('set_dialog_state',false)">確 定</el-button> </span> </el-dialog> </template> <script> import { mapState } from 'vuex' export default { name:'children', data() { return { }; }, computed:{ ...mapState({ show:state => state.app.show, //方式一 箭頭函數 showState(state){ //方式二 常規函數 return state.app.show } }) } } </script>
mapMutations
你可以在組件中使用 $store.commit('xxx') 提交 mutation,或者使用 mapMutations 輔助函數將組件中的 methods 映射為 store.commit 調用
父組件代碼如下:
<template> <div> <a href="javascript:;" @click="SET_DIALOG_STATE(true)">點擊</a> <common-dialog></common-dialog> 彈框是否顯示:{{$store.state.app.show}} </div> </template> <script> import { mapMutations } from 'vuex' import commondialog from '@/components/dialog' export default { name: 'parent', components:{ 'common-dialog':commondialog }, data () { return { } }, methods:{ ...mapMutations(['SET_DIALOG_STATE']) } } </script>
給方法名換個名字:
<template> <div> <a href="javascript:;" @click="changeState(true)">點擊</a> <common-dialog></common-dialog> 彈框是否顯示:{{$store.state.app.show}} </div> </template> <script> import { mapMutations } from 'vuex' import commondialog from '@/components/dialog' export default { name: 'parent', components:{ 'common-dialog':commondialog }, data () { return { } }, methods:{ ...mapMutations({ changeState:'SET_DIALOG_STATE' //改變名字 }) } } </script>
mapActions
你在組件中使用$store.dispatch('xxx') 分發 action,或者使用 mapActions 輔助函數將組件的 methods 映射為 store.dispatch 調用
父組件代碼:
<template> <div> <a href="javascript:;" @click="set_dialog_state(true)">點擊</a> <common-dialog></common-dialog> 彈框是否顯示:{{$store.state.app.show}} </div> </template> <script> import { mapActions } from 'vuex' import commondialog from '@/components/dialog' export default { name: 'parent', components:{ 'common-dialog':commondialog }, data () { return { } }, methods:{ ...mapActions (['set_dialog_state']) } } </script>
當然也可以換方法名,這兒就不貼代碼了。
