天天看點

vue的8種通信方式

原文連接配接:https://www.jianshu.com/bookmarks

一、props / emit

父元件通過 props 的方式向子元件傳遞資料,而通過 emit() 子元件可以向父元件通信。

  • 父元件向子元件傳值

下面通過一個例子說明父元件如何向子元件傳遞資料:

在子元件article.vue中如何擷取父元件 section.vue 中的資料 articles:['紅樓夢', '西遊記','三國演義']

// section父元件
<template>
  <div class="section">
    <com-article :articles="articleList"></com-article>
  </div>
</template>
 
<script>
import comArticle from "./test/article.vue";
export default {
  name: "HelloWorld",
  components: { comArticle },
  data() {
    return {
      articleList: ["紅樓夢", "西遊記", "三國演義"]
    };
  }
};
</script>
           
// 子元件 article.vue
<template>
  <div>
    <span v-for="(item, index) in articles" :key="index">{{item}}</span>
  </div>
</template>
 
<script>
export default {
  props: ["articles"]
};
</script>
           
總結: prop 隻可以從上一級元件傳遞到下一級元件(父子元件),即所謂的單向資料流。而且 prop 隻讀,不可被修改,所有修改都會失效并警告。
  • 子元件向父元件傳值

對于emit () 我自己的了解是這樣的 ,emit綁定一個自定義事件, 當這個語句被執行時, 就會将參數arg傳遞給父元件,父元件通過v-on監聽并接收參數。 通過一個例子,說明子元件如何向父元件傳遞資料。

在上個例子的基礎上, 點選頁面渲染出來的ariticle的item, 父元件中顯示在數組中的下标

// 父元件中
<template>
  <div class="section">
    <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
    <p>{{currentIndex}}</p>
  </div>
</template>
 
<script>
import comArticle from "./test/article.vue";
export default {
  name: "HelloWorld",
  components: { comArticle },
  data() {
    return {
      currentIndex: -1,
      articleList: ["紅樓夢", "西遊記", "三國演義"]
    };
  },
  methods: {
    onEmitIndex(idx) {
      this.currentIndex = idx;
    }
  }
};
</script>
           
// 在子元件中
<template>
  <div>
    <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
  </div>
</template>
 
<script>
export default {
  props: ["articles"],
  methods: {
    emitIndex(index) {
      this.$emit("onEmitIndex", index);
    }
  }
};
</script>
           

二 、children / parent
vue的8種通信方式

上面這張圖檔是vue官方的解釋,通過parent \ children就可以通路元件的執行個體,拿到執行個體代表什麼?代表可以通路此元件的所有方法和data。接下來就是怎麼實作拿到指定元件的執行個體
// 父元件中
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <com-a></com-a>
    <button @click="changeA">點選改變子元件值</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 得到的是 newValue 的執行個體,在這執行個體上在拿parent得到的是undefind,而在最底層的子元件拿children是個空數組,也要注意得到parent和children的值不一樣,children 的值是數組,而$parent是個對象

上面兩種方式用于父子元件之間的通信, 而使用props進行父子元件通信更加普遍;

二者皆不能用于非父子元件之間的通信。

三、provide/ inject 

provide/ inject 是vue2.2.0新增的api, 簡單來說就是父元件中通過provide來提供變量, 然後再子元件中通過inject來注入變量。

注意: 這裡不論子元件嵌套有多深, 隻要調用了inject 那麼就可以注入provide中的資料,而不局限于隻能從目前父元件的props屬性中回去資料

舉例驗證

接下來就用一個例子來驗證上面的描述:

假設有三個元件: A.vue、B.vue、C.vue 其中 C是B的子元件,B是A的子元件

// A.vue
 
<template>
  <div>
    <comB></comB>
  </div>
</template>
 
<script>
import comB from "../components/test/comB.vue";
export default {
  name: "A",
  provide: {
    for: "demo"
  },
  components: {
    comB
  }
};
</script>
           
// B.vue
 
<template>
  <div>
    {{demo}}
    <comC></comC>
  </div>
</template>
 
<script>
import comC from "../components/test/comC.vue";
export default {
  name: "B",
  inject: ["for"],
  data() {
    return {
      demo: this.for
    };
  },
  components: {
    comC
  }
};
</script>
           
// C.vue
<template>
  <div>{{demo}}</div>
</template>
 
<script>
export default {
  name: "C",
  inject: ["for"],
  data() {
    return {
      demo: this.for
    };
  }
};
</script>
           

四、ref / refs

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子元件上,引用就指向元件執行個體,可以通過執行個體直接調用元件的方法或通路資料, 我們看一個ref 來通路元件的例子:

// 子元件 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>
           

五、event/Bus

eventBus 又稱為事件總線,在vue中可以使用它來作為溝通橋梁的概念, 就像是所有元件共用相同的事件中心,可以向該中心注冊發送事件或接收事件, 是以元件都可以通知其他元件。

eventBus也有不友善之處, 當項目較大,就容易造成難以維護的災難

在Vue的項目中怎麼使用eventBus來實作元件之間的資料通信呢?具體通過下面幾個步驟

1. 初始化

首先需要建立一個事件總線并将其導出, 以便其他子產品可以使用或者監聽它.

 // event-bus.js

import Vue from 'vue'

export const EventBus = new Vue()

2. 發送事件

假設你有兩個元件: additionNum 和 showNum, 這兩個元件可以是兄弟元件也可以是父子元件;這裡我們以兄弟元件為例:

<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 中發送事件
 
<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>
           

3. 接收事件

// showNum.vue 中接收事件
 
<template>
 <div>計算和: {{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>
           

這樣就實作了在元件addtionNum.vue中點選相加按鈕, 在showNum.vue中利用傳遞來的 num 展示求和的結果.

4. 移除事件監聽者

如果想移除事件的監聽, 可以像下面這樣操作

import { eventBus } from'event-bus.js'

EventBus.$off(

'addition'

, {})

六、Vuex

1. Vuex介紹

Vuex 是一個專為 Vue.js 應用程式開發的 狀态管理 模式。它采用集中式存儲管理應用的所有元件的狀态,并以相應的規則保證狀态以一種可預測的方式發生變化.

Vuex 解決了多個視圖依賴于同一狀态和來自不同視圖的行為需要變更同一狀态的問題,将開發者的精力聚焦于資料的更新而不是資料在元件之間的傳遞上

2. Vuex各個子產品

  • state:用于資料的存儲,是store中的唯一資料源
  • getters:如vue中的計算屬性一樣,基于state資料的二次包裝,常用于資料的篩選和多個資料的相關性計算
  • mutations:類似函數,改變state資料的唯一途徑,且不能用于處理異步事件
  • actions:類似于mutation,用于送出mutation來改變狀态,而不直接變更狀态,可以包含任意異步操作
  • modules:類似于命名空間,用于項目中将各個子產品的狀态分開定義和操作,便于維護

3. Vuex執行個體應用

// 父元件
 
<template>
  <div id="app">
    <ChildA />
    <ChildB />
  </div>
</template>
 
<script>
import ChildA from "./components/ChildA"; // 導入A元件
import ChildB from "./components/ChildB"; // 導入B元件

export default {
  name: "App",
  components: { ChildA, ChildB } // 注冊A、B元件
};
</script>
           
// 子元件childA
 
<template>
  <div id="childA">
    <h1>我是A元件</h1>
    <button @click="transform">點我讓B元件接收到資料</button>
    <p>因為你點了B,是以我的資訊發生了變化:{{BMessage}}</p>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      AMessage: "Hello,B元件,我是A元件"
    };
  },
  computed: {
    BMessage() {
      // 這裡存儲從store裡擷取的B元件的資料
      return this.$store.state.BMsg;
    }
  },
  methods: {
    transform() {
      // 觸發receiveAMsg,将A元件的資料存放到store裡去
      this.$store.commit("receiveAMsg", {
        AMsg: this.AMessage
      });
    }
  }
};
</script>
           
// 子元件 childB
 
<template>
  <div id="childB">
    <h1>我是B元件</h1>
    <button @click="transform">點我讓A元件接收到資料</button>
    <p>因為你點了A,是以我的資訊發生了變化:{{AMessage}}</p>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      BMessage: "Hello,A元件,我是B元件"
    };
  },
  computed: {
    AMessage() {
      // 這裡存儲從store裡擷取的A元件的資料
      return this.$store.state.AMsg;
    }
  },
  methods: {
    transform() {
      // 觸發receiveBMsg,将B元件的資料存放到store裡去
      this.$store.commit("receiveBMsg", {
        BMsg: this.BMessage
      });
    }
  }
};
</script>
           

vuex的store,js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
 // 初始化A和B元件的資料,等待擷取
 AMsg: '',
 BMsg: ''
}
 
const mutations = {
 receiveAMsg(state, payload) {
  // 将A元件的資料存放于state
  state.AMsg = payload.AMsg
 },
 receiveBMsg(state, payload) {
  // 将B元件的資料存放于state
  state.BMsg = payload.BMsg
 }
}
 
export default new Vuex.Store({
 state,
 mutations
})
           

七、localStorage / sessionStorage

這種通信比較簡單,缺點是資料和狀态比較混亂,不太容易維護。

通過

window.localStorage.getItem(key) 

擷取資料

通過 

window.localStorage.setItem(key,value) 

存儲資料

注意用JSON.parse() / JSON.stringify() 做資料格式轉換

localStorage / sessionStorage可以結合vuex, 實作資料的持久儲存,同時使用vuex解決資料和狀态混亂問題.

八 attrs 與 listeners

現在我們來讨論一種情況, 我們一開始給出的元件關系圖中A元件與D元件是隔代關系, 那它們之前進行通信有哪些方式呢?

  • 使用props綁定來進行一級一級的資訊傳遞, 如果D元件中狀态改變需要傳遞資料給A, 使用事件系統一級級往上傳遞
  • 使用eventBus,這種情況下還是比較适合使用, 但是碰到多人合作開發時, 代碼維護性較低, 可讀性也低
  • 使用Vuex來進行資料管理, 但是如果僅僅是傳遞資料, 而不做中間處理,使用Vuex處理感覺有點大材小用了.
  • 在vue2.4中,為了解決該需求,引入了attr和listeners , 新增了inheritAttrs 選項。 在版本2.4以前,預設情況下,父作用域中不作為 prop 被識别 (且擷取) 的特性綁定 (class 和 style 除外),将會“回退”且作為普通的HTML特性應用在子元件的根元素上。接下來看一個跨級通信的例子:
// app.vue
// index.vue

<template>
  <div>
    <child-com1 :name="name" :age="age" :gender="gender" :height="height" title="程式員成長指北"></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      name: "zhang",
      age: "18",
      gender: "女",
      height: "158"
    };
  }
};
</script>
           
// childCom1.vue
<template class="border">
  <div>
    <p>name: {{ name}}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以關閉自動挂載到元件根元素上的沒有在props聲明的屬性
  props: {
    name: String // name作為props屬性綁定
  },
  created() {
    console.log(this.$attrs);
    // { "age": "18", "gender": "女", "height": "158", "title": "程式員成長指北" }
  }
};
</script>
           
// childCom2.vue
<template>
  <div class="border">
    <p>age: {{ age}}</p>
    <p>childCom2: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  inheritAttrs: false,
  props: {
    age: String
  },
  created() {
    console.log(this.$attrs);
    // { "gender": "女", "height": "158", "title": "程式員成長指北" }
  }
};
</script>
           

總結

常見使用場景可以分為三類:

父子元件通信: props; parent / children; provide / inject ; ref ;attrs /  listeners

兄弟元件通信: eventBus ; vuex

跨級通信: eventBus;Vuex;provide / inject 、atrs / listeners