关于vue.js:Vue组件的通信方式

8次阅读

共计 10850 个字符,预计需要花费 28 分钟才能阅读完成。

概述

vue 的两大个性是响应式编程和组件化。组件(Component)是 Vue 最外围的性能,然而各个组件实例的作用域是互相独立的,这表明不同组件之间的数据是无奈间接互相援用的。如果想要跨组件援用数据,就须要用到组件通信了,在通信之前先要了解组件之间的关系:

如上图所示:
父子关系:A 与 B,A 与 C,B 与 D,C 与 E
兄弟关系:B 与 C
隔代关系(可能隔更多代):A 与 D,A 与 E
跨级关系:B 与 E,D 与 E 等

通信形式

一、props/$emit

父组件通过 v-bind 绑定一个自定义的属性,子组件通过 props 接管父组件传来的数据;子组件通过 $emit 触发事件,父组件用 on() 或者在子组件的自定义标签上应用 v-on 来监听子组件触发的自定义事件,从而接管子组件传来的数据。

1、父组件向子组件传值

上面通过一个例子来阐明父组件向子组件传值,父组件 parent.vue 把数据 books:['JavaScript 高级程序设计', 'CSS 新世界', '图解 HTTP 彩色版'] 传给子组件 child.vue,并在 child.vue 中展现进去

// 父组件 parent.vue
<template>
  <div>
    <Child :books="books"/>
  </div>
</template>

<script>
import Child from './components/Child.vue'

export default {
  name: 'parent',
  components: {Child},
  data() {
    return {books: ['JavaScript 高级程序设计', 'CSS 新世界', '图解 HTTP 彩色版']
    }
  }
}
</script>
// 子组件 child.vue
<template>
  <div>
    <ul>
      <li v-for="(item, index) in books" :key="index">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    books: {
      type: Array,
      default: () => {return []
      }
    }
  }
}
</script>

留神:通过 props 传递数据是单向的,父组件数据变动时会传递给子组件,但子组件不能通过批改 props 传过来的数据来批改父组件的相应状态,即所谓的单向数据流。

2、子组件向父组件传值

上面通过子组件点击书籍列表,用 $emit() 触发,而后再父组件中获取

// 子组件 child.vue
<template>
  <div>
    <ul>
      <li v-for="(item, index) in books" :key="index" @click="like(item)">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    books: {
      type: Array,
      default: () => {return []
      }
    }
  },
  methods: {like(item) {this.$emit('likeBook', item)
    }
  }
}
</script>
// 父组件 parent.vue
<template>
  <div>
    <Child :books="books" @likeBook="likeBook"/>
  </div>
</template>

<script>
import Child from './components/Child.vue'

export default {
  name: 'parent',
  components: {Child},
  data() {
    return {books: ['JavaScript 高级程序设计', 'CSS 新世界', '图解 HTTP 彩色版']
    }
  },
  methods: {likeBook(val) {alert('我最喜爱的书籍是《' + val + '》')
    }
  }
}
</script>

二、&dollar;parent/$children

  • $parent:拜访父组件实例
  • $children:拜访子组件实例
// 父组件 parent.vue
<template>
  <div>
    <Child />
    <button @click="getChildData"> 获取子组件数据 </button>
  </div>
</template>

<script>
import Child from './components/Child.vue'

export default {
  name: 'parent',
  components: {Child},
  data() {
    return {books: ['JavaScript 高级程序设计', 'CSS 新世界', '图解 HTTP 彩色版']
    }
  },
  methods: {getChildData() {alert(this.$children[0].msg)
    }
  }
}
</script>
// 子组件 child.vue
<template>
  <div>
    <ul>
      <li v-for="(item, index) in bookLists" :key="index">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'child',
  data() {
    return {bookLists: [],
      msg: '我是子组件的值!'
    }
  },
  mounted() {this.bookLists = this.$parent.books}
}
</script>

