Vue组件通信---父子组件通信、兄弟组件通信、跨层级组件通信

1. 组件使用(引入(全局|局部)组件)

1. 全局引入

  1. 在main.js文件中引入并注册
import ChildrenComponent from '@/views/components/ChildrenComponent '
Vue.component('ChildrenComponent ',ChildrenComponent )// 第一个参数 全局组件的名字(字符串类型),第二个参数:引入的组件名(一般都与组件名保持一致)
之后就可以全局使用组件了

2. 局部引入

  1. 在父组件中引入
import ChildrenComponent from '@/views/components/ChildrenComponent'
export default {
  components: {
    ChildrenComponent,
  },
}
之后就可以在父组件中使用组件了

3. 页面使用

<ChildrenComponent ></ChildrenComponent >
<!-- 或 -->
<children-component></children-component>

2. props的写法

1. 字符串数组形式

```
  props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
```

2. 指定prop值类型

```
	props: {
	  title: String,
	  likes: Number,
	  isPublished: Boolean,
	  commentIds: Array,
	  author: Object,
	  callback: Function,
	  contactsPromise: Promise // or any other constructor (其他构造函数)
	}

```

3. 指定 prop 的验证要求

  1. 当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
```
  props: {
	    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
	    propA: Number,
	    // 多个可能的类型
	    propB: [String, Number],
	    // 必填的字符串
	    propC: {
	      type: String,
	      required: true
	    },
	    // 带有默认值的数字
	    propD: {
	      type: Number,
	      default: 100
	    },
	    // 带有默认值的对象
	    propE: {
	      type: Object,
	      // 对象或数组默认值必须从一个工厂函数获取
	      default: function () {
	        return { message: 'hello' }
	      }
	    },
	     // 数组类型
	    propE2: {
	      type: Array,
	      // 对象或数组默认值必须从一个工厂函数获取
	      default: function () {
	        return []
	      }
	    },
	    // 自定义验证函数
	    propF: {
	      validator: function (value) {
	        // 这个值必须匹配下列字符串中的一个
	        return ['success', 'warning', 'danger'].indexOf(value) !== -1
	      }
	    }
	  },
```
  1. 注意: prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。

4.备注(sync修饰符,native修饰符)

  1. 一般情况下,子组件不能直接修改从父组件接收的属性值,否则会报错,如果子组件需要接收值后处理再使用,可以将接收的值赋值给子组件本身的属性,如data中的属性或计算属性。

  2. 如果希望子组件prop父组件中的值改变时,将变化同步到父组件中,可使用事件监听或**.sync修饰符**,sync修饰符是一个语法糖,本质上等同于事件监听的方法

    1. 父组件

      <h1>父组件title值:{{ title }}</h1>
      <ChildrenComponent :title.sync="title"></ChildrenComponent >
      
    2. 子组件

      <template>
        <div class="ChildrenComponent ">
          <h1>子组件</h1>
          <input type="text" v-model="childTitle" />
        </div>
      </template>
      
      <script>
      export default {
        props: ["title"],
        data() {
          return {};
        },
        computed: {
          childTitle: {
            get() {
              return this.title;
            },
            set(val) {
              this.$emit("update:title", val);//更新父组件中的title
            },
          },
        }
      };
      </script>
      
      
    3. 效果:当子组件中input内容改变时,父组件中的title会同步改变

  3. 在一个组件的根元素上直接监听一个原生事件,需要使用native修饰符

    div id="app">
      <!-- 直接绑定原生事件无效 -->
      <!-- <child @click="show"></child> -->
    
      <!-- 添加native修饰符 -->
      <child @click.native="show"></child>
    </div>
    

3. 父子组件通信

1.父->子组件传递数据的注意事项

  1. 每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值
  2. 不应该在一个子组件内部改变 prop。如果这样做了,Vue会在浏览器的控制台中发出警告。 可以先把该值赋给子组件自己的变量,然后去更改复制后的变量
  3. 如果你传进来的是个对象,同时你又需要在子组件中操作传进来的这个数据,那么在父组件中的这个数据也会改变,因为你传递的只是个引用, 你只能对对象做深拷贝创建一个副本才能继续操作

2. 父子组件相互传递数据,子组件触发父组件方法

<template>
  <div>
    <h1>父组件</h1>
    //使用子组件
    <child-component :message="message" @test-event="handleEvent"></child-component>
  </div>
</template>

