zoukankan      html  css  js  c++  java
  • 【Vue】Vue中的父子组件通讯以及使用sync同步父子组件数据

    【Vue】Vue中的父子组件通讯以及使用sync同步父子组件数据

     

    正文

    前言: 之前写过一篇文章《在不同场景下Vue组件间的数据交流》,但现在来看,其中关于“父子组件通信”的介绍仍有诸多缺漏或者不当之处, 正好这几天学习了关于用sync修饰符做父子组件数据双向绑定的的用法, 于是决定写一篇文章, 再次总结下“Vue中的父子组件通信”。

    前面提示:本文文字略少,代码略多


    父子组件通讯,可分为两种情况:


    1. 父组件向子组件中传递数据
    2. 子组件向父组件中传递数据

    一般情况下, 1中情况可通过props解决数据传递的问题, 这里就不多赘述了。

    子组件向父组件中传递数据

    主要谈谈2中情景的实现,有三种方式:


    一. 通过props,父组件向子组件中传递数据和改变数据的函数,通过在子组件中调用父组件传过来的函数,达到更新父组件数据(向父组件传递数据)的作用(子组件中需要有相应的响应事件)
    . 通过在子组件中触发一个 自定义事件(vm.$emit),将数据作为vm.$emit方法的参数,回传给父组件用v-on:[自定义事件]监听的函数
    .通过ref对子组件做标记,父组件可以通过vm.$refs.[子组件的ref].[子组件的属性/方法]这种方式直接取得子组件的数据

    下面我将一 一展示

    一. 通过props从父向子组件传递函数,调用函数改变父组件数据

    这里就不做代码展示了
    一来是因为相对比较简单
    二来是因为这种方式显然不是Vue中的最佳实践(在react中倒比较常见) 
    想要看代码的话可以看这里:《【Vue】浅谈Vue不同场景下组件间的数据交流》http://www.cnblogs.com/penghuwan/p/7286912.html#_label1 (在兄弟组件的数据交流那一节)

    二.  通过自定义事件从子组件向父组件中传递数据


    我们可以在子组件中通过$emit(event, [...参数])触发一个自定义的事件,这样,父组件可以在使用子组件的地方直接用 v-on来监听子组件触发的事件, 并且可以在监听函数中依次取得所有从子组件传来的参数

    例如:
    在子组件中某个部分写入:

    this.emit('eventYouDefined', arg);


    然后你就可以在父组件的子组件模板里监听:
    // 这里是父组件的Template:

    <Son  v-on: eventYouDefined = "functionYours" />


    下面是一个实例


    父组件

    复制代码
    <template>
      <div id="father">
        <div>
           我是父组件,我接受到了:
          {{ text || '暂无数据'  }}
          <son v-on:sendData='getSonText'></son>
        </div>
      </div>
    </template>
     
    <script>
    import son from './son.vue'
    export default {
      data: function () {
        return {
          text: ''
        }
      },
      components: {
        son: son
      },
      methods: {
        getSonText (text) {
          this.text = text
        }
      }
    }
    
    </script>
     
    <style scoped>
    #father div {
      padding: 10px;
      margin: 10px;
      border: 1px solid grey;
      overflow: hidden;
    }
    </style>
    复制代码


    子组件:

    复制代码
    <template>
      <div>
        <p>我是子组件,我所拥有的数据: {{ text }}</p>
        <button @click="sendData">
          发送数据
        </button>
      </div>
    </template>
     
    <script>
    export default {
      data () {
        return {
          text: '来自子组件的数据'
        }
      },
      methods: {
        sendData () {
          this.$emit('sendData', this.text)
        }
      }
    }
    </script>
     
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
       button { float: left }
    </style>
    复制代码


    在点击子组件中的“发送数据”按钮前, 父组件还没有接受到数据(text为空字符串), 则通过  {{ text || '暂无数据'  }}将显示默认文本:‘暂无数据’


    点击“发送数据”按钮后:

    因为sendData自定义事件被触发,通过 

    this.$emit('sendData', this.text)   //此处的this指向子组件实例)

    子组件的text数据被父组件中:

     <son v-on:sendData='getSonText'></son>

    中的getSonText函数作为参数接传参受到, 从而完成了从子组件向父组件中的传参过程

    三. 通过ref属性在父组件中直接取得子组件的数据(data)


    对于我们上面讲的一和二的处理情景来说,有个局限性就是它们都需要以事件机制为基础(无论是像click那样的原生事件还是自定义事件),而在事件发生的时候才能调用函数将数据传递过来

    但如果子组件里没有类似“按钮”的东西,因而无法制造原生事件,同时也没办法找到一个触发自定义事件的时机的时候,怎么从子组件向父组件传递数据呢??

    这个时候, 我们就只能从父组件中“直接取”子组件的数据了,借助ref属性

    ref是我们经常用到的Vue属性,利用它可以简单方便地从本组件的template中取得DOM实例,而实际上,如果你在父组件中为子组件设置ref的话, 就可以直接通过vm.$refs.[子组件的ref].[子组件的属性]去拿到数据啦,例如:

    父组件:

    复制代码
    <template>
      <div id="father">
        <div>
           我是父组件,我接受到了:
          {{ text || '暂无数据'  }}
          <button @click="getSonText()">接受数据</button>
          <son ref='son'></son>
        </div>
      </div>
    </template>
     
    <script>
    import son from './son.vue'
    export default {
      data: function () {
        return {
          text: ''
        }
      },
      components: {
        son: son
      },
      methods: {
        getSonText () {
          this.text = this.$refs.son.text
        }
      }
    }
    
    </script>
     
    <style scoped>
    #father div {
      padding: 10px;
      margin: 10px;
      border: 1px solid grey;
      overflow: hidden;
    }
    </style>
    复制代码


    子组件:

    复制代码
    <template>
      <div>
        <p>我是子组件,我所拥有的数据: {{ text }}</p>
      </div>
    </template>
     
    <script>
    export default {
      data () {
        return {
          text: '来自子组件的数据'
        }
      }
    }
    </script>
     
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
       button { float: left }
    </style>
    复制代码

     
    demo:
    尚未点击“接受数据”按钮前:

    点击接受数据按钮后:

    通过sync实现数据双向绑定, 从而同步父子组件数据


    通过以上三种方式, 我想你应该能解决绝大多数父子组件通信的场景了,但让我们再仔细考虑一下上面的通信场景,就会发现它们还可能存在的问题:

    从子组件向父组件传递数据时,父子组件中的数据仍不是每时每刻都同步的

    在某些特殊的需求场景下,我们可能会希望父子组件中的数据时刻保持同步, 这时候你可能会像下面这样做:

    这是父组件中的template:

    <son :foo="bar" v-on:update="val => bar = val"></son>


    在子组件中, 我们通过props声明的方式接收foo并使用

    props: {
         foo: [type]
    }


    同时每当子组件中数据改变的时候,通过

    this.$emit('update', newValue)

    把参数newValue传递给父组件template中监听函数中的"val"。然后通过

    val => bar = val

    这个表达式就实现了bar = newValue. 这个时候,我们发现父组件中的关键数据bar被子组件改变(相等)了! 

    通过数据的双向绑定, 父(组件)可以修改子的数据, 子也可以修改父的数据

    Vue提供了sync修饰符简化上面的代码,例如:

    <comp :foo.sync="bar"></comp>


    会被扩展为:

    <comp :foo="bar" @update:foo="val => bar = val"></comp>


    然后你需要在子组件中改变父组件数据的时候, 需要触发以下的自定义事件:

    this.$emit("update:foo", newValue)


    【注意】你可能觉得这好像和我上面提到的二中的“通过自定义事件(emit)从子组件向父组件中传递数据”的那一节的内容似乎重叠了,。

    然而并不是, 两者有着父子组件关系上的不同, 下面我通过一行关键的代码证明它们的区别所在

    1.在我们讲解sync的这一小节里, 自定义事件发生时候运行的响应表达式是:
    <son :foo="bar" v-on:update="val => bar = val"></son> 中的 "val => bar = val"
    2.在二中的“通过自定义事件从子组件向父组件中传递数据” 里,自定义事件发生时候运行的响应表达式是:
    <Son  v-on: eventYouDefined = "arg => functionYours(arg)" /> 中的 "arg => functionYours(arg)"

    对前者, 表达式 val => bar = val意味着强制让父组件的数据等于子组件传递过来的数据, 这个时候,我们发现父子组件的地位是平等的。 父可以改变子(数据), 子也可以改变父(数据)

    对后者, 你的functionYours是在父组件中定义的, 在这个函数里, 你可以对从子组件接受来的arg数据做任意的操作或处理 决定权完全落在父组件中, 也就是:  父可以改变子(数据), 但子不能直接改变父(数据)!, 父中数据的变动只能由它自己决定

    下面是一个展示demo:

    父组件:

    复制代码
    <template>
      <div id="father">
        <div>
           我是父组件
          <son
            :wisdom.sync="wisdom"
            :magic.sync="magic"
            :attack.sync="attack"
            :defense.sync="defense">
          </son>
          <p>智力: {{ wisdom }}</p>
          <p>膜法: {{ magic }}</p>
          <p>攻击: {{ attack }}</p>
          <p>防御: {{ defense }}</p>
        </div>
      </div>
    </template>
     
    <script>
    import son from './son.vue'
    export default {
      data: function () {
        return {
          wisdom: 90,
          magic: 160,
          attack: 100,
          defense: 80
        }
      },
      components: {
        son: son
      }
    }
    
    </script>
     
    <style scoped>
    #father div {
      padding: 10px;
      margin: 10px;
      border: 1px solid grey;
      overflow: hidden;
    }
    </style>
    复制代码


    子组件:

    复制代码
    <template>
      <div>
        <p>我是子组件</p>
        <p>智力: {{ wisdom }}</p>
        <p>膜法: {{ magic }}</p>
        <p>攻击: {{ attack }}</p>
        <p>防御: {{ defense }}</p>
        <button @click="increment('wisdom')">增加智力</button>
        <button @click="increment('magic')">增加膜法</button>
        <button @click="increment('attack')">增加攻击</button>
        <button @click="increment('defense')">增加防御</button>
      </div>
    </template>
     
    <script>
    export default {
      props: {
        wisdom: Number,
        magic: Number,
        attack: Number,
        defense: Number
      },
    
      methods: {
        increment (dataName) {
          let newValue = this[dataName] + 1
          this.$emit(`update:${dataName}`, newValue)
        }
      }
    }
    </script>
     
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
       button { float: left }
    </style>
    复制代码


    点击前:


    点击增加子组件中“增加智力”按钮的时候, 父组件和子组件中的智力参数同时从90变为91


    点击增加子组件中“增加膜法”按钮的时候, 父组件和子组件中的智力参数同时从160变为161

    数据双向绑定是把双刃剑

    从好处上看: 
    1.它实现了父子组件数据的“实时”同步, 在某些数据场景下可能会使用到这一点
    2.sync提供的语法糖使得双向绑定的代码变得很简单


    从坏处上看:
    它破环了单向数据流的简洁性, 这增加了分析数据时的难度

    当sync修饰的prop是个对象


    我们对上面的例子修改一下, 把数据包裹在一个对象中传递下来:

    父组件

    复制代码
    <template>
      <div id="father">
        <div>
           我是父组件
          <son :analysisData.sync="analysisData">
          </son>
          <p>智力: {{ analysisData.wisdom }}</p>
          <p>膜法: {{ analysisData.magic }}</p>
          <p>攻击: {{ analysisData.attack }}</p>
          <p>防御: {{ analysisData.defense }}</p>
        </div>
      </div>
    </template>
     
    <script>
    import son from './son.vue'
    export default {
      data: function () {
        return {
          analysisData: {
            wisdom: 90,
            magic: 160,
            attack: 100,
            defense: 80
          }
        }
      },
      components: {
        son: son
      }
    }
    
    </script>
     
    <style scoped>
    #father div {
      padding: 10px;
      margin: 10px;
      border: 1px solid grey;
      overflow: hidden;
    }
    </style>
    复制代码


    子组件:

    复制代码
    <template>
      <div>
        <p>我是子组件</p>
        <p>智力: {{ analysisData.wisdom }}</p>
        <p>膜法: {{ analysisData.magic }}</p>
        <p>攻击: {{ analysisData.attack }}</p>
        <p>防御: {{ analysisData.defense }}</p>
        <button @click="increment('wisdom')">增加智力</button>
        <button @click="increment('magic')">增加膜法</button>
        <button @click="increment('attack')">增加攻击</button>
        <button @click="increment('defense')">增加防御</button>
      </div>
    </template>
     
    <script>
    export default {
      props: {
        analysisData: Object
      },
    
      methods: {
        increment (dataName) {
          let newObj = JSON.parse(JSON.stringify(this.analysisData))
          newObj[dataName] += 1
          this.$emit('update:analysisData', newObj)
        }
      }
    }
    </script>
     
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
       button { float: left }
    </style>
    复制代码



     demo同上

    不要通过在子组件中修改引用类型props达到“父子组件数据同步”的需求!


    父组件的数据传递给子组件, 一般通过props实现, 而在实现“父子组件数据同步”这一需求的时候, 小伙伴们可能会发现一点: 在子组件中修改引用类型的props(如数组和对象)是可行的

    1.不仅可以达到同时修改父组件中的数据(因为本来引用的就是同一个数据)
    2.而且还不会被Vue的检测机制发现!(不会报错)

    但千万不要这样做, 这样会让数据流变得更加难以分析,如果你尝试这样做, 上面的做法可能会更好一些

    不要这样做,糟糕的做法:

    父组件:

    复制代码
    <template>
      <div id="father">
        <div>
           我是父组件
          <son :analysisData="analysisData">
          </son>
          <p>智力: {{ analysisData.wisdom }}</p>
          <p>膜法: {{ analysisData.magic }}</p>
          <p>攻击: {{ analysisData.attack }}</p>
          <p>防御: {{ analysisData.defense }}</p>
        </div>
      </div>
    </template>
     
    <script>
    import son from './son.vue'
    export default {
      data: function () {
        return {
          analysisData: {
            wisdom: 90,
            magic: 160,
            attack: 100,
            defense: 80
          }
        }
      },
      components: {
        son: son
      }
    }
    
    </script>
     
    <style scoped>
    #father div {
      padding: 10px;
      margin: 10px;
      border: 1px solid grey;
      overflow: hidden;
    }
    </style>
    复制代码


    子组件:

    复制代码
    <template>
      <div>
        <p>我是子组件</p>
        <p>智力: {{ analysisData.wisdom }}</p>
        <p>膜法: {{ analysisData.magic }}</p>
        <p>攻击: {{ analysisData.attack }}</p>
        <p>防御: {{ analysisData.defense }}</p>
        <button @click="increment ('wisdom')">增加智力</button>
        <button @click="increment ('magic')">增加膜法</button>
        <button @click="increment ('attack')">增加攻击</button>
        <button @click="increment ('defense')">增加防御</button>
      </div>
    </template>
     
    <script>
    export default {
      props: {
        analysisData: Object
      },
    
      methods: {
        increment (dataName) {
          let obj = this.analysisData
          obj[dataName] += 1
        }
      }
    }
    </script>
     
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
       button { float: left }
    </style>
    复制代码


    demo同上, 但这并不是值得推荐的做法

     【完】

    其实啊,我只是把你们喝咖啡的时间,都用来喝啤酒而已
     
     
    标签: Vue
     
     
    « 上一篇:【javascript】详解变量,值,类型和宿主对象
    » 下一篇:【javascript】谈谈HTML5: Web-Worker、canvas、indexedDB、拖拽事件
    posted @ 2017-09-04 14:10 外婆的彭湖湾 阅读(12238) 评论(13) 编辑 收藏
  • 相关阅读:
    Beta 冲刺 (2/7)
    Beta 冲刺 (1/7)
    2017软件工程实践总结
    华为软件开发云评测
    android开发——用户头像
    学生&部门智能匹配程序
    学生会里学生汇
    数独棋盘生成器
    读与思
    Java接口
  • 原文地址:https://www.cnblogs.com/wang-sai-sai/p/10405221.html
Copyright © 2011-2022 走看看