Vue组件通讯黑科技
Vue组件通讯
组件可谓是 Vue框架的最有特色之一, 可以将一大块拆分为小零件最后组装起来。这样的好处易于维护、扩展和复用等。
提到 Vue的组件, 相必大家对Vue组件之间的数据流并不陌生。最常规的是父组件的数据通过 prop传递给子组件。子组件要进行数据更新,那么需要通过自定义事件通知父组件。
那么还有其他方法, 方便父子组件乃至跨组件之间的通讯吗?
props $emit
可以通过 prop属性从父组件向子组件传递数据
// child.vue
Vue.component('child', {
props: ['msg'],
template: `<div>{{ msg }}</div>`
})
// parent.vue
<div>
<child :msg="message"></child>
<div>
// 部分省略...
{
data(){
return {
message: 'hello.child'
}
}
}
以上代码父子组件通讯是通过 prop的传递, Vue是单向数据流, 子组件不能直接修改父组件的数据。可以通过自定义事件通知父组件修改,和要修改的内容
provide / inject
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。但是我们如果合理的运用, 那还是非常方便的
provide
应该是一个对象, 或者是一个函数返回一个对象
。它主要的作用就是给应用provide
的组件的子孙组件提供注入数据。就相当于 Vuex的store
,用来存放数据。
inject
: 给当前的组件注入 provide
提供的数据。切记 provide
和 inject
绑定并不是可响应的。
看一下这两个怎么使用
a组件给子组件b提供数据
// a.vue
export default {
provide: {
name: 'qiqingfu'
}
}
// b.vue
export default {
inject: ['name'],
mounted(){
console.log(this.name) // qiqingfu
}
}
以上代码父组件a提供了 name: qiqingfu
的数据, 子组件和子孙组件如果需要就通过 inject
注入进来就可以使用了
provide / inject 替代 Vuex存储用户的登录数据实例
这里并不是一定要替代 vuex, 介绍另一种可行性
我们在使用 webpack进行构建 Vue项目时, 都会有一个入口文件 main.js
, 里面通常都导入一些第三方库如 vuex
、element
、vue-router
等。但是我们也引入了一个 Vue的根组件 App.vue
。简单的app.vue是这样子的
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
这里的 App.vue
是一个最外层的组件, 可以用来存储一些全局的数据和状态。因为组件的解析都是以App.vue
作为入口。引入你项目中所有的组件,它们的共同根组件就是App.vue
。所以我们可以在 App.vue
中将自己暴露出去
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
provide () {
return {
app: this
}
}
}
</script>
以上代码把整个 app.vue
实例的 this对外提供, 并且命令为 app
。那么子组件使用时只需要访问或者调用 this.app.xxx
或者访问 app.vue的 data
、computed
等。
因为 App.vue
只会渲染一次, 很适合做一些全局的状态数据管理, 比如用户的登录信息保存在 App.vue
的 data
中。
app.vue
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
provide () {
return {
app: this
}
},
data: {
userInfo: null
},
mounted() {
this.getUserInfo()
},
methods: {
async getUserInfo() {
const result = await axios.get('xxxxxx')
if (result.code === 200) {
this.userInfo = result.data.userinfo
}
}
}
}
</script>
以上代码,通过接口获取用户对数据信息, 保存在 App.vue的data中。而且provide
将实例提供给任何子组件使用。所以任何页面和子组件都可以通过 inject
注入 app
。并且通过 this.app.userInfo
来取得用户信息。
那么如果用户要修改当前的信息怎么办? App.vue只初始化一次呀?
// header.vue
<template>
<div>
<p>用户名: {{ app.userInfo.username }}</p>
</div>
<template>
<script>
export default {
name: 'userHeader',
inject: ['app'],
methods: {
async updateUserName() {
const result = await axios.post(xxxxx, data)
if (result === 200) {
this.app.getUserInfo()
}
}
}
}
</script>
以上代码, 在header.vue
组件中用户修改了个人信息, 只需要修改完成之后再调用一次 App.vue
根组件的 getUserInfo
方法, 就又会同步最新的修改数据。
dispatch / broadcast
父子组件通讯的方法。可以支持:
- 父 - 子 传递数据 (广播)
- 子 - 父 传递数据 (派发)
先聊一下 Vue实例的方法 $emit()
和$on()
。
$emit: 触发当前实例上的事件。附加参数都会传给监听器回调。
$on: 监听当前实例上的事件
也就是一个组件向父组件通过 $emit
自定义事件发送数据的时候, 它会被自己的 $on
方法监听到
// child.vue
export default {
name: 'child',
methods: {
handleEvent() {
this.$emit('test', 'hello, 这是child.vue组件')
}
},
mounted() {
// 监听自定义事件
this.$on('test', data => {
console.log(data) // hello, 这是child.vue组件
})
}
}
// parent
<template>
<div>
<child v-on:test="handleTest"></child>
</div>
<template>
<script>
export default {
methods: {
handleTest(event) {
console.log(event) // hello, 这是child.vue组件
}
}
}
</script>
以上代码, $on
监听自己触发的$emit
事件, 因为不知道何时会触发, 所以会在组件的 created
和 mounted
钩子中监听。
看起来多此一举, 没有必要在自己组件中监听自己调用的 $emit
。 但是如果当前组件的 $emit
在别的组件被调用, 并且传递数据的话那就不一样了。
举个例子
// child.vue
export default {
name: 'iChild',
methods: {
sayHello() {
// 如果我在子组件中调用父组件实例的 $emit
this.$parent.$emit('test', '这是子组件的数据')
}
}
}
// parent.vue
export default = {
name: 'iParent',
mounted() {
this.$on('test', data => {
console.log(data) // 这是子组件的数据
})
}
}
以上代码, 在子组件调用父组件实例的 $emit
方法, 并且传递响应的数据。那么在父组件的 mounted
钩子中可以用 $on
监听事件。
如果这样写肯定不靠谱, 所以我们要封装起来。哪个子组件需要给父组件传递数据就将这个方法混入(mixins)到子组件
dispatch 封装
// emitter.js
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = context.$parent || context.$root
let name = parent.$options.name
while(parent && (!name || name !== componentNamee)) {
parent = parent.$parent
if (parent) {
name = parent.$options.name
}
}
if (parent) {
parent.call.$emit(parent, eventName, params)
}
}
}
}
以上代码对 dispatch
进行封装, 三个参数分别是 接受数据的父组件name
、自定义事件名称
、传递的数据
。
对上面的例子重新修改
import Emitter from './emitter'
// child.vue
export default {
name: 'iChild',
mixins: [ Emitter ], // 将方法混入到当前组件
methods: {
sayHello() {
// 如果我在子组件中调用父组件实例的 $emit
this.dispatch('iParent', 'test', 'hello,我是child组件数据')
}
}
}
// parent.vue
export default = {
name: 'iParent',
mounted() {
this.$on('test', data => {
console.log(data) // hello,我是child组件数据
})
}
}
以上代码, 子组件要向父组件传递数据。可以将先 Emitter混入。然后再调用 dispatch
方法。第一个参数是接受数据的父组件, 也可以是爷爷组件, 只要 name
值给的正确就可以。然后接受数据的组件需要通过 $on
来获取数据。
broadcast
广播是父组件向所有子孙组件传递数据, 需要在父组件中注入这个方法。实现如下:
// emitter.js
export default {
methods: {
broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
let name = child.$options.name
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params))
} else {
// 递归
broadcast.apply(child, [componentName, eventName].concat([params]))
}
})
}
}
}
以上代码是通过递归匹配子组件的 name
, 如果没有找到那么就递归寻找。找到之后调用子组件实例的 $emit()
方法并且传递数据。
使用:
// 儿子组件 child.vue
export default {
name: 'iChild',
mounted() {
this.$on('test', data => {
console.log(data)
})
}
}
// 父亲组件 parent.vue
<template>
<div>
<child></child>
</div>
<template>
export default {
name: 'iParent',
components: {
child
}
}
// 爷爷组件 root.vue
<template>
<div>
<parent></parent>
</div>
<template>
import Emitter from './emitter'
export default {
name: 'iRoot',
mixin: [ Emitter ],
components: {
parent
},
methods: {
this.broadcast('iChild', 'test', '爷爷组件给孙子传数据')
}
}
以上代码, root根组件给孙组件(child.vue)通过调用 this.broadcast
找到对应name的孙组件实例。child.vue只需要监听 test
事件就可以获取数据。
找到任意组件实例的方法
这些方法并非 Vue组件内置, 而是通过自行封装, 最终返回你要找的组件的实例,进而可以调用组件的方法和函数等。
使用场景:
- 由一个组件,向上找到最近的指定组件
- 由一个组件,向上找到所有的指定组件
- 由一个组件,向下找到最近的指定组件
- 由一个组件,向下找到所有的指定组件
- 由一个组件,找到指定组件的兄弟组件
找到最近的指定组件
findComponentUpwarp(context, componentName)
export function findComponentUpwarp(context, componentName) {
let parent = context.$parent
let name = parent.$options.name
while(parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.name
}
}
return parent
}
这个函数接受两个参数,分别是当前组件的上下文(this),第二个参数是向上查找的组件 name。最后返回找到组件的实例。
向上找到所有的指定组件
findComponentsUpwarp(context, componentName)
return Array
export function findComponentsUpwarp(context, componentName) {
let parent = context.$parent
let result = []
if (parent) {
if (parent.$options.name === componentName) result.push(parent)
return result.concat(findComponentsUpwarp(parent, componentName))
} else {
return []
}
}
这个函数接受两个参数,分别是当前组件的上下文(this),第二个参数是向上查找的组件 name。最后返回一个所有组件实例的数组
向下找到最近的指定组件
findComponentDownwarp(context, componentName)
export function findComponentDownwarp(context, componentName) {
let resultChild = null
context.$children.forEach(child => {
if (child.name === componentName) {
resultChild = child
break
} else {
resultChild = findComponentDownwarp(child, componentName)
if (resultChild) break
}
})
return resultChild
}
以上代码接受两个参数, 当前组件的上下文(this)和向下查找的组件name。返回第一个name和componentName相同的组件实例
向下找到所有的指定组件
findComponentsDownwarp(context, componentName)
export function findComponentsDownwarp(context, componentName) {
return context.$children.reduce((resultChilds, child) => {
if (child.$options.name === componentName) resultChilds.push(child)
// 递归迭代
let foundChilds = findComponentsDownwarp(child, componentName)
return resultChilds.concat(foundChilds)
}, [])
}
以上代码接受两个参数, 当前组件的上下文(this)和向下查找的组件name。返回一个所有组件实例的数组
找到当前组件的其他兄弟组件
findBrothersComponents(context, componentName, exceptMe?)
exceptMe默认为true, 排除它自己, 如果设置为false则包括当前组件
export function findBrothersComponents(context, componentName, exceptMe = true) {
let res = context.$parent.$children.filter(child => child.$options.name === componentName)
// 根据唯一表示_uid找到当前组件的下标
let index = res.findIndex(item => item._uid === context._uid)
if (exceptMe) res.splice(index, 1)
return res
}
以上代码通过第一个参数的上下文, 拿到它父组件中的所有子元素,最后根据 exceptMe
决定是否排除自己。
Vue组件通讯黑科技的更多相关文章
- vue 组件通讯方式到底有多少种 ?
前置 做大小 vue 项目都离不开组件通讯, 自己也收藏了很多关于 vue 组件通讯的文章. 今天自己全部试了试, 并查了文档, 在这里总结一下并全部列出, 都是简单的例子. 如有错误欢迎指正. 温馨 ...
- Vue组件通讯
Vue最常用的组件通讯有三种:父->子组件通讯.子->父组件通讯,兄弟组件通讯.(template用的pug模板语法) 1.父->子组件通讯 父->子组件通讯,是通过props ...
- vue组件通讯之provide / inject
什么是 provide / inject [传送门] vue的组件通讯方式我们熟知的有 props $emit bus vuex ,另外就是 provide/inject provide/inject ...
- vue组件通讯方法汇总(在不使用vuex的情况下)
前三种是父子组件通讯,最后一种是平级组件.
- 依赖VUE组件通讯机制实现场景游戏切换
- Vue最常用的组件通讯有三种:父->子组件通讯、子->父组件通讯,兄弟组件通讯.(template用的pug模板语法)
Vue组件通讯 Vue最常用的组件通讯有三种:父->子组件通讯.子->父组件通讯,兄弟组件通讯.(template用的pug模板语法) 1.父->子组件通讯 父->子组件通 ...
- 【Vue】Vue中的父子组件通讯以及使用sync同步父子组件数据
前言: 之前写过一篇文章<在不同场景下Vue组件间的数据交流>,但现在来看,其中关于“父子组件通信”的介绍仍有诸多缺漏或者不当之处, 正好这几天学习了关于用sync修饰符做父子组件数据双向 ...
- Vue中父子组件通讯——组件todolist
一.todolist功能开发 <div id="root"> <div> <input type="text" v-model=& ...
- seventBus(封装) 一个巧妙的解决vue同级组件通讯的思路
如果在你项目中需要多处用到同级组件通讯,而又不想去写繁琐的vuex,可以参考这个小思路.本人在写项目中琢磨出来的,感觉挺好用,分享一下. 1.在utils文件夹下添加BusEvent.js 注释已经很 ...
随机推荐
- sql: 生日赠品中的相关算法
---2013年10月9日生日,就以2012年9月1日至2013年8月31日計算 (因為係生日月份前兩個月之最後一天為結算日) DECLARE @birthday datetime,@now date ...
- Python进阶篇四:Python文件和流
摘要: Python对于文件和流的操作与其他编程语言基本差不多,甚至语句上比其他语言更为简洁.文件和流函数针对的对象除了这两者之外还有,类文件(file-like),即python中只支持读却不支持写 ...
- css3制作有动画效果的面板
.show-panel .slide-panels{ right: 0px; } .slide-panels{ z-index: 101; background: #fff; position: fi ...
- H5手机页面剖析
<!--强制使用webkit内核进行渲染--><meta http-equiv="X-UA-COMPATIBLE" content="IE=edge, ...
- linux里终端安转视频播放器的操作及显示
[enilu@enilu ~]$ mplayerbash: mplayer: command not found[enilu@enilu ~]$ yum list | grep mplayer^C^C ...
- Apache转发到Tomcat
#vi /etc/httpd/conf/httpd.conf 添加下面配置 NameVirtualHost *:80 <VirtualHost *:80>ProxyPreserveHost ...
- 我的SublimeText配置
我的SublimeText改键 [ /** * 我的改键 */ // f1控制中心:f2快速查找:f3查找下一个: { "keys": ["f1"], &quo ...
- matlab练习程序(模拟退火SA)
模拟退火首先从某个初始候选解开始,当温度大于0时执行循环. 在循环中,通过随机扰动产生一个新的解,然后求得新解和原解之间的能量差,如果差小于0,则采用新解作为当前解. 如果差大于0,则采用一个当前温度 ...
- solidity语言14
库(Libraries) 库类似合约,实现仅在专门地址部署一次,使用EVM的DELEGATECALL的功能重复使用的目的.意思是当库函数被调用后,代码执行在被调用的合约的环境.例如,使用this调用合 ...
- SQL Server ->> DISABLE索引后插入更新数据再REBUILD索引 和 保留索引直接插入更新数据的性能差异
之前对于“DISABLE索引后插入更新数据再REBUILD索引 和 保留索引直接插入更新数据的性能差异”这两种方法一直认为其实应该差不多,因为无论如何索引最后都需要被维护,只不过是个时间顺序先后的问题 ...