ホームページ > ウェブフロントエンド > Vue.js > Vue コンポーネント間で通信するにはどうすればよいですか?共有する 12 のコミュニケーション方法

Vue コンポーネント間で通信するにはどうすればよいですか?共有する 12 のコミュニケーション方法

青灯夜游
リリース: 2021-12-06 19:18:03
転載
2217 人が閲覧しました

vue コンポーネント間で通信するにはどうすればよいですか?この記事では、Vue コンポーネント間で通信する 12 の方法を詳しく紹介します。

Vue コンポーネント間で通信するにはどうすればよいですか?共有する 12 のコミュニケーション方法

vue は、データ駆動型のビュー更新のためのフレームワークです。私たちの日常の開発では、ページのさまざまなモジュールを vue コンポーネントに分割します。そのため、vue の場合、間のデータはコンポーネント 通信は非常に重要ですが、コンポーネント間でデータはどのように通信するのでしょうか?

まず、通信方法を理解しやすくするために、vue のコンポーネント間にどのような関係があるかを知る必要があります。 [関連する推奨事項: "vue.js チュートリアル "]

通常、次の関係に分かれます:

  • 親コンポーネントと子コンポーネント間の通信
  • 親コンポーネントと子コンポーネントの間の通信 (兄弟コンポーネント、世代間関係コンポーネントなど)

props / $emit

親コンポーネントは、以下を介して子コンポーネントと通信します。 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 は読み取り専用のため変更できず、すべての変更は無効となり、警告が発行されます。

  • まず、サブコンポーネント内で props を変更しないでください。これにより、一方向のデータ バインディングが破壊され、データ フローが理解しにくくなります。そのような必要がある場合は、data 属性を通じて受信するか、変換に computed 属性を使用できます。
  • 2 番目に、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- で使用される v-bind および v-on 糖衣構文 です。 model は、デフォルトで、value という名前の propinput という名前のイベントをコンポーネントにバインドします。 コンポーネント内の特定の

prop

が上記の「双方向バインディング」を実装する必要がある場合、v-model が活躍します。これを使用すると、コンポーネント上の現在のインスタンスでカスタム イベントを手動でバインドしてリッスンする必要がなくなり、 コードがより簡潔になります以下では、入力コンポーネントによって実装されたコア コードを使用して、

v-model

のアプリケーションを紹介します。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:html;toolbar:false;">&lt;!--父组件--&gt; &lt;template&gt; &lt;base-input v-model=&quot;inputValue&quot;&gt;&lt;/base-input&gt; &lt;/template&gt; &lt;script&gt; export default { data() { return { input: &amp;#39;&amp;#39; } }, } &lt;/script&gt;</pre><div class="contentsignin">ログイン後にコピー</div></div><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:html;toolbar:false;">&lt;!--子组件--&gt; &lt;template&gt; &lt;input type=&quot;text&quot; :value=&quot;currentValue&quot; @input=&quot;handleInput&quot;&gt; &lt;/template&gt; &lt;script&gt; export default { data() { return { currentValue: this.value === undefined || this.value === null ? &amp;#39;&amp;#39; } }, props: { value: [String, Number], // 关键1 }, methods: { handleInput(event) { const value = event.target.value; this.$emit(&amp;#39;input&amp;#39;, value); // 关键2 }, }, } &lt;/script&gt;</pre><div class="contentsignin">ログイン後にコピー</div></div>上の例でわかるように、

v-model="inputValue"

は本質的に v-bind および v-on の糖衣構文であり、デフォルトは親コンポーネント :value="inputValue" という名前のプロパティと、子コンポーネントの @input="(v) => { this.inputValue = v }" イベントをバインドします。 passthis.$emit('input', value) 親コンポーネントに通知するしたがって、彼の原理ではパラメータ

props / $emit

を渡す親子コンポーネントも使用します。上で説明した方法 双方向バインディングを実現するには一部の特定のコントロールの value という名前のプロパティが特別な意味を持っている場合があります。この場合、

v-model## を使用してこの競合を回避できます。 # オプション。 。

.sync 修飾子

.sync
    修飾子は vue 1.x バージョンで提供されています。バージョン 1.x では、サブコンポーネントの値が
  • .sync を持つ prop が変更されると、値は親コンポーネントの値と同期されます。これは非常に便利ですが、問題も非常に明らかです。これにより、一方向のデータ フローが破壊され、アプリケーションが複雑な場合、デバッグのコストが非常に高くなります。 つまり、.sync
  • は vue 2.0 で削除されました。しかし、実際のアプリケーションでは、
  • .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)
ログイン後にコピー

看到这里,是不是发现 .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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート