沃土前端社區(qū)Vue系列教程 - event bus 和 vuex

在開發(fā)過程中,父子組件傳遞數(shù)據(jù),我們用props和$emit可以解決問題,那么非父子組件之間的數(shù)據(jù)傳遞我們要怎么解決呢,一般有兩個方案,一個event bus(事件總線)和 vuex。我們先來說event bus。

一、用vue腳手架構(gòu)建一個簡單的項(xiàng)目

# 全局安裝 vue-cli
npm install --global vue-cli
# 創(chuàng)建一個基于 webpack 模板的新項(xiàng)目
vue init webpack vuexdemo
# 安裝依賴,走你
cd vuexdemo
npm install
npm run dev

二、使用event bus實(shí)現(xiàn)跨組件通信

event bus事件總線,組件之間的通信需要使用一個中介來實(shí)現(xiàn),在event bus里面使用一個空的Vue實(shí)例來做為通信的橋梁,下面是使用event bus來實(shí)現(xiàn)改變登錄狀態(tài)的demo。

  1. 在原有main.js里加上這么一句
// 把bus
Vue.prototype.bus = new Vue();
  1. 修改原有的Index.vue組件,添加如下代碼:
 data() {
    return {
      loginStatus: false
    };
  },
  created() {
    // 監(jiān)聽事件
    this.bus.$on("login", (data) => {
      this.loginStatus = data;
    });
  }

修改后的Index.vue代碼如下:

<template>
  <div class="hello">
    <h1>登錄狀態(tài):{{ loginStatus?'已登錄':'未登錄' }}
      <span v-if="!loginStatus">
        <router-link to="/login">去登錄</router-link>
      </span>
    </h1>
  </div>
</template>

<script>
export default {
  name: "Index",
  data() {
    return {
      loginStatus: false
    };
  },
  created() {
    this.bus.$on("login", (data) => {
      this.loginStatus = data;
    });
  }
};
</script>

<style scoped>
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>
  1. 新建組件Login.vue,在組件Login.vue添加登錄操作,在登錄成功的時候,觸發(fā)‘login’事件,Login完整代碼如下:
<template>
  <div>
    <input type="text" placeholder="請輸入用戶名"><br />
    <input type="text" name="" placeholder="請輸入密碼"/><br/>
    <button @click="login" class="login">立即登錄</button>
   <p>還沒有賬號,<router-link to="/register">立即注冊</router-link></p> 
  </div>
</template>

<script>
export default {
  methods: {
    login() {
      this.bus.$emit("login", true);
      this.$router.push('/');
    }
  }
};
</script>
<style>
.login {
  width: 100px;
  height: 30px;
  margin-top: 10px;
}
.psw {
  margin-top: 5px;
}
</style>
  1. 同時在vue-router里面配置路由/login,代碼如下:
import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'
import Login from '@/components/Login'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      
      component: Index
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    }
  ]
})
  1. 測試:Index.vue與Login.vue是兩個非父子關(guān)系的組件,當(dāng)我們點(diǎn)擊Login組件里面的立即登錄的時候,Index組件里面顯示的登錄狀態(tài)就會發(fā)生改變,這樣就實(shí)現(xiàn)了非父子組件之間的通信。需要注意的是:負(fù)責(zé)監(jiān)聽的組件必須先掛載,才能使用$emit來觸發(fā)事件。簡單來講,類似jQuery一樣,事件必須先綁定,你才能取觸發(fā)事件。

三、使用vuex實(shí)現(xiàn)跨組件通信

跨組件通信,使用event bus的做法,對于簡單的應(yīng)用來講,但是對于復(fù)雜的應(yīng)用來講,event bus對于事件的管理就可能力不從心了,你不一定記得你有多少個組件監(jiān)聽了該事件,你可能會擔(dān)心,那個組件到底掛載了沒有。所以在復(fù)雜的應(yīng)用中我們推薦使用vuex。

