好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

vue子组件通过.sync修饰符修改props属性方式

子组件通过.sync修饰符修改props属性

在vue子组件中,如果我们直接修改props中的属性,会报错:

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. Prop being mutated: [val]

vue为我们提供了.sync修饰符来解决这个问题:

// 父组件中,通过.sync修改符绑定属性
<my-children :val.sync='val'></my-children>
// 子组件中,修改属性
<script>
export default {
  props: ['val'],
  methods: {
    myClick() {
      this.$emit('update:val', this.val + 1)
    }
  }
}
</script>

子组件修改父组件prop的几种方式

实际工作项目开发中,很经常会有父组件先传递值给子组件,再由子组件渲染展示的场景,下面我总结一下目前工作中遇到和用过的一些方式

常用方式

推荐,遵循prop单向传递的规则,基本数据类型和引用数据类型均可。

1. 通过父组件on监听子组件emit事件实现修改prop

原理:

给子组件中的input标签绑定value属性赋值prop中的值,因为是用value而不是v-model,所以修改input值的时候并不会影响到prop。 然后给input绑定input事件,每次输入内容都会触发input事件,在事件里通过this.$emit(‘父要监听的事件名’, 修改后的值)去将值向上传递。 父组件中通过on监听子组件刚刚触发的emit事件,将事件传递过来的值更新到父组件的data中。 父组件data中的值更新后,因为有通过prop传递给子组件,所以子组件也会同步更新prop值并渲染视图层。

父组件代码如下:

<template>
? <div style="background-color: skyblue;">
? ? <h3>通过父组件on监听子组件emit事件实现修改prop</h3>
? ? <div>父obj:{{ obj }}</div>
? ? <div>父msg:{{ msg }}</div>
? ? <!-- 父组件调用子组件时通过on监听子组件触发的emit事件,以接收值并更新用 -->
? ? <emitChild
? ? ? :obj="obj"
? ? ? :msg="msg"
? ? ? @update-obj="updateObj"
? ? ? @update-msg="updateMsg"
? ? />
? </div>
</template>

<script>
import emitChild from './components/emitChild'
export default {
? name: 'emitUpdate',
? components: {
? ? emitChild
? },
? data () {
? ? return {
? ? ? obj: {
? ? ? ? name: 'zhangsan',
? ? ? ? age: 18
? ? ? },
? ? ? msg: 'hello'
? ? }
? },
? methods: {
? ? // 监听子组件触发的事件并更新data中的obj
? ? updateObj (key, newVal) {
? ? ? this.obj[key] = newVal
? ? },
? ? // 监听子组件触发的事件并更新data中的msg
? ? updateMsg (newVal) {
? ? ? this.msg = newVal
? ? }
? }
}
</script>

子组件代码如下:

<template>
? <div style="background-color: pink;">
? ? <div>
? ? ? <span>修改name:</span>
? ? ? <!-- 这里绑定值用value,因为input在这主要用作展示prop数据,实际修改不在子组件,子组件只是作为修改触发源 -->
? ? ? <!-- 绑定input事件,作为触发源的入口 -->
? ? ? <input type="text" :value="obj.name" @input="updateObj($event, 'name')">
? ? </div>
? ? <div>
? ? ? <span>修改age:</span>
? ? ? <input type="text" :value="obj.age" @input="updateObj($event, 'age')">
? ? </div>
? ? <div>
? ? ? <span>修改msg:</span>
? ? ? <input type="text" :value="msg" @input="updateMsg($event.target.value)">
? ? </div>
? </div>
</template>

<script>
export default {
? name: 'emitUpdateChild',
? props: {
? ? obj: {
? ? ? type: Object,
? ? ? default: () => {}
? ? },
? ? msg: {
? ? ? type: String,
? ? ? default: ''
? ? }
? },
? methods: {
? ? // 通知父组件更新obj
? ? updateObj ($event, key) {
? ? ? // 接收输入的值,和obj中对应需要更新值的属性,然后回传给父组件
? ? ? // 父组件就可以知道子组件需要更新obj中哪个属性的值
? ? ? this.$emit('update-obj', key, $event.target.value)
? ? },
? ? // 通知父组件更新msg
? ? updateMsg (newVal) {
? ? ? this.$emit('update-msg', newVal)
? ? }
? }
}
</script>

2. 通过父组件sync修饰符 + 子组件emit事件实现修改prop

原理:

给子组件中的input标签绑定value属性赋值prop中的值,因为是用value而不是v-model,所以修改input值的时候并不会影响到prop。 然后给input绑定input事件,每次输入内容都会触发input事件,在事件里通过this.$emit(‘父要监听的事件名’, 修改后的值)去将值向上传递。 父组件调用子组件传递prop时,在prop后加上.sync即可将事件传递过来的值更新到父组件的data中,因为sync其实就相当于执行了@监听子触发的事件名 = "父data中的属性名 = emit传递的值(即$event)"这一段代码。 父组件data中的值更新后,因为有通过prop传递给子组件,所以子组件也会同步更新prop值并渲染视图层。

