如何监听“props”变化

493
VueJs 2.0文档中,我找不到任何监听props变化的钩子函数。
VueJs是否有类似onPropsUpdated()的钩子函数或其他类似功能? 更新 如@wostex所建议的那样,我尝试watch我的属性,但没有任何变化。然后我意识到我有一个特殊情况:
<template>
    <child :my-prop="myProp"></child>
</template>

<script>
   export default {
      props: ['myProp']
   }
</script>

我将父组件接收到的myProp传递给了子组件,但是watch: {myProp: ...}没有起作用。


1
可能是重复的问题:VueJS 2.0 - Can't hook a component on props update - Bert
@wostex,这是CodePen链接。不好的是,在这个笔记本中它是可以工作的... - Amio.io
22个回答

605

您可以watch属性以在属性更改时执行一些代码:

new Vue({
  el: '#app',
  data: {
    text: 'Hello'
  },
  components: {
    'child' : {
      template: `<p>{{ myprop }}</p>`,
      props: ['myprop'],
      watch: { 
        myprop: function(newVal, oldVal) { // watch it
          console.log('Prop changed: ', newVal, ' | was: ', oldVal)
        }
      }
    }
  }
});
<script src="https://unpkg.com/vue@2/dist/vue.min.js"></script>

<div id="app">
  <child :myprop="text"></child>
  <button @click="text = 'Another text'">Change text</button>
</div>


1
我知道watch不被鼓励使用,因为通常最好使用计算属性,但是使用watch会有任何性能问题吗? - SethWhite
1
据我了解,Vue中处理响应性的方式是使用观察者模式。从这个角度来看,观察者并不一定比计算属性或其他响应式数据更低效。在某些情况下,一个属性的值依赖于许多值,计算属性将运行单个函数,而不是为结果依赖的每个值分别运行一个独立的观察回调函数。我认为在大多数常见用例中,计算属性会产生期望的结果。 - plong0
1
对于任何想要使用类(例如在TypeScript中)完成此操作的人,请查看此答案 - sonrad10
4
如果prop发生变化,无论使用props的组件是否会重新渲染,与使用watcher有关。 - Robert
当我观察一个props时,在watch部分中看到了一个没有键的符号。只有一个函数。这个符号有没有相关文档(Vue 2)? - Ivan Sudos
@sonrad10 有些情况下需要运行自定义代码。例如,当数据集发生变化以反映新的更新时,需要手动更新chartjs图表。 - TheRealChx101

170

你试过这个吗?

watch: {
  myProp: {
    // the callback will be called immediately after the start of the observation
    immediate: true, 
    handler (val, oldVal) {
      // do your stuff
    }
  }
}

https://v2.vuejs.org/v2/api/#watch


7
这对我有用,但如果有一个为什么它有效的解释会很有帮助。 - Andy Preston
@AndyPreston 如果术语有误,请纠正我,但这个方法是有效的,特别是针对组件重新渲染后的属性更改,这是因为它使用了 immediate 属性。否则,watch 只会在组件渲染后观察到更改,忘记了父组件在重新渲染期间传递的初始更改。 - 0x5929

92

在我的情况下,我需要一个解决方案,每当任何prop发生变化时,我都需要重新解析我的数据。我厌倦了为所有的props创建分离的watcher,所以我使用了这个:

  watch: {
    $props: {
      handler() {
        this.parseData();
      },
      deep: true,
      immediate: true,
    },
  },

从这个例子中需要记住的关键点是使用deep: true,这样它不仅会监视$props,还会监视其嵌套值,例如props.myProp

您可以在此处了解更多有关此扩展监视选项的信息:https://v2.vuejs.org/v2/api/#vm-watch


3
这应该标记为答案,因为这是设置深度对象监听器以观察其属性更改的适当方式。 - Omeri
1
谢谢,目前我们可以点赞它,我想。或者OP可以修改它。 - JoeSchr

27

您需要了解组件层次结构以及如何传递 props,确切地说,您的情况很特殊,开发人员通常不会遇到这种情况。

父组件 -传递myProp-> 子组件 -传递myProp-> 孙子组件

如果在父组件中更改了myProp,则子组件中也会反映该更改。

如果在子组件中更改了myProp,则孙子组件中也会反映该更改。

因此,如果在父组件中更改了myProp,则孙子组件中也会反映该更改。(目前为止还好)

因此,在组件层次结构中向下时,您无需做任何事情,props 将天然具有响应性。

现在来讨论向上移动。

如果在孙子组件中更改了myProp,则不会在子组件中反映出来。您必须在子组件中使用 .sync 修饰符,并从孙组件中发出事件。

如果在子组件中更改了myProp,则不会在父组件中反映出来。您必须在父组件中使用 .sync 修饰符,并从子组件中发出事件。

如果在孙子组件中更改了myProp,则不会在父组件中反映出来(显然)。您必须在子组件中使用 .sync 修饰符,并从孙子组件中发出事件,然后在子组件中监视该 prop,并在更改时发出一个事件,该事件通过 .sync 修饰符被父组件监听。

让我们看一些代码以避免混淆

Parent.vue

<template>
    <div>
    <child :myProp.sync="myProp"></child>
    <input v-model="myProp"/>
    <p>{{myProp}}</p>
</div>
</template>

<script>

    import child from './Child.vue'

    export default{
        data(){
            return{
                myProp:"hello"
            }
        },
        components:{
            child
        }
    }
</script>

<style scoped>
</style>

Child.vue