什么是vuex
  1. Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式,它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。vuex包含以下幾個元素state(存放數(shù)據(jù)),action(動作,我把它理解為指令,用戶派發(fā)action,可以看做是用戶發(fā)出指令),mutation(改變,用來改變state里面的數(shù)據(jù))一個完整的過程是視圖(用戶)派發(fā) action,action提交修改,mutation負(fù)責(zé)修改state里的數(shù)據(jù),state里的數(shù)據(jù)被修改了,對應(yīng)的視圖就被刷新,見下圖。


  2. 每一個 Vuex 應(yīng)用的核心就是 store(倉庫)?!皊tore”基本上就是一個容器,它包含著你的應(yīng)用中大部分的狀態(tài) (state)。Vuex 和單純的全局對象有相似的地方,都是把數(shù)據(jù)存到一個地方,然后取出來,或者進(jìn)行數(shù)據(jù)的修改,但有以下兩點(diǎn)不同:
    • Vuex 的狀態(tài)存儲是響應(yīng)式的。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時候,若 store 中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會相應(yīng)地得到高效更新。
    • 你不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用。
一個vuex demo
  1. 打開上面的例子,在當(dāng)前文件夾安裝vuex
npm i vuex --save
  1. 新建組件Headline.vue,詳細(xì)代碼如下:
    vuex中提供了mapGetters、mapActions、mapMutations用來映射store里面的相應(yīng)屬性,方便我們直接使用我們事先定義的getters、actions和mutations。
<template>
  <h1>組件名稱:{{title}}</h1>
</template>

<script>
import { mapGetters } from "vuex";
export default {
  computed: {
    // 映射 `this.title` 為 `this.$store.getters.title`
    ...mapGetters(["title"])
  }
};
</script>
  1. 在src文件夾下新建文件夾store,再新建文件index.js,在index.js里面添加以下內(nèi)容
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
    // 存儲數(shù)據(jù)
    state: {
        title: '根組件'
    },
    // 讀取數(shù)據(jù)
    getters: {
        title: state => state.title
    },
    // 動作
    actions: {
        // 修改標(biāo)題的action
        'UPDATE_TITLE_ACTION'({ commit }, payload) {
            commit('UPDATE_TITLE', payload);
        }
    },
    // 變化;轉(zhuǎn)變
    mutations: {
         // 修改state里的title
        'UPDATE_TITLE'(state, payload) {
            state.title = payload;
        }
    }
})

當(dāng)涉及的數(shù)據(jù)比較多的時候,getters、actions、mutations可以用單獨(dú)的文件導(dǎo)出。

  1. 在main.js里添加下面兩行代碼
import store from './store'

new Vue({
  el: '#app',
  // 新增代碼
  store,
  router,
  template: '<App/>',
  components: { App }
})
  1. 在Login組件里添加如下代碼,當(dāng)我們訪問Login組件的時候,就可以看到頁面上的組件名稱發(fā)生了改變
beforeRouteEnter: (to, from, next) => {
    next(vm => {
      vm.$store.dispatch('UPDATE_TITLE_ACTION','登錄組件')
    })
  },

同理,我們可以新建一個Register組件,做相關(guān)操作,當(dāng)我們訪問/register時改變組件的名稱,Register組件代碼如下,同時需要在router里面配置添加/register:

<template>
  <div>
      <button>立即注冊</button>
      <p>
          已有賬號? <router-link to="/login">立即登錄</router-link>
      </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      
    };
  },
  beforeRouteEnter: (to, from, next) => {
    next(vm => {
      vm.$store.dispatch("UPDATE_TITLE_ACTION", "注冊組件");
    });
  }
};
</script>

router/index.js里面的代碼如下:

import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'
import Login from '@/components/Login'
import Register from '@/components/Register'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      component: Index
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    },
    {
      path: '/register',
      name: 'register',
      component: Register
    }
  ]
})