父组件代码如下:

<template>
? <div style="background-color: skyblue;">
? ? <h3>通过父组件sync修饰符 + 子组件emit事件实现修改prop</h3>
? ? <div>父obj:{{ obj }}</div>
? ? <div>父msg:{{ msg }}</div>
? ? <!-- 父组件调用子组件传递prop时,在prop后加上.sync即可 -->
? ? <syncChild :obj.sync="obj" :msg.sync="msg" />
? ? <!--
? ? ? sync其实就相当于执行了
? ? ? ? @监听子触发的事件名 = "父data中的属性名 = emit传递的值(即$event)"
? ? ? 这一段代码
? ? -->
? ? <!-- 效果相当于下面的代码,所以父组件methods中不需要再定义修改data数据的方法 -->
? ? <!--
? ? ? <syncChild
? ? ? ? :obj="obj"
? ? ? ? :msg="msg"
? ? ? ? @update-obj="obj = $event"
? ? ? ? @update-msg="msg = $event"
? ? ? />
? ? -->
? </div>
</template>

<script>
import syncChild from './components/syncChild'
export default {
? name: 'syncUpdate',
? components: {
? ? syncChild
? },
? data () {
? ? return {
? ? ? obj: {
? ? ? ? name: 'zhangsan',
? ? ? ? age: 18
? ? ? },
? ? ? msg: 'hello'
? ? }
? }
}
</script>

子组件代码如下:

<template>
? <div style="background-color: pink;">
? ? <div>
? ? ? <span>修改name:</span>
? ? ? <!-- 这里绑定值用value,因为input在这主要用作展示prop数据,实际修改不在子组件,子组件只是作为修改触发源 -->
? ? ? <!-- 绑定input事件,作为触发源的入口 -->
? ? ? <input type="text" :value="childObj.name" @input="updateObj($event, 'name')">
? ? </div>
? ? <div>
? ? ? <span>修改age:</span>
? ? ? <input type="text" :value="childObj.age" @input="updateObj($event, 'age')">
? ? </div>
? ? <div>
? ? ? <span>修改msg:</span>
? ? ? <input type="text" :value="msg" @input="updateMsg($event.target.value)">
? ? </div>
? </div>
</template>

<script>
// 这里引入lodash工具库的cloneDeep深拷贝方法
// 官方文档地址 https://HdhCmsTestlodashjs测试数据/
import { cloneDeep } from 'lodash'
export default {
? name: 'emitUpdateChild',
? props: {
? ? obj: {
? ? ? type: Object,
? ? ? default: () => {}
? ? },
? ? msg: {
? ? ? type: String,
? ? ? default: ''
? ? }
? },
? data () {
? ? return {
? ? ? // 这里通过深拷贝将prop的obj复制了一份,主要为了:
? ? ? // 1.区分2个对象(引用类型)用的不是同一个内存地址
? ? ? // 2.保留对象中所有的值,方便子组件渲染/修改/回传用
? ? ? childObj: cloneDeep(this.obj)
? ? }
? },
? methods: {
? ? // 通知父组件更新obj
? ? updateObj ($event, key) {
? ? ? // 接收输入的值,和childObj中对应需要更新值的属性
? ? ? // 然后更新childOBj后,回传给父组件
? ? ? // 父组件直接把拿到的childObj更新到data的obj即可
? ? ? this.childObj[key] = $event.target.value
? ? ? this.$emit('update:obj', this.childObj)
? ? },
? ? // 通知父组件更新msg
? ? updateMsg (newVal) {
? ? ? this.$emit('update:msg', newVal)
? ? }
? }
}
</script>

取巧方式

主要针对引用数据类型,绕过了vue对于prop的检测机制,具体看项目规范是否允许这样写。

3.通过data实现修改prop

前提:只有引用数据类型可以实现

原理:

将父组件传入的prop直接赋值给子组件的data,此时prop和data两边的变量指向的都是同一个内存地址,所以修改data等于修改prop。 vue2开始不允许直接修改prop,但此时我们表面修改的是data不是prop,因此vue不会抛出错误,相当于绕过了vue对于不允许修改prop的检测机制。

父组件代码如下:

<template>
? <div style="background-color: skyblue;">
? ? <h3>通过赋值到data实现修改prop</h3>
? ? <div>父obj:{{ obj }}</div>
? ? <div>父msg:{{ msg }}</div>
? ? <dataChild :obj="obj" :msg.sync="msg" />
? </div>
</template>

