Как слушать изменения "реквизита"

257

В документах VueJs 2.0 я не могу найти никаких хуков, которые бы слушали propsизменения.

У VueJs есть такие хуки как onPropsUpdated()или похожие?

Обновить

Как и предложил @wostex, я попытался сделать watchэто, но ничего не изменилось. Тогда я понял, что у меня есть особый случай:

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

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

Я передаю, myPropчто родительский компонент получает childкомпонент. Тогда watch: {myProp: ...}не работает.

Amio.io
источник
1
Я не уверен, что правильно понимаю ваше дело. Можете ли вы объяснить, какова ваша точная цель? «Когда что-то случается, я хочу, чтобы что-то случилось здесь (где?)». Вы пытаетесь вручную изменить значение родительского реквизита? Если да, то это совсем другой случай.
Егор Стамбакио
@wostex, вот CodePen . Плохо то, что в ручке это работает ...
Amio.io
1
Я вижу, проблема в другом. Покажи мне свой код, я посмотрю.
Егор Стамбакио
1
Привет. Здесь: ChannelDetail.vue, вы нигде не импортируете компонент FacebookPageDetail, но объявляете его: gist.github.com/zatziky/… Я думаю, это должен быть FacebookChannelDetail.vue, импортированный в ChannelDetail.vue как FacebookPageDetail. Кроме того, он выглядит нормально
Егор Стамбакио

Ответы:

322

Вы можете 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/dist/vue.js"></script>

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

Егор Стамбакио
источник
1
Спасибо за wostex, я уже пробовал с watch. Благодаря вашему примеру я понял, что мой случай немного отличается. Я обновил свой вопрос.
Amio.io
1
посмотрите, как это выглядит: componentWillReceiveProps (), спасибо, например: D
yussan
@yussan да, но он применяется к одной опоре, которую нужно отслеживать.
Егор Стамбакио
1
Я знаю, что смотреть не рекомендуется, потому что обычно лучше использовать вычисляемое свойство, но есть ли проблемы с производительностью при использовании часов?
SethWhite
1
@SethWhite Из того, что я понимаю, как реактивность обрабатывается в Vue с использованием шаблона наблюдателя, наблюдатели не обязательно менее эффективны, чем вычисленные или другие реактивные данные. В тех случаях, когда значение свойства зависит от многих значений, вычисляемый реквизит будет запускать одну функцию вместо отдельного обратного вызова для каждого из значений, от которых зависит результат. Я думаю, что в большинстве случаев использования вычисляемое свойство будет иметь желаемый результат.
plong0
83

Вы пробовали это?

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

BearInBox
источник
7
Это сработало для меня, где ответ не получил - возможно, это обновление. Спасибо BearInBox :)
Грант
2
Немедленно: правда исправил это для меня
basbase
2
Настройка часов такова, что это исправило и для меня, спасибо!
adamk22
2
У меня тоже работает. Это должен быть принятый ответ
titou10
1
у меня тоже сработало, тогда как принятый ответ - нет. +1
Роберто
25

Он,

в моем случае мне нужно было решение, при котором в любое время менялся бы любой реквизит, мне нужно было снова анализировать мои данные. Я устал делать отдельный наблюдатель для всех моих реквизитов, поэтому я использовал это:

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

Ключевым моментом, который следует взять из этого примера, является использование, deep: trueчтобы он не только просматривал $ props, но и вложенные значения, такие как, например,props.myProp

Вы можете узнать больше об этих расширенных опциях просмотра здесь: https://vuejs.org/v2/api/#vm-watch

JoeSchr
источник
Рад заплатить вперед! Удачи!
JoeSchr
7

Вы должны понимать, иерархию компонентов, которую вы имеете, и то, как вы передаете реквизит, определенно, ваш случай особенный и обычно не встречается разработчиками.

Родительский компонент -myProp-> Дочерний компонент -myProp-> Внучатый компонент

Если myProp изменяется в родительском компоненте, это будет отражено и в дочернем компоненте.

И если myProp изменяется в дочернем компоненте, это будет отражено и в компоненте внука.

Таким образом, если myProp изменяется в родительском компоненте, это будет отражено в компоненте внука. (Все идет нормально).

Поэтому вниз по иерархии вам не нужно ничего делать, реквизит будет по своей сути реактивным.

