【问题标题】:如何监听“道具”的变化
【发布时间】:2017-11-18 22:31:37
【问题描述】:

VueJs 2.0 docs 中,我找不到任何可以监听props 更改的挂钩。

VueJs 是否有类似 onPropsUpdated() 或类似的钩子?

更新

正如@wostex 建议的那样,我尝试watch 我的财产,但没有任何改变。然后我意识到我遇到了一个特殊情况:

<template>
    <child :my-prop="myProp"></child>
</template>

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

我将父组件接收到的myProp 传递给child 组件。然后watch: {myProp: ...} 不起作用。

【问题讨论】:

标签: javascript vue.js vuejs2 vue-component


【解决方案1】:

您可以watchprops 在 props 更改时执行一些代码:

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/dist/vue.js"></script>

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

【讨论】:

  • 我知道不鼓励使用 watch,因为使用计算属性通常会更好,但是使用 watch 是否存在性能问题?
  • @SethWhite 根据我对reactivity is handled in Vue 如何使用观察者模式的理解,观察者不一定比计算数据或其他反应性数据效率低。在一个属性的值依赖于许多值的情况下,计算的 prop 将为结果所依赖的每个值运行一个单独的函数,而不是一个单独的 watch 回调。我认为在最常见的用例中,计算属性会产生所需的结果。
  • 对于希望使用类(例如在 typescript 中)执行此操作的任何人,请查看 this answer
  • 如果道具更改与使用观察者相比,是否应该使用道具重新渲染?
  • 我在看道具的时候在手表部分看到一个没有键的符号。只有一个功能。有没有关于这个 notaton (vue 2) 的文档?
【解决方案2】:

你试过了吗?

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

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

【讨论】:

  • 这对我有用,但如果能解释为什么它会起作用会很有帮助。
【解决方案3】:

在我的情况下,我需要一个解决方案,任何时候任何道具都会发生变化,我需要再次解析我的数据。我厌倦了为我的所有道具制作单独的观察者,所以我使用了这个:

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

从这个例子中带走的关键点是使用deep: true,所以它不仅监视$props,而且它的嵌套值,例如props.myProp

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

【讨论】:

  • 这应该被标记为答案,因为它是设置深度对象监视以观察其属性更改的适当方法。
  • 谢谢,我想现在我们可以投票了。或者OP可以改变它
【解决方案4】:

您需要了解,您拥有的组件层次结构以及您如何传递道具,绝对您的情况是特殊的,开发人员通常不会遇到。

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

如果 myProp 在父组件中发生更改,它也会在子组件中反映

如果 myProp 在子组件中发生更改,它也会在孙子组件中反映

因此,如果 myProp 在父组件中发生更改,那么它将反映在孙子组件中。 (到目前为止一切顺利)。

因此,在层次结构中你不必做任何事情,props 本质上是反应性的。

现在说的是等级上升

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

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

如果 myProp 在 grandChild 组件中发生更改,它不会反映在父组件中(显然)。您必须使用 .sync 修饰子子组件并从孙子组件发出事件,然后观察子组件中的道具并在更改时发出一个事件,该事件正在由使用 .sync 修饰符的父组件侦听。

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

父.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)。我仍然建议您应该重新考虑您的设计,并减少出现错误的可能性。

我希望它有所帮助。

