首頁  >  文章  >  web前端  >  總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

青灯夜游
青灯夜游轉載
2022-04-18 21:23:282248瀏覽

面試官:你有多少種方式實作Vue元件間通訊?以下這篇文章為大家總結分享幾種實現Vue組件間通訊的方式,再也不怕面試了,希望對大家有幫助!

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

元件化是Vue 框架的重要想法之一,在前端框架還未出現的時候,通常一個網站頁面就是一個文件,如果一個頁面有什麼資料需要大家使用,直接宣告一個全域變數就好了。但是Vue 框架出現後,將一個頁面組件化了,意味著一個頁面被分割為了很多個文件,那麼組件之間數據的共享就成了一大問題,當然Vue 為實現組件間的數據共享提供了很多種方法,今天我們就來梳理一下到底有哪些方法? (學習影片分享:vuejs教學

因為專案中常用的就那麼幾種,所有經常有很多小夥伴在面試的時候說不全,所以還是建議好好理一理。

1.那些場景需要通訊?

由於Vue 所有的元件呈現元件樹的形態,所以元件間的通訊也有很多種情況,大致有以下幾種:

  • 父子元件間通訊
  • 兄弟元件間通訊
  • 隔代元件間通訊
  • 無相關元件間通訊

每個情境下建議的通訊方式也不一樣,需要根據不同的情境選擇最適合的組件間通訊方式。

2.props 和$emit

這種方式通常用於父子元件之間的傳值,父元件透過屬性的方式將值傳遞給子元件,子元件透過props進行接收。子元件透過事件的方式向父元件傳遞資料。

初始化項目:

我們建立一個最簡單的Vue項目,分別建了3個元件:parent、child1、child2,然後在APP.vue中引入parent元件,parent元件中引入child1和child2兩個子元件,初始運作介面如下:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

2.1 父元件傳值給子元件

接下來我們利用屬性方式從父元件傳遞值給子元件。

父元件範例程式碼:

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <div>
      <button @click="changeMsg">更改数据</button>
    </div>
    <child1 :msg="msg"></child1>
    <child2 :msg="msg"></child2>
  </div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
    };
  },
  components: {
    child1,
    child2,
  },
  methods: {
    // 点击按钮更改数据
    changeMsg() {
      this.msg = "变成小猪课堂";
    },
  },
};
</script>

我們將父元件中的msg透過:msg="msg"的方式傳遞給子元件,並且點擊按鈕的時候會修改父元件中的msg。

子元件範例程式碼:

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
};
</script>

子元件透過props屬性的方式接收父元件傳來的資料。

輸出結果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

當我們點擊按鈕的時候,父元件的資料發生變化,子元件接收的資料也跟著發生了變化。

注意::msg="msg"接收的msg是一個變量,可以參考bind的使用原理,不加法:則接收的就是一個字串。

2.2 子元件傳值給父元件

子元件可以透過$emit自訂事件的方式向父元件傳遞值,父元件需要監聽此事件來進行接收子元件傳來的值。

父元件範例程式碼:

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <div>
      <button @click="changeMsg">更改数据</button>
    </div>
    <div>子组件数据:{{ childData }}</div>
    <child1 :msg="msg" @childData="childData"></child1>
    <child2 :msg="msg"></child2>
  </div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
      childData: "",
    };
  },
  components: {
    child1,
    child2,
  },
  methods: {
    changeMsg() {
      this.msg = "变成小猪课堂";
    },
    // 监听子组件事件
    childData(data) {
      this.childData = data;
    },
  },
};
</script>

子元件範例程式碼:

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="sendData">传递数据给父组件</button>
    </div>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  methods: {
    // 点击按钮,使用$emit向父组件传递数据
    sendData() {
      this.$emit("childData", "我是子组件数据");
    },
  },
};
</script>

輸出結果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