留神:&dollar;parent拿到的是对象,如果是最顶层没有父组件的状况下拿到的是 undefined$children 拿到的是数组,如果是做底层没有子组件的状况下,拿到的是空数组; 这两种通信形式只能用于父子组件通信

三、ref

ref 如果在一般 Dom 元素上应用,援用指向的就是 DOM 元素;如果在子组件上应用,援用就指向组件实例,能够通过实例间接调用组件的办法和数据

// 父组件 parent.vue
<template>
  <div>
    <Child ref="child" />
    <button @click="getChildData"> 获取子组件数据 </button>
  </div>
</template>

<script>
import Child from './components/Child.vue'

export default {
  name: 'parent',
  components: {Child},
  methods: {getChildData() {const msg = this.$refs['child'].msg
      console.log(msg)
      this.$refs['child'].say()}
  }
}
</script>
// 子组件 child.vue
<script>
export default {
  name: 'child',
  data() {
    return {msg: '我是子组件的值!'}
  },
  methods: {say() {alert('你好,我是子组件!')
    }
  },
}
</script>

四、provide/inject

先人组件通过 provide 来提供变量,子孙组件通过 inject 注入变量来获取先人组件的数据,不论子孙组件嵌套有多深, 只有调用了 inject 那么就能够注入 provide 中的数据。上面是具体代码:

// 父组件
<template>
  <div>
    <h1> 康熙 </h1>
    <Son />
  </div>
</template>

<script>
import Son from './components/Son.vue'

export default {
  components: {Son},
  provide() {
    return {
      FatherToSon: this.FatherToSon,
      FatherToGrandson: this.FatherToGrandson,
    }
  },
  data() {
    return {
      FatherToSon: '我是康熙,雍正,你是我儿子!',
      FatherToGrandson: '我是康熙,乾隆,你是我孙子!',
    }
  }
}
</script>
// 子组件
<template>
  <div>
    <h1> 雍正 </h1>
    <button @click="receive"> 接管 </button>
    <Grandson />
  </div>
</template>

<script>
import Grandson from './Grandson.vue'
export default {
  components: {Grandson},
  inject: ['FatherToSon'],
  methods: {receive() {alert(this.FatherToSon)
    }
  }
}
</script>
// 孙组件
<template>
  <div>
    <h1> 乾隆 </h1>
    <button @click="receive"> 接管 </button>
  </div>
</template>

<script>
export default {inject: ['FatherToGrandson'],
  methods: {receive() {alert(this.FatherToGrandson)
    }
  }
}
</script>

留神:provide/inject 只能从上往下传值,且不是响应式,若要变成响应式的数据 provide 须要提供函数

五、eventBus&dollar;emit/$on

eventBus 是消息传递的一种形式,基于一个音讯核心,订阅和公布音讯的模式,称为公布订阅者模式。
eventBus 又称为事件总线。在 Vue 中可应用 eventBus 来作为沟通桥梁的概念,就像是所有组件共用雷同的事件核心,可向该核心注册发送事件或接管事件,所以组件都能够高低平行地告诉其余组件。

  • $emit('name',args): name: 公布的音讯名称,args:公布的音讯
  • $on('name',fn): name: 订阅的音讯名称,fn: 订阅的音讯
  • &dollar;once('name',fn): name: 订阅的音讯名称,fn: 订阅的音讯。与 $on 类似然而只触发一次,一旦触发之后,监听器就会被移除
  • &dollar;off('name',callback):name: 事件名称,callback: 回调监听器
    eventbus 能够实现任何组件之前的通信,上面以兄弟组件为例

    1、初始化,全局引入

// main.js
// 全局增加事件总线
Vue.prototype.$bus = new Vue()

2、发送事件

在 parent.vue 引入 ChildA 和 ChildB 组件,使它们成为兄弟组件

// 父组件 parent.vue
<template>
  <div>
    <ChildA />
    <ChildB />
  </div>
</template>

