vue组件传值

153 阅读2分钟

父子传值

props和$emit

父组件通过props传递数据给子组件,子组件通过$emit发送事件传递给父组件,这两种方式是最常用的父子组件通信实现的方法。

这种父子组件通信方式也就是典型的单向数据流,父组件通过props传递数据,子组件不能直接修改props,而是必须通过发送事件的方式告知父组件修改数据

v-model和.sync

另外这两种方式还可以使用语法糖v-module来直接实现,因为v-module默认会解析成名为value的prop和名为input的事件。这种语法糖的方式是典型的双向绑定,常用于UI控件上,但是就其根本,还是通过事件的方法让父组件修改数据

.sync属性是个语法糖,可以很简单的实现子组件和父组件通信

<!--父组件中-->
<input :value.sync="value" />
<!--以上写法等同于-->
<input :value="value" @update:value="v => value = v"></comp>
<!--子组件中-->
<script>
  this.$emit('update:value', 1)
</script>

作用域插槽

可以先看一个例子,以便更好的理解作用域插槽的作用

在子组件中使用v-for创建一个列表循环,然后在父组件中通过子组件标签child调用,如下例。

 <div id="root">
        <child></child>
        <child></child>
    </div>
    <script>
        Vue.component('child',{
            data: function(){
                return {
                    list:[1,2,3,4]
                }
            },
            template: '<div><ul><li v-for="value in list">{{value}}</li></ul></div>',
        })
        var vm=new Vue({
            el: '#root'
        })
    </script>
    

结果:

调用了两次child组件,因为调用的是同一个子组件,所以显示的内容完全一样。如何在每次调用时能有各自的渲染效果?这就是作用域插槽的用武之地。

作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽,这个插槽为作用域插槽,该插槽必须放在template标签里面,同时声明从子组件接收的数据放在一个自定义属性内,并定义该数据的渲染方式。通过下列展示作用域插槽的使用方式:

<div id="root">
        <child>
            <template slot-scope="props"><!--定义一个插槽,该插槽必须放在template标签内-->
                <li>{{props.value}}</li><--!定义使用渲染方式-->
            </template>
        </child>
        <child>
            <template slot-scope="props">
                <h1>{{props.value}}</h1><!--定义不同的渲染方式-->
            </template>
        </child>
    </div>
    <script>
        Vue.component('child',{
            data: function(){
                return {
                    list:[1,2,3,4]
                }
            },
            template: `<div>
                            <ul>
                                <slot v-for="value in list" :value=value>//使用slot占位
                                </slot>
                            </ul>
                        </div>`
        })
        var vm=new Vue({
            el: '#root'
        })
    </script>

结果:

slot-scope 是插槽 slot 的一个特性。使用这个特性,父组件获取子组件 slot 绑定的属性值,传递数据(通过属性值传递数据)

$parent和$children和$ref

当然我们还可以用过访问parent或者parent或者children对象来访问组件实例中的方法和数据,或者使用this.$refs[compnentRefName]的方式来拿到具名子元素实例

$refs只会在组件渲染完成之后生效,并且他们不是响应式的,这仅作为一个用于直接操作子组件的逃生舱---你应该避免在模版或者计算属性中访问$refs

$attrs和$listeners

$attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。 简单点讲就是包含了所以父组件在子组件上设置的属性(除了prop传递的属性、class 和 style )

<div id="app">
      <base-input
        label="姓名"
        class="name-input"
        placeholder="请输入姓名"
        test-attrs="$attrs"
      ></base-input>
    </div>
      Vue.component("base-input", {
        inheritAttrs: true, //此处设置禁用继承特性
        props: ["label"],
        template: `
        <label>
          {{label}}-
          {{$attrs.placeholder}}-
          <input v-bind="$attrs"/>
        </label>
        `,
        mounted: function() {
          console.log(this.$attrs);
        }
      });
      const app = new Vue({
        el: "#app"
      });

$listeners