我們在父元件中透過@childData="getChildData"的方式監聽childData事件,從而取得子元件傳遞的數據,子元件中透過點擊按鈕觸發$emit事件向父元件傳遞資料。當我們點擊按鈕「將資料傳遞給父元件」時,父元件便可以取得資料。

3.$parent取得父元件值

這種方式可以讓子元件非常方便的取得父元件的值,不只包含數據,還可以是方法。

子元件範例程式碼:

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="sendData">传递数据给父组件</button>
    </div>
    <div>
      <button @click="getParentData">使用$parent</button>
    </div>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  methods: {
    sendData() {
      this.$emit("childData", "我是子组件数据");
    },
    // 通过$parent方式获取父组件值
    getParentData() {
      console.log("父组件", this.$parent);
    },
  },
};
</script>

点击“使用parent”按钮时,通过parent”按钮时,通过parent获取父组件的属性或数据。

输出结果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

我们可以看到控制台打印出了父组件的所有属性,不仅仅包含了data数据,还有里面定义的一些方法等。

4.$children$refs获取子组件值

这两种方式和$parent非常的类似,它们可以直接获取子组件的相关属性或方法,不仅限于数据。

父组件示例代码:

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <div>
      <button @click="changeMsg">更改数据</button>
    </div>
    <div>
      <button @click="getChildByRef">使用$children和$refs</button>
    </div>
    <div>子组件数据:{{ childData }}</div>
    <child1 ref="child1" :msg="msg" @childData="getChildData"></child1>
    <child2 :msg="msg"></child2>
  </div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
      childData: "",
    };
  },
  components: {
    child1,
    child2,
  },
  methods: {
    changeMsg() {
      this.msg = "变成小猪课堂";
    },
    // 监听子组件的自定义事件
    getChildData(data) {
      this.childData = data;
    },
    // 使用$chilren和$refs获取子组件
    getChildByRef() {
      console.log("使用$children", this.$children);
      console.log("使用$refs", this.$refs.child1);
    },
  },
};
</script>

输出结果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

上段代码中,我们点击按钮,分别通过childrenchildren和refs的方式获取到了子组件,从而拿到子组件数据。需要注意的是,children会返回当前组件所包含的所有子组件数组,使用children会返回当前组件所包含的所有子组件数组,使用refs时,需要在子组件上添加ref属性,有点类似于直接获取DOM节点的操作。

5.使用$attrs$listeners

#$attrs是在Vue2.4.0之後新提出的,通常在多層組件傳遞資料的時候使用。很多小夥伴如果遇到多層元件資料傳遞的場景,他可能會直接選用Vuex進行傳遞,但是如果我們需要傳遞的資料沒有涉及到資料的更新和修改時,建議使用$arrts的方式,畢竟Vuex還是比較重。

官網解釋:

包含了父作用域中不作為 prop 被識別 (且獲取) 的 attribute 綁定 (class 和 style 除外)。當一個元件沒有宣告任何prop 時,這裡會包含所有父作用域的綁定(class 和style 除外),並且可以透過v-bind="$attrs" 傳入內部元件-在建立高等級的元件時非常有用。

官網的解釋還是比較難理解的,我們可以用更通俗一點的話在解釋一遍。

通俗解釋:

當父元件傳遞了很多資料給子元件時,子元件沒有宣告props來進行接收,那麼子元件中的attr#s屬性就包含了所有父元件傳來的資料(除開已經props聲明了的),子元件也可以使用# vbind=#"attrs屬性就包含了所有父元件傳來的資料(除開已經props宣告了的),子元件也可以使用v-bind="

"################attrs"的形式向它的子元件(孫子組件)傳遞數據,孫子組件使用$attrs的方式和它的父組件原理類似。 ###

说的再多可能还是没有代码来得简单易懂,我们新建一个孙子组件child1-child.vue,编写之后界面如下:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

5.1 $attrs的使用

我们在parent父组件中多传一点数据给child1组件。

parent组件示例代码:

// src/views/parent.vue
<template>
  <div class="parent-box">
    <p>父级组件</p>
    <child1
      ref="child1"
      :msg="msg"
      :msg1="msg1"
      :msg2="msg2"
      :msg3="msg3"
      :msg4="msg4"
      @childData="getChildData"
    ></child1>
  </div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  data() {
    return {
      msg: "我是父组件的数据",
      msg1: "parent数据1",
      msg2: "parent数据2",
      msg3: "parent数据3",
      msg4: "parent数据4",
      childData: "",
    };
  },
  components: {
    child1,
    child2,
  }
};
</script>

这里我们删除了一些本节用不到的代码,大家需要注意一下。

child1组件示例代码:

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <!-- 子组件child1-child -->
    <child1-child v-bind="$attrs"></child1-child>
  </div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
  components: {
    Child1Child,
  },
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  mounted() {
    console.log("child1组件获取$attrs", this.$attrs);
  }
};
</script>

输出结果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

上段代码中我们的parent父组件传递了5个数据给子组件:msg、msg1、msg2、msg3、msg4。但是在子组件中的props属性里面,我们只接收了msg。然后我们在子组件mounted中打印了$attrs,发现恰好少了props接收过的msg数据。

当我们在child1组件中使用attrs接收了组件后,可以使用vbind="attrs接收了组件后,可以使用v-bind="attrs"的形式在传递给它的子组件child1-child,上段代码中我们已经加上了v-bind。

child1-child组件示例代码:

// src/views/child1-child.vue
<template>
  <div class="child1-child">
    <p>我是孙子组件child1-child</p>
  </div>
</template>
<script>
export default {
  props: {
    msg1: {
      type: String,
      default: "",
    },
  },
  mounted() {
    console.log("child1-child组件$attrs", this.$attrs);
  },
};
</script>

输出结果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

我们发现child1-child组件中打印的$attrs中少了msg1,因为我们已经在props中接收了msg1。

5.2 $listeners 的使用

listeners属性和listeners属性和attrs属性和类型,只是它们传递的东西不一样。

官网的解释:

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

通俗的解释:

当父组件在子组件上定义了一些自定义的非原生事件时,在子组件内部可以通过$listeners属性获取到这些自定义事件。

它和attrs的区别很明显,attrs的区别很明显,attrs用来传递属性,$listeners用来传递非原生事件,我们在child1组件中打印一下看看。

child1组件示例代码:

// src/views/child1.vue
mounted() {
  console.log("child1组件获取$attrs", this.$attrs);
  console.log("child1组件获取$listeners", this.$listeners);
},

输出结果:

總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

可以发现输出了childData方法,这是我们在它的父组件自定义的监听事件。除次之外,$listeners可以通过v-on的形式再次传递给下层组件。

child1组件示例代码:

// src/views/child1.vue
<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="sendData">传递数据给父组件</button>
    </div>
    <div>
      <button @click="getParentData">使用$parent</button>
    </div>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
    <!-- 子组件child1-child -->
    <child1-child v-bind="$attrs" v-on="$listeners"></child1-child>
  </div>
</template>

child1-child组件示例代码:

// src/views/child1-child.vue
mounted() {
  console.log("child1-child组件$attrs", this.$attrs);
  console.log("child1-child组件$listerners", this.$listeners);
},

输出结果:

1總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

可以看到在child1-child孙子组件中也获得了parent父组件中的childData自定义事件。使用listeners的好处在于:如果存在多层级组件,无需使用listeners的好处在于:如果存在多层级组件,无需使用emit的方式逐级向上触发事件,只需要使用$listerners就可以得到父组件中的自定义事件,相当于偷懒了。

5.3 inheritAttrs

可能细心的小伙伴会发现,我们在使用$attrs时,child1子组件渲染的DOM节点上将我们传递的属性一起渲染了出来,如下图所示:

1總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

这并不是我们想要的,为了解决这个问题,我们可以在子组件中设置inheritAttrs属性。

官网解释:

默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。

官网说了非常多,但是不太通俗,我们可以简单的理解。

通俗解释:

父组件传递了很多数据给子组件,子组件的props没有完全接收,那么父组件传递的这些数据就会渲染到HTML上,我们可以给子组件设置inheritAttrs 为false,避免这样渲染。

child1组件示例代码:

// src/views/child1.vue
props: {
  msg: {
    type: String,
    default: "",
  },
},
inheritAttrs: false,

输出结果:

1總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

此时我们节点上就没有那些无关的节点属性了。

5.4 总结

  • $attrs:用来传递属性,出了class、style之类的,它是一个对象。
  • $listeners:用来传递事件,出了原生事件,它也是一个对象。
  • attrsattrs和listeners这两个属性可以解决多层组件之间数据和事件传递的问题。
  • inheritAttrs解决未使用props接收的数据的属性渲染。

6.自定义事件:事件总线

在我们做项目的时候,会发现不相关的组件之间的数据传递是较为麻烦的,比如兄弟组件、跨级组件,在不使用Vuex情况下,我们可以使用自定义事件(也可以称作事件中心)的方式来实现数据传递。

事件中心的思想也比较简单:中间中心主要就两个作用:触发事件和监听事件。假如两个组件之间需要传递数据,组件A可以触发事件中心的事件,组件B监听事件中心的事件,从而让两个组件之间产生关联,实现数据传递。

实现步骤:

为了演示简单,我们在全局注册一个事件中心,修改main.js。

main.js代码如下:

// src/main.js
Vue.config.productionTip = false
Vue.prototype.$EventBus = new Vue()
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount(&#39;#app&#39;)

child1组件示例代码:

<template>
  <div class="child-1">
    <p>child1组件</p>
    <div>
      <button @click="toChild2">向child2组件发送数据</button>
    </div>
  </div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
  methods: {
    // 通过事件总线向child2组件发送数据
    toChild2() {
      this.$EventBus.$emit("sendMsg", "我是child1组件发来的数据");
    },
  },
};
</script>

child1组件中调用EventBus.EventBus.emit向事件中心添加sendMsg事件,这个用法有点类似与props和$emit的关系。

child2组件2示例代码:

// src/views/child1.vue
<template>
  <div class="child-2">
    <p>child2组件</p>
    <div>
      <p>parent组件数据:{{ msg }}</p>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    msg: {
      type: String,
      default: "",
    },
  },
  mounted() {
    this.$EventBus.$on("sendMsg", (msg) => {
      console.log("接收到child1发送来的数据", msg);
    });
  },
};
</script>

当我们点击child1组件中的按钮时,就会触发sendMsg事件,在child2组件中我们监听了该事件,所以会接收到child1组件发来的数据。

输出结果:

1總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

事件中心实现数据传递的这种方式,其实就是一个发布者和订阅者的模式,这种方式可以实现任何组件之间的通信。

7.provide和inject

这两个是在Vue2.2.0新增的API,provide和inject需要在一起使用。它们也可以实现组件之间的数据通信,但是需要确保组件之间是父子关系。

官网的解释:

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

官网的解释就已经说得很明确了,所以这里我们就不需要通俗的解释了,简单一句话:父组件可以向子组件(无论层级)注入依赖,每个子组件都可以获得这个依赖,无论层级。

parent示例代码:

// src/views/parent.vue
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  provide() {
    return { parentData: this.msg };
  },
  data() {
    return {
      msg: "我是父组件的数据",
      msg1: "parent数据1",
      msg2: "parent数据2",
      msg3: "parent数据3",
      msg4: "parent数据4",
      childData: "",
    };
  },
  components: {
    child1,
    child2,
  },
};
</script>

child1-child组件示例代码:

// src/views/child1-child.vue
<template>
  <div class="child1-child">
    <p>我是孙子组件child1-child</p>
    <p>parent组件数据:{{parentData}}</p>
  </div>