Теперь говорим о повышении в иерархии

Если myProp изменяется в компоненте grandChild, он не будет отражен в дочернем компоненте. Вы должны использовать модификатор .sync в дочернем элементе и генерировать событие из компонента grandChild.

Если myProp изменяется в дочернем компоненте, он не будет отражен в родительском компоненте. Вы должны использовать модификатор .sync в родительском и генерировать событие от дочернего компонента.

Если myProp изменяется в компоненте grandChild, он не будет отражен в родительском компоненте (очевидно). Вы должны использовать дочерний модификатор .sync и событие emit из компонента grandchild, затем наблюдать за пропуском в дочернем компоненте и генерировать событие при изменении, которое прослушивается родительским компонентом с использованием модификатора .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>

Grandchild.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>

Но после этого вы не заметите кричащих предупреждений о том, что

«Избегайте прямого изменения свойства, так как значение будет перезаписываться при повторном рендеринге родительского компонента».

Опять же, как я упоминал ранее, большинство разработчиков не сталкиваются с этой проблемой, потому что это анти паттерн. Вот почему вы получаете это предупреждение.

Но для того, чтобы решить вашу проблему (в соответствии с вашим дизайном). Я считаю, что вы должны сделать вышеупомянутую работу (взломать, если честно). Я все еще рекомендую вам пересмотреть свой дизайн и сделать его менее подверженным ошибкам.

Я надеюсь, что это помогает.

Человек-наблюдатель
источник
6

Не уверен, что вы решили (и правильно ли я понял), но вот моя идея:

Если parent получает myProp, и вы хотите, чтобы он передавался child и просматривал его в child, то parent должен иметь копию 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>

на самом деле вы должны быть очень осторожны при работе со сложными коллекциями в таких ситуациях (несколько раз)

m.cichacz
источник
Отличная идея. Я не могу просто подтвердить это в данный момент. Когда я снова буду работать с vue, я проверю это.
Amio.io
1
спасибо @ m.cichacz, сделал ту же ошибку и исправил быстро, благодаря вашему предложению передать копию ребенку
undefinederror
4

для двухстороннего связывания вы должны использовать модификатор .sync

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

подробнее ...

и вы должны использовать свойство watch в дочернем компоненте, чтобы прослушивать и обновлять любые изменения

props: ['myprop'],
  watch: { 
    myprop: function(newVal, oldVal) { // watch it
      console.log('Prop changed: ', newVal, ' | was: ', oldVal)
    }
  }
Г-н Махамудул Хасан
источник
Правда, это новый способ следить за изменениями реквизита в дочерних компонентах.
Amio.io
2

Я работаю с вычисляемым свойством, таким как:

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

Ресурсы в этом случае являются свойством:

props: [ 'resources' ]
Воутер Шуфс
источник
1

Для меня это вежливое решение, чтобы получить одно конкретное изменение реквизитов и создать с ним логику.

Я бы использовал 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 
  }
}

Таким образом, мы могли бы использовать _objectDetail на визуализации html

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

или в некотором методе:

literallySomeMethod: function() {
   if (this._objectDetail) {
   ....
   }
}
Мануэль Аланис
источник
0

Вы можете использовать режим просмотра, чтобы обнаружить изменения:

Делай все на атомном уровне. Поэтому сначала проверьте, вызывается ли сам метод watch или нет, утешая что-то внутри. Как только будет установлено, что часы вызывают, разбейте их своей бизнес-логикой.

watch: { 
  myProp: function() {
   console.log('Prop changed')
  }
}
Рахул Шарма
источник
0

Я использую propsи computedсвойства переменных, если мне нужно создать логику после получения изменений

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

        if (someValidation)
        ...
    }
}
Мануэль Аланис
источник
-1

если myProp - объект, его нельзя изменить в обычном режиме. поэтому часы никогда не сработают. причина, по которой myProp не изменяется, заключается в том, что в большинстве случаев вы просто устанавливаете некоторые ключи myProp. Сам myProp все еще один. попробуйте посмотреть реквизиты myProp, например "myProp.a", он должен работать.

adocking
источник
-1

Функция watch должна размещаться в дочернем компоненте. Не родитель.

Конг Нгуен
источник
-1

У @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);
    });
  },
Элвин Смит
источник