Maison  >  Article  >  interface Web  >  Analyse détaillée des méthodes de communication entre les composants Vue

Analyse détaillée des méthodes de communication entre les composants Vue

WBOY
WBOYavant
2022-10-01 09:00:252332parcourir

Cet article vous apporte des connaissances pertinentes sur vue, présentant principalement les méthodes de communication entre les composants Vue. La communication entre les composants Vue a toujours été un sujet important, bien que la solution de gestion d'état Vuex officiellement lancée puisse très bien résoudre le problème de la communication des composants. problèmes entre eux, mais utiliser Vuex dans la bibliothèque de composants est souvent plus difficile. Examinons-le ensemble, j'espère que cela sera utile à tout le monde.

[Recommandations associées : Tutoriel vidéo javascript, Tutoriel vue.js]

Pendant le processus de développement de la bibliothèque de composants Vue, la communication entre les composants Vue a toujours été un sujet important, bien que le Vuex officiel La solution de gestion d'état peut très bien résoudre le problème de communication entre les composants, mais l'utilisation de Vuex dans la bibliothèque de composants est souvent assez lourde. Cet article répertorie systématiquement plusieurs méthodes de communication plus pratiques entre les composants sans utiliser Vuex pour la référence de tous.

Scénarios de communication entre composants

Avant d'entrer dans notre sujet aujourd'hui, résumons d'abord plusieurs scénarios de communication entre composants Vue, qui peuvent généralement être divisés selon les scénarios suivants :

  • Père et fils Communication entre composants
  • Communication entre composants frères et sœurs
  • Communication entre composants générationnels

Communication entre composants parent et enfant

La communication entre composants parent et enfant devrait être la communication de composant Vue la plus simple. La plus courante, qui peut être résumée en deux. parts : Le composant parent transmet les données au composant enfant via des accessoires, et le composant enfant transmet les données au composant parent via des événements personnalisés.

Le composant parent transmet les données au composant enfant via des accessoires

Le sens du flux de données du composant Vue suit le principe du flux de données unidirectionnel Tous les accessoires forment une liaison aval unidirectionnelle entre eux. accessoires parent et enfant Certainement : les mises à jour des accessoires parent seront transmises aux composants enfants, mais pas l'inverse. Cela empêchera le composant enfant de modifier accidentellement l'état du composant parent, ce qui rendrait le flux de données de votre application difficile à comprendre.

De plus, chaque fois que le composant parent change, tous les accessoires du composant enfant seront actualisés avec les dernières valeurs. Cela signifie que vous ne devez pas modifier les accessoires à l'intérieur d'un composant enfant. Si vous faites cela, Vue émettra un avertissement dans la console du navigateur.

Composant parent ComposantA :

<template>
  <p>
    <component-b title="welcome"></component-b>
  </p>
</template>
<script>
import ComponentB from &#39;./ComponentB&#39;

export default {
  name: &#39;ComponentA&#39;,
  components: {
    ComponentB
  }
}
</script>

Composant enfant ComposantB :

<template>
  <p>
    <p>{{title}}</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String,
    }
  }
} 
</script>

Le composant enfant transmet les données au composant parent via des événements personnalisés

Dans le composant enfant, vous pouvez transmettre $emit au parent Un événement se produit dans le composant et est surveillé dans le composant parent via v-on/@.

$emit 向父组件发生一个事件,在父组件中通过 v-on/@ 进行监听。

子组件 ComponentA:

<template>
  <p>
    <component-b :title="title" @title-change="titleChange"></component-b>
  </p>
</template>
<script>
import ComponentB from &#39;./ComponentB&#39;

export default {
  name: &#39;ComponentA&#39;,
  components: {
    ComponentB
  },
  data: {
    title: &#39;Click me&#39;
  },
  methods: {
    titleChange(newTitle) {
      this.title = newTitle
    } 
  }
}
</script>

子组件 ComponentB:

<template>
  <p>
    <p @click="handleClick">{{title}}</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String,
    }
  },
  methods: {
    handleClick() {
      this.$emit(&#39;title-change&#39;, &#39;New title !&#39;)
    }  
  }
} 
</script>

这个例子非常简单,在子组件 ComponentB 里面通过 $emit 派发一个事件 title-change,在父组件 ComponentA 通过 @title-change 绑定的 titleChange 事件进行监听,ComponentB 向 ComponentA 传递的数据在 titleChange 函数的传参中可以获取到。

兄弟组件之间的通信

状态提升

写过 React 的同学应该对组件的 状态提升 概念并不陌生,React 里面将组件按照职责的不同划分为两类:展示型组件(Presentational Component) 和 容器型组件(Container Component)