</template>
<script>
export default {
  inject: ["parentData"],
  props: {
    msg1: {
      type: String,
      default: "",
    },
  },
  mounted() {
    console.log("child1-child组件$attrs", this.$attrs);
    console.log("child1-child组件$listerners", this.$listeners);
    console.log("child1-child组件获取parent组件数据", this.parentData)
  },
};
</script>

输出结果:

1總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

通过provide和inject结合的方式,我们在child1-child组件中获取到了parent组件中的数据。如果你下来尝试过的话,可能会发现一个问题,此时数据不是响应式,也就是parent组件更改了数据,child1-child组件中的数据不会更新。

想要变为响应式的,我们需要修改一下provide传递的方式。

parent代码如下:

// src/views/parent.vue
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
  provide() {
    return { parentData: this.getMsg };
  },
  data() {
    return {
      msg: "我是父组件的数据",
      msg1: "parent数据1",
      msg2: "parent数据2",
      msg3: "parent数据3",
      msg4: "parent数据4",
      childData: "",
    };
  },
  components: {
    child1,
    child2,
  },
  methods: {
    // 返回data数据
    getMsg() {
      return this.msg;
    },
  },
};
</script>

这个时候我们会发现数据变为响应式的了。

porvide和inject的原理可以参考下图:

1總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

8.Vuex和localStorage

这两种方式应该是小伙伴们在实际项目中使用最多的了,所以这里就不但展开细说,只是提一下这两者的区别即可。

Vuex:

  • Vuex是状态管理器,它存储的数据不是持久化存储,一旦刷新页面或者关闭项目数据便不见了。
  • Vuex存储的数据是响应式的。

localstorage:

  • loacalStorage是HTML5中的一种数据存储方式,持久化存储,存储的数据不是响应式的。

9.v-model

v-model是vue中的一个内置指令,它通常用在表单元素上以此来实现数据的双向绑定,它的本质是v-on和v-bind的语法糖。在这里我们也可以借助它来实现某些场景下的数据传递。注意,这儿的场景必须是父子组件。

parent组件示例代码:

<template>
  <div class="parent-box">
    <p>父级组件</p>
    <div>modelData: {{modelData}}</div>
    <child2 :msg="msg" v-model="modelData"></child2>
    <!-- 实际等同于 -->
    <!-- <child2 v-bind:value="modelData" v-on:input="modelData=$event"></child2>  -->
  </div>
</template>
<script>
import child2 from "./child2.vue";
export default {
  provide() {
    return { parentData: this.getMsg };
  },
  data() {
    return {
      modelData: "parent组件的model数据"
    };
  },
  components: {
    child1,
  },
};
</script>

child2组件示例代码:

<template>
  <div class="child-2">
    <p>child2组件</p>
    <div>
      <button @click="confirm">修改v-model数据</button>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: String,
      default: "",
    },
  },
  mounted() {
    console.log("child2组件接收附件见v-model传递的数据", this.value);
  },
  methods: {
    // 通过$emit触发父组件的input事件,并将第二个参数作为值传递给父组件
    confirm() {
      this.$emit("input", "修改parent传递的v-model数据");
    },
  },
};
</script>

我们在父组件中使用v-model向child2子组件传递数据,子组件的props中使用默认的value属性接收,在子组件中利用$emit触发父组件中默认input事件,此时传递的数据便会在子组件和父组件中发生变化,这就是数据双向绑定。

如果想要更加详细的学习v-model的使用,可以参考官网。

总结

Vue中组件通讯的方式有很多种,每一种应用的场景可能都有一些不一样,我们需要在合适的场景下选择合适的通讯方式。

  • 父子元件間通訊:props與#em#itemit、parent、refsrefs和children、v-model
  • 兄弟元件間通訊:活動匯流排、Vuex、localStorage
  • #隔代組件間通訊:provide與inject
  • 無相關元件間通訊:事件匯流排、Vuex、localStorage
(學習影片分享:

web前端開發程式設計入門

以上是總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除