组件可谓是 Vue框架的最有特点之一, 能够将一大块拆分为小零件最后组装起来。这样的好处易于维护、扩展和复用等。javascript
提到 Vue的组件, 相必你们对Vue组件之间的数据流并不陌生。最常规的是父组件的数据经过 prop传递给子组件。子组件要进行数据更新,那么须要经过自定义事件通知父组件。vue
那么还有其余方法, 方便父子组件乃至跨组件之间的通信吗?java
能够经过 prop属性从父组件向子组件传递数据webpack
// 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是单向数据流, 子组件不能直接修改父组件的数据。能够经过自定义事件通知父组件修改,和要修改的内容ios
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。可是咱们若是合理的运用, 那仍是很是方便的web
provide
应该是一个对象, 或者是一个函数返回一个对象
。它主要的做用就是给应用provide
的组件的子孙组件提供注入数据。就至关于 Vuex的store
,用来存放数据。vue-router
inject
: 给当前的组件注入 provide
提供的数据。切记 provide
和 inject
绑定并非可响应的。vuex
看一下这两个怎么使用axios
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
方法, 就又会同步最新的修改数据。
父子组件通信的方法。能够支持:
先聊一下 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)到子组件
// 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
来获取数据。
广播是父组件向全部子孙组件传递数据, 须要在父组件中注入这个方法。实现以下:
// 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
决定是否排除本身。