vue的父子组件通讯

最近使用vue来开发的场景比较多, 常常会遇到父子间通信问题, 所以想大体的总结一下, 和大家一起探讨遇到的问题.

组件的通讯经常用到以下两种

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

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

通过prop传递
通过prop传递的数据类型可以是String,Number,Boolean,Object, Array 和 Function, 也可以是表达式

在父组件中, 用v-bind给子组件绑定数据

<template>
    <div class="father-page">
        <h1>我是父组件</h1>
        <child :string="str" :number="num" :boolean="boo" :object="obj" :array="arr" :expression="obj.a+obj.b"  :childfunc="func"></child>
    </div>
</template>

<script> 
import child from './components/child.vue';
export default {
  data () {
    return {
      str: '父组件的msg',
      num: 0,
      arr: [1, 2, 3, 4],
      boo: true,
      obj: {
        a: 1,
        b: 2
      },
    }
  },
  methods: {
    func () {
      this.num++
      console.log(this.num)
    }
  },
  components: {child}
}
</script>

<style lang="less">
.father-page {
  background-color: aliceblue;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
h1, h2 {
	text-align: center;
}
p {
  text-align: center;
}

</style>

在子组件中, 用prop注册传进来的值

<template>
    <div class="child-page">
        <h2>我是子组件</h2>
        <p>string: {{string}}</p>
        <p>number: {{number}}</p>
        <p v-if="boolean">boolean: {{boolean}}</p>
        <p>object: {{object.a}}</p>
        <p>expression: {{expression}}</p>
        <p>function: <button @click="childfunc">点我吧</button></p>
    </div>
</template>

<script>
export default {
  props: {
    string: {
      type: String
    },
    number: {
      type: Number
    },
    boolean: {
      type: Boolean
    },
    object: {
      type: Object
    },
    array: {
      type: Array
    },
    childfunc: {
      type: Function
    },
    expression: {
      require: false
    }
  }
}
</script>

<style scoped lang="less">
.child-page {
  background-color: antiquewhite;
  button {
    display: inline-block;
    width: 50px;
    height: 20px;
    background-color: cadetblue;
    color: white;
  }
}
</style>

prop传值需要注意的是

  • vue提倡单项数据流
    父组件通过 props 向下传递数据给子组件, 而子组件不能直接修改prop中的数据, 否则会报以下错误:
    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value.
    如果子组件想要修改父组件的值, 可以使用下面介绍的子组件向父组件传递数据的方法.
  • 子组件的props参数值是异步更新
    请看下面的例子
    父组件中:
  <template>
    <div class="father-page">
        <h1>我是父组件</h1>
        <p><button @click="func">点我吧</button></p>
        <child :string="str" ref="child"></child>
    </div>
</template>

<script> 
import child from './components/child.vue';
export default {
  data () {
    return {
      str: 'hello world',
    }
  },
  methods: {
    func () {
      this.str = 'hi, world!'
      this.$refs.child.childfunc()
    }
  },
  components: {child}
}
</script>

子组件中:

<template>
    <div class="child-page">
        <h2>我是子组件</h2>
        <p>父组件的值: {{string}}</p>
    </div>
</template>

<script>
export default {
  props: {
    string: {
      type: String
    },
  },
  methods: {
    childfunc () {
      console.log(this.string)
    }
  }
}
</script>

点击父组件的button, 首先改变父组件的str值, 然后调用子组件中的childfunc方法, 出乎意料的是子组件的方法console.log出来的结果是’hello world’, 而不是改变后的str值’hi, world!’. 原因就是子组件的props参数值是异步更新的,所以在调用子组件methods里的childfunc方法时, 子组件的prop还未更新. 所以我们在子组件methods里使用props参数时要注意参数值是否是最新值。我们可以采用以下两种方法来保证子组件获得最新的props的值.
① 使用$nextTick,在数据变化之后等待 Vue 完成更新 DOM.
比如上方的例子里父组件可以更改为:

  methods: {
    func () {
      this.str = 'hi, world!'
      this.$nextTick(()=>{
        this.$refs.child.childfunc() 
      })
    }
  },

② 子组件使用watch监听prop参数变化

使用的方法根据场景而定.

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

1) 通过v-on 和 $emit 实现
父组件

<template>
    <div class="father-page">
        <h1>我是父组件</h1>
        <p><button @click="reduce">减少</button></p>
        <p>total: {{total}}</p>
        <child v-model="total"></child>
    </div>
</template>

<script> 
import child from './components/child.vue';
export default {
  data () {
    return {
      total: 0
    }
  },
  methods: {
    reduce () {
      this.total--
    }
  },
  components: {child}
}
</script>

子组件

<template>
  <div class="child-page">
    <h2>我是子组件</h2>
    <p><button @click="add">添加</button></p>
    <p>total: {{parentValue}}</p>
    <input type="text" :value="parentValue" @input="handleInput" >
  </div>
</template>

<script> 
export default {
  props: ['parentValue'],
  model: {
    prop: 'parentValue',
    event: 'change'
  },
  methods: {
    add () {
      this.$emit('change', this.parentValue + 1)
    },
    handleInput (e) {
      this.$emit('change', e.target.value)
    }
  }
}
</script>

从上面的例子可以看出, 子组件不直接修改父组件的数据, 而是通过$emit绑定事件名称, 触发父组件的事件,在父组件的方法中完成修改.

2) 使用ref
父组件通过在子组件添加ref属性,然后可以通过ref属性名称获取到子组件的实例。
父组件

<child :string="str" ref="child"></child>

<!-- 称获取到子组件的实例       -->
this.$refs.child

同样的, 子组件也可以使用this.$parent获取到父组件的实例, 但是这样的方法不被推荐, 因为这更属于一种主动的查找, 而不是数据的传递.

3.父子组件v-model实现双向绑定

v-model不仅能够用在input上, 也可以使用在组件上.只需在子组件的model下添加prop和event:

  • prop 通过v-model传的值来更新prop的值
  • event 通过将新变化的prop的值更新给v-model的数据
    父组件
<template>
    <div class="father-page">
        <h1>我是父组件</h1>
        <button @click="reduce">减少</button>
        <p>total: {{total}}</p>
        <child v-model="total"></child>
    </div>
</template>

<script> 
import child from './components/child.vue';
export default {
  data () {
    return {
      total: 0
    }
  },
  methods: {
    reduce () {
      this.total--
    }
  },
  components: {child}
}
</script>

子组件

<template>
  <div class="child-page">
    <h2>我是子组件</h2>
    <button @click="add">添加</button>
    <p>total: {{parentValue}}</p>
    input: <input type="text" :value="parentValue" @input="handleInput" >
  </div>
</template>

<script> 
export default {
  props: ['parentValue'],
  model: {
    prop: 'parentValue',
    event: 'change'
  },
  methods: {
    add () {
      this.$emit('change', this.parentValue + 1)
    },
    handleInput (e) {
      this.$emit('change', e.target.value)
    }
  }
}
</script>

v-model 在组件中使用相当于一个语法糖, 它的功能可以被v-bind, v-on以及emit来代替, 子组件通过v-bind接受父组件的值, 再通过v-on和emit来代替,子组件通过vbind接受父组件的值,再通过von和emit来改变父组件的值.总而言之, 数据是自上往下(父传子)的, 事件是自下往上(子传父)的.

实现组件之间通讯的方法还有很多, 比如给Vue实例绑定一个事件总线, 或者使用vuex来统一管理.不同场景使用不同方法.

发表评论