props和$emit
父组件通过props
的方式向子组件传值;子组件通过 $emit
方式向父组件传值
示例:
父组件
<template>
<div>
<p>this is parent compoent!</p>
<child :message="message" v-on:getChildData="getChildData"></child>
</div>
</template>
<script>
import child from './child'
export default {
components: {
child,
},
data() {
return {
message: 'hello',
}
},
methods: {
//执行子组件触发的事件
getChildData(val) {
console.log(val)
},
},
}
</script>
子组件
<template>
<div>
<input type="text" v-model="mymessage" @input="passData" />
</div>
</template>
<script>
export default {
props: ['message'], //设置props属性值,得到父组件传递过来的数据
data() {
return {
mymessage: this.message,
}
},
methods: {
passData(e) {
//触发父组件中的事件,向父组件传值
this.$emit('getChildData', e.target.value)
},
},
}
</script>
在上面的例子中,有父组件parent和子组件child
- 父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件;
- 子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件
$attrs和$listeners
- $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (
class
和style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和style
除外),并且可以通过v-bind="$attrs"
传入内部组件。通常配合inheritAttrs
选项一起使用。 - $listeners:包含了父作用域中的 (不含
.native
修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件 - inheritAttrs:在版本 2.4 之前,默认情况下父作用域的不被作为props特性绑定的属性,将会作为普通的 HTML 属性,应用在根元素上: 设置 interitAttrs 为 false,之后,不会应用到根元素上:
示例:
<template>
<div>
<p>this is parent compoent!</p>
<B
:toBMessage="toBMessage"
:toCMessage="toCMessage"
v-on:getBData="getBData"
v-on:getCData="getCData"
clearable="true"
disabled="true"
v-on:hideInListeners.native="hideInListeners"
></B>
</div>
</template>
<script>
import B from './B'
export default {
components: {
B },
data() {
return {
toBMessage: 'bMessage',
toCMessage: 'cMessage', //传递给c组件的数据
}
},
methods: {
getBData(val) {
console.log('这是来自B组件的数据' + val)
},
getCData(val) {
console.log('这是来自C组件的数据:' + val)
},
hideInListeners() {
// .native修饰的监听函数不会出现在$listeners中
},
},
}
</script>
B.vue组件
<template>
<div>
<input type="text" v-model="innerBMessage" @input="passData" />
<!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
<!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
</template>
<script>
import C from './C'
export default {
inheritAttrs: false,
components: {
C },
data() {
return {
innerBMessage: this.toBMessage,
}
},
props: ['toBMessage'], //得到父组件传递过来的数据
methods: {
passData(e) {
//触发父组件中的事件
this.$emit('getBData', e.target.value)
},
},
mounted() {
// 获取组件上绑定的$attrs
console.log('B $attrs', this.$attrs)
// 获取组件上绑定的$listeners
console.log('B $listeners', this.$listeners)
},
}
</script>
C.vue组件
<template>
<div>
<input type="text" v-model="$attrs.toCMessage" @input="passCData" />
</div>
</template>
<script>
export default {
methods: {
passCData(e) {
//触发父组件A中的事件
this.$emit('getCData', e.target.value)
},
},
mounted() {
// 获取组件上绑定的$attrs
console.log('C $attrs', this.$attrs)
// 获取组件上绑定的$listeners
console.log('C $listeners', this.$listeners)
},
}
</script>
注:inheritAttrs设置对B.vue和C.vue的影响如上图所示
中央事件总线(eventbus)
上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过$emit
触发事件,$on
监听触发的事件。
新创建一个 .js 文件,比如 event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
发送事件
假设你有两个Vue页面需要通信: A 和 B ,A页面 在按钮上面绑定了点击事件,发送一则消息,想通知 B页面。
<!-- A.vue -->
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
import {
EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", '来自A页面的消息');
}
}
};
</script>
接收事件
<!-- IncrementCount.vue -->
<template>
<p>{
{
msg}}</p>
</template>
<script>
import {
EventBus } from "../event-bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
})
}
};
</script>
这里主要用到的两个方法:
// 发送消息
EventBus.$emit(channel: string, callback(payload1,…))
// 监听接收消息
EventBus.$on(channel: string, callback(payload1,…))
详细细节可以参考我写的这篇文章:Vue事件总线(EventBus)使用介绍
provide和inject
简介
Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
举个例子
假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件
// A.vue
export default {
provide: {
name: '浪里个浪'
}
}
// B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 浪里个浪
}
}
可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 “浪里个浪”,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject
注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 “浪里个浪”。这就是 provide / inject API 最核心的用法。
需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档
所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是"浪里个浪"。
provide与inject 怎么实现数据响应式
一般来说,有两种办法:
- provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
- 使用2.6最新API
Vue.observable
优化响应式 provide(推荐)
核心代码如下:
// 父组件
<div>
<button @click="changeAge">changeAge</button>
<child></child>
</div>
......
// 方法一:提供祖先组件的实例
data() {
return {
age: 0,
}
},
provide() {
return {
componentInstance: this, // 组件实例
}
},
methods: {
changeAge() {
this.age = Number(this.age) + 1
},
},
// 方法二 Vue.observable 优化响应式 provide
provide() {
this.msg = Vue.observable({
age: 0,
})
return {
msg: this.msg, // 组件实例
}
},
methods: {
changeAge() {
this.msg.age = Number(this.msg.age) + 1
},
},
child 组件
// 方法一对应代码
<template>
<div>
{
{
componentInstance.age }}
</div>
</template>
<script>
export default {
inject: ['componentInstance'],
mounted() {
console.log('child', this.componentInstance)
},
}
// 方法二对应代码
<template>
<div>
{
{
msg.age }}
</div>
</template>
<script>
export default {
inject: ['msg'],
mounted() {
console.log('child', this.msg)
},
}
</script>
虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!
$parent和$children
在组件内部可以直接通过子组件$parent
对父组件进行操作,父组件通过$children
对子组件进行操作
关键代码举例如下
父组件
<div>
<button @click="changeChildAge">changeChildAge</button>
<child></child>
<div>name:{
{
name }}</div>
</div>
data() {
return {
name: 'initial name',
}
},
methods: {
changeChildAge() {
console.log(this.$children)
this.$children[0].age += 1
},
},
子组件
<div>
<div>{
{
age }}</div>
<button @click="changeParentName">changeParentName</button>
</div>
data() {
return {
age: 24,
}
},
methods: {
changeParentName() {
this.$parent.name = 'child pass name'
},
},
vuex
简要介绍Vuex原理
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。
简要介绍各模块在流程中的功能
- Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块,由组件中的
$store.dispatch('action名称', data1)
来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。 - commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法,由actions中的
commit('mutation名称')
来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。 - state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
- getters:state对象读取方法。Vue Components通过该方法读取全局state对象。
Vuex与localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
这里需要注意的是:由于vuex里,我们保存的状态,都是对象,而localStorage只支持字符串,所以需要用JSON转换:
// 存 object-> string
JSON.stringify(state[key]);
// 取 string -> object
JSON.parse(localStorage.getItem(key));