【讨论】:

    【解决方案5】:

    对于双向绑定,您必须使用 .sync 修饰符

    <child :myprop.sync="text"></child>
    

    more details...

    您必须在子组件中使用 watch 属性来监听和更新任何更改

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

    【讨论】:

      【解决方案6】:

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

      如果父母收到 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>
      

      实际上在这种情况下处理复杂的集合时必须非常小心(传递几次)

      【讨论】:

        【解决方案7】:

        我使用如下计算属性:

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

        在这种情况下,资源是一个属性:

        props: [ 'resources' ]
        

        【讨论】:

          【解决方案8】:

          道具和 v-model 处理。如何将值从父级传递给子级,从子级传递给父级。

          Watch 不是必需的! 另外,在 Vue 中改变 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>

          【讨论】:

          • 行:this.$emit("anumber", 6) 应该是:this.$emit("update:anumber", 6) ;除此之外,感谢您提供这个非常有用的答案;我特别看重你如何显示观看不是必需的! :)
          【解决方案9】:

          对我来说,这是一种礼貌的解决方案,可以让一个特定的 prop(s) 发生变化并用它创建逻辑

          我会使用props 和变量computed 属性在接收更改后创建逻辑

          export default {
          name: 'getObjectDetail',
          filters: {},
          components: {},
          props: {
            objectDetail: { // <--- we could access to this value with this.objectDetail
              type: Object,
              required: true
            }
          },
          computed: {
            _objectDetail: {
              let value = false
              // ...
              // if || do || while -- whatever logic
              // insert validation logic with this.objectDetail (prop value)
              value = true
              // ...
              return value 
            }
          }
          
          

          所以,我们可以在 html 渲染上使用 _objectDetail

          <span>
            {{ _objectDetail }}
          </span>
          

          或以某种方式:

          literallySomeMethod: function() {
             if (this._objectDetail) {
             ....
             }
          }
          

          【讨论】:

            【解决方案10】:

            对一些用例的有趣观察。

            如果您通过道具从商店中观看数据项,并且您在同一个商店突变中多次更改数据项,它将不会被观看。

            但是,如果您将数据项更改分成多个相同突变的调用,它将被监视。

            • 此代码不会触发观察者:

              // Somewhere in the code:
              this.$store.commit('changeWatchedDataItem');
              
              // In the 'changeWatchedDataItem' mutation:
              state.dataItem = false;
              state.dataItem = true;
              
            • 此代码将在每次突变时触发观察者:

              // Somewhere in the code:
              this.$store.commit('changeWatchedDataItem', true);
              this.$store.commit('changeWatchedDataItem', false);
              
              // In the 'changeWatchedDataItem' mutation:
              changeWatchedDataItem(state, newValue) {
                  state.dataItem = newValue;
              }
              

            【讨论】:

              【解决方案11】:

              监视功能应该放在子组件中。不是父母。

              【讨论】:

                【解决方案12】:

                您可以使用监视模式来检测变化:

                在原子级别上做所有事情。所以首先检查 watch 方法本身是否被调用,通过安慰里面的东西。一旦确定手表正在被调用,请使用您的业务逻辑将其粉碎。

                watch: { 
                  myProp: function() {
                   console.log('Prop changed')
                  }
                }
                

                【讨论】:

                  【解决方案13】:

                  如果我需要在接收更改后创建逻辑,我会使用 props 和变量 computed 属性

                  export default {
                  name: 'getObjectDetail',
                  filters: {},
                  components: {},
                  props: {
                      objectDetail: {
                        type: Object,
                        required: true
                      }
                  },
                  computed: {
                      _objectDetail: {
                          let value = false
                          ...
                  
                          if (someValidation)
                          ...
                      }
                  }
                  
                  

                  【讨论】:

                    【解决方案14】:

                    @JoeSchr 有答案。如果您不想要deep: true,这是另一种方法

                     mounted() {
                        this.yourMethod();
                        // re-render any time a prop changes
                        Object.keys(this.$options.props).forEach(key => {
                          this.$watch(key, this.yourMethod);
                        });
                      },
                    

                    【讨论】:

                      【解决方案15】:

                      如果你的 prop myProp 有嵌套项目,那些嵌套的项目不会是反应式的,所以你需要使用类似 lodash deepClone 的东西:

                      <child :myProp.sync="_.deepClone(myProp)"></child>
                      

                      就是这样,不需要观察者或其他任何东西。

                      【讨论】:

                      • 在这种情况下,您应该使用myProp = Object.assign({}, newValue);更新myProp
                      【解决方案16】:

                      如果有人使用带有组合 API 的 Vue 2,我的以下答案是适用的。 所以设置功能将是

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

                      但是,您需要从组合 API 中导入 watch 函数。

                      【讨论】:

                        【解决方案17】:

                        如果 myProp 是一个对象,它通常不会被改变。所以,手表永远不会被触发。 myProp 没有被改变的原因是你在大多数情况下只是设置了 myProp 的一些键。 myProp 本身仍然是一个。 尝试观看 myProp 的 props,例如“myProp.a”,它应该可以工作。

                        【讨论】:

                          猜你喜欢
                          • 2019-06-19
                          • 2021-09-21
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-04-07
                          • 1970-01-01
                          • 2023-04-07
                          • 1970-01-01
                          相关资源
                          最近更新 更多