Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue中组件通信的九种方法 #44

Open
YIngChenIt opened this issue Jul 3, 2020 · 0 comments
Open

Vue中组件通信的九种方法 #44

YIngChenIt opened this issue Jul 3, 2020 · 0 comments

Comments

@YIngChenIt
Copy link
Owner

Vue中组件通信的九种方法

前言

Vue中的组件通信是一个比较复杂的一个知识点,涵盖各种api和语法糖,接下来我们分别来介绍下常见的九种组件通信方法

props 和 $emit

这是我们日常开发最常见的一种父子组件通信的方法了,我们来看一个简单的例子,后文全部方法都围绕这个例子进行

// App.vue
<Parent />

import Parent from './components/Parent'
export default {
    components: {
        Parent,
    }
}
// Parent.vue
<Son @change="change" :number="number" />

import Son from './Son'
export default {
    data() {
        return {
            number: 100,
        }
    },
    methods: {
        change(newVale) {
            this.number = newVale
        }
    }
}
// Son.vue
<div>子组件
    {{ number }}
    <button @click="change">修改父组件的值</button>
</div>

export default {
    props: {
        number: {
            type: Number
        }
    },
    methods: {
        change() {
            this.$emit('change', 200)
        }
    }
}

我们可以发现,父组件通过props将数据传递给子组件,而子组件可以通过$emit的方式调用父组件的方法修改父组件的值,我们知道Vue和React都是单向数据流,也就是数据是从上往下传递的,当我们子组件修改父组件的数据的时候,自己拿到的数据也会随着修改

$parent 和 $children

介绍了最常见的一种用法,我们接下来看下一种更暴力的用法 $parent$children

// Son.vue
<button @click="change">修改父组件的值</button>
methods: {
    change() {
        this.$parent.change(200)
    }
}

我们可以通过$parent拿到父组件的实例,然后调用父组件的方法改变父组件的数据,同样道理我们在父组件中也可以使用$children的方法修改子组件的值,这里就不演示啦

$dispatch

这个也是在element ui 的源码中看到的一个方法

// main.js
Vue.prototype.$dispatch = function(eventName, newValue) {
    let parent = this.$parent
    while (parent) {
        parent.$emit(eventName, newValue)
        parent = parent.$parent // 继续递归接着往上找
    }
}

我们将$dispatch挂载到Vue的原型上方便调用,我们可以看到,$dispatch的原理是一直递归找父组件,然后执行父组件中对应的$emit方法,我们来看下怎么用

// Parent.vue
<div>父组件
    <Son @change="change" :number="number" />
</div>

export default {
    data() {
        return {
            number: 100,
        }
    },
    methods: {
        change(newVale) {
            this.number = newVale
        }
    }
}
// Son.vue
<div>子组件
    {{ number }}
    <br/>
    <Grandson />
</div>

export default {
    props: {
        number: {
            type: Number
        }
    },
}
// Grandson.vue
<div>孙组件
    <button @click="change">修改父组件的值</button>
</div>

export default {
    methods: {
        change(newVale) {
            this.$dispatch('change', 200)
        }
    }
}

我们可以发现在孙子组件中调用$dispatch方法,他会触发所以父组件中的$emit('xxx', xxx)方法,达到修改对应父组件数据的目的

$broadcast

$broadcast的原理和$dispatch很像,$dispatch是一直往父元素上面找,而$broadcast刚刚相反,一直往子元素上面找对应的方法执行

// main.js
Vue.prototype.$broadcast = function(eventName, newValue) {
    let children = this.$children
    function broad(children) {
        children.forEach(child => {
            child.$emit(eventName, newValue)
            if (child.$children) {
                broad(child.$children)
            }
        })
    }
    broad(children)
}

同样是挂载原型的方法,有了这个方法我们就可以在父组件中主动调用全部后代组件中的$emit方法了

.sync 和 v-modal

.sync

接下来我们看到的这种用法其实是 props$emit的一些语法糖, 我们来看下使用props$emit时候的代码

