Vue
中组件间通讯包括父子组件、兄弟组件、隔代组件之间通讯。javascript
这种组件通讯的方式是咱们运用的很是多的一种,props
以单向数据流的形式能够很好的完成父子组件的通讯,所谓单向数据流,就是数据只能经过props
由父组件流向子组件,而子组件并不能经过修改props
传过来的数据修改父组件的相应状态,全部的prop
都使得其父子prop
之间造成了一个单向下行绑定,父级prop
的更新会向下流动到子组件中,可是反过来则不行,这样会防止从子组件意外改变父级组件的状态,致使难以理解数据的流向而提升了项目维护难度。实际上若是传入一个基本数据类型给子组件,在子组件中修改这个值的话Vue
中会出现警告,若是对于子组件传入一个引用类型的对象的话,在子组件中修改是不会出现任何提示的,这两种状况都属于改变了父子组件的单向数据流,是不符合可维护的设计方式的。
正由于这个特性,而咱们会有须要更改父组件值的需求,就有了对应的$emit
,当咱们在组件上定义了自定义事件,事件就能够由vm.$emit
触发,回调函数会接收全部传入事件触发函数的额外参数,$emit
实际上就是是用来触发当前实例上的事件,对此咱们能够在父组件自定义一个处理接受变化状态的逻辑,而后在子组件中如若相关的状态改变时,就触发父组件的逻辑处理事件。html
父组件向子组件传值经过prop
传递值便可。vue
<!-- 子组件 --> <template> <div> <div>我是子组件,接收:{{ msg }}</div> </div> </template> <script> export default { name: "child", components: {}, props: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父组件 --> <template> <div> <child :msg="msg"></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "父组件 Msg" }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
子组件向父组件传值须要经过事件的触发,将更改值的行为传递到父组件去执行。java
<!-- 子组件 --> <template> <div> <div>我是子组件,接收:{{ msg }}</div> <button @click="$emit('changeMsg', '子组件传值 Msg')">触发事件并传递值到父组件</button> </div> </template> <script> export default { name: "child", components: {}, props: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父组件 --> <template> <div> <child :msg="msg" @changeMsg="changeMsg" ></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "父组件 Msg" }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
v-model
一般称为数据双向绑定,也能够称得上是一种父子组件间传值的方式,是当前组件与input
等组件进行父子传值,其本质上就是一种语法糖,经过props
以及input
(默认状况下)的事件的event
中携带的值完成,咱们能够自行实现一个v-model
。git
<template> <div> <div>{{msg}}</div> <input :value="msg" @input="msg = $event.target.value"> </div> </template> <script> export default { data: () => ({ msg: "Msg" }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
sync
修饰符也能够称为一个语法糖,在Vue 2.3
以后新的.sync
修饰符所实现的已经再也不像Vue 1.0
那样是真正的双向绑定,而是和v-model
相似,是一种语法糖的形式,也能够称为一种缩写的形式,在下面父组件两种写法是彻底等同的。github
<!-- 子组件 --> <template> <div> <div>我是子组件,接收:{{ msg }}</div> <button @click="$emit('update:msg', '子组件传值 Msg')">触发事件并传递值到父组件</button> </div> </template> <script> export default { name: "child", components: {}, props: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父组件 --> <template> <div> <child :msg="msg1" @update:msg="msg1 = $event" ></child> <child :msg.sync="msg2" ></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg1: "父组件 Msg1", msg2: "父组件 Msg2", }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
相似于React
的Context API
,在父组件中经过provider
来提供属性,而后在子组件中经过inject
来注入变量,不论子组件有多深,只要调用了inject
那么就能够注入在provider
中提供的数据,而不是局限于只能从当前父组件的prop
属性来获取数据,只要在父组件内的数据,子组件均可以调用。固然Vue
中注明了provide
和inject
主要在开发高阶插件/
组件库时使用,并不推荐用于普通应用程序代码中。vuex
<!-- 子组件 --> <template> <div> <div>inject: {{msg}}</div> </div> </template> <script> export default { name: "child", inject: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<template> <div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ }), provide: { msg: "provide msg" }, beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
这种组件通讯的方式适合直接的父子组件,假设此时咱们有三个组件分别为A
、B
、C
,父组件A
下面有子组件B
,父组件B
下面有子组件C
,这时若是组件A
直接想传递数据给组件C
那就不能直接传递了,只能是组件A
经过props
将数据传给组件B
,而后组件B
获取到组件A
传递过来的数据后再经过props
将数据传给组件C
,固然这种方式是很是复杂的,无关组件中的逻辑业务增多了,代码维护也没变得困难,再加上若是嵌套的层级越多逻辑也复杂,无关代码越多,针对这样一个问题,Vue 2.4
提供了$attrs
和$listeners
来实现可以直接让组件A
直接传递消息给组件C
。segmentfault
<!-- 子子组件 --> <template> <div> </div> </template> <script> export default { name: "child-child", components: {}, data: () => ({ }), beforeCreate: function() {}, created: function() { console.log(this.$attrs); // {param: 1, test: 2} console.log(this.$listeners); // {testEvent: ƒ} }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 子组件 --> <template> <div> <!-- 直接将剩余的参数传递给子组件 --> <child-child v-bind="$attrs" v-on="$listeners"></child-child> </div> </template> <script> import childChild from "./child-child"; export default { name: "child", components: { childChild }, props: ["msg"], // 声明了接收名为msg的prop 此时在此组件的$attrs则不会再有msg参数 data: () => ({ }), inheritAttrs: false, // 默认设置为true也可 // 默认状况下true 父做用域的不被认做 props 的 attribute 绑定将会回退且做为普通的 HTML attribute 应用在子组件的根元素上。 beforeCreate: function() {}, created: function() { console.log(this.$attrs); // {param: 1, test: 2} console.log(this.$listeners); // {testEvent: ƒ} }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父组件 --> <template> <div> <child :msg="msg" :param="1" :test="2" @testEvent="tips" ></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "Msg", }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: { tips: function(...args){ console.log(args); } } } </script> <style scoped> </style>
这种方式就比较直观了,直接操做父子组件的实例,$parent
就是父组件的实例对象,而$children
就是当前实例的直接子组件实例数组了,官方文档的说明是子实例能够用this.$parent
访问父实例,子实例被推入父实例的$children
数组中,节制地使用$parent
和$children
它们的主要目的是做为访问组件的应急方法,更推荐用props
和events
实现父子组件通讯。此外在Vue2
以后移除的$dispatch
和$broadcast
也能够经过$children
与$parent
进行实现,固然不推荐这样作,官方推荐的方式仍是更多简明清晰的组件间通讯和更好的状态管理方案如Vuex
,实际上不少开源框架都仍是本身实现了这种组件通讯的方式,例如Mint UI
、Element UI
和iView
等。数组
<!-- 子组件 --> <template> <div> </div> </template> <script> export default { name: "child", data: () => ({ }), beforeCreate: function() {}, mounted: function() { console.log(this.$parent); // VueComponent {_uid: 2, ...} console.log(this.$children); // [] }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父组件 --> <template> <div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ }), beforeCreate: function() {}, mounted: function() { console.log(this.$parent); // VueComponent {_uid: 1, ...} console.log(this.$children); // [VueComponent] }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
在项目规模不大的状况下,彻底可使用中央事件总线EventBus
的方式,EventBus
能够比较完美地解决包括父子组件、兄弟组件、隔代组件之间通讯,实际上就是一个观察者模式,观察者模式创建了一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其余对象,其余对象将相应作出反应。因此发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标能够对应多个观察者,并且这些观察者之间没有相互联系,能够根据须要增长和删除观察者,使得系统更易于扩展。首先咱们须要实现一个订阅发布类,并做为全局对象挂载到Vue.prototype
,做为Vue
实例中可调用的全局对象使用,此外务必注意在组件销毁的时候卸载订阅的事件调用,不然会形成内存泄漏。app
// 实现一个PubSub模块 var PubSub = function() { this.handlers = {}; } PubSub.prototype = { on: function(key, handler) { // 订阅 if (!(key in this.handlers)) this.handlers[key] = []; this.handlers[key].push(handler); }, off: function(key, handler) { // 卸载 const index = this.handlers[key].findIndex(item => item === handler); if (index < 0) return false; if (this.handlers[key].length === 1) delete this.handlers[key]; else this.handlers[key].splice(index, 1); return true; }, commit: function(key, ...args) { // 触发 if (!this.handlers[key]) return false; this.handlers[key].forEach(handler => handler.apply(this, args)); return true; }, } export { PubSub } export default { PubSub }
<!-- 子组件 --> <template> <div> <div>{{msg}}</div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "init" }), beforeCreate: function() {}, created: function() { this.eventBus.on("ChangeMsg", this.changeMsg); }, beforeDestroy: function(){ this.eventBus.off("ChangeMsg", this.changeMsg); }, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
<!-- 父组件 --> <template> <div> <div>{{msg}}</div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "init" }), beforeCreate: function() {}, created: function() { this.eventBus.on("ChangeMsg", this.changeMsg); }, beforeDestroy: function(){ this.eventBus.off("ChangeMsg", this.changeMsg); }, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
Vuex
是一个专为Vue.js
应用程序开发的状态管理模式,其采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
每个Vuex
应用的核心就是store
仓库,store
基本上就是一个容器,它包含着你的应用中大部分的状态state
。Vuex
和单纯的全局对象有如下两点不一样:
Vuex
的状态存储是响应式的,当Vue
组件从store
中读取状态的时候,若store
中的状态发生变化,那么相应的组件也会相应地获得高效更新。store
中的状态,改变store
中的状态的惟一途径就是显式地提交mutation
,这样使得咱们能够方便地跟踪每个状态的变化。实际上咱们能够获得更多使用Vuex
的优势:
Vuex
专作态管理,由一个统一的方法去修改数据,所有的修改都是能够追溯的。Vuex
更方便。Vuex
不会形成全局变量的污染,同时解决了父组件与孙组件,以及兄弟组件之间通讯的问题。固然若是项目足够小,使用Vuex
多是繁琐冗余的。若是应用够简单,最好不要使用Vuex
,上文中的一个简单的store
模式就足够了。
在下面例子中,咱们经过提交mutation
的方式,而非直接改变store.state.count
,是由于咱们想要更明确地追踪到状态的变化。这个简单的约定可以让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外这样也让咱们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment: function(state) { state.count++; } } }) store.commit("increment"); console.log(store.state.count); // 1
因为store
中的状态是响应式的,在组件中调用store
中的状态简单到仅须要在计算属性中返回便可。触发变化也仅仅是在组件的methods
中提交mutation
便可。
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment: state => state.count++, decrement: state => state.count-- } }) new Vue({ el: "#app", store, computed: { count: function() { return this.$store.state.count; } }, methods: { increment: function() { this.$store.commit("increment"); }, decrement: function() { this.$store.commit("decrement"); } } })
https://github.com/WindrunnerMax/EveryDay
https://zhuanlan.zhihu.com/p/109700915 https://juejin.cn/post/6844903887162310669 https://juejin.cn/post/6844903784963899405 https://segmentfault.com/a/1190000022083517 https://github.com/yangtao2o/learn/issues/97 https://github.com/YangYmimi/read-vue/issues/12