Vue組件間通信的實現(xiàn)方法講解
前言
前面介紹過幾種可以實現(xiàn)組件間通信的方式props、ref、自定義事件綁定、全局事件總線、插槽…,這些要么就是實現(xiàn)組件間通信只能在特定條件下使用,要么就是實現(xiàn)起來太復(fù)雜,今天介紹到的vuex是Vue中一款強大的插件,使用vuex后可以實現(xiàn)任意組件間通信,并且支持模塊化管理。接下來將會圍繞一個計數(shù)案例進(jìn)行展開,逐步引入vuex如何使用。
核心代碼:
<template> <div> <h2>目前的數(shù)值結(jié)果是:{{n}}</h2> <div class="container1"> <select v-model.number="step"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="addNum">增加</button> <button @click="subNum">減少</button> <button @click="oddNum">偶數(shù)再加</button> <button @click="longTimeAdd">延遲增加</button> </div> </div> </template> <script> export default { // eslint-disable-next-line vue/multi-word-component-names name:"Counts", data(){ return { n:0, step:1 } }, methods:{ addNum(){ this.n+=this.step }, subNum(){ this.n-=this.step }, oddNum(){ if (this.n%2==0){ this.n+=this.step } }, longTimeAdd(){ setTimeout(() => { this.n+=this.step }, 1000); } } } </script> <style> .container1>*{ margin-left: 10px; } </style>
一、vuex是什么
簡單點說:是一個數(shù)據(jù)倉庫,用于數(shù)據(jù)存儲,數(shù)據(jù)共享,可以進(jìn)行數(shù)據(jù)存放與獲取,并全程監(jiān)視數(shù)據(jù)的動向
使用官方語言入下:
Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式 + 庫。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。
這個狀態(tài)自管理應(yīng)用包含以下幾個部分:
- 狀態(tài),驅(qū)動應(yīng)用的數(shù)據(jù)源;
- 視圖,以聲明方式將狀態(tài)映射到視圖;
- 操作,響應(yīng)在視圖上的用戶輸入導(dǎo)致的狀態(tài)變化。
但是,當(dāng)我們的應(yīng)用遇到多個組件共享狀態(tài)時,單向數(shù)據(jù)流的簡潔性很容易被破壞:
多個視圖依賴于同一狀態(tài)。
來自不同視圖的行為需要變更同一狀態(tài)。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態(tài)傳遞無能為力。對于問題二,我們經(jīng)常會采用父子組件直接引用或者通過事件來變更和同步狀態(tài)的多份拷貝。以上的這些模式非常脆弱,通常會導(dǎo)致無法維護的代碼。
因此,我們?yōu)槭裁床话呀M件的共享狀態(tài)抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構(gòu)成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態(tài)或者觸發(fā)行為!
通過定義和隔離狀態(tài)管理中的各種概念并通過強制規(guī)則維持視圖和狀態(tài)間的獨立性,我們的代碼將會變得更結(jié)構(gòu)化且易維護。
這就是 Vuex 背后的基本思想,借鑒了 Flux、Redux 和 The Elm Architecture。與其他模式不同的是,Vuex 是專門為 Vue.js 設(shè)計的狀態(tài)管理庫,以利用 Vue.js 的細(xì)粒度數(shù)據(jù)響應(yīng)機制來進(jìn)行高效的狀態(tài)更新。
vuex適用于大型單頁應(yīng)用的開發(fā),如果是小型的不推薦使用。
其中vuex3適配vue2,vuex4適配vue3。
vue工作原理:
vuex中主要包含三個部分
- Actions:相當(dāng)于對數(shù)據(jù)的預(yù)處理(可以在這里使用異步加載進(jìn)行數(shù)據(jù)校驗等)
- Mutations (跟開發(fā)者工具關(guān)系密切)
- State (存放數(shù)據(jù)的地方,經(jīng)過校驗與變換的數(shù)據(jù)都存放在這里)
vuex中的兩種數(shù)據(jù)操作
- dispatch:將數(shù)據(jù)交給Actions
- commit:將數(shù)據(jù)交給Mutations
vuex在項目中使用
大致有兩種(實質(zhì)上一樣):
- 1.vuex/store.js 創(chuàng)建vuex文件夾,將代碼放進(jìn)store.js中
- 2.store/index.js 創(chuàng)建store文件夾,將代碼放進(jìn)index.js中
安裝vuex
npm i vuex
二、vuex的使用
繼續(xù)從以下三個模塊與兩個方法講起:
三個模塊:
- Actions:相當(dāng)于對數(shù)據(jù)的預(yù)處理(可以在這里使用異步加載進(jìn)行數(shù)據(jù)校驗等)
- Mutations (跟開發(fā)者工具關(guān)系密切)
- State (存放數(shù)據(jù)的地方,經(jīng)過校驗與變換的數(shù)據(jù)都存放在這里)
兩個方法:
- dispatch:將數(shù)據(jù)交給Actions
- commit:將數(shù)據(jù)交給Mutations
如果調(diào)用vuex的dispatch方法會將數(shù)據(jù)提交到Actions中的函數(shù),此時數(shù)據(jù)并不是最終的結(jié)果,在Actions中可以對數(shù)據(jù)進(jìn)行進(jìn)一步加工,然后調(diào)用commit方法提交,在Actions與Mutations中定義的函數(shù)可以獲取到上下文找到state對象中存儲的數(shù)據(jù)。
接下來結(jié)合代碼看一下vuex如何使用(這一段代碼暴露出去一個可以使用的vuex對象):
import Vue from "vue" import Vuex from "vuex" Vue.use(Vuex) const actions={ odd_Num(comtext,value){ // 可以通過上下文comtext找到commit方法,dispatch方法 // 可以找到state對象,并找到其中的屬性 if (comtext.state.n%2===0){ comtext.commit("add_Num",value) } }, long_Time_Add(comtext,value){ setTimeout(() => { comtext.commit("add_Num",value) }, 1000); } } const mutations={ add_Num(state,value){ // 這里的state被稱為上下文,這里可以拿到state中的數(shù)據(jù) // console.log(state,value) state.n+=value }, sub_Num(state,value){ // console.log(state,value) state.n-=value }, } //數(shù)據(jù)源,源數(shù)據(jù) const state={ n:0 } //創(chuàng)建一個vuex對象 export default new Vuex.Store({ actions, mutations, state })
以下代碼是使用vuex:
main.js
import App from "./App" //這個便是剛才封裝的vuex對象 import store from "./store" import Vue from "vue" new Vue({ el:"#App", store, render:h=>h(App) })
功能性組件:
export default { // eslint-disable-next-line vue/multi-word-component-names name:"Counts", data(){ return { step:1 } }, computed:{ n(){ return this.$store.state.n } } , methods:{ addNum(){ this.$store.commit("add_Num",this.step) }, subNum(){ this.$store.commit("sub_Num",this.step) }, oddNum(){ this.$store.dispatch("odd_Num",this.step) }, longTimeAdd(){ this.$store.dispatch("long_Time_Add",this.step) } } }
除此之外vuex還有一個重要的模塊供我們使用getters,這里面的方法通常用于獲取定制后的state中的屬性
比如下面一個實例,就是將state中的n擴大10倍后返回了出去。
const getters={ numBig(state){ return state.n*10 } }
三、四個重要的映射函數(shù)
如果vuex中的函數(shù)較多時,一個一個引入會相當(dāng)麻煩,官方給出了四個映射函數(shù),可以根據(jù)自己的需求將想要的函數(shù)一下引進(jìn)來,分別是:mapState,mapGetters,mapActions,mapMutations。
以下兩種寫法,在使用時效果是一樣的
不使用映射函數(shù):
<script> export default { // eslint-disable-next-line vue/multi-word-component-names name:"Counts", data(){ return { step:1 } }, computed:{ n(){ return this.$store.state.n }, bigNum(){ // 以下兩種寫法均可以 // return this.$store.getters["numBig"] return this.$store.getters.numBig } } , methods:{ addNum(){ this.$store.commit("add_Num",this.step) }, subNum(){ this.$store.commit("sub_Num",this.step) }, oddNum(){ this.$store.dispatch("odd_Num",this.step) }, longTimeAdd(){ this.$store.dispatch("long_Time_Add",this.step) } } } </script>
使用映射函數(shù):
<script> import {mapState,mapGetters,mapActions,mapMutations} from "vuex" export default { // eslint-disable-next-line vue/multi-word-component-names name:"Counts", data(){ return { step:1 } }, computed:{ ...mapState(["n"]), ...mapGetters({bigNum:"numBig"}) } , methods:{ // 傳入對象是常規(guī)寫法,當(dāng)對象中的鍵值相等時,可以用傳入列表的方式進(jìn)行傳參 // 例如上面的mapState(["n"]) ...mapMutations({addNum:"add_Num",subNum:"sub_Num"}), ...mapActions({oddNum:"odd_Num",longTimeAdd:"long_Time_Add"}) } } </script>
四、多組件數(shù)據(jù)共享
經(jīng)過將多個組件使用到的數(shù)據(jù)提取到vuex,可以實現(xiàn)多組件間的數(shù)據(jù)共享,也就是說在a組件修改共享數(shù)據(jù)之后,在b組件可以渲染出數(shù)據(jù)改變后的結(jié)果,同樣b組件中修改a組件也可以渲染。
以下案例一個計數(shù)組件負(fù)責(zé)計數(shù),一個person組件,負(fù)責(zé)將人員列表渲染出來,person組件支持增加用戶,我們要做的就是person組件中進(jìn)行人員添加后,計數(shù)組件感應(yīng)到人數(shù)變動,重新渲染列表,將總?cè)藬?shù)進(jìn)行改變。
vuex插件
import Vue from "vue" import Vuex from "vuex" import {nanoid} from "nanoid" Vue.use(Vuex) const actions={ odd_Num(comtext,value){ // 可以通過上下文comtext找到commit方法,dispatch方法 // 可以找到state對象,并找到其中的屬性 if (comtext.state.n%2===0){ comtext.commit("add_Num",value) } }, long_Time_Add(comtext,value){ setTimeout(() => { comtext.commit("add_Num",value) }, 1000); }, add_Person(context,value){ var someone={id:nanoid(),name:value} context.commit("add_Person",someone) } } const mutations={ add_Num(state,value){ // 這里的state被稱為上下文,這里可以拿到state中的數(shù)據(jù) // console.log(state,value) state.n+=value }, sub_Num(state,value){ // console.log(state,value) state.n-=value }, add_Person(state,value){ state.personList.unshift(value) } } const state={ n:0, personList:[ { id:"0001", name:"張三" } ] } const getters={ numBig(state){ return state.n*10 }, personNum(state){ return state.personList.length // console.log("index"+state.personList.length) } } export default new Vuex.Store({ actions, mutations, state, getters })
計數(shù)組件
<template> <div> <h2>目前的數(shù)值結(jié)果是:{{n}}</h2> <h2>放大后的數(shù)值結(jié)果是:{{bigNum}}</h2> <div class="container1"> <select v-model.number="step"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="addNum(step)">增加</button> <button @click="subNum(step)">減少</button> <button @click="oddNum(step)">偶數(shù)再加</button> <button @click="longTimeAdd(step)">延遲增加</button> </div> <h2>當(dāng)前的總?cè)藬?shù)為{{personNum}}</h2> </div> </template> <script> import {mapState,mapGetters,mapActions,mapMutations} from "vuex" export default { // eslint-disable-next-line vue/multi-word-component-names name:"Counts", data(){ return { step:1 } }, computed:{ ...mapState(["n"]), ...mapGetters({bigNum:"numBig",personNum:"personNum"}) } , methods:{ // 傳入對象是常規(guī)寫法,當(dāng)對象中的鍵值相等時,可以用傳入列表的方式進(jìn)行傳參 // 例如上面的mapState(["n"]) ...mapMutations({addNum:"add_Num",subNum:"sub_Num"}), ...mapActions({oddNum:"odd_Num",longTimeAdd:"long_Time_Add"}) } } </script> <style> .container1>*{ margin-left: 10px; } </style>
person組件
<template> <div> <h2>目前的計算結(jié)果為{{n}}</h2> //監(jiān)聽的鍵盤事件,當(dāng)輸入enter時添加用戶 <input v-model="tempname" @keydown.enter="add_Person(tempname)"> <ul> <li v-for="p in personList" :key="p.id"> 學(xué)生id:{{p.id}} 學(xué)生姓名:{{p.name}} </li> </ul> </div> </template> <script> import {mapState,mapActions} from "vuex" export default { // eslint-disable-next-line vue/multi-word-component-names name:"Persons", data(){ return { tempname:"" } }, computed:{ ...mapState(["n","personList"]) }, methods:{ ...mapActions(["add_Person"]) } } </script> <style> </style>
五、模塊化使用vuex
如果不按照數(shù)據(jù)功能對數(shù)據(jù)進(jìn)行模塊劃分,那么vuex將會顯得非常雜亂,我們希望vuex可以根據(jù)數(shù)據(jù)的不同作用將其進(jìn)行模塊化,便于我們管理,對于接下來的代碼大家需要熟悉的是如何使用劃分后的函數(shù)、函數(shù)映射。
劃分后的代碼大概長這個樣子,其中store中的index會將count.js與person.js引入并使用。vue代碼中使用vuex依舊是引入index.js,值得注意的是進(jìn)行模塊劃分后的vuex,在使用時跟以往有了不同。詳細(xì)看代碼
index.js
import Vue from "vue" import Vuex from "vuex" import counts from "./count" import person from "./person" Vue.use(Vuex) export default new Vuex.Store({ modules:{ counts, person } })
count.js
// import axios from "axios" export default{ namespaced:true, actions:{ /* ... */ }, mutations:{ /* ... */ }, state:{ n:0, }, getters:{ numBig(state){ return state.n*10 } } }
person.js
import {nanoid} from "nanoid" export default{ namespaced:true, actions:{ /* ... */ }, mutations:{ /* ... */ }, state:{ personList:[ { id:"0001", name:"張三" } ] }, getters:{ personNum(state){ return state.personList.length // console.log("index"+state.personList.length) } } }
count.vue
<template> <div> <h2>目前的數(shù)值結(jié)果是:{{n}}</h2> <h2>放大后的數(shù)值結(jié)果是:{{bigNum}}</h2> <div class="container1"> <select v-model.number="step"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="addNum(step)">增加</button> <button @click="subNum(step)">減少</button> <button @click="oddNum(step)">偶數(shù)再加</button> <button @click="longTimeAdd(step)">延遲增加</button> </div> <h2>當(dāng)前的總?cè)藬?shù)為{{personNum}}</h2> </div> </template> <script> import {mapState,mapGetters,mapActions,mapMutations} from "vuex" export default { // eslint-disable-next-line vue/multi-word-component-names name:"Counts", data(){ return { step:1 } }, computed:{ ...mapState("counts",["n"]), ...mapGetters("counts",{bigNum:"numBig"}), ...mapGetters("person",{personNum:"personNum"}) } , methods:{ // 傳入對象是常規(guī)寫法,當(dāng)對象中的鍵值相等時,可以用傳入列表的方式進(jìn)行傳參 // 例如上面的mapState(["n"]) ...mapMutations("counts",{addNum:"add_Num",subNum:"sub_Num"}), ...mapActions("counts",{oddNum:"odd_Num",longTimeAdd:"long_Time_Add"}) } } </script> <style> .container1>*{ margin-left: 10px; } </style>
persons.vue
<template> <div> <h2>目前的計算結(jié)果為{{n}}</h2> <input v-model="tempname" @keydown.enter="addPerson(tempname)"> <button v-if="tag" @click="tag=!tag">目前可增加任意姓氏</button> <button v-if="!tag" @click="tag=!tag">目前只可以添加王氏</button> <ul> <li v-for="p in personList" :key="p.id"> 學(xué)生id:{{p.id}} 學(xué)生姓名:{{p.name}} </li> </ul> </div> </template> <script> import {mapState,mapActions} from "vuex" export default { // eslint-disable-next-line vue/multi-word-component-names name:"Persons", data(){ return { tag:true, tempname:"" } }, computed:{ ...mapState("counts",["n"]), ...mapState("person",["personList"]) }, methods:{ ...mapActions("person",["add_Person"]), addPerson(){ if (this.tempname===""){ return "" } if(this.tag){ // 映射出來的函數(shù) this.add_Person(this.tempname) }else{ //[Vue warn]: Error in v-on handler: "TypeError: this.addPersonWang is not a function" // 如果沒有使用映射的話,不能這樣使用 // this.addPersonWang(this.tempname) // 正確的使用方式(使用模塊命名空間進(jìn)行限定) this.$store.dispatch("person/addPersonWang",this.tempname) } this.tempname="" }, } } </script> <style> </style>
除此之外還可以通過下面方法獲取到vuex中的state中的屬性。
this.$store.state.模塊名.屬性
this.$store.state.loginModel.userInfo
到此這篇關(guān)于Vue組件間通信的實現(xiàn)方法講解的文章就介紹到這了,更多相關(guān)Vue組件間通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vuex+axios+element-ui實現(xiàn)頁面請求loading操作示例
這篇文章主要介紹了vuex+axios+element-ui實現(xiàn)頁面請求loading操作,結(jié)合實例形式分析了vuex+axios+element-ui實現(xiàn)頁面請求過程中l(wèi)oading遮罩層相關(guān)操作技巧與使用注意事項,需要的朋友可以參考下2020-02-02關(guān)于element-ui?單選框默認(rèn)值不選中的解決
這篇文章主要介紹了關(guān)于element-ui?單選框默認(rèn)值不選中的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09