您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“vue組件間如何進(jìn)行通信”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
vue是數(shù)據(jù)驅(qū)動(dòng)視圖更新的框架, 我們平時(shí)開(kāi)發(fā),都會(huì)把頁(yè)面不同模塊拆分成一個(gè)一個(gè)vue組件, 所以對(duì)于vue來(lái)說(shuō)組件間的數(shù)據(jù)通信非常重要,那么組件之間如何進(jìn)行數(shù)據(jù)通信的呢?
首先我們需要知道在vue中組件之間存在什么樣的關(guān)系, 才更容易理解他們的通信方式。
一般我們分為如下關(guān)系:
父子組件之間通信
非父子組件之間通信(兄弟組件、隔代關(guān)系組件等)
父組件通過(guò)props
的方式向子組件傳遞數(shù)據(jù),而通過(guò)$emit
子組件可以向父組件通信。
父組件向子組件傳值
<!-- 父組件 --> <template> <div class="section"> <child :msg="articleList"></child> </div> </template> <script> import child from './child.vue' export default { name: 'HelloWorld', components: { comArticle }, data() { return { msg: '阿離王' } } } </script>
<!-- 子組件 child.vue --> <template> <div> {{ msg }} </div> </template> <script> export default { props: { msg: String } } </script>
注意:
prop 只可以從上一級(jí)組件傳遞到下一級(jí)組件(父子組件),即所謂的單向數(shù)據(jù)流。而且 prop 只讀,不可被修改,所有修改都會(huì)失效并警告。
第一,不應(yīng)該在一個(gè)子組件內(nèi)部改變 prop,這樣會(huì)破壞單向的數(shù)據(jù)綁定,導(dǎo)致數(shù)據(jù)流難以理解。如果有這樣的需要,可以通過(guò) data 屬性接收或使用 computed
屬性進(jìn)行轉(zhuǎn)換。
第二,如果 props
傳遞的是引用類(lèi)型(對(duì)象或者數(shù)組)
,在子組件中改變這個(gè)對(duì)象或數(shù)組,父組件的狀態(tài)會(huì)也會(huì)做相應(yīng)的更新,利用這一點(diǎn)就能夠?qū)崿F(xiàn)父子組件數(shù)據(jù)的“雙向綁定”
,雖然這樣實(shí)現(xiàn)能夠節(jié)省代碼,但會(huì)犧牲數(shù)據(jù)流向的簡(jiǎn)潔性
,令人難以理解,最好不要這樣去做。
想要實(shí)現(xiàn)父子組件的數(shù)據(jù)“雙向綁定”,可以使用 v-model
或 .sync
。
子組件向父組件傳值
使用 $emit
向父組件傳數(shù)據(jù),原理這樣的: 父組件在子組件通過(guò)v-on
監(jiān)聽(tīng)函數(shù)并接收參數(shù),vue框架就在子組件監(jiān)聽(tīng)了你v-on="fn"
的fn事件函數(shù),在子組件使用$emit
就能觸發(fā)了,下面寫(xiě)個(gè)例子。
<!-- 父組件 --> <template> <div class="section"> <child :msg="articleList" @changMsg="changMsg"></child> </div> </template> <script> import child from './child.vue' export default { name: 'HelloWorld', components: { comArticle }, data() { return { msg: '阿離王' } }, methods:{ changMsg(msg) { this.msg = msg } } } </script>
<!-- 子組件 child.vue --> <template> <div> {{ msg }} <button @click="change">改變字符串</button> </div> </template> <script> export default { props: { msg: String }, methods: { change(){ this.$emit('changMsg', '阿離王帶你學(xué)習(xí)前端') } } } </script>
v-model
是用來(lái)在表單控件
或者組件
上創(chuàng)建雙向綁定
的,他的本質(zhì)是 v-bind
和 v-on
的語(yǔ)法糖
,在一個(gè)組件上使用 v-model
,默認(rèn)會(huì)為組件綁定名為 value 的 prop
和名為 input
的事件。
當(dāng)我們組件中的某一個(gè) prop
需要實(shí)現(xiàn)上面所說(shuō)的”雙向綁定“時(shí),v-model
就能大顯身手了。有了它,就不需要自己手動(dòng)在組件上綁定監(jiān)聽(tīng)當(dāng)前實(shí)例上的自定義事件,會(huì)使代碼更簡(jiǎn)潔
。
下面以一個(gè) input 組件實(shí)現(xiàn)的核心代碼,介紹下 v-model
的應(yīng)用。
<!--父組件--> <template> <base-input v-model="inputValue"></base-input> </template> <script> export default { data() { return { input: '' } }, } </script>
<!--子組件--> <template> <input type="text" :value="currentValue" @input="handleInput"> </template> <script> export default { data() { return { currentValue: this.value === undefined || this.value === null ? '' } }, props: { value: [String, Number], // 關(guān)鍵1 }, methods: { handleInput(event) { const value = event.target.value; this.$emit('input', value); // 關(guān)鍵2 }, }, } </script>
上面例子看到,v-model="inputValue"
他的本質(zhì)就是 v-bind 和 v-on 的語(yǔ)法糖
,默認(rèn)為父組件綁定名為 :value="inputValue"
的屬性,和@input="(v) => { this.inputValue = v }"
事件,子組件通過(guò) this.$emit('input', value)
通知父組件
所以他原理也是利用了我們上面講的父子組件傳參 props / $emit
方式來(lái)實(shí)現(xiàn)雙向綁定
有時(shí),在某些特定的控件中名為 value 的屬性會(huì)有特殊的含義,這時(shí)可以通過(guò) v-model
選項(xiàng)來(lái)回避這種沖突。
.sync
修飾符在 vue 1.x 的版本中就已經(jīng)提供,1.x 版本中,當(dāng)子組件改變了一個(gè)帶有 .sync
的 prop
的值時(shí),會(huì)將這個(gè)值同步到父組件中的值。這樣使用起來(lái)十分方便,但問(wèn)題也十分明顯,這樣破壞了單向數(shù)據(jù)流,當(dāng)應(yīng)用復(fù)雜時(shí),debug 的成本會(huì)非常高。
于是乎,在vue 2.0中移除了 .sync
。但是在實(shí)際的應(yīng)用中,.sync
是有它的應(yīng)用場(chǎng)景的,所以在 vue 2.3
版本中,又迎來(lái)了全新的 .sync
。
新的 .sync
修飾符所實(shí)現(xiàn)的已經(jīng)不再是真正的雙向綁定,它的本質(zhì)和 v-model
類(lèi)似,只是一種縮寫(xiě)。
正常封裝組件例子:
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" />
上面的代碼,使用 .sync
就可以寫(xiě)成
<text-document v-bind:title.sync="doc.title" />
這樣,在子組件中,就可以通過(guò)下面代碼來(lái)實(shí)現(xiàn)對(duì)這個(gè) prop 重新賦值了。
this.$emit('update:title', newTitle)
看到這里,是不是發(fā)現(xiàn) .sync
修飾符 和 v-model
很相似,也是語(yǔ)法糖, v-bind:title.sync
也就是 等效于 v-bind:title="doc.title" v-on:update:title="doc.title = $event"
.sync
從功能上看和 v-model
十分相似,都是為了實(shí)現(xiàn)數(shù)據(jù)的“雙向綁定”
,本質(zhì)上,也都不是真正的雙向綁定,而是語(yǔ)法糖
。
相比較之下,.sync
更加靈活,它可以給多個(gè) prop
使用,而 v-model
在一個(gè)組件中只能有一個(gè)。
從語(yǔ)義上來(lái)看,v-model
綁定的值是指這個(gè)組件的綁定值,比如 input 組件
,select 組件
,日期時(shí)間選擇組件
,顏色選擇器組件
,這些組件所綁定的值使用 v-model
比較合適。其他情況,沒(méi)有這種語(yǔ)義,個(gè)人認(rèn)為使用 .sync
更好。
通過(guò)$parent
和$children
就可以訪(fǎng)問(wèn)組件的實(shí)例,拿到實(shí)例代表什么?代表可以訪(fǎng)問(wèn)此組件的所有方法
和data
。列子如下:
<!-- 父組件 --> <template> <div class="hello_world"> <div>{{msg}}</div> <com-a></com-a> <button @click="changeA">點(diǎn)擊改變子組件值</button> </div> </template> <script> import ComA from './test/comA.vue' export default { name: 'HelloWorld', components: { ComA }, data() { return { msg: 'Welcome' } }, methods: { changeA() { // 獲取到子組件A this.$children[0].messageA = 'this is new value' } } } </script>
<!-- 子組件 --> <template> <div class="com_a"> <span>{{messageA}}</span> <p>獲取父組件的值為: {{parentVal}}</p> </div> </template> <script> export default { data() { return { messageA: 'this is old' } }, computed:{ parentVal(){ return this.$parent.msg; } } } </script>
要注意邊界情況,如在#app
上拿$parent
得到的是new Vue()
的實(shí)例,在這實(shí)例上再拿$parent
得到的是undefined
,而在最底層的子組件拿$children
是個(gè)空數(shù)組
。也要注意得到children的值不一樣,$children 的值是數(shù)組
,而$parent是個(gè)對(duì)象
props $emit
、 $parent $children
兩種方式用于父子組件之間的通信, 而使用props進(jìn)行父子組件通信更加普遍,二者皆不能用于非父子組件之間的通信。
provide / inject
是vue2.2.0新增的api, 簡(jiǎn)單來(lái)說(shuō)就是父組件中通過(guò)provide來(lái)提供變量
, 然后再子組件中通過(guò)inject來(lái)注入變量
。
官方描述: 這對(duì)選項(xiàng)需要一起使用,以允許一個(gè)祖先組件向其所有子孫后代注入一個(gè)依賴(lài),不論組件層次有多深,并在其上下游關(guān)系成立的時(shí)間里始終生效
provide
選項(xiàng)應(yīng)該是
一個(gè)對(duì)象或返回一個(gè)對(duì)象的函數(shù)。該對(duì)象包含可注入其子孫的屬性。在該對(duì)象中你可以使用 ES2015 Symbols 作為 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的環(huán)境下可工作。
inject
選項(xiàng)應(yīng)該是:
一個(gè)字符串?dāng)?shù)組
一個(gè)對(duì)象(詳情點(diǎn)擊這里)
// 祖先組件 提供foo //第一種 export default { name: "father", provide() { return { foo: 'hello' } }, } //第二種 export default { name: "father", provide: { foo:'hello~~~~' }, }
//后代組件 注入foo, 直接當(dāng)做this.foo來(lái)用 export default { inject:['foo'], }
上面的兩種用法有什么區(qū)別嗎?
如果你只是傳一個(gè)字符串,像上面的hello
,那么是沒(méi)有區(qū)別的,后代都能讀到。
如果你需要this
對(duì)象屬性的值(如下所示代碼),那么第二種是傳不了的,后代組件拿不到數(shù)據(jù)。所以建議只寫(xiě)第一種
//當(dāng)你傳遞對(duì)象給后代時(shí) provide() { return { test: this.msg } },
注意:一旦注入了某個(gè)數(shù)據(jù),比如上面示例中的 foo,那這個(gè)組件中就不能再聲明 foo 這個(gè)數(shù)據(jù)了,因?yàn)樗呀?jīng)被父級(jí)占有。
這是刻意為之的。然而,如果你傳入了一個(gè)可監(jiān)聽(tīng)的對(duì)象
,那么其對(duì)象的屬性還是可響應(yīng)的。因?yàn)閷?duì)象是引用類(lèi)型。
先來(lái)個(gè)值類(lèi)型的數(shù)據(jù)(也就是字符串)例子,不會(huì)響應(yīng)
provide(){ return{ test:this.msg } }, data() { return { msg: "Welcome to Your Vue.js App", } } mounted(){ setTimeout(()=>{ this.msg = "halo world"; console.log(this._provided.msg) //log:Welcome to Your Vue.js App },3000) },
如上所示,這樣做是不行的,打印出來(lái)的 _provided
中的數(shù)據(jù)并沒(méi)有改,子組件取得值也沒(méi)變。
你甚至可以直接給 this._provided.msg
賦值,但是即使是_provided.msg 里面的值改變了,子組件的取值,依然沒(méi)有變。
當(dāng)你的參數(shù)是對(duì)象的時(shí)候,就可以響應(yīng)了,如下:
provide(){ return{ test:this.activeData } }, data() { return { activeData:{name:'halo'}, } } mounted(){ setTimeout(()=>{ this.activeData.name = 'world'; },3000) }
這就是vue官方中寫(xiě)道的對(duì)象的屬性是可以響應(yīng)的
provide/inject不是只能從祖先傳遞給后代嗎?是的,但是,如果我們綁定到最頂層的組件app.vue,是不是所有后代都接收到了,就是當(dāng)做全局變量來(lái)用了。
//app.vue export default { name: 'App', provide(){ return{ app:this } }, data(){ return{ text:"it's hard to tell the night time from the day" } }, methods:{ say(){ console.log("Desperado, why don't you come to your senses?") } } }
//其他所有子組件,需要全局變量的,只需要按需注入app即可 export default { inject:['foo','app'], mounted(){ console.log(this.app.text); // 獲取app中的變量 this.app.say(); // 可以執(zhí)行app中的方法,變身為全局方法! } }
用vue-router
重新路由到當(dāng)前頁(yè)面,頁(yè)面是不進(jìn)行刷新的
采用window.reload()
,或者router.go(0)
刷新時(shí),整個(gè)瀏覽器進(jìn)行了重新加載,閃爍,體驗(yàn)不好
那我們?cè)趺醋瞿兀?/strong>
跟上面的原理差不多,我們只在控制路由的組件中寫(xiě)一個(gè)函數(shù)(使用v-if控制router-view的顯示隱藏,這里的原理不作贅述),然后把這個(gè)函數(shù)傳遞給后代,然后在后代組件中調(diào)用這個(gè)方法即可刷新路由啦。
//app.vue <router-view v-if="isShowRouter"/> export default { name: 'App', provide() { return { reload: this.reload } }, data() { return { isShowRouter: true, } }, methods:{ reload() { this.isShowRouter = false; this.$nextTick(() => { this.isShowRouter = true; }) } } }
//后代組件 export default { inject: ['reload'], }
這里 provide 使用了函數(shù)傳遞給后代,然后后代調(diào)用這個(gè)函數(shù),這種思路,也是可以做子后代向父組件傳參通訊的思路了。這里的原理,和 event 事件訂閱發(fā)布就很像了
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
;如果用在子組件
上,引用就指向組件實(shí)例
,可以通過(guò)實(shí)例直接調(diào)用組件的方法或訪(fǎng)問(wèn)數(shù)據(jù), 我們看一個(gè)ref 來(lái)訪(fǎng)問(wèn)組件的例子:
// 子組件 A.vue export default { data () { return { name: 'Vue.js' } }, methods: { sayHello () { console.log('hello') } } }
// 父組件 app.vue <template> <component-a ref="comA"></component-a> </template> <script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.name); // Vue.js comA.sayHello(); // hello } } </script>
ref 這種方式,就是獲取子組件的實(shí)例,然后可以直接子組件的方法和訪(fǎng)問(wèn)操作data的數(shù)據(jù),就是父組件控制子組件的一種方式,子組件想向父組件傳參或操作,只能通過(guò)其他的方式了
eventBus
呢,其實(shí)原理就是 事件訂閱發(fā)布,eventBus
又稱(chēng)為事件總線(xiàn),在vue中可以使用它來(lái)作為溝通橋梁的概念, 就像是所有組件共用相同的事件中心,可以向該中心注冊(cè)發(fā)送事件或接收事件, 所以組件都可以通知其他組件。
這里我們可以直接使用 vue 自帶的事件監(jiān)聽(tīng),也就是 on,我們來(lái)簡(jiǎn)單封裝下:
首先需要?jiǎng)?chuàng)建一個(gè)事件總線(xiàn)并將其導(dǎo)出, 以便其他模塊可以使用或者監(jiān)聽(tīng)它.
新建一個(gè) event-bus.js
文件
// event-bus.js import Vue from 'vue' export const EventBus = new Vue()
發(fā)生事件
假設(shè)你有兩個(gè)組件: additionNum 和 showNum, 這兩個(gè)組件可以是兄弟組件也可以是父子組件;這里我們以兄弟組件為例:
<template> <div> <show-num-com></show-num-com> <addition-num-com></addition-num-com> </div> </template> <script> import showNumCom from './showNum.vue' import additionNumCom from './additionNum.vue' export default { components: { showNumCom, additionNumCom } } </script>
// addtionNum.vue 中發(fā)送事件 <template> <div> <button @click="additionHandle">+加法器</button> </div> </template> <script> import { EventBus } from './event-bus.js' console.log(EventBus) export default { data() { return { num: 1 } }, methods: { additionHandle() { EventBus.$emit('addition', { num: this.num++ }) } } } </script>
接收事件
// showNum.vue 中接收事件 <template> <div>計(jì)算和: {{count}}</div> </template> <script> import { EventBus } from './event-bus.js' export default { data() { return { count: 0 } }, mounted() { EventBus.$on('addition', param => { this.count = this.count + param.num; }) } } </script>
這樣就實(shí)現(xiàn)了在組件addtionNum.vue
中點(diǎn)擊相加按鈕, 在showNum.vue中利用傳遞來(lái)的 num 展示求和的結(jié)果.
移除事件監(jiān)聽(tīng)者
如果想移除事件的監(jiān)聽(tīng), 可以像下面這樣操作:
import { eventBus } from 'event-bus.js' EventBus.$off('addition')
這里使用自己封裝一套eventBus也行,方便自己想干啥就干啥, 下面貼封裝好的一套給大家
/* eslint-disable no-console */ // 事件映射表 let eventMap = {} /** * 監(jiān)聽(tīng)事件 * @param {string} eventName 事件名 * @param {function} listener 回調(diào)函數(shù) * @param {object} instance 注冊(cè)事件的實(shí)例 */ function on(eventName, listener, instance) { eventMap[eventName] = eventMap[eventName] || [] eventMap[eventName].push({ listener, instance, }) } // 監(jiān)聽(tīng)事件,只執(zhí)行一次 function once(eventName, listener, instance) { eventMap[eventName] = eventMap[eventName] || [] eventMap[eventName].push({ listener, instance, once: true, }) } // 解除事件監(jiān)聽(tīng) function off(eventName, listener) { // 解除所有事件監(jiān)聽(tīng) if (!eventName) { eventMap = {} return } // 沒(méi)有對(duì)應(yīng)事件 if (!eventMap[eventName]) { return } // 解除某事件監(jiān)聽(tīng) eventMap[eventName].forEach((currentEvent, index) => { if (currentEvent.listener === listener) { eventMap[eventName].splice(index, 1) } }) } // 發(fā)送事件,執(zhí)行對(duì)應(yīng)響應(yīng)函數(shù) function emit(eventName, ...args) { if (!eventMap[eventName]) { return } eventMap[eventName].forEach((currentEvent, index) => { currentEvent.listener(...args) if (currentEvent.once) { eventMap[eventName].splice(index, 1) } }) } // 顯示當(dāng)前注冊(cè)的事件,代碼優(yōu)化時(shí)使用 function showEventMap(targetEventName) { if (targetEventName) { // 查看具體某個(gè)事件的監(jiān)聽(tīng)情況 eventMap[targetEventName].forEach(eventItem => { console.log(targetEventName, eventItem.instance, eventItem.listener) }) } else { // 查看所以事件的監(jiān)聽(tīng)情況 Object.keys(eventMap).forEach(eventName => { eventMap[eventName].forEach(eventItem => { console.log(eventName, eventItem.instance, eventItem.listener) }) }) } } // 提供 vue mixin 方法,在 beforeDestroy 自動(dòng)注銷(xiāo)事件監(jiān)聽(tīng) export const mixin = { created() { // 重載 on 函數(shù),收集本組件監(jiān)聽(tīng)的事件,待消除時(shí),銷(xiāo)毀事件監(jiān)聽(tīng) this.$eventListenerList = [] this.$event = { off, once, emit, showEventMap } this.$event.on = (eventName, listener) => { this.$eventListenerList.push({ eventName, listener }) on(eventName, listener) } }, // 消除組件時(shí),自動(dòng)銷(xiāo)毀事件監(jiān)聽(tīng) beforeDestroy() { this.$eventListenerList.forEach(currentEvent => { off(currentEvent.eventName, currentEvent.listener) }) }, } export default { on, off, once, emit, showEventMap }
如何使用呢,只需在 項(xiàng)目的 main.js
, 引入 ,然后 Vue.mixin 即可,如下:
// main.js import Vue from 'vue' import { mixin as eventMixin } from '@/event/index' Vue.mixin(eventMixin)
在vue項(xiàng)目其他文件,就可以直接 this.$event.on
this.$event.$emit
如下:
this.$event.on('test', (v) => { console.log(v) }) this.$event.$emit('test', 1)
還順便封裝了個(gè)mixin, 好處呢,就是在vue頁(yè)面監(jiān)聽(tīng)事件后,頁(yè)面銷(xiāo)毀后,也自動(dòng)銷(xiāo)毀了事件監(jiān)聽(tīng)
Vuex
是一個(gè)專(zhuān)為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式
。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化.
Vuex
解決了多個(gè)視圖依賴(lài)于同一狀態(tài)和來(lái)自不同視圖的行為需要變更同一狀態(tài)的問(wèn)題,將開(kāi)發(fā)者的精力聚焦于數(shù)據(jù)的更新而不是數(shù)據(jù)在組件之間的傳遞上
state
:用于數(shù)據(jù)的存儲(chǔ),是store中的唯一數(shù)據(jù)源
getters
:如vue中的計(jì)算屬性一樣,基于state數(shù)據(jù)的二次包裝,常用于數(shù)據(jù)的篩選和多個(gè)數(shù)據(jù)的相關(guān)性計(jì)算
mutations
:類(lèi)似函數(shù),改變state數(shù)據(jù)的唯一途徑,且不能用于處理異步事件
actions
:類(lèi)似于mutation,用于提交mutation來(lái)改變狀態(tài),而不直接變更狀態(tài),可以包含任意異步操作
modules
:類(lèi)似于命名空間,用于項(xiàng)目中將各個(gè)模塊的狀態(tài)分開(kāi)定義和操作,便于維護(hù)
這里我們先新建 store文件夾
, 對(duì)Vuex進(jìn)行一些封裝處理
在 store 文件夾下添加 index.js
文件
// index.js // 自動(dòng)掛載指定目錄下的store import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) let modules = {} // @/store/module 目錄下的文件自動(dòng)掛載為 store 模塊 const subModuleList = require.context('@/store/modules', false, /.js$/) subModuleList.keys().forEach(subRouter => { const moduleName = subRouter.substring(2, subRouter.length - 3) modules[moduleName] = subModuleList(subRouter).default }) export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules })
在 store 文件夾下添加 module
文件夾,在module文件夾再新建 user.js
文件
// user.js import user from '@/utils/user.js' import userApi from '@/apis/user' import { OPEN_ACCOUNT_STAGE, STAGE_STATUS } from '@/constant' let getUserPromise = null export default { namespaced: true, state() { return { userInfo: null, // 用戶(hù)信息 isLogined: !!user.getToken(), // 是否已經(jīng)登錄 } }, mutations: { // 更新用戶(hù)信息 updateUser(state, payload) { state.isLogined = !!payload state.userInfo = payload }, }, actions: { // 獲取當(dāng)前用戶(hù)信息 async getUserInfo(context, payload) { // forceUpdate 表示是否強(qiáng)制更新 if (context.state.userInfo && !payload?.forceUpdate) { return context.state.userInfo } if (!getUserPromise || payload?.forceUpdate) { getUserPromise = userApi.getUserInfo() } // 獲取用戶(hù)信息 try { const userInfo = await getUserPromise context.commit('updateUser', userInfo) } finally { getUserPromise = null } return context.state.userInfo }, // 登出 async logout(context, payload = {}) { // 是否手動(dòng)退出 const { manual } = payload if (manual) { await userApi.postLogout() } user.clearToken() context.commit('updateUser', null) }, } }
然后在項(xiàng)目的 main.js
文件中引入
import Vue from 'vue' import App from '@/app.vue' import { router } from '@/router' import store from '@/store/index' const vue = new Vue({ el: '#app', name: 'root', router, store, render: h => h(App), })
封裝的很愉快了,然后就正常操作即可。
this.$store.state.user.isLogined this.$store.state.user.userInfo this.$store.commit('user/updateUser', {}) await this.$store.dispatch('user/logout', { manual: true })
這種通信比較簡(jiǎn)單,缺點(diǎn)是數(shù)據(jù)和狀態(tài)比較混亂,不太容易維護(hù)。
通過(guò)window.localStorage.getItem(key)
獲取數(shù)據(jù)
通過(guò)window.localStorage.setItem(key,value)
存儲(chǔ)數(shù)據(jù)
注意用JSON.parse()
/ JSON.stringify()
做數(shù)據(jù)格式轉(zhuǎn)換, localStorage / sessionStorage可以結(jié)合vuex, 實(shí)現(xiàn)數(shù)據(jù)的持久保存
,同時(shí)使用vuex解決數(shù)據(jù)和狀態(tài)混亂問(wèn)題.
對(duì)于小型的項(xiàng)目,通信十分簡(jiǎn)單,這時(shí)使用 Vuex 反而會(huì)顯得冗余和繁瑣,這種情況最好不要使用 Vuex,可以自己在項(xiàng)目中實(shí)現(xiàn)簡(jiǎn)單的 Store。
// store.js const store = { debug: true, state: { author: 'yushihu!' }, setAuthorAction (newValue) { if (this.debug) console.log('setAuthorAction triggered with', newValue) this.state.author = newValue }, deleteAuthorAction () { if (this.debug) console.log('deleteAuthorAction triggered') this.state.author = '' } } export default store
上面代碼原理就是,store.js文件
暴露出一個(gè)對(duì)象 store
,通過(guò)引入 store.js 文件
各個(gè)頁(yè)面來(lái)共同維護(hù)這個(gè)store對(duì)象
和 Vuex 一樣,store 中 state 的改變都由 store 內(nèi)部的 action 來(lái)觸發(fā),并且能夠通過(guò) console.log()
打印觸發(fā)的痕跡。這種方式十分適合在不需要使用 Vuex 的小項(xiàng)目中應(yīng)用。
與 $root
訪(fǎng)問(wèn)根實(shí)例的方法相比,這種集中式狀態(tài)管理的方式
能夠在調(diào)試過(guò)程中,通過(guò) console.log()
記錄來(lái)確定當(dāng)前變化是如何觸發(fā)的,更容易定位問(wèn)題。
通過(guò) $root
,任何組件都可以獲取當(dāng)前組件樹(shù)的根 Vue 實(shí)例
,通過(guò)維護(hù)根實(shí)例上的 data,就可以實(shí)現(xiàn)組件間的數(shù)據(jù)共享
。
//main.js 根實(shí)例 new Vue({ el: '#app', store, router, // 根實(shí)例的 data 屬性,維護(hù)通用的數(shù)據(jù) data: function () { return { author: '' } }, components: { App }, template: '<App/>', }); <!--組件A--> <script> export default { created() { this.$root.author = '于是乎' } } </script> <!--組件B--> <template> <div><span>本文作者</span>{{ $root.author }}</div> </template>
注意:通過(guò)這種方式,雖然可以實(shí)現(xiàn)通信,但在應(yīng)用的任何部分,任何時(shí)間發(fā)生的任何數(shù)據(jù)變化,都不會(huì)留下變更的記錄,這對(duì)于稍復(fù)雜的應(yīng)用來(lái)說(shuō),調(diào)試是致命的,不建議在實(shí)際應(yīng)用中使用。
現(xiàn)在我們來(lái)討論一種情況, 我們一開(kāi)始給出的組件關(guān)系圖中A組件與D組件是隔代關(guān)系, 那它們之前進(jìn)行通信有哪些方式呢?
使用props
綁定來(lái)進(jìn)行一級(jí)一級(jí)的信息傳遞, 如果D組件中狀態(tài)改變需要傳遞數(shù)據(jù)給A, 使用事件系統(tǒng)一級(jí)級(jí)往上傳遞
使用eventBus
,這種情況下還是比較適合使用, 但是碰到多人合作開(kāi)發(fā)時(shí), 代碼維護(hù)性較低, 可讀性也低
使用Vuex
來(lái)進(jìn)行數(shù)據(jù)管理, 但是如果僅僅是傳遞數(shù)據(jù)
, 而不做中間處理,使用Vuex處理感覺(jué)有點(diǎn)大材小用
了.
所以就有了 $attrs
/ $listeners
,通常配合 inheritAttrs
一起使用。
inheritAttrs
默認(rèn)情況下父作用域的不被認(rèn)作 props
的 attribute
綁定 (attribute bindings) 將會(huì)“回退”且作為普通的 HTML attribute 應(yīng)用在子組件的根元素上。當(dāng)撰寫(xiě)包裹一個(gè)目標(biāo)元素或另一個(gè)組件的組件時(shí),這可能不會(huì)總是符合預(yù)期行為。
通過(guò)設(shè)置 inheritAttrs
到 false
,這些默認(rèn)行為將會(huì)被去掉。而通過(guò) (同樣是 2.4 新增的) 實(shí)例 property $attrs 可以讓這些 attribute 生效,且可以通過(guò) v-bind 顯性的綁定到非根元素上。
注意:這個(gè)選項(xiàng)不影響 class
和 style
綁定。
上面是官方描述:還是很難懂。
簡(jiǎn)單的說(shuō)就是
inheritAttrs:true
時(shí)繼承除props之外的所有屬性
inheritAttrs:false
只繼承class 和 style屬性
$attrs
:包含了父作用域中不被認(rèn)為 (且不預(yù)期為) props 的特性綁定 (class 和 style 除外
),并且可以通過(guò) v-bind="$attrs"
傳入內(nèi)部組件。當(dāng)一個(gè)組件沒(méi)有聲明任何 props
時(shí),它包含所有父作用域的綁定 (class 和 style 除外)。
$listeners
:包含了父作用域中的 (不含 .native 修飾符
) v-on 事件監(jiān)聽(tīng)器。它可以通過(guò) v-on="$listeners"
傳入內(nèi)部組件。它是一個(gè)對(duì)象,里面包含了作用在這個(gè)組件上的所有事件監(jiān)聽(tīng)器,相當(dāng)于子組件繼承了父組件的事件
。
講了這么多文字概念,我們還是來(lái)看代碼例子吧:
新建一個(gè) father.vue 組件
<template> <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" /> </template> <script> import Child from '../components/child.vue' export default { name: 'father', components: { Child }, data () { return { name: '阿離王', age: 22, infoObj: { from: '廣東', job: 'policeman', hobby: ['reading', 'writing', 'skating'] } } }, methods: { updateInfo() { console.log('update info'); }, delInfo() { console.log('delete info'); } } } </script>
child.vue 組件:
<template> <!-- 通過(guò) $listeners 將父作用域中的事件,傳入 grandSon 組件,使其可以獲取到 father 中的事件 --> <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" /> </template> <script> import GrandSon from '../components/grandSon.vue' export default { name: 'child', components: { GrandSon }, props: ['name'], data() { return { height: '180cm', weight: '70kg' }; }, created() { console.log(this.$attrs); // 結(jié)果:age, infoObj, 因?yàn)楦附M件共傳來(lái)name, age, infoObj三個(gè)值,由于name被 props接收了,所以只有age, infoObj屬性 console.log(this.$listeners); // updateInfo: f, delInfo: f }, methods: { addInfo () { console.log('add info') } } } </script>
grandSon.vue 組件
<template> <div> {{ $attrs }} --- {{ $listeners }} <div> </template> <script> export default { props: ['weight'], created() { console.log(this.$attrs); // age, infoObj, height console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f this.$emit('updateInfo') // 可以觸發(fā) father 組件中的updateInfo函數(shù) } } </script>
這種方式的傳值雖然說(shuō)不常用,感覺(jué)可讀性不是很好。但其對(duì)于組件層級(jí)嵌套比較深,使用props會(huì)很繁瑣,或者項(xiàng)目比較小,不太適合使用 Vuex 的時(shí)候,可以考慮用它
“vue組件間如何進(jìn)行通信”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。