ホームページ  >  記事  >  ウェブフロントエンド  >  Vue コンポーネント間の通信方法の詳細な分析

Vue コンポーネント間の通信方法の詳細な分析

WBOY
WBOY転載
2022-10-01 09:00:252346ブラウズ

この記事では、vue に関する関連知識を提供し、主に Vue コンポーネント間の通信方法を紹介します。Vue コンポーネント間の通信は常に重要なトピックですが、正式にリリースされた Vuex 状態管理ソリューションでは、この問題を解決できます。コンポーネント間の通信の問題は非常にうまくいきますが、コンポーネント ライブラリ内で Vuex を使用すると比較的重いことがよくあります。一緒に見てみましょう。皆さんのお役に立てれば幸いです。

[関連する推奨事項: javascript ビデオ チュートリアルvue.js チュートリアル]Vue コンポーネントの

ライブラリ 開発プロセス中、Vue コンポーネント間の通信は常に重要なトピックでした。正式にリリースされた Vuex 状態管理ソリューションはコンポーネント間の通信の問題を非常にうまく解決できますが、コンポーネント ライブラリ内で Vuex を使用すると、多くの場合比較的重いです。この記事のシステム参考として、Vuex を使用しないコンポーネント間のより実用的な通信方法をいくつかリストします。

コンポーネント間の通信のシナリオ

今日のトピックに入る前に、まず Vue コンポーネント間の通信に関するいくつかのシナリオを要約しましょう。次のシナリオ:

  • 親コンポーネントと子コンポーネント間の通信
  • 兄弟コンポーネント間の通信
  • 世代コンポーネント間の通信

##親コンポーネントと子コンポーネント間の通信

親コンポーネントと子コンポーネント間の通信は、最も単純で最も一般的なタイプの Vue コンポーネント通信である必要があり、2 つの部分にまとめられています。 prop を通じてデータを子コンポーネントに渡し、子コンポーネントはカスタム イベントを通じて親コンポーネントにデータを渡します。

親コンポーネントは prop を通じて子コンポーネントにデータを渡します

Vue コンポーネントのデータ フロー方向は

一方向の原則に従いますデータ フロー

、すべてのプロップは、親プロップと子プロップの間で 一方向の下向きバインディングを形成します。親プロップへの更新は子コンポーネントに下向きに流れますが、その逆は起こりません。これにより、子コンポーネントが親コンポーネントの状態を誤って変更して、アプリケーションのデータ フローが理解しにくくなるのを防ぐことができます。 さらに、親コンポーネントが変更されるたびに、子コンポーネント内のすべてのプロパティが最新の値に更新されます。これは、子コンポーネント内の props を変更すべきではないことを意味します。これを行うと、Vue はブラウザーのコンソールに警告を発行します。

親コンポーネント コンポーネント A:

<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>

子コンポーネント コンポーネント B:

<template>
  <p>
    <p>{{title}}</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String,
    }
  }
} 
</script>
子コンポーネントは、カスタムを通じて親コンポーネントに渡されます。イベント データ

を子コンポーネントに渡して、

$emit

を通じて親コンポーネントにイベントを生成したり、親コンポーネントで v-on/# を通じてイベントを生成したりできます。 ##@ モニター。 サブコンポーネント コンポーネント A:

<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>
サブコンポーネント コンポーネント B:

<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 をディスパッチし、@title-change によってバインドされた titleChange イベントをリッスンします。親コンポーネント ComponentA では、 ComponentB によって ComponentA に渡されるデータは、 titleChange 関数のパラメーターで取得できます。 兄弟コンポーネント間の通信

ステータスの改善

React を作成した学生は、React Divide コンポーネントのコンポーネント

State Promotion

の概念に精通している必要があります。異なる役割に応じて、

プレゼンテーション コンポーネントコンテナ コンポーネント(コンテナ コンポーネント) の 2 つのカテゴリに分類されます。 表示コンポーネントは、コンポーネントによって使用されるデータがどのように取得されるか、コンポーネント データがどのように変更されるべきかについては考慮しません。必要なのは、このデータでコンポーネント UI がどのように見えるかを知ることだけです。外部コンポーネントは、プレゼンテーション コンポーネントが必要とするデータと、これらのデータを変更するコールバック関数に props を渡します。プレゼンテーション コンポーネントは単なるユーザーです。

コンテナ コンポーネントの役割は、データとそのデータの処理ロジックを取得し、props を通じてデータとロジックをサブコンポーネントに提供することです。

したがって、React コンポーネントの

State Promotion

の概念を参照して、2 つの兄弟コンポーネントの上にコンテナ コンポーネントに相当し、処理を担当する親コンポーネントを提供します。兄弟コンポーネントは props を渡します パラメーターとコールバック関数を受け取ることは、兄弟コンポーネント間の通信の問題を解決するためにコンポーネントを表示することと同じです。

ComponentA (兄弟コンポーネント A):

<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>
ComponentB (兄弟コンポーネント B):

<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>
ComponentC (コンテナ コンポーネント C):

<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>
ご覧のとおり、上記の「ステータスの昇格」方法は比較的面倒で、特に兄弟コンポーネント間の通信には親コンポーネントの助けが必要です。複雑で、対処するのが非常に面倒です。

世代を分けたコンポーネント間の通信

世代を分けたコンポーネント間の通信は、次の方法で実現できます:

  • $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结构中可以看到是不会继承父组件传递过来的属性:

Vue コンポーネント間の通信方法の詳細な分析

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

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教程

以上がVue コンポーネント間の通信方法の詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjb51.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。