简单点讲它是一个对象,里面包含了作用在这个组件上所有的监听器(监听事件),可以通过 v-on="$listeners" 将事件监听指向这个组件内的子元素(包括内部的子组件)。

  <div id="app">
    <child1
      :p-child1="child1"
      :p-child2="child2"
      :p-child-attrs="1231"
      v-on:test1="onTest1"
      v-on:test2="onTest2">
    </child1>
  </div>
   <script>
      Vue.component("Child1", {
        inheritAttrs: true,
        props: ["pChild1"],
        template: `
        <div class="child-1">
        <p>in child1:</p>
        <p>props: {{pChild1}}</p>
        <p>$attrs: {{this.$attrs}}</p>
        <hr>
        <child2 v-bind="$attrs" v-on="$listeners"></child2></div>`,
        mounted: function() {
          this.$emit("test1");
        }
      });
      Vue.component("Child2", {
        inheritAttrs: true,
        props: ["pChild2"],
        template: `
        <div class="child-2">
        <p>in child->child2:</p>
        <p>props: {{pChild2}}</p>
        <p>$attrs: {{this.$attrs}}</p>
          <button @click="$emit('test2','按钮点击')">触发事件</button>
        <hr> </div>`,
        mounted: function() {
          this.$emit("test2");
        }
      });
      const app = new Vue({
        el: "#app",
        data: {
          child1: "pChild1的值",
          child2: "pChild2的值"
        },
        methods: {
          onTest1() {
            console.log("test1 running...");
          },
         onTest2(value) {
            console.log("test2 running..." + value);
          }
        }
      });
    </script>

上例中,父组件在child1组件中设置两个监听事件test1和test2,分别在child1组件和child1组件内部的child2组件中执行。还设置了三个属性p-child1、p-child2、p-child-attrs。其中p-child1、p-child2被对应的组件的prop识别。所以: p-child1组件中$attrs为{ "p-child2": "pChild2的值", "p-child-attrs": 1231 }; p-child2组件中$attrs为{ "p-child-attrs": 1231 },效果如下图:

我们再点击child2组件中的按钮,触发事件,控制台可以打印出:

兄弟组件通信

对于这种情况可以通过查找父组件中的子组件实现,也就是 this.parent.parent.children,在 $children 中可以通过组件 name 查询到需要的组件实例,然后进行通信。

_uid为唯一标示

跨多层次组件通信

provide / inject

对于这种情况可以使用 Vue 2.2 新增的 API provide / inject,虽然文档中不推荐直接使用在业务中,但是如果用得好的话还是很有用的。 provide 选项允许我们指定我们想要 提供 给后代组件的数据 / 方法,然后在后代组件中使用 inject 注入,就可以访问组件组件提供的 provide 出来的数据或者调用 provide 出来的方法。 假设有父组件 A,然后有一个跨多层级的子组件 B:

// 父组件 A
export default {
  provide: {
    data: 1,
    getMap: this.getMap // getMap 是个方法
  }
}
// 子组件 B
export default {
  inject: ['data', 'getMap'],
  mounted() {
    // 无论跨几层都能获得祖先组件的 data 属性
    console.log(this.data) // => 1
    // 无论跨几层都能调用祖先组件的 getMap 方法
    this.getMap() // => 1
  }
}

任意组件

这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况

Event Bus

Event Bus 的通信机制其实非常简单:独立与主 Vue 实例再创建一个单独的 Vue 实例,利用 vm.onvm.on vm.off vm.$once 等事件监听 / 解绑的 api ,专门就做事件处理的事情。因为他式独立的 Vue 实例,所以并不需要知道谁监听了事件,谁触发了事件。非常的方便。

有两种方式来处理eventBus

第一种:新创建一个.js文件,比如event-bus.js

// event-bus.js


import Vue from 'vue'
export const EventBus = new Vue()

第二种:直接在项目中的main.js初始化EventBus

// main.js
Vue.prototype.$EventBus = new Vue()