<script>
// 引用子组件
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
  // 声明子组件
    ChildComponent
  },
  data() {
    return {
      message: '这是父组件的数据'
    }
  },
  methods: {
    handleEvent(data) {
      console.log('父组件接收到子组件触发的事件,数据为:', data);
    }
  },
}
</script>

  1. 代码中在父组件中定义了一个名为 message 的数据,并通过 :message=“message” 的方式将其传递给子组件。
  2. 父组件可以通过自定义事件的方式向子组件传递事件。子组件可以通过 $emit 方法触发父组件定义的事件。
  3. 代码中在父组件中通过 @test-event=“handleEvent” 的方式监听子组件触发的 test-event 事件,并定义一个名为 handleEvent 的方法来处理事件。
<template>
  <div>
    <h2>子组件</h2>
    <p>{{ message }}</p>
     <button @click="emitEvent">点击触发事件</button>
  </div>
</template>

<script>
export default {
  props: ['message'],
    methods: {
    emitEvent() {
      this.$emit('test-event', '这是子组件传递的数据');
    }
  }
}
</script>

  1. 在子组件中,可以通过 props: [‘message’] 的方式声明一个名为 message 的属性,用于接收父组件传递的数据。
  2. 在子组件中,可以通过 this.$emit(‘test-event’, ‘这是子组件传递的数据’) 的方式触发父组件定义的 test-event 事件,并传递一些数据给父组件。

3. 使用 $refs (父组件访问子组件的数据和方法)

  1. 使用时需在调用子组件时给子组件定义一个 ref 名
//父组件:
<ChildComponent ref="childComponent "></ChildComponent >
<button @click="getChildData">点击获取子组件数据</button>

getChildData: function () {
  let child = this.$refs.childComponent //获取子组件实例
  console.log(child.value);//访问子组件属性
  child.childFn() //调用子组件的childFn()方法
},
  1. 注意:
    1. $refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs。
    2. 由于ref 需要在dom渲染完成后才会有,在使用的时候确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。

4.使用$children:(父组件访问子组件的数据和方法)

  1. 获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法等。
  2. c h i l d r e n 和 children和 childrenparent 并不保证顺序,也不是响应式的,只能拿到一级父组件和子组件。
// Parent.vue
export default{
    mounted(){
        this.$children[0].someMethod() // 调用第一个子组件的方法
        this.$children[0].name // 获取第一个子组件中的属性
    }
}

5. 使用$parent(子组件访问父组件的数据和方法)

子组件:
<button @click="getParentData">点击获取父组件数据</button>

getParentData(){
  let parent = this.$parent //获取父组件实例
  console.log(parent.parentValue) //访问父组件属性
  parent.parentFn() //调用父组件的方法parentFn()
}

6.使用v-model进行双向绑定数据传递

  1. 在父组件中,我们定义一个变量作为数据源,并将其传递给子组件。在子组件中,我们接受这个数据源,并将其绑定到一个输入框上。当用户在输入框中输入内容时,这个变量将会被更新,同时也会更新父组件中的数据。
<!-- 父组件 -->
<template>
  <div>
    <h1>父组件</h1>
    <p>父组件的数据:{{data}}</p>
    <ChildComponent v-mode:value="data"></ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      data: ''
    };
  }
};
</script>
<!-- 子组件 -->
<template>
  <div>
    <h2>子组件</h2>
    <input type="text" v-model:value="value">
    <p>子组件的数据:{{value}}</p>
  </div>
</template>

<script>
export default {
  props: ['value']
};
</script>
  1. 在这个例子中,我们在父组件中使用了ChildComponent,并通过v-model指令将data传递给了子组件。在子组件中,我们用v-model指令将value绑定到了一个输入框上,同时展示了当前的value值。当用户在输入框中输入内容时,value将会被更新,同时也会更新父组件中的data。

  2. 这是v-model的魔力所在:父组件和子组件之间的数据变化是双向的。无论是从父组件到子组件,还是从子组件到父组件,数据都将保持同步。

4.兄弟组件通信

  1. 思路: 把兄弟组件共享的数据定义在父组件,A组件通过子传父的方式,向父组件传值,父组件再通过父向子的方式,向B组件传值。

5. 全局事件总线的使用

  1. EventBus 是中央(全局)事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作。
  2. 声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上;
  3. 类似于 Vuex。但这种方式只适用于极小的项目;
  4. 原理就是利用on和emit 并实例化一个全局 vue 实现数据共享;
  5. 可以实现平级,嵌套组件传值,但是对应的事件名eventTarget必须是全局唯一的;

1. 方法一 直接挂载到全局

  1. main.js中安装事件总线

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线
    	},
    })
    
  2. 发送数据 (组件A)

    <template>
    	<div class="student">
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<button @click="sendStudentName">把学生名给School组件</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男',
    			}
    		},
    		mounted() {
    			// console.log('Student',this.x)
    		},
    		methods: {
    			sendStudentName(){
    				this.$bus.$emit('hello',this.name)
    			}
    		},
    	}
    </script>
    
    <style lang="less" scoped>
    	.student{
    		background-color: pink;
    		padding: 5px;
    		margin-top: 30px;
    	}
    </style>
    
    
  3. 接收数据(组件B)

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'测试',
    				address:'测试地址',
    			}
    		},
    		mounted() {
    			// console.log('School',this)
    			this.$bus.$on('hello',(data)=>{
    				console.log('我是School组件,收到了数据',data)
    			})
    		},
    		beforeDestroy() {
    			this.$bus.$off('hello')
    		},
    	}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    
  4. 总结(4个步骤)

    1. main.js中安装全局事件总线

    2. 组件A中用this.$ bus.$emit(‘事件名’,val)发送要传的数据

    3. 组件B中用this.$ bus.$on(‘事件名’,callback)监听(接收)A传过来的数据

    4. 组件B中this.$ bus.$off(‘事件名’)移除事件监听(销毁全局事件总线)

2. 方法二 抽离成一个单独的 js 文件 Bus.js ,然后在需要的地方引入

  1. 创建一个event_bus.js 文件
    import Vue from 'vue'
    export const EventBus = new Vue()
    

在这里插入图片描述

  1. 具体使用
    // 在需要向外部发送自定义事件的组件内
    <template>
        <button @click="handlerClick">按钮</button>
    </template>
    import {
    		EventBus
    	} from '@/event/event_bus.js'
    export default{
        methods:{
            handlerClick(){
                // 自定义事件名 sendMsg
                EventBus.$emit("sendMsg", "这是要向外部发送的数据")
            }
        }
    }
    
    // 在需要接收外部事件的组件内
    import {
    		EventBus
    	} from '@/event/event_bus.js'
    export default{
        mounted(){
            // 监听事件的触发
            EventBus.$on("sendMsg", data => {
                console.log("这是接收到的数据:", data)
            })
        },
        beforeDestroy(){
            // 取消监听
            EventBus.$off("sendMsg")
        }
    }
    

6. $ attrs和$listener 实现步骤

  1. 主要用于孙组件获取父组件的属性和方法。
  2. 多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,这就有点大材小用了。所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。
  3. nheritAttrs:默认值为 true。
  4. inheritAttrs:true 继承除props之外的所有属性;inheritAttrs:false 只继承class属性
  5. 默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。

父组件的代码:

<template>
  <div>
    <child-component  :foo="foo" :bar="bar"></child-component >
  </div>
</template>
​
<script>
import ChildComponent from "../components/ChildComponent /ChildComponent .vue";
export default {
  components: {
    ChildComponent ,
  },
  data() {
    return {
      foo: "foo",
      bar: "bar",
    };
  },
};
</script>

子组件的代码:

<template>
  <div>
    <p>foo:{{ foo }}</p>
  </div>
</template>
​
<script>
export default {
  props: ["foo"],
};
</script>

在这里插入图片描述

1.$attrs

$ attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。

2.$listener

$ listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。

父组件:

<template>
   <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
    import Child from '../components/child.vue'

    export default {
        name: 'father',
        components: { Child },
        data () {
            return {
                name: 'mary',
                age: 25,
                infoObj: {
                    from: '杭州',
                    job: 'IT',
                    hobby: ['reading', 'writing', 'hopping']
                }
            }
        },
        methods: {
            updateInfo() {
                console.log('update info');
            },
            delInfo() {
                console.log('delete info');
            }
        }
    }
</script>

子组件:

<template>
    <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners"  />
    // 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
    import GrandSon from '../components/grandSon.vue'
    export default {
        name: 'child',
        components: { GrandSon },
        props: ['name'],
        data() {
          return {
              height: '160cm',
              weight: '50kg'
          };
        },
        created() {
            console.log(this.$attrs); 
       // 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
            console.log(this.$listeners); // updateInfo: f, delInfo: f
        },
        methods: {
            addInfo () {
                console.log('add info')
            }
        }
    }
</script>

孙子组件:

<template>
    <div>
        {{ $attrs }} --- {{ $listeners }}
    <div>
</template>
<script>
    export default {
        ... ... 
        props: ['weight'],
        created() {
            console.log(this.$attrs); // age, infoObj, height 
            console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
            this.$emit('updateInfo') // 可以触发 father 组件中的updateInfo函数
        }
    }
