vue中如何监听props的变化

15,082 阅读3分钟

应用场景

在vue中父子组件是通过props传递数据的。通常有以下几种场景:

  • 子组件展示父组件传递过来的props,一般是字符串
  • 子组件接受父组件传递过来的props,作为本地数据使用
  • 子组件接受父组件的传递过来的props,进行转换后展示(计算得到某个值)
  • 子组件修改父组件传递过来的props

子组件展示父组件传递过来的props

【1】传递的prop是基础类型的值,父组件改变data,子组件随之改变

parent.vue

<template>
  <div>
    <p>这是父元素的内容</p>
    <div>{{city}}</div>
    <button v-on:click='changeCity'>点我改变city</button>
    <child v-bind:city='city'></child>
  </div>
</template>

<script>
  import Child from './passPropChild'
  export default {
    name: 'parent',
    data: function () {
      return {
        city: '北京'
      }
    },
    components: {
      child: Child
    },
    methods: {
      changeCity: function () {
        this.city = this.city + Math.random().toFixed(2)
      }
    }
  }
</script>

<style scoped>
</style>

child.vue

<template>
  <div>
    <p>这是子元素的内容</p>
    <div>{{ city}}</div>
  </div>
</template>

<script>
  export default {
    name: 'child',
    // eslint-disable-next-line vue/no-dupe-keys
    props: ['city'],
    data: function () {
      return {
      }
    },
    methods: {
    }
  }
</script>

<style scoped>
</style>

【2】传递的数据类型是引用类型,父组件改变data,子组件随之改变

parent.vue

<template>
  <div>
    <p>这是父元素的内容</p>
    <ul>
      <li v-for='item in cityList' :key="item.id">
        {{item.name}}
      </li>
    </ul>
    <button v-on:click='changeCity'>点我改变city</button>
    <child v-bind:cityList='cityList'></child>
  </div>
</template>

<script>
  import Child from './passPropChild'
  export default {
    name: 'parent',
    data: function () {
      return {
        cityList: [
          {
            id: 1,
            name: '北京'
          },
          {
            id: 2,
            name: '上海'
          },
          {
            id: 3,
            name: '广州'
          }
        ]
      }
    },
    components: {
      child: Child
    },
    methods: {
      changeCity: function () {
        this.cityList.push({
          id: 4,
          name: '我是乱入的'
        })
      }
    }
  }
</script>

<style scoped>
</style>

child.vue

<template>
  <div>
    <p>这是子元素的内容</p>
    <ul>
      <li v-for='item in cityList' :key="item.id">
        {{item.name}}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    name: 'child',
    // eslint-disable-next-line vue/no-dupe-keys
    props: ['cityList'],
    data: function () {
      return {
      }
    },
    methods: {
    }
  }
</script>

<style scoped>
</style>

子组件接受父组件的props作为本地数据使用

定义一个本地的data属性,将这个prop作为初始值

【1】传递的prop是基础类型的值,父组件改变data,子组件不改变

parent.vue

<template>
  <div>
    <p>这是父元素的内容</p>>
    <div>{{city}}</div>
    <button v-on:click='changeCity'>点我改变city</button>
    <child v-bind:city='city'></child>
  </div>
</template>

<script>
  import Child from './passPropChild'
  export default {
    name: 'parent',
    data: function () {
      return {
        city: '北京'
      }
    },
    components: {
      child: Child
    },
    methods: {
      changeCity: function () {
        this.city = this.city + Math.random().toFixed(2)
      }
    }
  }
</script>

<style scoped>
</style>

child.vue

<template>
  <div>
    <p>这是子元素的内容</p>
    <p>{{cityC}}</p>
  </div>
</template>

<script>
  export default {
    name: 'child',
    // eslint-disable-next-line vue/no-dupe-keys
    props: ['city'],
    data: function () {
      return {
        cityC: this.city
      }
    },
    methods: {
    }
  }
</script>

<style scoped>
</style>

【2】传递的数据类型是引用类型,父组件改变data,子组件随之改变

parent.vue

<template>
  <div>
    <p>这是父元素的内容</p>
    <ul>
      <li v-for='item in cityList' :key="item.id">
        {{item.name}}
      </li>
    </ul>
    <button v-on:click='changeCity'>点我改变city</button>
    <child v-bind:cityList='cityList'></child>
  </div>
</template>

<script>
  import Child from './passPropChild'
  export default {
    name: 'parent',
    data: function () {
      return {
        cityList: [
          {
            id: 1,
            name: '北京'
          },
          {
            id: 2,
            name: '上海'
          },
          {
            id: 3,
            name: '广州'
          }
        ]
      }
    },
    components: {
      child: Child
    },
    methods: {
      changeCity: function () {
        this.cityList.push({
          id: 4,
          name: '我是乱入的'
        })
      }
    }
  }
</script>

<style scoped>
</style>

child.vue

<template>
  <div>
    <p>这是子元素的内容</p>
    <ul>
      <li v-for='item in cityListC' :key="item.id">
        {{item.name}}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    name: 'child',
    // eslint-disable-next-line vue/no-dupe-keys
    props: ['cityList'],
    data: function () {
      return {
        cityListC: this.cityList
      }
    },
    methods: {
    }
  }
</script>

<style scoped>
</style>

子组件接受父组件的props进行转换后展示

用这个prop的值定义一个计算属性

【1】传递的prop是基础类型的值,父组件改变data,子组件随之改变

parent.vue

<template>
  <div>
    <p>这是父元素的内容</p>
    <div>{{city}}</div>
    <button v-on:click='changeCity'>点我改变city</button>
    <child v-bind:city='city'></child>
  </div>
</template>

<script>
  import Child from './passPropChild'
  export default {
    name: 'parent',
    data: function () {
      return {
        city: '北京'
      }
    },
    components: {
      child: Child
    },
    methods: {
      changeCity: function () {
        this.city = this.city + Math.random().toFixed(2)
      }
    }
  }
</script>

<style scoped>
</style>

child.vue

<template>
  <div>
    <p>这是子元素的内容</p>
    <p>{{cityChange}}</p>
  </div>
</template>

<script>
  export default {
    name: 'child',
    // eslint-disable-next-line vue/no-dupe-keys
    props: ['city'],
    data: function () {
      return {
        cityC: this.city
      }
    },
    methods: {
    },
    computed: {
      cityChange: function () {
        return this.city + '1'
      }
    }
  }
</script>
<style scoped>
</style>

【2】传递的数据类型是引用类型,父组件改变data,子组件随之改变

parent.vue

<template>
  <div>
    <p>这是父元素的内容</p>
    <ul>
      <li v-for='item in cityList' :key="item.id">
        {{item.name}}
      </li>
    </ul>
    <button v-on:click='changeCity'>点我改变city</button>
    <child v-bind:cityList='cityList'></child>
  </div>
</template>

<script>
  import Child from './passPropChild'
  export default {
    name: 'parent',
    data: function () {
      return {
        cityList: [
          {
            id: 1,
            name: '北京'
          },
          {
            id: 2,
            name: '上海'
          },
          {
            id: 3,
            name: '广州'
          }
        ]
      }
    },
    components: {
      child: Child
    },
    methods: {
      changeCity: function () {
        this.cityList.push({
          id: 4,
          name: '我是乱入的'
        })
      }
    }
  }
</script>

<style scoped>
</style>

child.vue

<template>
  <div>
    <p>这是子元素的内容</p>
    <ul>
      <li v-for='item in cityListC' :key="item.id">
        {{item.name}}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    name: 'child',
    // eslint-disable-next-line vue/no-dupe-keys
    props: ['cityList'],
    data: function () {
      return {
      }
    },
    methods: {
    },
    computed: {
      cityListC: function () {
        var newCityList = []
        for (let i = 0; i < this.cityList.length; i++) {
          // eslint-disable-next-line vue/no-side-effects-in-computed-properties
          this.cityList[i].name = this.cityList[i].name + 1
          // eslint-disable-next-line vue/no-side-effects-in-computed-properties
          newCityList.push(this.cityList[i])
        }
        return newCityList
      }
    }
  }
</script>

<style scoped>
</style>

子组件修改父组件传递过来的props

【1】传递的数据类型是基础类型,子组件修改父组件传递过来的props,报错。

【2】传递的数据类型是引用类型,子组件将父组件传递过来的props作为本地数据,子组件修改父组件传递过来的props,父组件也会随之改变。

总结:

【1】子组件接受父组件传递过来的props仅作为展示时:无论什么数据类型,父组件改变,子组件随之改变。

对于这个场景,子组件只是展示,子组件随父组件变化是符合场景需求的

【2】子组件接受父组件传递过来的props作为本地数据时:prop为基础类型的值,父组件改变,子组件不变;prop为引用类型的值,父组件改变,子组件随之改变。

对于这个场景,子组件是在父组件的数据基础上进行加工,父组件数据改变,子组件是希望同步响应的,解决的方法:使用watch来监听prop。产生这个问题的原因是,因为生命周期的关系,组件的data属性,在reated,只在初始化的时候赋值了一次。

【3】子组件接受父组件传递过来的props作为计算属性时:无论什么数据类型,父组件改变,子组件随之改变。

【4】子组件修改父组件,基础类型的值报错;引用类型的值父组件也改变

参考链接: