溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

vue組件間如何進(jìn)行通信

發(fā)布時(shí)間:2021-12-07 08:59:49 來(lái)源:億速云 閱讀:119 作者:iii 欄目:編程語(yǔ)言

本篇內(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)系組件等)

props / $emit

父組件通過(guò)props的方式向子組件傳遞數(shù)據(jù),而通過(guò)$emit 子組件可以向父組件通信。

  1. 父組件向子組件傳值

<!-- 父組件 -->
<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。

  1. 子組件向父組件傳值

使用 $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 指令

v-model 是用來(lái)在表單控件或者組件上創(chuàng)建雙向綁定的,他的本質(zhì)是 v-bindv-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 修飾符

  • .sync 修飾符在 vue 1.x 的版本中就已經(jīng)提供,1.x 版本中,當(dāng)子組件改變了一個(gè)帶有 .syncprop 的值時(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"

v-model 和 .sync 對(duì)比

.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 更好。

parent/parent /children

通過(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ù)組。也要注意得到parentparent和children的值不一樣,$children 的值是數(shù)組,而$parent是個(gè)對(duì)象

props $emit 、 $parent $children兩種方式用于父子組件之間的通信, 而使用props進(jìn)行父子組件通信更加普遍,二者皆不能用于非父子組件之間的通信。

provide / inject

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í)占有。

provide 和 inject 綁定并不是可響應(yīng)的。

這是刻意為之的。然而,如果你傳入了一個(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 實(shí)現(xiàn)全局變量

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中的方法,變身為全局方法!
  }
}

provide/inject 實(shí)現(xiàn)頁(yè)面刷新,不閃爍

  1. vue-router重新路由到當(dāng)前頁(yè)面,頁(yè)面是不進(jìn)行刷新的

  2. 采用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 / $refs

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

eventBus呢,其實(shí)原理就是 事件訂閱發(fā)布,eventBus 又稱(chēng)為事件總線(xiàn),在vue中可以使用它來(lái)作為溝通橋梁的概念, 就像是所有組件共用相同的事件中心,可以向該中心注冊(cè)發(fā)送事件或接收事件, 所以組件都可以通知其他組件。

這里我們可以直接使用 vue 自帶的事件監(jiān)聽(tīng),也就是 emitemiton,我們來(lái)簡(jiǎn)單封裝下:

  1. 首先需要?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()
  1. 發(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>
  1. 接收事件

// 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é)果.

  1. 移除事件監(jiān)聽(tīng)者

如果想移除事件的監(jiān)聽(tīng), 可以像下面這樣操作:

import { eventBus } from 'event-bus.js'
EventBus.$off('addition')

自己封裝一套 eventBus

這里使用自己封裝一套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

Vuex介紹

  • 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ù)在組件之間的傳遞上

Vuex各個(gè)模塊

  • 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ù)

Vuex實(shí)例應(yīng)用

這里我們先新建 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 })

localStorage / sessionStorage

這種通信比較簡(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)題.

自己實(shí)現(xiàn)簡(jiǎn)單的 Store 模式

對(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 訪(fǎng)問(wèn)根實(shí)例

通過(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)用中使用。

attrsattrs與listeners

現(xiàn)在我們來(lái)討論一種情況, 我們一開(kāi)始給出的組件關(guān)系圖中A組件與D組件是隔代關(guān)系, 那它們之前進(jìn)行通信有哪些方式呢?

  1. 使用props綁定來(lái)進(jìn)行一級(jí)一級(jí)的信息傳遞, 如果D組件中狀態(tài)改變需要傳遞數(shù)據(jù)給A, 使用事件系統(tǒng)一級(jí)級(jí)往上傳遞

  2. 使用eventBus,這種情況下還是比較適合使用, 但是碰到多人合作開(kāi)發(fā)時(shí), 代碼維護(hù)性較低, 可讀性也低

  3. 使用Vuex來(lái)進(jìn)行數(shù)據(jù)管理, 但是如果僅僅是傳遞數(shù)據(jù), 而不做中間處理,使用Vuex處理感覺(jué)有點(diǎn)大材小用了.

所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。

inheritAttrs

默認(rèn)情況下父作用域的不被認(rèn)作 propsattribute 綁定 (attribute bindings) 將會(huì)“回退”且作為普通的 HTML attribute 應(yīng)用在子組件的根元素上。當(dāng)撰寫(xiě)包裹一個(gè)目標(biāo)元素或另一個(gè)組件的組件時(shí),這可能不會(huì)總是符合預(yù)期行為。

通過(guò)設(shè)置 inheritAttrsfalse,這些默認(rèn)行為將會(huì)被去掉。而通過(guò) (同樣是 2.4 新增的) 實(shí)例 property $attrs 可以讓這些 attribute 生效,且可以通過(guò) v-bind 顯性的綁定到非根元素上。

注意:這個(gè)選項(xiàng)不影響 classstyle 綁定。

上面是官方描述:還是很難懂。

簡(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í)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

vue
AI