美文网首页
Vue组件传值与通信集合

Vue组件传值与通信集合

作者: Max_Law | 来源:发表于2019-04-22 11:05 被阅读0次

    Vue的组件化给前端开发带来极大的便利,这种依赖数据来控制Dom的模式,区别于以前的开发控制Dom的开发理念,这也导致了一种情况,在Vue中是单向数据流的,意味着只能从父组件向子组件传值,不允许子组件向父组件传值。

    这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。 ---vue教程

    然而当我们把组件拆分到足够细的时候,子组件控制父组件的数据,或者兄弟组件之间的传值就变得尤为突出,这里我将总结各式各样的传值,函数调用的方法。

    父组件中的通信方法

    阅读完官方文档后,我们一定会对props有强烈的印象,然而在父组件中可不止有这种通信的方式。

    序号 方法名 概要 推荐程度
    1 props 通过对子组件的v-bind绑定或标签属性值进行传值 强烈推荐
    2 provide/inject 通过注入的方式,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。 谨慎使用
    3 vm.$children 通过查询当前父级的子组件获得子组件的数据与方法,并不保证顺序,也不是响应式的 谨慎使用
    4 vm.$slots 插槽<slot></slot>或者用vm.$slots获取 推荐

    props

    • 命名规范

    HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

    <blog-post post-title="hello!"></blog-post>
    
    Vue.component('blog-post', {
      // 在 JavaScript 中是 camelCase 的
      props: ['postTitle'],
      template: '<h3>{{ postTitle }}</h3>'
    })
    
    • 传递静态或动态 Prop
      假如你想传字符串给子组件只需要给便签添加属性就可以了
    //这里的title的内容就会传给子组件
    <blog-post title="My journey with Vue"></blog-post>
    

    当然那些非字符串的类型就直接依赖于v-bind进行传值(Number、Boolean、Array...)

    <!-- 动态赋予一个变量的值 -->
    <blog-post v-bind:title="post.title"></blog-post>
    
    <!-- 动态赋予一个复杂表达式的值 -->
    <blog-post
      v-bind:title="post.title + ' by ' + post.author.name"
    ></blog-post>
    
    • 子组件接收

    常见的就是声明式接收:
    props: [...] 数组形式

    // 定义一个名为 button-counter 的新组件
    Vue.component('button-counter', {
      props: ['title', 'count', 'isPublished', 'commentIds', 'author'],
      data: function () {
        return {
          counts: this.count //this指向即可获取props值
        }
      },
      template: ''
    })
    

    但官方更推荐对象形式的接收,至少为每个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
          }
        }
      }
    })
    

    type 可以是下列原生构造函数中的一个:

    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol

    使用上我们除了this.prop外还可以使vm.$props(当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象属性的访问。)

    • 禁用特性继承

    如果你不希望组件的根元素继承特性,这尤其适合配合实例的 $attrs 属性使用,你可以在组件的选项中设置 :

    Vue.component('my-component', {
      inheritAttrs: false,
      // ...
    })
    

    当然还有非声明式的接收 $attrs (2.4.0 新增):

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

    <base-input label="姓名" class="username-input" 
      placeholder="Enter your username" data-date-picker="activated"
    ></base-input>
    
    Vue.component("base-input", {
         inheritAttrs: false, //此处设置禁用继承特性
         props: ["label"],
         template: `
            <label>
              {{label}}
              {{$attrs.placeholder}} //这里不用声明props可以直接调用
              {{$attrs["data-date-picker"]}}
              <input v-bind="$attrs"/>
            </label>
            `,
          mounted: function() {
                console.log(this.$attrs);
            }
    })
    

    provide/inject

    provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

    // 父级组件提供 'foo'
    var Provider = {
      provide: {
        foo: 'bar'
      },
      // ...
    }
    
    // 子组件注入 'foo'
    var Child = {
      inject: ['foo'],
      created () {
        console.log(this.foo) // => "bar"
      }
      // ...
    }
    

    提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

    更多的用法是用来注入方法的:

    // 父级组件提供 'foo'
    var Provider = {
      provide: {
        foo: this.fn
      },
      methods:{
        fn(){
          console.log('bar')
        }
      }
      // ...
    }
    
    // 子组件注入 'foo'
    var Child = {
      inject: ['foo'],
      created () {
        this.foo(); // => "bar"
      }
      // ...
    }
    

    vm.$children

    通过Vue实例代理的$children获取其子组件的值,为一个类数组,你可以在控制台中打印出来,里面有子组件中的一切属性。


    vm.$slots

    插槽的使用更多在官方文档中查看,这里显示基础的使用方法:

    <navigation-link url="/profile">
      Your Profile
    </navigation-link>
    

    然后你在 <navigation-link> 的模板中可能会写为:

    <a
      v-bind:href="url"
      class="nav-link"
    >
      <slot></slot>
    </a>
    

    除了用标签获取传递的数据,我更喜欢用$slots处理,比如在这个输入框中,你既要设定lable又要设定placeholder,然而它们两其实是同一个数据,这时候我们可以这样:


    输入框控件
    <inputBox :readonly='readonly' v-model='item.contactName'>联系人</inputBox>
    <inputBox :readonly='readonly' v-model='item.mobile'>电话</inputBox>
    
    var Component = {
            props: {
                readonly: {
                    default: false
                },
                type: {
                    default: 'text'
                },
                value: [String, Number],
                maxlength: {
                    default: 20
                }
            },
            data() {
                return {
                    inputValue: this.value
                }
            },
            watch: {
                value(newValue) {
                    this.inputValue = newValue
                },
                inputValue(newValue) {
                    this.$emit('input', newValue)
                }
            },
            template: `
            <div class='input'>
                <cube-input v-model.trim="inputValue" :placeholder="'请输入' + this.$slots.default[0].text" :readonly="readonly" :type="type" :maxlength="maxlength">
                    <template v-slot:prepend>
                        <slot></slot>
                    </template>
                </cube-input>
            </div>
            `
        };
    

    其他的为ui框架的组件不用在意,重要的是this.$slots.default[0].text这一段对$slots的应用。


    小结

    通过上面的四种方法,我们可以看出,它们都是可以完成值得传递或者获取的,除了$slots不能传递方法函数外,其他三种均可完成。


    子组件中的通信方法

    首先我们先要理解子组件传递的原理,下面这张图大家在很多的地方都看过了,实际上就是父组件通过prop给子组件下发数据,子组件通过事件给父组件发送信息,而使用的工具为:

    父子组件通信过程

    $on(evntName)监听事件;
    $emit(eventName,optionalPayload)触发事件;
    接下来的方法都是对这个原理实现的变形

    序号 方法名 概要 推荐程度
    1 v-on/vm.$emit 通过绑定父级函数,用子级触发,父子组件双向绑定 推荐
    2 v-model/vm.$emit 官方提供的父子组件双向绑定方法 强烈推荐
    3 this.$parent 获取直接上级的实例,调用父组件的函数 推荐

    v-on / $emit

    首先我们来实现一个单向传递的例子:

    //父组件
    <template>
       <child @childHandler="parentHandle"></child>
    </template>
    
    <script>
    export default {
        data: {
            message: ''
        },
        methods: {
          parentHandle(send) {
             this.message = send;
          }
        }
    }
    </script>
    
    //子组件
    <template>
      <button @cilck="sendHandle"></button>
    </template>
    <script>
    export default {
      date: {
        news:"from children"
      },
      methods: {
        sendHandle() {
          this.$emit('childHandler',this.news);
        }
      }
    }
    </script>
    

    上面的过程我就不再描述一遍了,你们自己看代码理解理解,这样我们就实现了由子组件传递数据到父组件。但在更复杂的场景时,我们不仅需要修改父组件的值,还需要通过父组件的值影响到子组件的渲染,也就是实现子父组件的数据双向绑定。

    更多的场景下我们希望其是自动触发数据交互的,这时我们需要用到watch,现在我们对上面的代码进行修改一下。

    //父组件
    <template>
       <child @childHandler="parentHandle" :status="status" :name="name">
       <button @cilck="emptyHandle">清空</button>
    </child>
    </template>
    
    <script>
    export default {
        data: {
            status:'男',
            name:'Max.Law'
        },
        methods: {
           parentHandle(send) {
            this.child.status = send;
          },
          emptyHandle(){
            status = '';
          }
        }
    }
    </script>
    
    //子组件
    <template>
      <p><span>姓名:</span>{{name}}</p>
      <p><span>性别:</span>{{childStatus}}</p>
      <button @cilck="sendHandle('男')">男</button>
      <button @cilck="sendHandle('女')">女</button>
    </template>
    <script>
    export default {
      props: {
         status: [String, Number],
         name: [String, Number]
      },
      date: {
        childStatus:this.status
      },
      methods: {
        sendHandle(data) {
          this.childStatus = data
      },
      watch: {
          status(newValue) {
             this.childStatus = newValue
           },
           childStatus(newValue) {
              this.$emit('childHandler', newValue)
           }
       },
    }
    </script>
    

    这里实现的是在子组件的性别状态继承于父组件,子组件修改性别的状态的同时改变父组件的数据以便父组件使用。

    我们在使用vue的过程中很清楚,假如子组件直接写{{status}}来继承父组件的值,在修改状态的时候vue会提示不建议直接修改父组件的值来改变子组件(vue中一个重要逻辑,当前组件只处理当前组件的数据),所以我们使用childStatus来接收父组件的值。

    那我们如何做到子组件的改变能影响父组件,父组件更新值时又能影响子组件呢?这时候重点都在这个watch上:

    status改变时则改变childStatus的值,
    childStatus改变时,用上面的方法与父组件通信,改变父组件的值

    这样我们就完成子父组件的数据双向绑定,整个过程为:

    1. 给子组件帮一个通信方法
    2. 绑定一个传值对象
    3. 监控数据的变化

    看到这里有没有熟悉的感觉,是不是跟 v-modle 很像,vue提供的双向绑定的指令,而 v-modle 的本质就是绑定了一个input的方法,和一个value值,这时候我们就能把上面的双向数据绑定的方法简化了(v-modle是在本组件内实现双向绑定,并没有做到子父组件双向绑定)

    v-model/vm.$emit

    还记得我上面讲$slot的代码吗?这里我们简化一下:

    //父组件
    <template>
       <inputBox :readonly='readonly' v-model='brand'>设备品牌</inputBox>
    </template>
    
    <script>
    export default {
        data: {
            brand:'默认'
        }
    }
    </script>
    
    //子组件
    <template>
        <div>
            <h1><slot></slot></h1>
            <input v-model="inputValue"></input>
        </div>
    </template>
    
    <script>
    export default {
       props: {
          value: [String, Number]
       },
       data() {
           inputValue: this.value
        },
        watch: {
            value(newValue) {
               this.inputValue = newValue
          },
          inputValue(newValue) {
               this.$emit('input', newValue)
          }
       }
    }
    </script>
    

    这时候,我们不仅可以修改子组件中的输入框值,还是能同时改变父组件的值,这在做上传表单的时候尤为重要,保证我们父组件一直都是获取用户最新输入的值。(这个在我做微信公众号一个表单项目时候突发奇想的实现)

    this.$parent

    这个是vue提供的api,使用也很简单,场景一般适合在父组件写入多个子组件需要调用的公共方法,比provide/inject占用性能要低,也更明确作用域。

    只需要在父组件注册方法,在子组件这样使用:
    this.$parent.parentFn();
    这里要注意层级,也许是孙组件,或者从孙组件,可以这样调用:
    this.$parent.$parent.grandfatherFn();
    只需要打印出当前的this.$parent查看,确定层级即可。

    好了,上面就是 《 Vue组件传值与通信集合 》的全部内容了,希望能够对你有所帮助,如有疑问欢迎留言~


    该篇收录于文集:前端技术栈

    相关文章

      网友评论

          本文标题:Vue组件传值与通信集合

          本文链接:https://www.haomeiwen.com/subject/hklomqtx.html