<template>
<div>   <grand-child :myProp.sync="myProp"></grand-child>
    <p>{{myProp}}</p>
</div>

</template>

<script>
    import grandChild from './Grandchild.vue'

    export default{
        components:{
            grandChild
        },
        props:['myProp'],
        watch:{
            'myProp'(){
                this.$emit('update:myProp',this.myProp)

            }
        }
    }
</script>

<style>

</style>

孙子组件.vue

<template>
    <div><p>{{myProp}}</p>
    <input v-model="myProp" @input="changed"/>
    </div>
</template>

<script>
    export default{
        props:['myProp'],
        methods:{
            changed(event){
                this.$emit('update:myProp',this.myProp)
            }
        }
    }
</script>

<style>

</style>

但是在这之后,你一定会注意到Vue发出的尖叫警告:

'避免直接变异Prop,因为每当父组件重新呈现时该值将被覆盖。'

正如我之前提到的,大多数开发人员不会遇到此问题,因为它是一种反模式。这就是为什么你会收到这个警告。

但是为了解决你的问题(根据你的设计),我认为你必须做出上述的变通方法(说实话是hack)。我仍然建议你重新考虑你的设计,并使其更不容易出现错误。

希望对你有所帮助。


15
对于双向绑定,您需要使用.sync修饰符:
<child :myprop.sync="text"></child>

更多细节...

而且你必须在子组件中使用watch属性来监听和更新任何变化

props: ['myprop'],
watch: { 
  myprop: function(newVal, oldVal) { // watch it
    console.log('Prop changed: ', newVal, ' | was: ', oldVal)
  }
},

9

不确定您是否已经解决了这个问题(如果我理解正确),但这是我的想法:

如果父组件接收到 myProp,并且您想将其传递到子组件并在子组件中监视它,则父组件必须拥有 myProp 的副本(而不是引用)。

尝试这样做:

new Vue({
  el: '#app',
  data: {
    text: 'Hello'
  },
  components: {
    'parent': {
      props: ['myProp'],
      computed: {
        myInnerProp() { return myProp.clone(); } //eg. myProp.slice() for array
      }
    },
    'child': {
      props: ['myProp'],
      watch: {
        myProp(val, oldval) { now val will differ from oldval }
      }
    }
  }
}

在HTML中:

<child :my-prop="myInnerProp"></child>

在这种情况下(多次传递时),处理复杂集合时你需要非常小心。


6

我使用计算属性来处理如下内容:

    items:{
        get(){
            return this.resources;
        },
        set(v){
            this.$emit("update:resources", v)
        }
    },

在这种情况下,“Resources”是一个属性:

props: [ 'resources' ]

4

如果有人正在使用Vue 2和组合API,则我的下面的答案适用。因此,安装函数将是

setup: (props: any) => {
  watch(() => (props.myProp), (updatedProps: any) => {
    // you will get the latest props into updatedProp
  })
}

不过,你需要从组合API中导入watch函数。


4

Props和v-model的处理。如何从父组件向子组件传递值,以及从子组件向父组件传递值。

注意:不需要使用watch 在Vue中突变(mutating)props是反模式的,因此您不应该在子组件或组件中更改prop值。使用$emit来更改值,Vue将始终按预期工作。

/* COMPONENT - CHILD */
Vue.component('props-change-component', {
  props: ['value', 'atext', 'anumber'],
  mounted() {
    var _this = this
    
    this.$emit("update:anumber", 6)
    
    setTimeout(function () {
      // Update the parent binded variable to 'atext'
      _this.$emit("update:atext", "4s delay update from child!!")
    }, 4000)
    
    setTimeout(function () {
      // Update the parent binded v-model value
      _this.$emit("input", "6s delay update v-model value from child!!")
    }, 6000)
  },
  template: '<div> \
    v-model value: {{ value }} <br> \
    atext: {{ atext }} <br> \
    anumber: {{ anumber }} <br> \
    </div>'
})

/* MAIN - PARENT */
const app = new Vue({
  el: '#app',
  data() {
    return {
      myvalue: 7,
      mynumber: 99,
      mytext: "My own text",
    }
  },
  mounted() {
    var _this = this
    
    // Update our variable directly
    setTimeout(function () {
      _this.mytext = "2s delay update mytext from parent!!"
    }, 2000)
  },
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">

  <props-change-component
    v-model='myvalue'
    :atext.sync='mytext'
    :anumber.sync='mynumber'>
    
  </props-change-component>
  
</div>


1
这一行代码: this.$emit("anumber", 6) 应该改为: this.$emit("update:anumber", 6)除此之外,非常感谢您提供的这个非常有用的答案;我特别重视您展示了不需要使用watch的方法! :) - keykey76
在Vue3中,没有全局的emit会发生什么? - ritwick_ _D
@ritwick__D const emit = defineEmits(["update:anumber"]) - undefined

3

我认为在大多数情况下,Vue在prop改变时会更新组件的DOM。

如果这是你的情况,那么你可以使用beforeUpdate()updated()钩子函数(文档)来监视props。

如果你只对newVal感兴趣并且不需要oldVal,你可以这样做。

new Vue({
  el: '#app',
  data: {
    text: ''
  },
  components: {
    'child': {
      template: `<p>{{ myprop }}</p>`,
      props: ['myprop'],
      beforeUpdate() {
        console.log(this.myprop)
      },
      updated() {
        console.log(this.myprop)
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <child :myprop="text"></child>
  <input v-model="text" placeholder="Type here to view prop changes" style="width:20em">
</div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接