</script>

7. provide / inject

1.背景

  1. 通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

  2. 对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

2.provide

  1. 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

3.inject

  1. 获取祖先组件提供的依赖,并注入到当前组件中使用。形式可以是一个字符串数组,也可以为一个对象。

4.类型

provide:Object | () => Object
inject:Array | { [key: string]: string | Symbol | Object }

5.注意事项

  1. provide是在祖先组件中提供注入一个依赖,这个依赖可以是一个变量,可以是一个函数方法。那么,显然易得的是,当祖先组件失活销毁(如:祖先组件销毁,路由改变等)时,那么后代子孙组件自然而然无法获取祖先组件提供的依赖。

6. 例子

  1. 假设有一个组件A,A组件引入B组件(A为B的父组件) ,B组件引入C组件(B为C的父组件),即A为C的祖先组件,此时二者可以使用provide / inject进行通信。

  2. A与C使用provide / inject方式进行通信

    A使用provide

    <template>
      <div>
        <B></B>
      </div>
    </template>
     
    <script>
    import B from "./B.vue";
    export default {
      name: "A",
      components: {
        B
      },
      provide:{
        name:this.name
      },
      data(){
        return {
          name:"mary"
        }
      },
    };
    </script>
    

    C使用inject

    <template>
      <div>
        <span>{{name}}</span>
      </div>
    </template>
     
    <script>
    export default {
      name: "C",
      inject:["name"]
    };
    </script>
    

    我们希望当name改变时,对应的C中的name也要相应改变,但是使用以上方式时,C中的name并未随着改变,此时需要我们进一步处理,即处理响应性。

    处理响应性

    1. A使用provide,此时传入的应是一个响应式对象(如以下的obj)
    <template>
      <div>
        <B></B>
      </div>
    </template>
     
    <script>
    import B from "./B.vue";
    export default {
      name: "A",
      components: {
        B
      },
      provide(){
        return {
          obj:this.obj      //传入一个响应式对象
        }
      },
      data(){
        return {
          obj:{
            name:"tom"
          }
        }
      },
      methods:{
        changeName(){
          this.obj.name = "mary"
        }
      }
    };
    </script>
    
    1. C使用inject
    <template>
      <div>
        <span>{{obj.name}}</span>
      </div>
    </template>
     
    <script>
    export default {
      name: "C",
      inject:["obj"]    //接收响应式对象
    };
    </script>
    
    1. 此时A中的name改变,C中的值也会相应跟着变化。
      以上为A向C传数据,如果C向A传数据(或者说C需要改变A中的数据),该如何做?
      我们这里不让C直接改变A中的数据,而是将A改变数据的方法通过provide传给C,C执行该方法,触发改变A中的数据。

      A使用provide传入一个方法

      <template>
        <div>
          <span>{{obj.name}}</span>
          <B></B>
        </div>
      </template>
       
      <script>
      import B from "./B.vue";
      export default {
        name: "A",
        components: {
          B
        },
        provide(){
          return {
            changeVal:this.changeName      //传入一个方法
          }
        },
        data(){
          return {
            obj:{
              name:"mary"
            }
          }
        },
        methods:{
          changeName(val){          //C中触发该方法执行,此时变成"lion"
            this.obj.name = val
          }
        }
      };
      </script>
      

      C使用inject

      <template>
        <div>
          <span @click="changeName">点击改变A组件数据</span>
        </div>
      </template>
       
      <script>
      export default {
        name: "C",
        inject:["changeVal"],    //接收一个方法
        methods:{
          changeName(){
            this.changeVal("tom")     //执行此方法,改变A中的数据
          }
        }
      };
      </script>
      

8. 消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

  3. 提供数据:pubsub.publish('xxx',数据)

    ```c
    <template>
    	<div class="student">
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<button @click="sendStudentName">把学生名给School组件</button>
    	</div>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男',
    			}
    		},
    		mounted() {
    			// console.log('Student',this.x)
    		},
    		methods: {
    			sendStudentName(){
    				pubsub.publish('hello',666)
    			}
    		},
    	}
    </script>
    
    <style lang="less" scoped>
    	.student{
    		background-color: pink;
    		padding: 5px;
    		margin-top: 30px;
    	}
    </style>
    
    ```
    
  4. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'测试',
    				address:'测试地址',
    			}
    		},
    		mounted() {
    			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
    				console.log(this)
    				// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
    			})
    		},
    		beforeDestroy() {
    			pubsub.unsubscribe(this.pubId)
    		},
    	}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    
  5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值