<script>
import ChildA from './components/childA'
import ChildB from './components/childB'
export default {
  components: {
    ChildA,
    ChildB
  }
}
</script>

在 ChildA 组件中用 $emit 发送事件

// ChildA 组件
<template>
  <div>
    <h1> 组件 A </h1>
    <button @click="send"> 发送 </button>
  </div>
</template>

<script>
export default {
  methods: {
    // 发送事件
    send() {this.$bus.$emit('message', '欢送应用 eventBus!')
    }
  }
}
</script>

3、接管事件发送的事件

在 ChildB 组件中用 $on 接管 ChildA 发送的事件

// ChildB 组件
<template>
  <div>
    <h1> 组件 B </h1>
  </div>
</template>

<script>
export default {mounted() {
    // 接管事件
    this.$bus.$on('message', data => {alert('我是组件 B,我收到的音讯为:' + data)
    })
  },
  beforeDestroy() {this.$bus.$off('message')
  }
}
</script>

留神:&dollar;on监听的事件不会主动移除监听,因而在不必时最好应用 $off 移除监听免得产生问题

六、&dollar;attrs/$listeners

1、简介

当组件为两级嵌套时,个别采纳 props&dollar;emit,但遇到多级组件嵌套时这种办法就不太实用了,如果不做两头解决,只传递数据用 vuex 有点大材小用了。因而在 vue2.4 中为了解决这一需要,便引入了 &dollar;attrs$listeners,新增了 inheritAttrs 属性

  • &dollar;attrs:当父组件传递了很多数据给子组件时,子组件没有申明 props 来进行接管,么子组件中的 attrs 属性就蕴含了所有父组件传来的数据 (除开曾经 props 申明了的);子组件还能够应用v−bind="$attrs" 的模式将所有父组件传来的数据 (除开曾经 props 申明了的) 传向下一级子组件, 通常和 interitAttrs 属性一起应用。
  • &dollar;listeners:蕴含了父组件中 (不含.native 润饰器的)v-on 事件监听器,通过v-on="$listeners",能够将这些事件绑定给它本人的子组件

    2、实例

    上面看一个例子:

// 父组件
<template>
  <div>
    <ChildA :name="name" :sex="sex" :age="age" @getName="getName" @getAge="getAge" />
  </div>
</template>

<script>
import ChildA from './components/childA'
export default {
  name: 'parent',
  components: {ChildA,},
  data() {
    return {
      name: '小明',
      age: 18,
      sex: '男'
    }
  },
  methods: {
    // 获取名字
    getName() {console.log('我的名字是' + this.name)
    },
    // 获取年龄
    getAge() {console.log('我往年' + this.age + '岁');
    }
  }
}
</script>
// 子组件 A
<template>
  <div>
    <h1> 组件 A </h1>
    {{msgA}}
    <hr/>
    <ChildB v-bind="$attrs" :height="height" v-on="$listeners" @getHeight="getHeight" />
  </div>
</template>

<script>
import ChildB from './childB.vue'
export default {
  name: 'ChildA',
  components: {ChildB},
  data() {
    return {
      msgA: null,
      height: '175cm'
    }
  },
  props: {
    sex: {
      type: String,
      default: ''
    }
  },
  mounted() {
    this.msgA = this.$attrs
    console.log('组件 A 获取的 $listeners:', this.$listeners)
  },
  methods: {
    // 获取身高
    getHeight() {console.log('我的身高是' + this.height);
    }
  }
}
</script>
// 孙组件 B
<template>
  <div>
    <h1> 组件 B </h1>
    {{msgB}}
  </div>
</template>

<script>
export default {
  name: 'ChildB',
  data() {
    return {msgB: null}
  },
  mounted() {
    this.msgB = this.$attrs
    console.log('组件 B 获取的 $listeners:', this.$listeners)
  }
}
</script>

$attrs 获取的后果:

$listeners 获取的后果:

