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

 

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

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


父子组件通信,可分为两种状况:vue


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

通常状况下, 1中状况可经过props解决数据传递的问题, 这里就很少赘述了。react

 

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

 

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


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

 

下面我将一 一展现函数

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

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

 

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


咱们能够在子组件中经过$emit(event, [...参数])触发一个自定义的事件,这样,父组件能够在使用子组件的地方直接用 v-on来监听子组件触发的事件, 而且能够在监听函数中依次取得全部从子组件传来的参数

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

this.emit('eventYouDefined', arg);


而后你就能够在父组件的子组件模板里监听:
// 这里是父组件的Template:spa

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


下面是一个实例3d


父组件

<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同上, 但这并非值得推荐的作法

 【完】

 

 

相关文章
相关标签/搜索