Vue 구성요소 간에 통신하는 방법은 무엇입니까? 공유할 수 있는 12가지 의사소통 방법

青灯夜游
풀어 주다: 2021-12-06 19:18:03
앞으로
2199명이 탐색했습니다.

Vue 구성요소 간에 어떻게 통신하나요? 이 기사에서는 Vue 구성 요소 간 통신하는 12가지 방법에 대해 매우 자세히 소개합니다. 도움이 되기를 바랍니다.

Vue 구성요소 간에 통신하는 방법은 무엇입니까? 공유할 수 있는 12가지 의사소통 방법

vue는 데이터 기반 뷰 업데이트를 위한 프레임워크입니다. 일상적인 개발에서는 페이지의 여러 모듈을 vue 구성 요소로 분할합니다. 따라서 구성 요소 간의 데이터 통신은 vue에서 매우 중요하므로 어떻게 진행해야 할까요? 구성요소는 어떻습니까?

먼저 vue의 구성 요소들 사이에 어떤 관계가 존재하는지 알아야 그들의 통신 방식을 더 쉽게 이해할 수 있습니다. [관련 추천: "vue.js Tutorial"]

일반적으로 우리는 다음과 같은 관계로 나누어집니다.

  • 부모와 자식 컴포넌트 간의 통신
  • 부모가 아닌 컴포넌트와 자식 컴포넌트 간의 통신(형제 컴포넌트, 세대 구분)

props / $emit

부모 컴포넌트는 props를 통해 자식 컴포넌트에 데이터를 전달하고, 자식 컴포넌트는 $emit. <code>props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。

  1. 父组件向子组件传值
<!-- 父组件 -->
<template>
  <div class="section">
    <child :msg="articleList"></child>
  </div>
</template>

<script>
import child from &#39;./child.vue&#39;
export default {
  name: &#39;HelloWorld&#39;,
  components: { comArticle },
  data() {
    return {
      msg: &#39;阿离王&#39;
    }
  }
}
</script>
로그인 후 복사
<!-- 子组件 child.vue -->
<template>
  <div>
    {{ msg }}
  </div>
</template>

<script>
export default {
  props: {
      msg: String
  }
}
</script>
로그인 후 복사

注意:

prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。

  • 第一,不应该在一个子组件内部改变 prop,这样会破坏单向的数据绑定,导致数据流难以理解。如果有这样的需要,可以通过 data 属性接收或使用 computed 属性进行转换。
  • 第二,如果 props 传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。
  • 想要实现父子组件的数据“双向绑定”,可以使用 v-model.sync
  1. 子组件向父组件传值

使用 $emit 向父组件传数据,原理这样的: 父组件在子组件通过v-on监听函数并接收参数,vue框架就在子组件监听了你v-on="fn"的fn事件函数,在子组件使用$emit就能触发了,下面写个例子。

<!-- 父组件 -->
<template>
  <div class="section">
    <child :msg="articleList" @changMsg="changMsg"></child>
  </div>
</template>

<script>
import child from &#39;./child.vue&#39;
export default {
  name: &#39;HelloWorld&#39;,
  components: { comArticle },
  data() {
    return {
      msg: &#39;阿离王&#39;
    }
  },
  methods:{
      changMsg(msg) {
          this.msg = msg
      }
  }
}
</script>
로그인 후 복사
<!-- 子组件 child.vue -->
<template>
  <div>
    {{ msg }}
    <button @click="change">改变字符串</button>
  </div>
</template>

<script>
export default {
  props: {
      msg: String
  },
  methods: {
      change(){
          this.$emit(&#39;changMsg&#39;, &#39;阿离王带你学习前端&#39;)
      }
  }
}
</script>
로그인 후 복사

v-model 指令

v-model 是用来在表单控件或者组件上创建双向绑定的,他的本质是 v-bindv-on语法糖,在一个组件上使用 v-model,默认会为组件绑定名为 value 的 prop 和名为 input 的事件。

当我们组件中的某一个 prop 需要实现上面所说的”双向绑定“时,v-model 就能大显身手了。有了它,就不需要自己手动在组件上绑定监听当前实例上的自定义事件,会使代码更简洁

下面以一个 input 组件实现的核心代码,介绍下 v-model 的应用。

<!--父组件-->
<template>
    <base-input v-model="inputValue"></base-input>
</template>
<script>
    export default {
        data() {
            return {
                input: &#39;&#39;
            }
        },
    }
</script>
로그인 후 복사
<!--子组件-->
<template>
    <input type="text" :value="currentValue"  @input="handleInput">
</template>
<script>
    export default {
        data() {
            return {
                currentValue: this.value === undefined || this.value === null ? &#39;&#39;
            }
        },
        props: {
            value: [String, Number], // 关键1
        },
        methods: {
            handleInput(event) {
                const value = event.target.value;
                this.$emit(&#39;input&#39;, value); // 关键2
            },
        },
}
</script>
로그인 후 복사

上面例子看到,v-model="inputValue" 他的本质就是 v-bind 和 v-on 的语法糖,默认为父组件绑定名为 :value="inputValue"的属性,和@input="(v) => { this.inputValue = v }"事件,子组件通过 this.$emit(&#39;input&#39;, value) 通知父组件

所以他原理也是利用了我们上面讲的父子组件传参 props / $emit 方式来实现双向绑定

有时,在某些特定的控件中名为 value 的属性会有特殊的含义,这时可以通过 v-model 选项来回避这种冲突。

.sync 修饰符

  • .sync 修饰符在 vue 1.x 的版本中就已经提供,1.x 版本中,当子组件改变了一个带有 .syncprop 的值时,会将这个值同步到父组件中的值。这样使用起来十分方便,但问题也十分明显,这样破坏了单向数据流,当应用复杂时,debug 的成本会非常高。
  • 于是乎,在vue 2.0中移除了 .sync。但是在实际的应用中,.sync 是有它的应用场景的,所以在 vue 2.3 版本中,又迎来了全新的 .sync
  • 新的 .sync 修饰符所实现的已经不再是真正的双向绑定,它的本质和 v-model 类似,只是一种缩写。

正常封装组件例子:

<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" />
로그인 후 복사

上面的代码,使用 .sync

    상위 구성 요소가 하위 구성 요소에 값을 전달합니다

<text-document v-bind:title.sync="doc.title" />
로그인 후 복사
this.$emit(&#39;update:title&#39;, newTitle)
로그인 후 복사
로그인 후 복사

참고: 🎜🎜prop는 상위 수준 구성 요소에서 하위 수준 구성 요소(상위-하위 구성 요소)로만 전달될 수 있습니다. 이는 소위 단방향 데이터 흐름입니다. 또한 prop은 읽기 전용이므로 수정할 수 없습니다. 모든 수정 사항이 유효하지 않으며 경고가 표시됩니다. 🎜🎜🎜첫째, 하위 구성 요소 내에서 props를 변경하면 안 됩니다. 이렇게 하면 단방향 데이터 바인딩이 파괴되고 데이터 흐름을 이해하기 어려워집니다. 그러한 필요가 있는 경우 data 속성을 통해 받거나 computed 속성을 ​​사용하여 변환할 수 있습니다. 🎜🎜두 번째, props참조 유형(객체 또는 배열)을 전달하고 객체 또는 배열이 하위 구성 요소에서 변경되면 상위 구성 요소의 상태는 해당 업데이트도 변경될 수 있습니다. 이는 상위-하위 구성 요소 데이터의 "양방향 바인딩"을 달성하는 데 사용할 수 있습니다. 이 구현은 코드를 절약할 수 있지만 데이터의 단순성을 희생합니다. 흐름은 이해할 수 없으므로 하지 않는 것이 가장 좋습니다. 🎜🎜상위 구성 요소와 하위 구성 요소 간의 데이터 "양방향 바인딩"을 달성하려면 v-model 또는 .sync를 사용할 수 있습니다. 🎜🎜
    🎜하위 구성 요소는 값을 상위 구성 요소에 전달합니다🎜
🎜$emit를 사용하여 상위 구성 요소에 데이터를 전달하는 원칙은 다음과 같습니다. : 상위 구성 요소는 v-on을 전달하고 함수를 수신하고 vue 프레임워크는 v-on="fn"의 fn 이벤트 함수를 수신합니다. 하위 구성 요소에서 $emit를 사용하면 다음과 같은 예가 있습니다. 🎜
<!-- 父组件 -->
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <com-a></com-a>
    <button @click="changeA">点击改变子组件值</button>
  </div>
</template>

<script>
import ComA from &#39;./test/comA.vue&#39;
export default {
  name: &#39;HelloWorld&#39;,
  components: { ComA },
  data() {
    return {
      msg: &#39;Welcome&#39;
    }
  },

  methods: {
    changeA() {
      // 获取到子组件A
      this.$children[0].messageA = &#39;this is new value&#39;
    }
  }
}
</script>
로그인 후 복사
로그인 후 복사
rrree

v-model 지시문 🎜🎜v-model양식 제어 또는 구성 요소</code에서 사용됩니다. > <code>양방향 바인딩은 기본적으로 v-bindv-on<의 <code>구문 설탕인 code>에서 생성됩니다. /code> > 구성 요소에서 v-model을 사용하면 value라는 prop과 input이라는 이벤트가 구성 요소에 바인딩됩니다. 기본적으로. 🎜🎜구성 요소의 특정 prop가 위에서 언급한 "양방향 바인딩"을 구현해야 하는 경우 v-model이 작동할 수 있습니다. 이를 사용하면 구성 요소의 현재 인스턴스에 대한 맞춤 이벤트를 수동으로 바인딩하고 수신 대기할 필요가 없으므로 코드가 더욱 간결해집니다. 🎜🎜다음은 입력 컴포넌트로 구현된 핵심 코드를 사용하여 v-model 적용을 소개합니다. 🎜
<!-- 子组件 -->
<template>
  <div class="com_a">
    <span>{{messageA}}</span>
    <p>获取父组件的值为:  {{parentVal}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messageA: &#39;this is old&#39;
    }
  },
  computed:{
    parentVal(){
      return this.$parent.msg;
    }
  }
}
</script>
로그인 후 복사
로그인 후 복사
// 祖先组件 提供foo
//第一种
export default {
  name: "father",
  provide() {
    return {
      foo: &#39;hello&#39;
    }
  },
}
//第二种
export default {
  name: "father",
  provide: {
    foo:&#39;hello~~~~&#39;
  },
}
로그인 후 복사
로그인 후 복사
🎜위의 예에서 볼 수 있듯이 v-model="inputValue"는 기본적으로 v-bind 및 v-on에 대한 구문 설탕이며 상위 항목에 바인딩됩니다. 기본적으로 :value="inputValue"라는 속성과 @input="(v) => { this.inputValue = v }" 이벤트인 하위 구성 요소 this.$emit('input', value)를 전달하여 상위 구성 요소🎜🎜에 알리므로 해당 원칙은 props / $emit</code 매개 변수를 전달하는 상위-하위 구성 요소를 사용하여 구현됩니다. > 위에서 언급한 방법 양방향 바인딩🎜🎜때때로 일부 특정 컨트롤의 value라는 속성은 특별한 의미를 갖습니다. 이 경우 <code>v-model 옵션을 통해 이러한 충돌을 피할 수 있습니다. 🎜

.sync 수정자🎜🎜🎜.sync 수정자는 vue 1.x 버전에서 이미 제공됩니다. .sync를 사용하여 prop의 값을 변경하면 해당 값은 상위 구성 요소의 값과 동기화됩니다. 이는 사용하기 매우 편리하지만 문제는 매우 명백합니다. 이는 단방향 데이터 흐름을 파괴하므로 애플리케이션이 복잡할 경우 디버깅 비용이 매우 높아집니다. 🎜🎜그래서 .sync는 vue 2.0에서 제거되었습니다. 그러나 실제 애플리케이션에서는 .sync에 해당 애플리케이션 시나리오가 있으므로 vue 2.3 버전에서는 new .sync입니다. 🎜🎜새로운 .sync 수정자가 구현하는 것은 더 이상 진정한 양방향 바인딩이 아닙니다. 그 본질은 단지 약어인 v-model과 유사합니다. 🎜🎜🎜일반 캡슐화된 컴포넌트의 예: 🎜
//后代组件 注入foo, 直接当做this.foo来用
export default {
  inject:[&#39;foo&#39;],
}
로그인 후 복사
로그인 후 복사
🎜위 코드는 .sync를 사용하여 🎜
//当你传递对象给后代时
provide() {
    return {
        test: this.msg
    }
},
로그인 후 복사
로그인 후 복사
🎜로 작성할 수 있습니다. 이런 식으로 하위 컴포넌트에서는 다음 코드를 사용할 수 있습니다. 이 소품을 다시 할당하세요. 🎜
this.$emit(&#39;update:title&#39;, newTitle)
로그인 후 복사
로그인 후 복사

看到这里,是不是发现 .sync 修饰符 和 v-model 很相似,也是语法糖, v-bind:title.sync 也就是 等效于 v-bind:title="doc.title" v-on:update:title="doc.title = $event"

v-model 和 .sync 对比

.sync 从功能上看和 v-model 十分相似,都是为了实现数据的“双向绑定”,本质上,也都不是真正的双向绑定,而是语法糖

相比较之下,.sync 更加灵活,它可以给多个 prop 使用,而 v-model 在一个组件中只能有一个。

从语义上来看,v-model 绑定的值是指这个组件的绑定值,比如 input 组件select 组件日期时间选择组件颜色选择器组件,这些组件所绑定的值使用 v-model 比较合适。其他情况,没有这种语义,个人认为使用 .sync 更好。

parent/parent /children

通过$parent$children就可以访问组件的实例,拿到实例代表什么?代表可以访问此组件的所有方法data。列子如下:

<!-- 父组件 -->
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <com-a></com-a>
    <button @click="changeA">点击改变子组件值</button>
  </div>
</template>

<script>
import ComA from &#39;./test/comA.vue&#39;
export default {
  name: &#39;HelloWorld&#39;,
  components: { ComA },
  data() {
    return {
      msg: &#39;Welcome&#39;
    }
  },

  methods: {
    changeA() {
      // 获取到子组件A
      this.$children[0].messageA = &#39;this is new value&#39;
    }
  }
}
</script>
로그인 후 복사
로그인 후 복사
<!-- 子组件 -->
<template>
  <div class="com_a">
    <span>{{messageA}}</span>
    <p>获取父组件的值为:  {{parentVal}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messageA: &#39;this is old&#39;
    }
  },
  computed:{
    parentVal(){
      return this.$parent.msg;
    }
  }
}
</script>
로그인 후 복사
로그인 후 복사

要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到parentparent和children的值不一样,$children 的值是数组,而$parent是个对象

props $emit$parent $children两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍,二者皆不能用于非父子组件之间的通信。

provide / inject

provide / inject 是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量

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

provide 选项应该是

  • 一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。

inject 选项应该是:

  • 一个字符串数组
  • 一个对象(详情点击这里)

基本用法

// 祖先组件 提供foo
//第一种
export default {
  name: "father",
  provide() {
    return {
      foo: &#39;hello&#39;
    }
  },
}
//第二种
export default {
  name: "father",
  provide: {
    foo:&#39;hello~~~~&#39;
  },
}
로그인 후 복사
로그인 후 복사
//后代组件 注入foo, 直接当做this.foo来用
export default {
  inject:[&#39;foo&#39;],
}
로그인 후 복사
로그인 후 복사

上面的两种用法有什么区别吗?

  • 如果你只是传一个字符串,像上面的hello,那么是没有区别的,后代都能读到。
  • 如果你需要this对象属性的值(如下所示代码),那么第二种是传不了的,后代组件拿不到数据。所以建议只写第一种
//当你传递对象给后代时
provide() {
    return {
        test: this.msg
    }
},
로그인 후 복사
로그인 후 복사

注意:一旦注入了某个数据,比如上面示例中的 foo,那这个组件中就不能再声明 foo 这个数据了,因为它已经被父级占有。

provide 和 inject 绑定并不是可响应的。

这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。因为对象是引用类型。

先来个值类型的数据(也就是字符串)例子,不会响应

provide(){
  return{
    test:this.msg
  }
},
data() {
  return {
    msg: "Welcome to Your Vue.js App",
  }
}
mounted(){
  setTimeout(()=>{
    this.msg = "halo world";
    console.log(this._provided.msg)
    //log:Welcome to Your Vue.js App
  },3000)
},
로그인 후 복사

如上所示,这样做是不行的,打印出来的 _provided 中的数据并没有改,子组件取得值也没变。

你甚至可以直接给 this._provided.msg 赋值,但是即使是_provided.msg 里面的值改变了,子组件的取值,依然没有变。

当你的参数是对象的时候,就可以响应了,如下:

provide(){
  return{
    test:this.activeData
  }
},
data() {
  return {
    activeData:{name:&#39;halo&#39;},
  }
}
mounted(){
  setTimeout(()=>{
    this.activeData.name = &#39;world&#39;;
  },3000)
}
로그인 후 복사

这就是vue官方中写道的对象的属性是可以响应的

provide/inject 实现全局变量

provide/inject不是只能从祖先传递给后代吗?是的,但是,如果我们绑定到最顶层的组件app.vue,是不是所有后代都接收到了,就是当做全局变量来用了。

//app.vue
export default {
  name: &#39;App&#39;,
  provide(){
    return{
      app:this
    }
  },
  data(){
    return{
      text:"it&#39;s hard to tell the night time from the day"
    }
  },
  methods:{
    say(){
      console.log("Desperado, why don&#39;t you come to your senses?")
    }
  }
}
로그인 후 복사
//其他所有子组件,需要全局变量的,只需要按需注入app即可
export default {
  inject:[&#39;foo&#39;,&#39;app&#39;],
  mounted(){
    console.log(this.app.text); // 获取app中的变量
    this.app.say(); // 可以执行app中的方法,变身为全局方法!
  }
}
로그인 후 복사

provide/inject 实现页面刷新,不闪烁

  1. vue-router重新路由到当前页面,页面是不进行刷新的
  2. 采用window.reload(),或者router.go(0)刷新时,整个浏览器进行了重新加载,闪烁,体验不好

那我们怎么做呢?

跟上面的原理差不多,我们只在控制路由的组件中写一个函数(使用v-if控制router-view的显示隐藏,这里的原理不作赘述),然后把这个函数传递给后代,然后在后代组件中调用这个方法即可刷新路由啦。

//app.vue
<router-view v-if="isShowRouter"/>

export default {
  name: &#39;App&#39;,
  provide() {
    return {
      reload: this.reload
    }
  },
  data() {
    return {
      isShowRouter: true,
    }
  },
  methods:{
    reload() {
      this.isShowRouter = false;
      this.$nextTick(() => { 
        this.isShowRouter = true;
      })
    }
  }
}
로그인 후 복사
//后代组件
export default {
  inject: [&#39;reload&#39;],  
}
로그인 후 복사

这里 provide 使用了函数传递给后代,然后后代调用这个函数,这种思路,也是可以做子后代向父组件传参通讯的思路了。这里的原理,和 event 事件订阅发布就很像了

ref / $refs

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据, 我们看一个ref 来访问组件的例子:

// 子组件 A.vue

export default {
  data () {
    return {
      name: &#39;Vue.js&#39;
    }
  },
  methods: {
    sayHello () {
      console.log(&#39;hello&#39;)
    }
  }
}
로그인 후 복사
// 父组件 app.vue

<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.name);  // Vue.js
      comA.sayHello();  // hello
    }
  }
</script>
로그인 후 복사

ref 这种方式,就是获取子组件的实例,然后可以直接子组件的方法和访问操作data的数据,就是父组件控制子组件的一种方式,子组件想向父组件传参或操作,只能通过其他的方式了

eventBus

eventBus呢,其实原理就是 事件订阅发布,eventBus 又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。

这里我们可以直接使用 vue 自带的事件监听,也就是 emitemiton,我们来简单封装下:

  1. 首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它.

新建一个 event-bus.js 文件

// event-bus.js

import Vue from &#39;vue&#39;
export const EventBus = new Vue()
로그인 후 복사
  1. 发生事件

假设你有两个组件: additionNum 和 showNum, 这两个组件可以是兄弟组件也可以是父子组件;这里我们以兄弟组件为例:

<template>
  <div>
    <show-num-com></show-num-com>
    <addition-num-com></addition-num-com>
  </div>
</template>

<script>
import showNumCom from &#39;./showNum.vue&#39;
import additionNumCom from &#39;./additionNum.vue&#39;
export default {
  components: { showNumCom, additionNumCom }
}
</script>
로그인 후 복사
// addtionNum.vue 中发送事件

<template>
  <div>
    <button @click="additionHandle">+加法器</button>    
  </div>
</template>

<script>
import { EventBus } from &#39;./event-bus.js&#39;
console.log(EventBus)
export default {
  data() {
    return {
      num: 1
    }
  },

  methods: {
    additionHandle() {
      EventBus.$emit(&#39;addition&#39;, {
        num: this.num++
      })
    }
  }
}
</script>
로그인 후 복사
  1. 接收事件
// showNum.vue 中接收事件

<template>
  <div>计算和: {{count}}</div>
</template>

<script>
import { EventBus } from &#39;./event-bus.js&#39;
export default {
  data() {
    return {
      count: 0
    }
  },

  mounted() {
    EventBus.$on(&#39;addition&#39;, param => {
      this.count = this.count + param.num;
    })
  }
}
</script>
로그인 후 복사

这样就实现了在组件addtionNum.vue中点击相加按钮, 在showNum.vue中利用传递来的 num 展示求和的结果.

  1. 移除事件监听者

如果想移除事件的监听, 可以像下面这样操作:

import { eventBus } from &#39;event-bus.js&#39;
EventBus.$off(&#39;addition&#39;)
로그인 후 복사

自己封装一套 eventBus

这里使用自己封装一套eventBus也行,方便自己想干啥就干啥, 下面贴封装好的一套给大家

/* eslint-disable no-console */
// 事件映射表
let eventMap = {}

/**
 * 监听事件
 * @param {string}    eventName 事件名
 * @param {function}  listener 回调函数
 * @param {object}    instance 注册事件的实例
 */
function on(eventName, listener, instance) {
  eventMap[eventName] = eventMap[eventName] || []
  eventMap[eventName].push({
    listener,
    instance,
  })
}

// 监听事件,只执行一次
function once(eventName, listener, instance) {
  eventMap[eventName] = eventMap[eventName] || []
  eventMap[eventName].push({
    listener,
    instance,
    once: true,
  })
}

// 解除事件监听
function off(eventName, listener) {
  // 解除所有事件监听
  if (!eventName) {
    eventMap = {}
    return
  }

  // 没有对应事件
  if (!eventMap[eventName]) {
    return
  }

  // 解除某事件监听
  eventMap[eventName].forEach((currentEvent, index) => {
    if (currentEvent.listener === listener) {
      eventMap[eventName].splice(index, 1)
    }
  })
}

// 发送事件,执行对应响应函数
function emit(eventName, ...args) {
  if (!eventMap[eventName]) {
    return
  }

  eventMap[eventName].forEach((currentEvent, index) => {
    currentEvent.listener(...args)
    if (currentEvent.once) {
      eventMap[eventName].splice(index, 1)
    }
  })
}

// 显示当前注册的事件,代码优化时使用
function showEventMap(targetEventName) {
  if (targetEventName) { // 查看具体某个事件的监听情况
    eventMap[targetEventName].forEach(eventItem => {
      console.log(targetEventName, eventItem.instance, eventItem.listener)
    })
  } else { // 查看所以事件的监听情况
    Object.keys(eventMap).forEach(eventName => {
      eventMap[eventName].forEach(eventItem => {
        console.log(eventName, eventItem.instance, eventItem.listener)
      })
    })
  }
}

// 提供 vue mixin 方法,在 beforeDestroy 自动注销事件监听
export const mixin = {
  created() {
    // 重载 on 函数,收集本组件监听的事件,待消除时,销毁事件监听
    this.$eventListenerList = []
    this.$event = { off, once, emit, showEventMap }
    this.$event.on = (eventName, listener) => {
      this.$eventListenerList.push({ eventName, listener })
      on(eventName, listener)
    }
  },

  // 消除组件时,自动销毁事件监听
  beforeDestroy() {
    this.$eventListenerList.forEach(currentEvent => {
      off(currentEvent.eventName, currentEvent.listener)
    })
  },
}

export default { on, off, once, emit, showEventMap }
로그인 후 복사

如何使用呢,只需在 项目的 main.js, 引入 ,然后 Vue.mixin 即可,如下:

// main.js
import Vue from &#39;vue&#39;
import { mixin as eventMixin } from &#39;@/event/index&#39;

Vue.mixin(eventMixin)
로그인 후 복사

在vue项目其他文件,就可以直接 this.$event.on this.$event.$emit 如下:

this.$event.on(&#39;test&#39;, (v) => { console.log(v) })   this.$event.$emit(&#39;test&#39;, 1)
로그인 후 복사

还顺便封装了个mixin, 好处呢,就是在vue页面监听事件后,页面销毁后,也自动销毁了事件监听

Vuex

Vuex介绍

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.

  • Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上

Vuex各个模块

  • state:用于数据的存储,是store中的唯一数据源
  • getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
  • mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
  • actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
  • modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

Vuex实例应用

这里我们先新建 store文件夹, 对Vuex进行一些封装处理

在 store 文件夹下添加 index.js 文件

// index.js

// 自动挂载指定目录下的store
import Vue from &#39;vue&#39;
import Vuex from &#39;vuex&#39;

Vue.use(Vuex)

let modules = {}

// @/store/module 目录下的文件自动挂载为 store 模块
const subModuleList = require.context(&#39;@/store/modules&#39;, false, /.js$/)
subModuleList.keys().forEach(subRouter => {
  const moduleName = subRouter.substring(2, subRouter.length - 3)
  modules[moduleName] = subModuleList(subRouter).default
})

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules
})
로그인 후 복사

在 store 文件夹下添加 module 文件夹,在module文件夹再新建 user.js 文件

// user.js

import user from &#39;@/utils/user.js&#39;
import userApi from &#39;@/apis/user&#39;
import { OPEN_ACCOUNT_STAGE, STAGE_STATUS } from &#39;@/constant&#39;

let getUserPromise = null

export default {
  namespaced: true,
  state() {
    return {
      userInfo: null, // 用户信息
      isLogined: !!user.getToken(), // 是否已经登录
    }
  },
  mutations: {
    // 更新用户信息
    updateUser(state, payload) {
      state.isLogined = !!payload
      state.userInfo = payload
    },
  },
  actions: {
    // 获取当前用户信息
    async getUserInfo(context, payload) {
      // forceUpdate 表示是否强制更新
      if (context.state.userInfo && !payload?.forceUpdate) {
        return context.state.userInfo
      }
      if (!getUserPromise || payload?.forceUpdate) {
        getUserPromise = userApi.getUserInfo()
      }
      // 获取用户信息
      try {
        const userInfo = await getUserPromise
        context.commit(&#39;updateUser&#39;, userInfo)
      } finally {
        getUserPromise = null
      }
      return context.state.userInfo
    },

    // 登出
    async logout(context, payload = {}) {
      // 是否手动退出
      const { manual } = payload
      if (manual) {
        await userApi.postLogout()
      }
      user.clearToken()
      context.commit(&#39;updateUser&#39;, null)
    },
  }
}
로그인 후 복사

然后在项目的 main.js 文件中引入

import Vue from &#39;vue&#39;
import App from &#39;@/app.vue&#39;
import { router } from &#39;@/router&#39;
import store from &#39;@/store/index&#39;

const vue = new Vue({
  el: &#39;#app&#39;,
  name: &#39;root&#39;,
  router,
  store,
  render: h => h(App),
})
로그인 후 복사

封装的很愉快了,然后就正常操作即可。

this.$store.state.user.isLogined
this.$store.state.user.userInfo
this.$store.commit(&#39;user/updateUser&#39;, {})
 await this.$store.dispatch(&#39;user/logout&#39;, { manual: true })
로그인 후 복사

localStorage / sessionStorage

这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。

  • 通过window.localStorage.getItem(key)获取数据
  • 通过window.localStorage.setItem(key,value)存储数据

注意用JSON.parse() / JSON.stringify() 做数据格式转换, localStorage / sessionStorage可以结合vuex, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题.

自己实现简单的 Store 模式

对于小型的项目,通信十分简单,这时使用 Vuex 反而会显得冗余和繁琐,这种情况最好不要使用 Vuex,可以自己在项目中实现简单的 Store。

// store.js
const store = {
  debug: true,
  state: {
    author: &#39;yushihu!&#39;
  },
  setAuthorAction (newValue) {
    if (this.debug) console.log(&#39;setAuthorAction triggered with&#39;, newValue)
    this.state.author = newValue
  },
  deleteAuthorAction () {
    if (this.debug) console.log(&#39;deleteAuthorAction triggered&#39;)
    this.state.author = &#39;&#39;
  }
}
export default store
로그인 후 복사

上面代码原理就是,store.js文件暴露出一个对象 store,通过引入 store.js 文件 各个页面来共同维护这个store对象

和 Vuex 一样,store 中 state 的改变都由 store 内部的 action 来触发,并且能够通过 console.log() 打印触发的痕迹。这种方式十分适合在不需要使用 Vuex 的小项目中应用。

$root 访问根实例的方法相比,这种集中式状态管理的方式能够在调试过程中,通过 console.log() 记录来确定当前变化是如何触发的,更容易定位问题。

通过 $root 访问根实例

通过 $root,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data,就可以实现组件间的数据共享

//main.js 根实例
new Vue({
    el: &#39;#app&#39;,
    store,
    router,
    // 根实例的 data 属性,维护通用的数据
    data: function () {
        return {
            author: &#39;&#39;
        }
    },
    components: { App },
    template: &#39;<App/>&#39;,
});


<!--组件A-->
<script>
export default {
    created() {
        this.$root.author = &#39;于是乎&#39;
    }
}
</script>


<!--组件B-->
<template>
    <div><span>本文作者</span>{{ $root.author }}</div>
</template>
로그인 후 복사

注意:通过这种方式,虽然可以实现通信,但在应用的任何部分,任何时间发生的任何数据变化,都不会留下变更的记录,这对于稍复杂的应用来说,调试是致命的,不建议在实际应用中使用。

attrsattrs与listeners

现在我们来讨论一种情况, 我们一开始给出的组件关系图中A组件与D组件是隔代关系, 那它们之前进行通信有哪些方式呢?

  1. 使用props绑定来进行一级一级的信息传递, 如果D组件中状态改变需要传递数据给A, 使用事件系统一级级往上传递
  2. 使用eventBus,这种情况下还是比较适合使用, 但是碰到多人合作开发时, 代码维护性较低, 可读性也低
  3. 使用Vuex来进行数据管理, 但是如果仅仅是传递数据, 而不做中间处理,使用Vuex处理感觉有点大材小用了.

所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。

inheritAttrs

默认情况下父作用域的不被认作 propsattribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。

通过设置 inheritAttrsfalse,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。

注意:这个选项不影响 classstyle 绑定。

上面是官方描述:还是很难懂。

简单的说就是

  • inheritAttrs:true 时继承除props之外的所有属性
  • inheritAttrs:false 只继承class 和 style属性

$attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。

$listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件

讲了这么多文字概念,我们还是来看代码例子吧:

新建一个 father.vue 组件

<template>
   <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
    import Child from &#39;../components/child.vue&#39;

    export default {
        name: &#39;father&#39;,
        components: { Child },
        data () {
            return {
                name: &#39;阿离王&#39;,
                age: 22,
                infoObj: {
                    from: &#39;广东&#39;,
                    job: &#39;policeman&#39;,
                    hobby: [&#39;reading&#39;, &#39;writing&#39;, &#39;skating&#39;]
                }
            }
        },
        methods: {
            updateInfo() {
                console.log(&#39;update info&#39;);
            },
            delInfo() {
                console.log(&#39;delete info&#39;);
            }
        }
    }
</script>
로그인 후 복사

child.vue 组件:

<template>
    <!-- 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件 -->
    <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners"  />
</template>
<script>
    import GrandSon from &#39;../components/grandSon.vue&#39;
    export default {
        name: &#39;child&#39;,
        components: { GrandSon },
        props: [&#39;name&#39;],
        data() {
          return {
              height: &#39;180cm&#39;,
              weight: &#39;70kg&#39;
          };
        },
        created() {
            console.log(this.$attrs); 
       // 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
            console.log(this.$listeners); // updateInfo: f, delInfo: f
        },
        methods: {
            addInfo () {
                console.log(&#39;add info&#39;)
            }
        }
    }
</script>
로그인 후 복사

grandSon.vue 组件

<template>
    <div>
        {{ $attrs }} --- {{ $listeners }}
    <div>
</template>
<script>
    export default {
        props: [&#39;weight&#39;],
        created() {
            console.log(this.$attrs); // age, infoObj, height 
            console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
            this.$emit(&#39;updateInfo&#39;) // 可以触发 father 组件中的updateInfo函数
        }
    }
</script>
로그인 후 복사

这种方式的传值虽然说不常用,感觉可读性不是很好。但其对于组件层级嵌套比较深,使用props会很繁琐,或者项目比较小,不太适合使用 Vuex 的时候,可以考虑用它

总结

常见使用场景可以分为三类:

  • 父子组件通信: props$parent / $children provide / injectref \ $refs $attrs / $listeners
  • 兄弟组件通信: eventBusvuex自己实现简单的 Store 模式
  • 跨级通信: eventBusVuex自己实现简单的 Store 模式provide / inject$attrs / $listeners

更多编程相关知识,请访问:编程入门!!

위 내용은 Vue 구성요소 간에 통신하는 방법은 무엇입니까? 공유할 수 있는 12가지 의사소통 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