// Parent.vue
<Son @update:number="newValue =>number = newValue" :number="number" />
// Son.vue
<button @click="change">更新父组件的方法</button>
methods: {
    change() {
        this.$emit('update:number', 200)
    }
}

我们只需要将$emit(fn)中的fn改为update:xxx就可以了,其中xxx为父组件传递给子组件的数据名

为什么要这样写呢?因为上述代码Vue给我们提供了一个语法糖

// Parent.vue
<Son @update:number="newValue =>number = newValue" :number="number" />
// 等价于
<Son :number.sync="number" />

不过需要注意的是,在子组件中调用$emit(fn, val)的时候fn的名字一定是这样的格式的update:xxx

v-modal

既然 props$emit 可以这么玩了,那我们再试一下另外的玩法

// Parent.vue
 <Son @input="newValue =>number = newValue" :value="number" />
// Son.vue
props: {
    value: {
        type: Number
    }
},
change() {
    this.$emit('input', 200)
}

我们只需要稍微的修改一下props$emit的用法也可以实现相应的逻辑,但是这样写的话我们不由想到这种写法是的语法糖是
v-modal,那我们的父组件可以这样使用

<Son v-model="number" />

但是啊v-modal的局限性是只能传递value, 而且只可以传递一次

$attrs 和 $listeners

$attrs 和 v-bind

假设我们有一个需求,要将父组件的数据传递给孙组件,但是我们的子组件又没有使用到这些数据,这个时候我们可以使用$attrs

// Parent.vue
 <Son :number="number" :count="count" />
// Son.vue
<Grandson v-bind="$attrs" />
// Grandson.vue
{{ $attrs }}

在子组件中可以通过v-bind="$attrs"的方式将父组件传递下来的数据原封不动的传递给孙组件,而孙组件可以通过$attrs来接收,不过这个用法有个局限性就是子组件不可以接收父组件的数据,也就是不可以有props钩子

$listeners 和 v-on

既然可以这样传数据了,那方法是不是也可以这么玩,答案是肯定的

// Parent.vue
<Son :number="number" @change="change" @say="say" :count="count" />
// Son.vue
<Grandson v-on="$listeners" />
// Grandson.vue
mounted() {
    console.log(this.$listeners)
}

通过 $listenersv-on的配合可以将全部事件传递给孙组件,但是这里需要注意的一点是,子组件也是可以通过$listeners来接收父组件传递的全部事件的

eventbus

我们先来说下eventbus的原理,假设现在2个组件A和B需要通信,那么我可以在组件C上面用$on发布一个事件,那么A和B就可以通过C.$emit()来互相通信了吧,就是弄一个全局的发布订阅

// main.js
Vue.prototype.$bug = function() {
    return new Vue()
}

我们知道Vue本身就是具有发布订阅能力的,这里我们直接new Vue()就好啦

那么怎么使用呢?

// 组件A - 在组件A中发布事件
this.$bus.$on('change', ()=>{})
// 组件B - 在组件B中可以订阅这个事件实现通信
this.$bus.$emit('change')

ref

其实一直在纠结ref算不算组件通信的一种,但是我们可以通过ref拿到某一个组件的实例,也就是拿到某一个组件的方法和数据

// Parent.vue
<Son ref="son"/>
mounted() {
    console.log(this.$refs.son)
}

可以通过$refs拿到某个组件的实例

provide 和 inject

接下来我们来说一下很多人都不知道的一种组件传值方法provideinject
我们只需要在父组件使用provide定义需要向下传递的数据,不管层级多深后代组件都可以通过inject接收

// Parent.vue
export default {
    provide() {
        return {vm: this}
    },
}

父组件向下传递数据vm

// Son.vue
export default {
    inject: ['vm'],
    mounted() {
        console.log(this.vm)
    }
}

子组件可以通过inject接收

但是这个方法在日常开发中不建议使用,因为太乱太难管理了

总结

Vue的组件通信太复杂了,因为语法糖和api太多了,所以我们还是选择vuex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant