vue中的组件通信

1. 组件通信的场景

  • 父->子组件间的数据传递
  • 子->父组件间的数据传递
  • 兄弟组件间的数据传递
  • 组件深层嵌套,祖先组件与子组件间的数据传递

2. 组件通信的方式

1. 组件通信一:父子通信

父组件通过自定义属性向子组件传值
子组件通过props接收父组件的值

1.1 父->子组件传递数据的案例

html模板

<div id="app">
  <h1>父组件的内容----{{num}}</h1>
	<p><button @click="change">改变父组件自己的值:{{msg}}</button></p>
  <hr>
  <!-- 父组件向子组件传值 -->
  <child :msg="msg" :num="num" :things-list="thingsList"></child>
</div>

js代码

<script src="./js/vue.js"></script>
<script>
  let Child = {
    props: ['msg', 'num', 'thingsList'],
    template: `
      <div class="box">
      <p><button>子组件</button></p>
      <p><em>{{msg}}----{{num}}</em></p>
      <p>{{thingsList}}</p>
      </div>
    `
  }
//根组件,父组件
var vm = new Vue({
  el: '#app',
  data: {
    msg: 'Happy New Year',
    num: [23, 45],
    thingsList: ['a']
  },
  methods: {
    change() {
      this.msg = '父组件改变的值'
    }
  },
  components: {
    Child
  }
});
</script>
1.2 父->子组件传递数据的注意事项
  • 每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值
  • 应该在一个子组件内部改变 prop。如果这样做了,Vue 会在浏览器的控制台中发出警告。
  • 可以先把该值赋给子组件自己的变量,然后去更改复制后的变量
  • 如果你传进来的是个对象,同时你又需要在子组件中操作传进来的这个数据,那么在父组件中的这个据也会改变,因为你传递的只是个引用
  • 你只能对对象做深拷贝创建一个副本才能继续操作
1.3. Props
1.3.1 Prop的大小写

使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

<child :msg="msg" :num="num" :things-list="thingsList"></child>

在子组件内部用驼峰的方式接收

props: ['thingsList'],
1.3.2 Prop的类型

通常希望每个 prop 都有指定的值类型。

<script>
  Vue.component("Child", {
  props: {
    title: String,
    likes: Number,
    isPublished: Boolean,
    commentIds: Array,
    author: Object,
  },
  template: ``
})
</script>
1.3.3 Prop的验证
Vue.component('my-component', {
  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' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

2. 组件通信二:子父通信

2.1 子父通信

子组件传递数据给父组件是通过$emit触发自定义事件来做到的
父组件通过监听子组件发送的自定义事件来接收数据

推荐始终使用 kebab-case (短横线分隔命名)的事件名

<div id="app">
  <h1>父组件</h1>
  <p>子组件传过来的值:{{info}}</p>
  <hr>
  <!-- send即为自定义事件 -->
  <child @send-data="handleSend"></child>
</div>

<script src="./js/vue.js"></script>
<script>
  //子组件
  let Child = {
    template: `
      <div class="box">
      	<p><button @click="handleClick">向父组件传值</button></p>
       </div>
      `,
    data() {
      return {
        msg: '子组件的数据'
      }
    },
    methods: {
      handleClick() {
        //$emit向父组件发送一个自定义事件
        this.$emit("send-data", this.msg)
      }
    }
  }
  var vm = new Vue({
    el: '#app',
    data: {
      info: ''
    },
    methods: {
      handleSend(param) {
        console.log(param);
        this.info = param
      }
    },
    components: {
      Child
    }
  });
</script>
2.2 组件中特殊的事件绑定
2.2.1 将原生事件绑定到组件

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

<div id="app">
  <!-- 直接绑定原生事件无效 -->
  <!-- <child @click="show"></child> -->

  <!-- 添加native修饰符 -->
  <child @click.native="show"></child>
</div>

<script src="./js/vue.js"></script>
<script>
  // 子组件
 let Child = {
    template: `
      <div class="box">
        <button>按钮1</button>
        <br>
        <button>按钮2</button>
       </div>
    `
  }
  // 根组件
  var vm = new Vue({
    el: '#app',
    data: {},
    methods: {
      show() {
        alert(1);
      }
    },
    components: {
      Child
    }
  });
</script>
2.2.2 .sync修饰符

真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。

可以使用sync修饰符进行父子组件双向绑定

<div id="app">
  <!-- 监听更新属性的事件 -->
  <!-- <child @update:msg="getData"></child> -->

  <!-- 
		update:msg的语法糖
		动态绑定父组件属性,属性后添加sync修饰符 
	-->
  <!--:msg.sync为扩展事件的监听方式,表示当监听到该事件时,将同步的把父组件msg的值修改为事件中携带的参数-->
  <child :msg.sync="msg"></child>
  <br> {{msg}}
</div>

<script src="./js/vue.js"></script>
<script>
  let Child = {
    template: `
<button @click="send">改变</button>
`,
    methods: {
      send() {
        // 因为子组件要修改父组件,要在属性前加update
        // update:msg为扩展的事件,目的是配合sync使用
        this.$emit("update:msg", 'child')
      }
    }
  }
  var vm = new Vue({
    el: '#app',
    data: {
      msg: 'parent'
    },
    methods: {
      getData(newVal) {
        this.msg = newVal
      }
    },
    components: {
      Child
    }
  });
</script>

3. 组件通信三: 兄弟组件通信

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

4. 组件通信四:Bus总线传值

  • 适用场景 : 非父子组件传值

  • 中央事件总线(EventBus 非父子组件间通信)

    新建一个Vue事件bus对象,然后通过bus. e m i t 触 发 事 件 , b u s . emit触发事件,bus. emitbus.on监听触发的事件

  • 缺点

    EventBus通信方式是无法进行有效的组件化开发的,假设一个场景,一个页面上有多个公共组件,我们只要向其中的一个传递数据,但是每个公共组件都绑定了数据接收的方法。

css样式

<style>
.brother1 {
  border: 1px solid #000;
  margin: 50px;
}
.brother2 {
  border: 1px solid #000;
  margin: 50px;
}
</style>

html代码

<div id="app">
  <brother1></brother1>
  <brother2></brother2>
</div>

<script src="./js/vue.js"></script>
<script>
  Vue.component('brother1', {
    data() {
      return {
        mymessage: 'hello brother1'
      }
    },
    template: `
      <div class="brother1">
        <p>this is brother1 component!</p>
        <input type="text" v-model="mymessage" @input="passData(mymessage)">
      </div>
		`,
    methods: {
      passData(val) {
        //触发全局事件globalEvent
        bus.$emit('globalEvent', val)
      }
    }
  })
  Vue.component('brother2', {
    template: `
      <div class="brother2">
        <p>this is brother2 component!</p>
        <p>brother1传递过来的数据:{{brothermessage}}</p>
       </div>
      `,
    data() {
      return {
        mymessage: 'hello brother2',
        brothermessage: ''
      }
    },
    mounted() {
      //绑定全局事件globalEvent
      bus.$on('globalEvent', (val) => {
        this.brothermessage = val;
      })
    }
  })
  //中央事件总线
  var bus = new Vue();
  var app = new Vue({
    el: '#app'
  })
</script>

5. 组件通信五: Vuex

vuex算是vue中处理复杂组件通信的最佳方案,毕竟vue和vuex一个娘胎里出来的。而且vuex底层也是用vue实现的。后续会有专门的文章来讲解Vuex

6. 组件通信六: a t t r 和 attr和 attrlistener

attrs和attrs和listeners 主要用于孙组件获取父组件的属性和方法

6.1 $attrs

  • $attrs是在vue的2.40版本以上添加的
  • 项目中有多层组件传参可以使用$attrs
  • 使用普通的父子组件传参prop$emit$on会很繁琐
  • 使用vuex会大材小用,只是在这几个组件中使用,没必要使用vuex
  • 使用事件总线eventBus,使用不恰当的话,有可能会出现事件多次执行

6.2 $attrs 实现步骤

步骤1:祖先组件自定义属性
<div id="app">
  <!-- 使用子组件 -->
  <child :msg="msg" :num="num"></child>
</div>

<script src="./js/vue.js"></script>
<script>
  var vm = new Vue({
  el: '#app',
  data: {
    msg: '祖先的数据',
    num: 10
  },
  components: {
    // 注册子组件
    Child
  },
  methods: {}
});
</script>

父组件(Father.vue),给子组件关联数据,子组件如果不用props接收,那么这些数据就作为普通的HTML特性应用在子组件的根元素上

步骤2: 中间层组件添加v-bind="$attrs"
// 子组件
let Child = {
  // 注册孙组件
  components: {
    Grandson
  },
  // 使用孙组件
  template: `
  	<grandson v-bind="$attrs"></grandson>
  `,
  inheritAttrs: true,
}

inheritAttrs: false的含义是不希望本组件的根元素继承父组件的attribute,同时父组件传过来的属性(没有被子组件的props接收的属性),也不会显示在子组件的dom元素上

步骤3: 后代组件用$attrs接受数据
  // 孙组件
  let Grandson = {
    template: `
		<h1></h1>
		`,
    created() {
      console.log(this.$attrs);
    }
  }

6.3 $listener

l i s t e n e r s − − 属 性 , 它 是 一 个 对 象 , 里 面 包 含 了 作 用 在 这 个 组 件 上 的 所 有 监 听 器 , 你 就 可 以 配 合 ‘ v − o n = " listeners--属性,它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合 `v-on=" listenersvon="listeners"` 将所有的事件监听器指向这个组件的某个特定的子元素。

<div id="app">
  根组件A <br>
  <com-b :msg-a1="msgA1" :msg-a2="msgA2" @test1="onTest1" @test2="onTest2"></com-b>
</div>

<script src="./js/vue.js"></script>
<script>
  let ComC = {
    template: `
			<div class="c"> 孙组件ComC </div>
		`,
    mounted() {
      // 孙组件直接获取A组件的属性值
      console.log('c', this.$attrs);

      // A组件可以直接监听到C组件触发的事件
      // console.log('c', this.$listeners);
      this.$emit("test1", 'C的数据')
    }
  }
  let ComB = {
    // C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性
    // 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)
    template: `
      <div class="b"> 
      	子组件ComB 
      	<br>
      	<com-c v-bind="$attrs" v-on="$listeners"></com-c>
       </div>
		`,
    components: {
      ComC
    }
  }

  // 根组件A
  var vm = new Vue({
    name: "A",
    el: '#app',
    data: {
      // 根组件A的数据
      msgA1: 'msgA1',
      msgA2: 'msgA2'
    },
    methods: {
      // 根组件A的事件处理程序
      onTest1(data) {
        console.log('onTest1', data);
      },
      onTest2() {
        console.log('onTest2');
      }
    },
    components: {
      ComB
    }
  });
</script>

7.组件通信七: provide和inject

  • provide 和 inject 方式通常用于祖孙组件之间的通信,主要用于祖先组件向子组件传值

父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。
不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。

<div id="app">
  <parent></parent>
</div>

<script src="./js/vue.js"></script>
<script>
  Vue.component('child', {
    inject: ['for'], //得到父组件传递过来的数据
    data() {
      return {
        mymessage: this.for
      }
    },
    template: `
    	<div>
    		<input type="tetx" v-model="mymessage">
      </div>
    `
  })
  Vue.component('parent', {
    template: ` 
      <div>
        <p> this is parent compoent! </p> 
        <child></child> 
      </div>
    `,
    // 向后代传递数据
    provide: {
      for: '父组件的test'
    },
    data() {
      return {
        message: 'hello'
      }
    }
  })
  var vm = new Vue({
    el: '#app',
  })
</script>

8. 组件通信八: r e f s 、 refs、 refsparent和$children

parent和children 用于有直接父子关系 的 组件 之间的通信

  • $refs 获取子组件的实例

  • $parent 用于 获取组件的父组件实例

  • $children 用于 获取组件的子组件实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app"> 
    <h3>父组件----{{msg}}---------------</h3>
    <com-a ref="coma"></com-a>
    <com-b ref="comb"></com-b>
  </div>

  <script src="./js/vue.js"></script>

  <script>
    // 子组件
    const ComA = {
      template: '<p>子组件A--{{val}}--<button @click="callParentFun">调用父组件的方法</button></p>',
      data(){
        return {
          childNum: 10
        }
      },
      computed: {
        val(){
          return this.$parent.msg
        }
      },
      methods: {
        childFun(){
          console.log('子组件的方法');
        },
        callParentFun(){
          this.$parent.parentFun()
        }
      }
    }

    // 子组件
    const ComB = {
      template: `<p>子组件B</p>`
    }
    // 根组件
    const vm = new Vue({
      el: '#app', //绑定到DOM上
      data: {
        msg: 'old'
      },
      components: {
        ComA,
        ComB
      },
      methods: {
        parentFun(){
          console.log('父组件方法');
        }
      },
      mounted(){
        // 通过this.$ref或this.$children访问子组件的数据或方法
        // console.log(this.$refs.coma.childNum);
        // this.$refs.coma.childFun();

        // console.log(this.$children[0].childNum);
        // this.$children[0].childFun()
      }
    })
  </script>
</body>

</html>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值