>  기사  >  웹 프론트엔드  >  Vue 구성 요소 간의 통신 방법에 대한 자세한 분석

Vue 구성 요소 간의 통신 방법에 대한 자세한 분석

WBOY
WBOY앞으로
2022-10-01 09:00:252276검색

이 기사에서는 Vue 구성 요소 간의 통신 방법을 주로 소개하는 vue에 대한 관련 지식을 제공합니다. 공식적으로 출시된 Vuex 상태 관리 솔루션은 통신 문제를 매우 잘 해결할 수 있지만 Vue 구성 요소 간의 통신은 항상 중요한 주제였습니다. 하지만 컴포넌트 라이브러리 내에서 Vuex를 사용하는 것이 더 어려운 경우가 많습니다. 모두에게 도움이 되기를 바랍니다.

[관련 권장사항: javascript 비디오 튜토리얼, vue.js 튜토리얼]

Vue 컴포넌트 라이브러리 개발 과정에서 Vue 컴포넌트 간의 통신은 항상 중요한 주제였습니다. 상태 관리 솔루션은 구성 요소 간의 통신 문제를 매우 잘 해결할 수 있지만 구성 요소 라이브러리 내에서 Vuex를 사용하는 것은 종종 상당히 무거워집니다. 이 기사에서는 모든 사람이 참고할 수 있도록 구성 요소 간의 몇 가지 실제 통신 방법을 체계적으로 나열합니다.

컴포넌트 간 통신 시나리오

오늘 주제를 시작하기 전에 먼저 Vue 컴포넌트 간 통신에 대한 여러 시나리오를 요약해 보겠습니다. 일반적으로 다음 시나리오로 나눌 수 있습니다.

  • 아버지와 아들 컴포넌트 간 통신
  • 형제 컴포넌트 간 통신
  • 세대 컴포넌트 간 통신

부모와 자식 컴포넌트 간 통신

부모와 자식 컴포넌트 간 통신은 Vue 컴포넌트 통신 중 가장 단순해야 하며, 다음과 같이 요약할 수 있습니다. 두 부분: 상위 구성 요소는 props를 통해 하위 구성 요소에 데이터를 전달하고, 하위 구성 요소는 사용자 정의 이벤트를 통해 상위 구성 요소에 데이터를 전달합니다.

상위 구성 요소는 props를 통해 하위 구성 요소에 데이터를 전달합니다.

Vue 구성 요소의 데이터 흐름 방향은 단방향 데이터 흐름 원칙을 따릅니다. 모든 props는 상위와 하위 간의 단방향 하향 바인딩을 형성합니다. 확실히: 상위 props에 대한 업데이트는 하위 구성요소로 아래로 흐르지만 그 반대는 아닙니다. 이렇게 하면 하위 구성 요소가 실수로 상위 구성 요소의 상태를 변경하여 애플리케이션의 데이터 흐름을 이해하기 어렵게 만드는 것을 방지할 수 있습니다.

또한 상위 구성 요소가 변경될 때마다 하위 구성 요소의 모든 props가 최신 값으로 새로 고쳐집니다. 즉, 하위 구성 요소 내부의 소품을 변경하면 안 됩니다. 이렇게 하면 Vue는 브라우저 콘솔에 경고를 표시합니다.

상위 구성 요소 ComponentA:

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

하위 구성 요소 ComponentB:

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

하위 구성 요소는 사용자 정의 이벤트를 통해 상위 구성 요소에 데이터를 전달합니다.

하위 구성 요소에서는 $emit를 전달할 수 있습니다. > to the parent 구성 요소에서 이벤트가 발생하고 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 组件中的 状态提升

하위 구성 요소 ComponentA:

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

<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>
이 예는 하위 구성 요소 ComponentB에서 $emit를 사용하여 이벤트를 전달합니다. code>title-change, 상위 구성 요소 ComponentA는 @title-change에 바인딩된 titleChange 이벤트를 모니터링하고 ComponentB에서 ComponentA로 전달된 데이터는 titleChange 함수 매개변수에서 얻을 수 있습니다.

형제 구성요소 간의 통신상태 승격

React를 작성한 학생들은 구성 요소에 대한 상태 승격 개념에 익숙해야 합니다. React는 구성 요소를 서로 다른 책임에 따라 두 가지 범주로 나눕니다. 프리젠테이션 구성 요소 및 컨테이너 구성 요소.

디스플레이 구성 요소는 구성 요소가 사용하는 데이터를 어떻게 가져오는지, 구성 요소 데이터를 어떻게 수정해야 하는지 신경 쓰지 않습니다. 이 데이터를 사용하여 구성 요소 UI가 어떻게 보이는지 알면 됩니다. 외부 구성 요소는 프레젠테이션 구성 요소에 필요한 데이터와 이러한 데이터를 수정하는 콜백 함수에 prop을 전달합니다.

컨테이너 컴포넌트의 역할은 데이터와 이러한 데이터의 처리 로직을 얻고, props를 통해 하위 컴포넌트에 데이터와 로직을 제공하는 것입니다. 따라서 React 구성요소의 상태 승격 개념을 참고하여 두 형제 구성요소 위에 상위 구성요소를 제공합니다. 이는 컨테이너 구성요소와 동일하며 데이터 처리를 담당합니다. 형제 구성 요소는 props를 통해 매개변수를 받습니다. 콜백 함수는 형제 구성 요소 간의 통신 문제를 해결하기 위한 표시 구성 요소와 동일합니다.

🎜🎜ComponentA(형제 컴포넌트 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(형제 컴포넌트 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(컨테이너 컴포넌트 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>
🎜보시다시피 위에서 언급한 "상태 개선" " 이 방법은 상대적으로 번거롭고, 특히 형제 구성 요소 간의 통신에는 상위 구성 요소의 도움이 필요합니다. 복잡한 구성 요소를 처리하는 것은 상당히 번거롭습니다. 🎜🎜세대로 구분된 구성 요소 간의 통신🎜🎜🎜세대로 구분된 구성 요소 간의 통신은 다음과 같은 방법으로 달성할 수 있습니다. 🎜🎜
  • $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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 jb51.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제