7.modules,當(dāng)我們需要管理很多的狀態(tài)時,會使用modules來進(jìn)行分割,具體做法如下:
在store的文件夾新建modules文件夾,里面封裝某個具體組件的state、getters、actions和mutations,一下新建一個Foot組件,設(shè)置關(guān)于該組件的狀態(tài)信息,其他組件可以修改它的狀態(tài),從而改變Foot組件顯示的內(nèi)容(統(tǒng)計(jì)瀏覽過的路由的次數(shù))。

  • 在components下新建組件Foot.vue,代碼如下:
<template>
  <div class="foot">
      瀏覽過的路由個數(shù)<span class="num">10</span>
  </div>
</template>

<script>
export default {};
</script>

<style>
.foot {
  background: gray;
  position: fixed;
  bottom: 0px;
  height: 100px;
  width: 100%;
  color: #ffffff;
  line-height: 100px;
  font-size: 20px;
}
.num {
    border-radius: 50%;
    border: 1px solid;
    margin-left: 20px;
    padding: 5px;
}
</style>
  • 在store/modules下新建foot.js,代碼如下:
export default {
    state: {
        routeNum: '1'
    },
    getters: {
        routeNum: state => state.routeNum
    },
    actions: {
        'UPDATE_ROUTENUM_ACTION'({ commit }, payload) {
            commit('UPDATE_ROUTENUM',payload);
        }
    },
    mutations: {
        'UPDATE_ROUTENUM'(state, payload) {
            state.routeNum = state.routeNum + payload;
        } 
    }
}
  • 修改store/index.js,修改后代碼如下:
import Vue from 'vue'
import Vuex from 'vuex'
import foot from './modules/footer'
Vue.use(Vuex)
// import getters from './getters'
// import mutations from './mutations'
// import actions from './actions';

export default new Vuex.Store({
    // 存儲數(shù)據(jù)
    state: {
        title: '根組件'
    },
    // 讀取數(shù)據(jù)
    getters: {
        title: state => state.title
    },
    actions: {
        // 修改標(biāo)題
        'UPDATE_TITLE_ACTION'({ commit }, payload) {
            commit('UPDATE_TITLE', payload);
        }
    },
    mutations: {
        'UPDATE_TITLE'(state, payload) {
            state.title = payload;
        }
    },
    modules: {
        foot,
    }
})
  • 在其他組件去派發(fā)action來改變Foot組件的狀態(tài)
    可以在每一個組件都加上這么一句
beforeRouteEnter: (to, from, next) => {
    next(vm => {
      vm.$store.dispatch("UPDATE_TITLE_ACTION", "登錄組件");
      vm.$store.dispatch("UPDATE_ROUTENUM_ACTION",1);
    });
  },

當(dāng)然這個是比較笨的方法,更好的辦法是,在main.js里面加上以下這段代碼,那么當(dāng)每一次變換路由的時候我們給底部加圓圈的小數(shù)字加1了。

router.beforeEach((to, from, next) => {
  store.dispatch('UPDATE_ROUTENUM_ACTION',1);
  next();
})
在開始的event bus的例子里我們通過event bus改變組件的登錄狀態(tài),在這里我們也可以使用vuex輕松實(shí)現(xiàn),我們用state用loginState來保存登錄的狀態(tài),當(dāng)我們登錄成功的時候,就修改loginState的值,具體實(shí)現(xiàn)過程如下。
  1. 在store/modules下新建文件login.js,具體代碼如下:
import { mapActions } from "vuex";

export default {
    state: {
        loginState: false
    },
    getters: {
        loginState: state => state.loginState
    },
    actions: {
        'UPDATE_LOGINSTATE_ACTION'({ commit }, payload) {
            // UPDATE_LOGINSTATE為對應(yīng)的mutation
            commit('UPDATE_LOGINSTATE', payload);
        }
    },
    mutations: {
        'UPDATE_LOGINSTATE'(state, payload) {
            state.loginState = payload;
        }
    }
}
  1. 修改store/index.js,在modules里添加,修改后的代碼如下:
import Vue from 'vue'
import Vuex from 'vuex'
import foot from './modules/footer'
import login from './modules/login'
Vue.use(Vuex)

export default new Vuex.Store({
    // 存儲數(shù)據(jù)
    state: {
        title: '根組件'
    },
    // 讀取數(shù)據(jù)
    getters: {
        title: state => state.title
    },
    actions: {
        // 修改標(biāo)題
        'UPDATE_TITLE_ACTION'({ commit }, payload) {
            commit('UPDATE_TITLE', payload);
        }
    },
    mutations: {
        'UPDATE_TITLE'(state, payload) {
            state.title = payload;
        }
    },
    modules: {
        foot,
        login
    }
})
  1. 修改login.vue,代碼如下:
<template>
  <div>
    <input type="text" placeholder="請輸入用戶名"><br />
    <input class="psw" type="text" name="" placeholder="請輸入密碼"/><br/>
    <button @click="login" class="login">立即登錄</button>
    <p>還沒有賬號,<router-link to="/register">立即注冊</router-link></p> 
  </div>
</template>

<script>
export default {
  beforeRouteEnter: (to, from, next) => {
    next(vm => {
      vm.$store.dispatch("UPDATE_TITLE_ACTION", "登錄組件");
    });
  },
  methods: {
    login() {
      this.$store.dispatch('UPDATE_LOGINSTATE_ACTION', true);
      this.$router.push("/");
    }
  }
};
</script>
<style>
.login {
  width: 100px;
  height: 30px;
  margin-top: 10px;
}
.psw {
  margin-top: 5px;
}
</style>
  1. 修改components下的index.vue,代碼如下:
<template>
  <div class="hello">
    <h1>登錄狀態(tài):{{ loginState?'已登錄':'未登錄' }}
      <span v-if="!loginState">
        <router-link to="/login">去登錄</router-link>
      </span>
      <button v-else @click="logout">退出登錄</button>
    </h1>
  </div>
</template>

<script>
import { mapGetters,mapActions } from 'vuex'
export default {
  name: "HelloWorld",
  data() {
    return {
      
    };
  },
  computed: {
    ...mapGetters(['loginState'])
  },
  beforeRouteEnter: (to, from, next) => {
    next(vm => {
      vm.$store.dispatch("UPDATE_TITLE_ACTION", "根組件");
    });
  },
  methods: {
    ...mapActions(['UPDATE_LOGINSTATE_ACTION']),
    logout() {
      // this.$store.dispatch('UPDATE_LOGINSTATE_ACTION',false);
      this.UPDATE_LOGINSTATE_ACTION(false);
    }
  }
};
</script>

<style scoped>
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>
探討問題:
  1. devtool
  2. vue路由的history模式
  3. 刷新后的store里面的數(shù)據(jù)清空,怎么處理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載 :OpenDiggawesome-github-vue 是由OpenDigg整理并維護(hù)的Vue相關(guān)開源項(xiàng)目庫...
    果汁密碼閱讀 23,401評論 8 124
  • 來源:github.com Vue.js開源項(xiàng)目速查表:https://www.ctolib.com/cheats...
    zhangtaiwei閱讀 11,921評論 1 159
  • 生活總是這樣促狹,每當(dāng)你靜靜地努力好久,以為這個逼就要裝成的時候,甚至你已經(jīng)默默的告訴自己做人要低調(diào),贏了也要戒驕...
    三十二畫生閱讀 353評論 0 0
  • 1 當(dāng)我開始寫身邊人系列,強(qiáng)哥跳出來說,你怎么不寫寫我。 我說,你太陰暗了,不能寫,我要的是正能量。 強(qiáng)哥說,難道...
    唐小麥閱讀 407評論 0 3
  • 當(dāng)然并沒有! 近視, 通常(不通常的那些更麻煩, 不要幻想)是眼球(眼軸)變長, 變長的眼軸是不可能再縮短回來的....
    goldengrape閱讀 2,433評論 0 3

友情鏈接更多精彩內(nèi)容