如代码和图所示组件 A 中 props 申明接管了 sex 属性,因而组件中 &dollar;attrs 获取的是父组件中绑定的除去 sex 属性的值;组件 A 中应用了 v-bind="&dollar;attrs"v-on="$listeners",则组件 B 获取不仅是组件 A 中自身绑定的属性和办法还蕴含组件 A 获取父组件绑定的属性和办法

3、inheritAttrs

如果父组件传递了很多参数给子组件,而子组件没有用 props 齐全接管,那么没有接管的这些属性作为一般的 HTML attribute 利用在子组件的根元素上
如果你不心愿子组件的根元素继承个性,你能够在组件的选项中设置inheritAttrs: false

以下面的组件 B 为例,当 inheritAttrs 为 true(inheritAttrs 默认为 true)

当 inheritAttrs 为 false 时

// 孙组件 B
export default {
  name: 'ChildB',
  inheritAttrs: false,
  data() {
    return {msgB: null}
  },
  mounted() {
    this.msgB = this.$attrs
    console.log('组件 B 获取的 $listeners:', this.$listeners)
  }
}

七、Vuex

1、Vuex 概述

Vuex 是一个专为 Vue.js 利用程序开发的 状态管理模式 + 库 。它采纳集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化。
状态治理蕴含以下几个局部:

  • 状态(State),驱动利用的数据源
  • 视图(View),以申明形式将 状态 映射到视图;
  • 操作(Actions),响应在 视图 上的用户输出导致的状态变动

视图发生变化会导致数据源的扭转,数据源发生变化则会扭转视图,则下面示意是一个“单向数据流”。然而当咱们的利用遇到多个组件共享状态时,单向数据流的简洁性很容易被毁坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为须要变更同一状态。

因而,为了解决这种问题咱们把组件的共享状态抽取进去,以一个全局单例模式治理。在这种模式下,咱们的组件树形成了一个微小的“视图”,不论在树的哪个地位,任何组件都能获取状态或者触发行为!

通过定义和隔离状态治理中的各种概念并通过强制规定维持视图和状态间的独立性,咱们的代码将会变得更结构化且易保护。

2、Vuex 各个模块

1、state:存储利用中须要共享的状态,是 Vuex 中的惟一数据源。
2、getters: 相似 Vue 中的计算属性 computedgetter 的返回值会依据它的依赖被缓存起 来,且只有当它的依赖值产生了扭转才会被从新计算。
3、mutations: 更改 Vuex 的 store 中的状态(state) 的惟一办法,且 mutation 必须是同步函数
4、actions:相似于 mutation,提交的是 mutation,而不是间接变更状态;能够蕴含任意异步操作
5、modules:将 store 宰割成模块(module)。每个模块领有本人的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样形式的宰割

3、Vuex 举例

// 父组件
<template>
  <div class="home">
    <h1> 父组件 </h1>
    <hr/>
    <ChildA />
    <hr/>
    <ChildB />
  </div>
</template>

<script>
import ChildA from './components/ChildA'
import ChildB from './components/ChildB'

export default {
  name: 'parent',
  components: {
    ChildA,
    ChildB
  }
}
</script>
// 子组件 A
<template>
  <div>
    <h1> 组件 A </h1>
    <p>A 获取的值: {{count}}</p>
    <button @click="add(5)">ChildA-add</button>
  </div>
</template>

<script>
export default {
  computed: {count() {return this.$store.state.count}
  },
  methods: {
    // 扭转 store 里 count 的值
    add(num) {this.$store.dispatch('countAdd', num)
    }
  }
}
</script>

<style>

</style>
// 子组件 B
<template>
  <div>
    <h1> 组件 B </h1>
    <p>B 获取的值: {{countB}}</p>
    <button @click="add(10)">ChildB-add</button>
  </div>
</template>

<script>
import {mapMutations, mapGetters} from 'vuex'
export default {
  computed: {
    ...mapGetters({countB: 'getCount'})
  },
  methods: {...mapMutations(['countAdd']),
    // 扭转 store 里 count 的值
    add(num) {this.countAdd(num)
    }
  }
}
</script>

<style>

</style>

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {count: 0,},
  getters: {getCount: (state) => {return state.count}
  },
  mutations: {countAdd(state, num) {state.count += num}
  },
  actions: {countAdd(context, num) {context.commit('countAdd', num)
    }
  },
  modules: {}})

八、localStorage/sessionStorage

1、介绍

localStorage:本地存储对象,存储的数据是永久性数据, 页面刷新,即便浏览器重启,除非被动删除不然存储的数据会始终存在
sessionStorage:与 localStorage 类似,然而只有在以后页面下无效,敞开页面或浏览器存储的数据将会清空

localStorage 和 sessionStorage 罕用的 API:

setItem (key, value) ——  保留数据,以键值对的形式贮存信息。getItem (key) ——  获取数据,将键值传入,即可获取到对应的 value 值。removeItem (key) ——  删除单个数据,依据键值移除对应的信息。clear () ——  删除所有的数据
key (index) —— 获取某个索引的 key

2、举例

// 存储
setItem() {window.localStorage.setItem('name1', '小明')
  window.sessionStorage.setItem('name2', '小红')
}
// 接管
receive() {const name1 = window.localStorage.getItem('name1')
  const name2 = window.sessionStorage.getItem('name2')
  console.log(name1) // 打印后果为:小明
  console.log(name2) // 打印后果为:小红
}

3、setItem()和 getItem()应用时的类型转换

localStorage 和 sessionStorage 通过 setItem() 存储数据会主动转换为 String 类型,然而通过 getItem() 其类型并不会转换回来(localStorage 和 sessionStorage 应用办法一样,上面均以 localStorage 为例)

const num = 1
window.localStorage.setItem('num', num)

const numRec = window.localStorage.getItem('num')
console.log(numRec, typeof(numRec)) // 1 string

因而正确的存储形式应该为:存储之前用 JSON.stringify() 办法将数据转换成 json字符串 模式;须要应用数据的时候用 JSON.parse() 办法将之前存储的字符串转换成 json 对象

const num = 1
window.localStorage.setItem('num', JSON.stringify(num))
const obj = {
   name: '小红',
   age: 18
 }
window.localStorage.setItem('obj', JSON.stringify(obj))

const numRec = JSON.parse(window.localStorage.getItem('num'))
console.log(numRec, typeof(numRec)) // 1 'number'
const objRec = JSON.parse(window.localStorage.getItem('obj'))
console.log(objRec, typeof(objRec)) // {name: '小红', age: 18} 'object'

留神 :localStorage.setItem() 和 sessionStorage.setItem()不能间接存储对象,必须应用 JSON.stringify()JSON.parse()转换实现

总结

以上 8 种通信形式次要利用在以下三类场景:

  • 父子组件通信:最常常应用通信形式的是 props/&dollar;emit,繁多的父子组件通信应用&dollar;parent>/&dollar;children 也比拟不便;父组件也常应用 ref 获取子组件实例;也可应用 provide/inject&dollar;attrs/&dollar;listeners 以及localStorage/sessionStorage
  • 兄弟组件通信:简略的数据传递可应用 eventBus&dollar;emit/&dollar;on;简单的数据应用 Vuex 比拟不便; 也能够应用localStorage/sessionStorage;
  • 跨级组件通信:父子孙等嵌套组件通信形式多应用 provide/inject&dollar;attrs/&dollar;listeners;跨级组件通信的数据如果不简单可应用 eventBuslocalStorage/sessionStorage;如果数据简单可应用Vuex,然而要留神刷新界面 Vuex 存储的数据会隐没

结尾

本篇文章只是简略记录了一下平时应用的组件通信形式,并没有深刻的介绍其细节,如果有谬误的中央欢送斧正

正文完
 0