Vue的常見問題(七) - 元件的通訊方式

莫名就發表於2020-10-23

前言

元件結構示意圖
A-B、B-C、B-D都是父子關係;
C-D是兄弟關係;
A-C、A-D是隔代關係;

一、元件的通訊方式

1. props

父元件通過props向下傳遞資料給子元件。

<!-- 父元件 -->
<template> 
    <div id="app"> 
    	<!-- 前者自定義名稱便於子元件呼叫,後者要傳遞 資料名 -->
        <Child v-bind:child="users"></Child>  
    </div> 
</template> 
<script> 
    import Child from "./components/Child" //子元件 
    export default { 
        name: 'App', 
        data(){ 
            return{ 
              users:["Eric","Andy","Sai"]
            } 
        }, 
        components:{ 
            "Child":Child 
        } 
    } 
</script> 
<!-- 子元件 --> 
<template> 
    <div class="hello"> 
        <ul> 
            <li v-for="item in child">{{ item }}</li>
        </ul> 
    </div> 
</template> 
<script> 
    export default { 
        name: 'Hello World', 
        props:{ 
            child:{           //這個就是父元件中子標籤自定義名字 
              type:Array,     //對傳遞過來的值進行校驗 
              required:true   //必添 
            } 
          } 
    } 
</script> 

2. $emit()、$on()

子元件通過events給父元件傳送訊息,實際上就是子元件把自己的資料傳送到父元件。

<!-- 子元件 Header.vue -->
<template> 
  <div> 
    <h1 @click="changeTitle">{{ title }}</h1> //繫結一個點選事件 
  </div> 
</template> 
<script> 
    export default { 
      name: 'header', 
      data() { 
        return { 
          title:"Vue.js Demo" 
        } 
      }, 
      methods:{ 
        changeTitle() { 
          this.$emit("titleChanged","子向父元件傳值"); //自定義事件  傳遞值“子向父元件 傳值” 
        } 
      } 
    } 
</script> 
<!-- 父元件 -->
<template> 
  <div id="app"> 
    <header v-on:titleChanged="updateTitle"></header>
    <!-- 與子元件titleChanged自定義事件保持一致 
	   updateTitle($event)接受傳遞過來的文字 -->
    <h2>{{ title }}</h2> 
  </div> 
</template> 
<script> 
import Header from "./components/Header" 
    export default { 
      name: 'App', 
      data(){ 
        return{ 
          title:"傳遞的是一個值" 
        } 
      }, 
      methods:{ 
        updateTitle(e){   //宣告這個函式 
          this.title = e; 
        } 
      }, 
      components:{ 
       "app-header":Header, 
      } 
    } 
</script> 

3. eventbus事件匯流排

vue例項 作為事件匯流排(事件中心)用來觸發事件和監聽事件,可以通過此種方式進行元件間通。

// 建立事件匯流排bus
import Vue from 'vue' 

export defult new Vue() 
<!-- gg元件 -->
<template id="a"> 
  <div> 
    <h3>gg元件</h3> 
    <button @click="sendMsg">將資料傳送給dd元件</button>   </div> 
</template> 
<script> 
import bus from './bus' 
export default { 
    methods: { 
        sendMsg(){ 
            bus.$emit('sendTitle','傳遞的值') 
		}
	} 
} 
</script> 
<!-- dd元件 -->
<template> 
    <div> 
        接收gg傳遞過來的值:{{msg}} 
    </div> 
</template> 
<script> 
import bus from './bus' 
export default { 
    data(){ 
        return { 
            mag: '' 
        } 
    } 
    mounted(){ 
        bus.$on('sendTitle',(val)=>{
        	this.mag = val 
        }) 
    } 
} 
</script> 

4. $attrs和$listeners

$attrs:包含了父作用域中不被 prop 所識別 (且獲取) 的特性繫結 (class 和 style 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class 和 style 除外),並且可以通過v-bind="$attrs" 傳入內部元件。通常配合 interitAttrs 選項一起使用。

$listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件。

<!-- index.vue --> 
<template> 
  <div> 
    <h2>王者峽谷</h2> 
    <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"> </child-com1> 
  </div> 
</template> 
<script> 
    const childCom1 = () => import("./childCom1.vue"); 
    export default { 
      components: { childCom1 }, 
      data() { 
        return { 
          foo: "Javascript", 
          boo: "Html", 
          coo: "CSS", 
          doo: "Vue" 
        }; 
      } 
    }; 
</script> 
<!-- childCom1.vue --> 
<template class="border">   <div> 
    <p>foo: {{ foo }}</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: { 
        foo: String // foo作為props屬性繫結 
      }, 
      created() { 
        console.log(this.$attrs);  
        // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }       } 
    }; 
</script> 
<!-- childCom2.vue -->
<template> 
  <div class="border"> 
    <p>boo: {{ boo }}</p> 
    <p>childCom2: {{ $attrs }}</p> 
    <child-com3 v-bind="$attrs"></child-com3> 
  </div> 
</template> 
<script> 
const childCom3 = () => import("./childCom3.vue"); 
export default { 
  components: { 
    childCom3 
  }, 
  inheritAttrs: false, 
  props: { 
    boo: String 
  }, 
  created() { 
    console.log(this.$attrs);  
    // {"coo": "CSS", "doo": "Vue", "title": "前端工匠" }   } 
}; 
</script> 
<!-- childCom3.vue -->
<template> 
  <div class="border"> 
    <p>childCom3: {{ $attrs }}</p> 
  </div> 
</template> 
<script> 
    export default { 
      props: { 
        coo: String, 
        title: String
      } 
    }; 
</script> 

所示$attrs表示沒有繼承資料的物件,格式為{屬性名:屬性值}。Vue2.4提供了$attrs ,

$listeners 來傳遞資料與事件,跨級元件之間的通訊變得更簡單。

簡單來說:$attrs與$listeners 是兩個物件,$attrs 裡存放的是父元件中繫結的非 Props 屬性, $listeners裡存放的是父元件中繫結的非原生事件。

5. $parent、$children

訪問父 / 子例項。接得到元件例項,使用後可以直接呼叫元件的方法或訪問資料。

6. ref

如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子元件上,引用就指向元件例項。

// childCom.vue
export default { 
  data () { 
    return { 
      title: 'Vue.js' 
    } 
  }, 
  methods: { 
    sayHello () { 
      window.alert('Hello');     } 
  } 
} 
<template> 
  <childCom ref="comA"></childCom>
</template> 
<script> 
  export default { 
    mounted () { 
      const comA = this.$refs.comA; 
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 彈窗 
    } 
  } 
</script>

7. provide、inject

Vue2.2.0新增API,這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在起上下游關係成立的時間裡始終生效。
一言而蔽之:祖先元件中通過provider來 提供變數,然後在子孫元件中通過inject來注入變數。
provide / inject API 主要解決了跨級元件間的通訊問題,不過它的使用場景,主要是子元件獲取上級元件的狀態,跨級元件間建立了一種主動提供與依賴注入的關係。

7.1 基本用法

//a.vue 
export default { 
    provide: { 
        name: '王者峽谷' //這種繫結是不可響應的     } 
} 
// b.vue 
export default { 
    inject: ['name'], 
    mounted () { 
        console.log(this.name) //輸出王者峽谷     } 
} 

A.vue,我們設定了一個provide:name,值為王者峽谷,將name這個變數提供給它的所有子元件。

B.vue ,通過 inject 注入了從A元件中提供的name變數,元件B中,直接通過this.name訪問這個變數了。

這就是 provide / inject API 最核心的用法。

需要注意的是:provide 和inject繫結並不是可響應的。這是刻意為之的。
然而,如果你傳入了一個可監聽的物件,那麼其物件的屬性還是可響應的----vue官方文件,所以,上面 A.vue 的 name 如果改變了,B.vue 的 this.name 是不會改變的。

7.2 實現provide與inject的資料響應式的兩種方式

  1. provide祖先元件的例項,然後在子孫元件中注入依賴,這樣就可以在子孫元件中直接修改祖先元件的例項的屬性,不過這種方法有個缺點就是這個例項上掛載很多沒有必要的東西比如props, methods。
  2. 使用2.6最新API Vue.observable 優化響應式 provide(推薦) 。
    在這裡插入圖片描述
<!-- A 元件 -->  
<div> 
      <h1>A 元件</h1> 
      <button @click="() => changeColor()">改變color</button>
      <ChildrenB/> 
      <ChildrenC/> 
</div> 
<script>
  data() { 
    return { 
      color: "blue" 
    }; 
  }, 
  // provide() { 
  //   return { 
  //     theme: { 
  //       color: this.color //這種方式繫結的資料並不是可響應的 
  //     } // 即A元件的color變化後,元件D、E、F不會跟著變 
  //   }; 
  // }, 
  provide() { 
    return { 
      theme: this//方法一:提供祖先元件的例項 
    }; 
  }, 
  methods: { 
    changeColor(color) { 
      if (color) { 
        this.color = color; 
      } else { 
        this.color = this.color === "blue" ? "red" : "blue"; 
      } 
    } 
  } 
  // 方法二:使用2.6最新API Vue.observable 優化響應式 provide 
  // provide() { 
  //   this.theme = Vue.observable({ 
  //     color: "blue" 
  //   }); 
  //   return { 
  //     theme: this.theme 
  //   }; 
  // }, 
  // methods: { 
  //   changeColor(color) { 
  //     if (color) { 
  //       this.theme.color = color; 
  //     } else { 
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";   //     } 
  //   } 
  // } 
</script>
<!-- F 元件 --> 
<template functional> 
  <div class="border2"> 
    <h3 :style="{ color: injections.theme.color }">F 元件</h3>   </div> 
</template> 
<script> 
export default { 
  inject: { 
    theme: { 
      //函式式元件取值不一樣 
      default: () => ({}) 
    } 
  } 
}; 
</script> 

provide 和 inject主要為高階外掛/元件庫提供用例,能在業務中熟練運用,可以達到事半功倍的效果!

8. vuex

Vuex實現了一個單向資料流,在全域性擁有一個State存放資料,當元件要更改State中的資料時,必 須通過Mutation提交修改資訊,Mutation同時提供了訂閱者模式供外部外掛呼叫獲取State資料的更新。

二、總結

1. 父子元件間的通訊

  1. props
    ★★。父傳子。
  2. $emit()和$on()
    ★★。子傳父。
  3. $parent和$children
    非必要不推薦使用。 不符合低耦合原則。
  4. $attrs和$listeners
    ★★。視情況使用。

2. 兄弟(同級)元件間的通訊

  1. $parent、$root
    ★★。
  2. eventBus事件匯流排
    ★。視情況使用。專案過大可能不易維護。
  3. vuex
    ★★★。

3. 跨層級元件間的通訊

  1. $provide和$inject
    ★★★。provide 和 inject主要為高階外掛/元件庫提供用例,能在業務中熟練運用,可以達到事半功倍的效果!
  2. eventBus事件匯流排
    ★★。
  3. vuex
    ★★。

相關文章