展示型组件不关心组件使用的数据是如何获取的,以及组件数据应该如何修改,它只需要知道有了这些数据后,组件UI是什么样子的即可。外部组件通过 props 传递给展示型组件所需的数据和修改这些数据的回调函数,展示型组件只是它们的使用者。

容器型组件的职责是获取数据以及这些数据的处理逻辑,并把数据和逻辑通过 props 提供给子组件使用。

因此,参考 React 组件中的 状态提升

Sous-composant ComposantA:

<template>
  <p>
    <p>{{title}}</p>
    <p @click="changeTitle">click me</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentA&#39;,
  props: {
    title: {
      type: String
    },
    changeTitle: Function
  }
}
</script>

Sous-composant ComposantB:

<template>
  <p>
    <p>{{title}}</p>
    <p @click="changeTitle">click me</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String
    },
    changeTitle: Function
  }
}
</script>
Cet exemple est très simple Dans le sous-composant ComposantB, utilisez $emit pour distribuer un événement <. code>title-change, le composant parent ComponentA surveille l'événement titleChange lié ​​par @title-change, et les données transmises par ComponentB à ComponentA sont dans titleChange Il peut être obtenu à partir des paramètres de la fonction.

Communication entre composants frères et sœursPromotion du statut

Les étudiants qui ont écrit React doivent être familiers avec le concept de promotion du statut pour les composants. React divise les composants en deux catégories selon différentes responsabilités : . Composant de présentation et Composant conteneur.

Le composant d'affichage ne se soucie pas de la manière dont les données utilisées par le composant sont obtenues et de la manière dont les données du composant doivent être modifiées. Il a seulement besoin de savoir à quoi ressemble l'interface utilisateur du composant avec ces données. Les composants externes transmettent des accessoires aux données requises par les composants de présentation et les fonctions de rappel qui modifient ces données. Les composants de présentation ne sont que leurs utilisateurs.

La responsabilité du composant conteneur est d'obtenir les données et la logique de traitement de ces données, et de fournir les données et la logique aux sous-composants via des accessoires. Par conséquent, en nous référant au concept de Promotion d'état dans les composants React, nous fournissons un composant parent au-dessus des deux composants frères, qui est équivalent au composant conteneur et est responsable du traitement des données. Les composants frères et sœurs reçoivent des paramètres via les accessoires et la fonction de rappel est équivalente au composant d'affichage pour résoudre le problème de communication entre les composants frères.

🎜🎜ComponentA (composant frère A) : 🎜🎜
<template>
  <p>
    <component-a :title="titleA" :change-title="titleAChange"></component-a>
    <component-b :title="titleB" :change-title="titleBChange"></component-b>
  </p>
</template>
<script>
import ComponentA from &#39;./ComponentA&#39;
import ComponentB from &#39;./ComponentB&#39;

export default {
  name: &#39;ComponentC&#39;,
  components: {
    ComponentA,
    ComponentB
  },
  data: {
    titleA: &#39;this is title A&#39;,
    titleB: &#39;this is title B&#39;
  },
  methods: {
    titleAChange() {
      this.titleA = &#39;change title A&#39;
    },
    titleBChange() {
      this.titleB = &#39;change title B&#39;
    }
  }
}
</script>
🎜🎜ComponentB (composant frère B) : 🎜🎜
<template>
  <component-a name="Lin" age="24" sex="male"></component-a>
</template>
<script>
import ComponentB from &#39;@/components/ComponentB.vue&#39;

export default {
  name: &#39;App&#39;,
  components: {
    ComponentA
  }
}
</script>
🎜🎜ComponentC (composant conteneur C) : 🎜🎜
<template>
  <p>
    I am component B
    <component-c v-bind="$attrs"></component-c>
  </p>
</template>
<script>
import ComponentC from &#39;@/components/ComponentC.vue&#39;

export default {
  name: &#39;ComponentB&#39;,
  inheritAttrs: false,
  components: {
    ComponentC
  }
}
</script>
🎜Comme vous pouvez le constater, « l'amélioration du statut » mentionnée ci-dessus " La méthode est assez lourde, en particulier la communication entre les composants frères et sœurs nécessite l'aide des composants parents. Il est assez gênant de gérer des composants complexes. 🎜🎜Communication entre composants séparés par génération🎜🎜🎜La communication entre composants séparés par génération peut être réalisée des manières suivantes : 🎜🎜
  • $attrs/$listeners
  • rovide/inject
  • 基于 $parent/$children 实现的 dispatch 和 broadcast

attrs/attrs/listeners

Vue 2.4.0 版本新增了 $attrs 和 $listeners 两个方法。先看下官方对 $attrs 的介绍:

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定(class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

看个例子:

组件A(ComponentA):

<template>
  <component-a name="Lin" age="24" sex="male"></component-a>
</template>
<script>
import ComponentB from &#39;@/components/ComponentB.vue&#39;

export default {
  name: &#39;App&#39;,
  components: {
    ComponentA
  }
}
</script>

组件B(ComponetB):

<template>
  <p>
    I am component B
    <component-c v-bind="$attrs"></component-c>
  </p>
</template>
<script>
import ComponentC from &#39;@/components/ComponentC.vue&#39;

export default {
  name: &#39;ComponentB&#39;,
  inheritAttrs: false,
  components: {
    ComponentC
  }
}
</script>

组件C(ComponetC):

<template>
  <p>
    I am component C
  </p>
</template>
<script>

export default {
  name: &#39;ComponentC&#39;,
  props: {
    name: {
      type: String
    }
  },
  mounted: function() {
    console.log(&#39;$attrs&#39;, this.$attrs)
  }
}
</script>

这里有三个组件,祖先组件(ComponentA)、父组件(ComponentB)和子组件(ComponentC)。这三个组件构成了一个典型的子孙组件之间的关系。

ComponetA 给 ComponetB 传递了三个属性 name、age 和 sex,ComponentB 通过 v-bind="$attrs" 将这三个属性再透传给 ComponentC, 最后在 ComponentC 中打印 $attrs 的值为:

{age: &#39;24&#39;, sex: &#39;male&#39;}

为什么我们一开始传递了三个属性,最后只打印了两个属性 age 和 sex 呢?因为在 ComponentC 的props 中声明了 name 属性,$attrs 会自动排除掉在 props 中声明的属性,并将其他属性以对象的形式输出。

说白了就是一句话,$attrs 可以获取父组件中绑定的非 Props 属性

一般在使用的时候会同时和 inheritAttrs 属性配合使用。

如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false

在 ComponentB 添加了 inheritAttrs=false 属性后,ComponentB 的dom结构中可以看到是不会继承父组件传递过来的属性:

Analyse détaillée des méthodes de communication entre les composants Vue

如果不加上 inheritAttrs=false 属性,就会自动继承父组件传递过来的属性:

Analyse détaillée des méthodes de communication entre les composants Vue

再看下 $listeners 的定义:

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

$listeners也能把父组件中对子组件的事件监听全部拿到,这样我们就能用一个v-on把这些来自于父组件的事件监听传递到下一级组件。

继续改造 ComponentB 组件:

<template>
  <p>
    I am component B
    <component-c v-bind="$attrs" v-on="$listeners"></component-c>
  </p>
</template>
<script>
import ComponentC from &#39;@/components/ComponentC.vue&#39;

export default {
  name: &#39;ComponentB&#39;,
  inheritAttrs: false,
  components: {
    ComponentC
  }
}
</script>

这里利用 $attrs 和 $listeners 方法,可以将祖先组件(ComponentA) 中的属性和事件透传给孙组件(ComponentC),这样就可以实现隔代组件之间的通信。

provide/inject

provide/inject 是 Vue 2.2.0 版本后新增的方法。

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

先看下简单的用法:

父级组件:

export default {
  provide: {
    name: &#39;Lin&#39;
  }
}

子组件:

export default {
  inject: [&#39;name&#39;],
  mounted () {
    console.log(this.name);  // Lin
  }
}

上面的例子可以看到,父组件通过 privide 返回的对象里面的值,在子组件中通过 inject 注入之后可以直接访问到。

但是需要注意的是,provide 和 inject 绑定并不是可响应的,按照官方的说法,这是刻意为之的

也就是说父组件 provide 里面的name属性值变化了,子组件中 this.name 获取到的值不变。

如果想让 provide 和 inject 变成可响应的,有以下两种方式:

  • provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
  • 使用 Vue 2.6 提供的 Vue.observable 方法优化响应式 provide

看一下第一种场景:

祖先组件组件(ComponentA):

export default {
  name: &#39;ComponentA&#39;,
  provide() {
    return {
      app: this
    }
  },
  data() {
    return {
       appInfo: {
         title: &#39;&#39;
       }
    }
  },
  methods: {
    fetchAppInfo() {
      this.appInfo = { title: &#39;Welcome to Vue world&#39;}
    }
  }
}

我们把整个 ComponentA.vue 的实例 this 对外提供,命名为 app。接下来,任何组件只要通过 inject 注入 app 的话,都可以直接通过 this.app.xxx 来访问 ComponentA.vue 的 datacomputedmethods 等内容。

子组件(ComponentB):

<template>
  <p>
    {{ title }}
    <button @click="fetchInfo">获取App信息</button>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  inject: [&#39;app&#39;],
  computed: {
    title() {
      return this.app.appInfo.title
    }
  },
  methods: {
    fetchInfo() {
      this.app.fetchAppInfo()
    } 
  }
}
</script>

这样,任何子组件,只要通过 inject 注入 app 后,就可以直接访问祖先组件中的数据了,同时也可以调用祖先组件提供的方法修改祖先组件的数据并反应到子组件上。

当点击子组件(ComponentB)的获取App信息按钮,会调用 this.app.fetchAppInfo 方法,也就是访问祖先组件(ComponentA)实例上的 fetchAppInfo 方法,fetchAppInfo 会修改fetchAppInfo的值。同时子组件(ComponentB)中会监听 this.app.appInfo 的变化,并将变化后的title值显示在组件上。

再看一下第二种场景,通过 Vue.observable 方法来实现 provide 和 inject 绑定并可响应。

基于上面的示例,改造祖先组件(ComponentA):

import Vue from &#39;vue&#39;

const state = Vue.observable({ title: &#39;&#39; });
export default {
  name: &#39;ComponentA&#39;,
  provide() {
    return {
      state
    }
  }
}

使用 Vue.observable 定义一个可响应的对象 state,并在 provide 中返回这个对象。

改造子组件(ComponentB):

<template>
  <p>
    {{ title }}
    <button @click="fetchInfo">获取App信息</button>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentInject&#39;,
  inject: [&#39;state&#39;],
  computed: {
    title() {
      return this.state.title
    }
  },
  methods: {
    fetchInfo() {
      this.state.title = &#39;Welcome to Vue world22&#39;
    } 
  }
}
</script>

与之前的例子不同的是,这里我们直接修改了 this.state.title 的值,因为 state 被定义成了一个可响应的数据,所以 state.title 的值被修改后,视图上的 title 也会立即响应并更新,从这里看,其实很像 Vuex 的处理方式。

以上两种方式对比可以发现,第二种借助于 Vue.observable 方法实现 provide 和 inject 的可响应更加简单高效,推荐大家使用这种方式。

基于 $parent/$children 实现的 dispatch 和 broadcast

先了解下 dispatch 和 broadcast 两个概念:

  • dispatch: 派发,指的是从一个组件内部向上传递一个事件,并在组件内部通过 $on 进行监听
  • broadcast: 广播,指的是从一个组件内部向下传递一个事件,并在组件内部通过 $on 进行监听

在实现 dispatch 和 broadcast 方法之前,先来看一下具体的使用方法。有 ComponentA.vue 和 ComponentB.vue 两个组件,其中 ComponentB 是 ComponentA 的子组件,中间可能跨多级,在 ComponentA 中向 ComponentB 通信:

组件ComponentA:

<template>
  <button @click="handleClick">派发事件</button>
</template>
<script>
import Emitter from &#39;../mixins/emitter.js&#39;;
export default {
  name: &#39;ComponentA&#39;,
  mixins: [Emitter],
  methods: {
    handleClick () {
      this.dispatch(&#39;ComponentB&#39;, &#39;on-message&#39;, &#39;Hello Vue.js&#39;)
    }
  }
}
</script>

组件ComponentB:

export default {
  name: &#39;ComponentB&#39;,
  created () {
    this.$on(&#39;on-message&#39;, this.showMessage)
  },
  methods: {
    showMessage (text) {
      console.log(text)
    }
  }
}

dispatch 的逻辑写在 emitter.js 中,使用的时候通过 mixins 混入到组件中,这样可以很好的将事件通信逻辑和组件进行解耦。

dispatch 的方法有三个传参,分别是:需要接受事件的组件的名字(全局唯一,用来精确查找组件)、事件名和事件传递的参数。

dispatch 的实现思路非常简单,通过 $parent 获取当前父组件对象,如果组件的name和接受事件的name一致(dispatch方法的第一个参数),在父组件上调用 $emit 发射一个事件,这样就会触发目标组件上 $on 定义的回调函数,如果当前组件的name和接受事件的name不一致,就递归地向上调用此逻辑。

dispath:

export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) {
          name = parent.$options.name
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    }
  }
}

broadcast逻辑和dispatch的逻辑差不多,只是一个是通过 $parent 向上查找,一个是通过 $children 向下查找,

export default {
  methods: {
    broadcast(componentName, eventName, params) {
      this.$children.forEach(child => {
        const name = child.$options.name
        if (name === componentName) {
          child.$emit.apply(child, [eventName].concat(params))
        } else {
          broadcast.apply(child, [componentName, eventName].concat([params]))
        }
      })
    }
  }
}

【相关推荐:javascript视频教程vue.js教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer