概述
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>
二、$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>
留神:$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
的$emit
/$on
eventBus是消息传递的一种形式,基于一个音讯核心,订阅和公布音讯的模式,称为公布订阅者模式。
eventBus 又称为事件总线。在 Vue 中可应用 eventBus 来作为沟通桥梁的概念,就像是所有组件共用雷同的事件核心,可向该核心注册发送事件或接管事件,所以组件都能够高低平行地告诉其余组件。
$emit('name',args)
: name:公布的音讯名称 , args:公布的音讯$on('name',fn)
: name:订阅的音讯名称, fn: 订阅的音讯$once('name',fn)
: name:订阅的音讯名称, fn: 订阅的音讯。与$on类似然而只触发一次,一旦触发之后,监听器就会被移除-
$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>
留神:$on
监听的事件不会主动移除监听,因而在不必时最好应用$off
移除监听免得产生问题
六、$attrs
/$listeners
1、简介
当组件为两级嵌套时,个别采纳props
和$emit
,但遇到多级组件嵌套时这种办法就不太实用了,如果不做两头解决,只传递数据用vuex有点大材小用了。因而在vue2.4
中为了解决这一需要,便引入了$attrs
和$listeners
, 新增了inheritAttrs
属性
$attrs
:当父组件传递了很多数据给子组件时,子组件没有申明props来进行接管,么子组件中的attrs属性就蕴含了所有父组件传来的数据(除开曾经props申明了的);子组件还能够应用v−bind="$attrs"
的模式将所有父组件传来的数据(除开曾经props申明了的)传向下一级子组件,通常和interitAttrs
属性一起应用。-
$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属性,因而组件中$attrs
获取的是父组件中绑定的除去sex属性的值;组件A中应用了v-bind="$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中的计算属性computed
,getter 的返回值会依据它的依赖被缓存起 来,且只有当它的依赖值产生了扭转才会被从新计算。
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
/$emit
,繁多的父子组件通信应用$parent>
/$children
也比拟不便;父组件也常应用ref
获取子组件实例;也可应用provide
/inject
、$attrs
/$listeners
以及localStorage
/sessionStorage
- 兄弟组件通信:简略的数据传递可应用
eventBus
的$emit
/$on
;简单的数据应用Vuex
比拟不便;也能够应用localStorage
/sessionStorage
; - 跨级组件通信:父子孙等嵌套组件通信形式多应用
provide
/inject
和$attrs
/$listeners
;跨级组件通信的数据如果不简单可应用eventBus
和localStorage
/sessionStorage
;如果数据简单可应用Vuex
,然而要留神刷新界面Vuex存储的数据会隐没
结尾
本篇文章只是简略记录了一下平时应用的组件通信形式,并没有深刻的介绍其细节,如果有谬误的中央欢送斧正
发表回复