<script>
import dataChild from './components/dataChild'
export default {
? name: 'dataUpdate',
? components: {
? ? dataChild
? },
? data () {
? ? return {
? ? ? obj: {
? ? ? ? name: 'zhangsan',
? ? ? ? age: 18
? ? ? },
? ? ? msg: 'hello'
? ? }
? }
}
</script>

子组件代码如下:

<template>
  <div style="background-color: pink;">
    <div>
      <span>修改name:</span>
      <!-- 这里因为我们直接把prop赋值给data了,所以可以直接用v-model双向绑定修改数据 -->
      <input type="text" v-model="dataObj.name">
    </div>
    <div>
      <span>修改age:</span>
      <input type="text" v-model="dataObj.age">
    </div>
    <div>
      <span>修改msg:</span>
      <!-- 这里提供另一种修改基本数据类型的思路,可以通过watch侦听器实现 -->
      <input type="text" v-model="dataMsg">
    </div>
  </div>
</template>

<script>
export default {
  name: 'dataChild',
  props: {
    obj: {
      type: Object,
      default: () => {}
    },
    msg: {
      type: String,
      default: ''
    }
  },
  data () {
    return {
      // 引用数据类型直接赋值时是浅拷贝,只会复制内存地址,修改其中一个会影响另一个
      dataObj: this.obj,
      // 基本数据类型直接赋值时是复制值,修改其中一个不会影响另一个
      dataMsg: this.msg
    }
  },
  watch: {
    // 这里侦听data中复制过来的dataMsg,当修改时执行emit通知父组件进行更新
    dataMsg (newVal) {
      this.$emit('update:msg', newVal)
    }
  }
}
</script>

4.通过计算属性computed实现修改prop

前提:只有引用数据类型可以实现

原理:

在子组件中直接通过计算属性computed监听父组件传入的prop,此时计算属性computed和prop两边的变量指向的都是同一个内存地址,所以修改计算属性computed等于修改prop。 vue2开始不允许直接修改prop,但此时我们表面修改的是计算属性computed不是prop,因此vue不会抛出错误,相当于绕过了vue对于不允许修改prop的检测机制。

父组件代码如下:

<template>
? <div style="background-color: skyblue;">
? ? <h3>通过计算属性监听实现修改prop</h3>
? ? <div>父obj:{{ obj }}</div>
? ? <div>父msg:{{ msg }}</div>
? ? <computedChild :obj="obj" :msg.sync="msg" />
? </div>
</template>

<script>
import computedChild from './components/computedChild'
export default {
? name: 'computedUpdate',
? components: {
? ? computedChild
? },
? data () {
? ? return {
? ? ? obj: {
? ? ? ? name: 'zhangsan',
? ? ? ? age: 18
? ? ? },
? ? ? msg: 'hello'
? ? }
? }
}
</script>

子组件代码如下:

<template>
? <div style="background-color: pink;">
? ? <div>
? ? ? <span>修改name:</span>
? ? ? <!-- 这里因为我们直接通过计算属性computed监听prop了,所以可以直接用v-model双向绑定修改数据 -->
? ? ? <input type="text" v-model="computedObj.name">
? ? </div>
? ? <div>
? ? ? <span>修改age:</span>
? ? ? <input type="text" v-model="computedObj.age">
? ? </div>
? ? <div>
? ? ? <span>修改msg:</span>
? ? ? <!-- 这里提供另一种修改基本数据类型的思路,可以通过计算属性computed的setter实现 -->
? ? ? <input type="text" v-model="computedMsg">
? ? </div>
? </div>
</template>

<script>
export default {
? name: 'computedChild',
? props: {
? ? obj: {
? ? ? type: Object,
? ? ? default: () => {}
? ? },
? ? msg: {
? ? ? type: String,
? ? ? default: ''
? ? }
? },
? computed: {
? ? computedObj () {
? ? ? // 这里直接return引用数据类型obj,此时computedObj相当于obj
? ? ? // 所以是浅拷贝,只会复制内存地址,修改其中一个会影响另一个
? ? ? return this.obj
? ? },
? ? computedMsg: {
? ? ? get () {
? ? ? ? // 这里prop每次更新时,都会触发计算属性的getter方法获取最新的值
? ? ? ? // 直接return基本数据类型时是复制值,修改其中一个不会影响另一个
? ? ? ? return this.msg
? ? ? },
? ? ? set (newVal) {
? ? ? ? // 这里利用计算属性的setter方法,监听到值修改时触发emit通知父组件更新值
? ? ? ? this.$emit('update:msg', newVal)
? ? ? }
? ? }
? }
}
</script>

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

查看更多关于vue子组件通过.sync修饰符修改props属性方式的详细内容...

  阅读:38次