在编写一个vue项目的时候,组件之间的数据通信是必不可少的。也是非常的重要的一步。今天写几种vue中组件的数据通信方式以及应用场景。
一.props&@
1.props
父组件把数据通过v-bind指令绑定在子组件参数上传下去.子组件通过props这个属性获取. props可以以数组和对象的形式接收
数组形式:
父组件:
<Children v-bind:msg="msg"></Children>
子组件:
props: ["msg"] //字符串对应的是父组件绑定的v-bind值.
对象形式:
父组件:
<Children v-bind:msg="msg"></Children>
子组件 : – 对象可以设置其他条件
props: {
msg: {
type: String, //判断传入的类型
default: "默认数据", //默认值
require: true, // 是否必须传入项
},
},
2.@
父组件传入一个方法给子组件.子组件可以调用这个方法修改父组件的值(解决了子组件不能修改父组件的值)。
父组件定义好的fn传下去,供子组件调用
<Children @fn="fn"></Children>
子组件通过$emit调用fn
this.$emit("fn", 1, 2, 3);
二.ref
在父组件注册的子组件中添加ref属性.父组件能通过this.$refs.属性名拿到这个子组件的实例.就可以直接做操作啦.
父组件:
<Children ref="subInst" />
this.$refs.subInst // 父组件可以通过this.$refs.属性名这个方法直接拿到子组件的data和methods.
三.provide&inject
在父组件提供数据(provide),子组件注入数据(inject).
provide 选项是:一个对象或返回一个对象的函数
inject 选项是:一个字符串数组,或 一个对象,对象的 key 是本地的绑定名
父组件上添加这个方法
provide() {
return {
msg: this.msg, // 这里需要注意传基本的值,没有响应式.需要传一个响应式的对象.
}
},
子(孙)组件
inject: ["msg"]
官方着重说明provide 和 inject 主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中。
四.attrs&listeners
这两个属性是一个对象.分别保存父组件中传下来的数据和方法.
首先我们来看下面的一张图,图中表示一个多级组件嵌套的情形。
现在我们来讨论一种情况,A组件与C组件之间数据怎么通信?
1.attrs
官方解释:
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。
简言之: 接收除了props声明外的所有绑定属性(class、style除外)
//A组件 Parent.vue
<template>
<child name="Joe" age="20" gender="man"/>
</template>
<script>
import child from "@/components/Child.vue";
export default {
name:'parent',
components:{child}
}
</script>
//B组件 Child.vue
<template>
<grandson v-bind="$attrs" height="180"/>
</template>
<script>
import grandson from './Grandson';
export default {
name:'child'
components:{grandson}
}
</script>
//C组件 Grandson.vue
<template>
<div>我是grandson组件</div>
</template>
<script>
export default {
name:'grandson',
props:['age'],
created(){
console.log(this.$attrs) //{ name: "Joe", gender: "man" , height="180"}
}
}
</script>
可以在 Child.vue组件里注册的grandson上通过 v-bind="$attrs", 可以将属性继续向下传递,让 Grandson.vue也能访问到Parent.vue组件的属性,这在传递多个属性时会显得很便捷,而不用一条条的进行绑定。
如果想要添加其他属性,可继续绑定属性(例如: height=“180”)。但要注意的是,继续绑定的属性和 $attrs 中的属性有重复时,继续绑定的属性优先级会更高。
由于Grandson.vue 在 props 中声明了 age 属性,$attrs 中只有name、gender、height三个属性,输出结果为:{ name: “Joe”, gender: “man” , height=“180”}
2.listeners
官方解释:
包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
简言之: 接收除了带有.native事件修饰符的所有事件监听器
//A组件 Parent.vue
<template>
<child @click.native="clikeHandle" @customEvent="customHandle"/>
</template>
<script>
import child from "@/components/Child.vue";
export default {
name:'parent',
components:{child}
}
</script>
//B组件 Child.vue
<template>
<grandson v-on="$listeners"/>
</template>
<script>
import grandson from './Grandson';
export default {
name:'child'
components:{grandson}
}
</script>
//C组件 Grandson.vue
<template>
<div>我是grandson组件</div>
</template>
<script>
export default {
name:'grandson',
created(){
this.$emit('customEvent')
console.log(this.$listeners) //{ customEvent: fn }
}
}
</script>
同 attrs 属性一样,可以在Child.vue组件里注册的grandson上通过 v-on="$listeners",将事件监听器继续向下传递,让 Grandson.vue 访问到事件,且可以使用 $emit 触发 Garent.vue 的函数。
如果想要添加其他事件监听器,可继续绑定事件。但要注意的是,继续绑定的事件和 $listeners 中的事件有重复时,不会被覆盖。当 Grandson.vue 触发 customEvent 时,Child.vue 和 Garent.vue 的事件都会被触发,触发顺序类似于冒泡,先到 Child.vue 再到 Garent.vue 。
Garent.vue 中对 Child绑定了两个事件,带有.native的 click 事件和一个自定义事件,所以在 Grandson.vue 中,输出$listeners的结果为:{ customEvent: fn }
使用场景:
-
组件传值时使用: 爷爷在父亲组件传递值,父亲组件会通过 a t t r s 获 取 到 不 在 父 亲 p r o p s 里 面 的 所 有 属 性 , 父 亲 组 件 通 过 在 孙 子 组 件 上 绑 定 attrs获取到不在父亲props里面的所有属性,父亲组件通过在孙子组件上绑定 attrs获取到不在父亲props里面的所有属性,父亲组件通过在孙子组件上绑定attrs 和 $listeners 使孙组件获取爷爷传递的值并且可以调用在爷爷那里定义的方法;
-
对一些UI库进行二次封装时使用:比如element-ui,里面的组件不能满足自己的使用场景的时候,会二次封装,但是又想保留他自己的属性和方法,那么这个时候时候 a t t r s 和 attrs和 attrs和listners是个完美的解决方案。
五.sync&model
这是vue组件中自带的语法糖。方便调用。
1.sync
父组件:
<Children :msg.sync="msg"></Children> // 在父组件注册的子组件上挂载修饰符.sync
子组件:
this.$emit("update:msg", "789"); //子组件上直接调用这个方法,传值改值.
.sync修饰符相当于vue在内部定义方法可以修改值,相当于父组件上使用@把函数传下来这种. 在子组件打印实例,会发现在listen上出现update:msg方法。
2.model
父组件:
<template>
<!--v-model 是语法糖,v-model的默认行为是input,默认prop是value-->
<Child v-model="model"></Child>
<!--相当于下面的代码-->
<Child :value="model" @input="model = $event"></Child>
</template>
子组件: 可以通过model选项来修改v-model的默认行为和prop
model: {
prop: 'checked',
event: 'change'
}
所以相应的父组件使用v-model的时候的等效操作为:
父组件:
<template>
<Child :checked="model" @change="model = $event"></Child>
<template/>
六.eventBus
通过创建一个新的vue实例,当作事件车然后挂载在vue的原型上。通过发布订阅的方式进行通讯。这个方法可任意组件之间使用,前面的都是父组件传下来的.
// 创建一个新的VUE实例绑定在Vue的原型上
Vue.prototype.$eventBus = new Vue();
//任意组件创建订阅callback这个方法,
this.$eventBus.$on("callback", (data) => {
console.log("接收到data----", data);
});
//任意组件上传入数据通讯(发布)`
this.$eventBus.$emit("callback", "传入数据");
使用这个方法可以在任意两个组件上进行通讯。方便至极。缺点是项目复杂的是时候难以维护,追踪。
七.parent&children
通过挂载在Vue的原型上,递归执行$emit方法.在需要通讯的组件上订阅发布.
parent
在原型上定义一个方法,内部就是一直往上级执行$emit这个方法.
Vue.prototype.$dispatch = function (eventName, params) {
const parent = this.$parent;
while (parent) {
parent.$emit(eventName, params);
parent = parent.$parent;
}
}
在需要使用组件数据的父组件中订阅
this.$on("Up", function (params) {
this.msg = params;
});
子组件或孙组件调用方法
this.$dispatch("Up", "我是通过parent方式改变的数据");
children
在原型上定义一个方法,内部就是递归调用下级的$emit这个方法
Vue.prototype.$notice = function (eventName, params) {
const children = this.$children;
function deep(children) {
children.forEach(item => {
item.$emit(eventName, params);
if (item.children) {
deep(children);
}
});
}
deep(children);
}
在需要使用组件数据的子组件中订阅
this.$on("down", function (params) {
this.msg = params;
});
调用方法的父组件:
this.$notice("down", "我是通过children方式改变的数据");
八.slot
这种写法是在父组件中使用子组件的数据.也称之为作用域插槽. (solt的其它用法就不说了需要的去官网直接瞄一瞄).
父组件:
<children>
<template v-slot:header="scope">
<h1>{{scope.header}}</h1> //这里展示的就是123.
</template>
</children>
子组件:
<slot name="header" :header="123"></slot>
九.Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
vuex可以说是这几种中数据管理最好的了. 最主要的是响应式(基于vue),组件之间数据共享
看下几个常用的属性:
state
存储数据的属性, 不可直接修改state的属性值.(不方便追踪)
mutations
同步修改state的方法,通过commit触发
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state,a) { //a是传进来的参数.
// 变更状态
state.count++
}
}
})
// 触发
store.commit("increment",2)
actions
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
//触发
store.dispatch("increment"); // 这里需要异步操作返回promise.
getters
相当于vue中计算属性computed.
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
//访问
store.getters.doneTodos
modules
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
//访问
store.state.a.count
辅助函数
- mapState
- mapGetters返回的是函数,参数是在vuex定义对应的属性名的数组. 返回一个对象.
- mapMutations
- mapActions
来看一个例子:
首先引入vuex 以后,我们需要在state中定义变量,类似于vue中的data,通过state来存放状态
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: { //存放状态
nickname:'Simba',
firstname:'张',
lastname:'三丰',
age:20,
gender:'男',
money:1000
},
getters:{
realname(state){
return state.firstname + state.lastname
},
money_us(state){
return (state.money/7).toFixed(2)
}
},
mutations: {
addAge(state,payLoad){
state.age += payLoad.number
}
},
actions: {
getUserInfo(){
return {
nickname:'Simba',
age:20
}
}
},
modules: {}
})
注册vhome.vue组件引入到app.vue中
//app.vue
<div id="app">
<vhome> </vhome>
</div>
//vhome.vue
<template>
<div class="home">
{{$store.state.nickname}}:{{$store.state.age}}
<button @click="addAge({number:5})">测试</button>
</div>
</template>
//页面显示:Simba:20
直接就可以通过$store来获取不同的数据,但是如果还需要vuex中的多个数据(比如gender)的时候,这样写就太啰嗦了,我们可以l用mapState方法将它定义在computed中。
mapState
<template>
<div class="home">
{{nickname}}:{{age}}
<button @click="addAge({number:5})">测试</button>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'vhome',
computed: mapState(['nickname','age','gender'])
}
</script>
mapState(['nickname','age','gender']) //映射哪些字段就填入哪些字段,这一句就相当于下面的代码
nickname (){ return this.$store.state.nickname },
age (){ return this.$store.state.age },
gender (){ return this.$store.state.gender }
记住:用mapState等这种辅助函数的时候,前面的方法名和获取的属性名是一致的
如果我们需要自定义一个计算属性,需要es6中的展开运算符:…
<template>
<div class="home">{{nickname}}:{{age}}</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'home',
computed: { //computed是不能传参数的
value(){
return this.val/7
},
...mapState(['nickname','age','gender'])
}
}
</script>
mapGetters
<script>
import {mapGetters} from 'vuex'
export default {
name: 'vhome',
computed: { //computed是不能传参数的
valued(){
return this.value/7
},
...mapGetters(['realname','money_us'])
}
}
</script>
mapGetters(['realname','money_us']) //这一句就相当于下面的代码
realname (){ return this.$store.getters.realname },
money_us (){ return this.$store.getters.money_us },
mapMutations
<template>
<div class="home">
<button @click="addAge({number:5})">测试</button>
</div>
</template>
<script>
import {mapMutations} from 'vuex'
export default {
name: 'vhome',
methods:{
...mapMutations(['addAge'])
}
}
</script>
mapMutations(['addAge']) //这一句就相当于下面的代码
addAge(payLoad){
this.$store.commit('addAge',payLoad)
}
mapActions
<script>
import {mapActions} from 'vuex'
export default {
name: 'vhome',
created(){
var res = this.getUserInfo()
console.log(res)
},
methods:{
...mapActions(['getUserInfo'])
}
}
</script>
mapActions(['getUserInfo']) //这一句就相当于下面的代码
getUserInfo(){
return this.$store.dispatch(‘getUserInfo’)
}