九种简洁的Vue数据通讯

在编写一个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

这两个属性是一个对象.分别保存父组件中传下来的数据和方法.

首先我们来看下面的一张图,图中表示一个多级组件嵌套的情形。

img

现在我们来讨论一种情况,A组件与C组件之间数据怎么通信?

1.attrs

官方解释:

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 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 }

使用场景:

  1. 组件传值时使用: 爷爷在父亲组件传递值,父亲组件会通过 a t t r s 获 取 到 不 在 父 亲 p r o p s 里 面 的 所 有 属 性 , 父 亲 组 件 通 过 在 孙 子 组 件 上 绑 定 attrs获取到不在父亲props里面的所有属性,父亲组件通过在孙子组件上绑定 attrspropsattrs 和 $listeners 使孙组件获取爷爷传递的值并且可以调用在爷爷那里定义的方法;

  2. 对一些UI库进行二次封装时使用:比如element-ui,里面的组件不能满足自己的使用场景的时候,会二次封装,但是又想保留他自己的属性和方法,那么这个时候时候 a t t r s 和 attrs和 attrslistners是个完美的解决方案。

五.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’)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值