Как вызвать функцию на дочернем компоненте на родительских событиях

164

контекст

В Vue 2.0 документация и другие ясно указывают, что общение между родителями и детьми происходит через реквизит.

Вопрос

Как родитель сообщает своему ребенку, что событие произошло с помощью реквизита?

Должен ли я просто посмотреть реквизит называется событие? Это не правильно, как и альтернативы ( $emit/ $onдля дочерних по отношению к родителям, а модель-концентратор для удаленных элементов).

пример

У меня есть родительский контейнер, и он должен сообщить своему дочернему контейнеру, что можно выполнять определенные действия в API. Мне нужно иметь возможность запускать функции.

jbodily
источник

Ответы:

235

Дайте дочернему компоненту a refи используйте $refsдля непосредственного вызова метода дочернего компонента.

HTML:

<div id="app">
  <child-component ref="childComponent"></child-component>
  <button @click="click">Click</button>  
</div>

JavaScript:

var ChildComponent = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  }
}

new Vue({
  el: '#app',
  components: {
    'child-component': ChildComponent
  },
  methods: {
    click: function() {
        this.$refs.childComponent.setValue(2.0);
    }
  }
})

Для получения дополнительной информации см. Документацию Vue по ссылкам .

joerick
источник
13
Таким образом родительские и дочерние компоненты становятся связанными. Для реальных событий, скажем, когда вы не можете просто изменить реквизит, чтобы вызвать действие, я бы выбрал решение для шины, предложенное @Roy J
Jared
3
Ссылка
ctf0
В моем дочернем компоненте по особым причинам мне пришлось использовать v-один раз, чтобы завершить реактивность. Таким образом, передача опоры от родителя к ребенку была невозможной, так что это решение помогло!
Джон
1
Вопрос новичка: зачем использовать refвместо создания реквизита, который следит за его значением, а затем отправляет его другой функции в parent? Я имею в виду, что у него много дел, но refбезопасно ли его использовать ? Спасибо
Ирфанди Джип
5
@IrfandyJip - да, refбезопасно. Как правило, это не рекомендуется, поскольку сообщество Vue предпочитает передавать состояние детям, а события - родителям. Вообще говоря, это приводит к более изолированным, внутренне согласованным компонентам (хорошо ™). Но если информация, которую вы передаете ребенку, действительно является событием (или командой), изменение состояния не является правильным шаблоном. В этом случае вызов метода с использованием a refвполне подходит, и он не будет аварийно завершен или что-то в этом роде.
Джоерик
77

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

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

Рой Дж
источник
2
Я думаю, что это единственный ответ в соответствии с официальным руководством по стилю Vue.JS и лучшими практиками. Если вы используете сокращение v-modelдля компонента, вы также можете легко сбросить значение, отправив соответствующее событие с меньшим количеством кода.
Falco
Например, я хочу дать предупреждение, когда пользователь нажимает кнопку. Вы предлагаете, например: - смотреть флаг - установить этот флаг от 0 до 1, когда происходит щелчок, - сделать что-то - сбросить флаг
Синан Эрдем
9
Это очень неудобно, вы должны создать propв дочернем элементе дополнительное свойство data, затем добавить watch... Было бы удобно, если бы была встроенная поддержка для передачи событий от родителя к ребенку. Такая ситуация встречается довольно часто.
Илья Зеленько
1
Как заявляет @ ИльяЗеленько, это случается довольно часто, это было бы настоящей находкой прямо сейчас.
Крейг
1
Спасибо @RoyJ, я полагаю, что для того, чтобы ребенок подписывался на него, необходима существующая автобусная опора, хотя, я полагаю, вся идея отсылки событий детям не приветствуется в Vue.
Бен Виндинг
35

Вы можете использовать $emitи $on. Используя код @RoyJ:

HTML:

<div id="app">
  <my-component></my-component>
  <button @click="click">Click</button>  
</div>

JavaScript:

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  },
  created: function() {
    this.$parent.$on('update', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
    click: function() {
        this.$emit('update', 7);
    }
  }
})

Пример выполнения: https://jsfiddle.net/rjurado/m2spy60r/1/

drinor
источник
6
Я удивлен, что это работает. Я думал, что излучение для ребенка является анти-паттерном, или что целью было излучение только от ребенка к родителю. Есть ли потенциальные проблемы с движением в другую сторону?
jbodily
2
Это не может считаться лучшим способом, я не знаю, но если вы знаете, что делаете, я думаю, это не проблема. Другой способ - использовать центральный автобус: vuejs.org/v2/guide/…
drinor
14
Это создает связь между ребенком и родителем и считается плохой практикой
morrislaptop
5
Это работает только потому, что родитель не является компонентом, а на самом деле приложением vue. В действительности это использование экземпляра Vue в качестве автобуса.
Хулио Родригес
2
@Bsienn вызов this. $ Parent делает этот компонент зависимым от родителя. использует $ emit to и props, поэтому единственные зависимости - через систему связи Vue. Этот подход позволяет использовать один и тот же компонент в любом месте иерархии компонентов.
morrislaptop
6

Если у вас есть время, используйте хранилище Vuex для наблюдения за переменными (он же состояние) или запускайте (он же диспетчеризация) действие напрямую.

brightknight08
источник
2
из-за реактивности vuejs / vuex, который является лучшим подходом, в родительском элементе выполните действие / мутацию, которые изменяют значение свойства vuex, а в дочернем элементе есть вычисленное значение, которое получает то же самое vuex $ store.state.property.value или "watch" "метод, который делает что-то, когда vuex" $ store.state.property.value "изменяется
FabianSilva
6

Не понравился подход события-шины, использующий $onпривязки у ребенка во время create. Зачем? Последующие createвызовы (я использую vue-router) связывают обработчик сообщений более одного раза, что приводит к нескольким ответам на сообщение.

Ортодоксальное решение передачи реквизита от родителя к ребенку и помещения в ребенка наблюдателя за имуществом работало немного лучше. Единственная проблема заключается в том, что ребенок может действовать только на переход ценностей. Передача одного и того же сообщения несколько раз требует какой-то бухгалтерии для принудительного перехода, чтобы ребенок мог получить изменения.

Я обнаружил, что если я оберну сообщение в массив, он всегда будет вызывать дочерний наблюдатель - даже если значение останется неизменным.

родитель:

{
   data: function() {
      msgChild: null,
   },
   methods: {
      mMessageDoIt: function() {
         this.msgChild = ['doIt'];
      }
   }   
   ...
}

Ребенок:

{
   props: ['msgChild'],
   watch: {
      'msgChild': function(arMsg) {
         console.log(arMsg[0]);
      }
   }
}

HTML:

<parent>
   <child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
Джейсон Стюарт
источник
1
Я думаю, что это не будет работать, если msgChild всегда имеет одинаковый статус на родительском. Например: я хочу компонент, который открывает модальное. Родителю все равно, является ли текущий статус открытым или закрытым, он просто хочет открыть модальный в любой момент. Итак, если родитель делает this.msgChild = true; модальное окно закрыто, и тогда родитель делает это .msgChild = true, ребенок не получит событие
Хорхе Сайнс,
1
@JorgeSainz: Вот почему я упаковываю значение в массив перед тем, как присвоить его элементу данных. Без переноса значения в массив, он ведет себя так, как вы указываете. Итак, msgChild = true, msgChild = true - нет события. msgChild = [true], msgChild = [true] - событие!
Джейсон Стюарт
1
Я этого не видел. Спасибо за разъяснения
Хорхе Сайнс
Это круто, но кажется немного хакерским. Я собираюсь использовать его, поскольку он более чистый, чем использование компонента ref hack, и менее сложен, чем решение шины событий. Я знаю, что vue хочет разъединение и позволяет только изменениям состояния влиять на компонент, но должен быть какой-то встроенный способ вызова дочерних методов, если это необходимо. Возможно модификатор на подпорке, который, как только он меняет состояние, вы можете автоматически сбросить его на значение по умолчанию, чтобы наблюдатель был готов к следующему изменению состояния. В любом случае спасибо за размещение вашей находки.
Крейг,
6

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

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
  	setValue(value) {
    	this.value = value;
    }
  },
  created() {
    this.$emit('handler', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
  	setValueHandler(fn) {
    	this.setter = fn
    },
    click() {
    	this.setter(70)
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<div id="app">
  <my-component @handler="setValueHandler"></my-component>
  <button @click="click">Click</button>  
</div>

Родитель отслеживает дочерние функции-обработчики и при необходимости вызывает их.

nilobarp
источник
Мне нравится, где это решение идет, но что именно это "this.setter" в родительском?
Крейг
Это ссылка на функцию setValue, испускаемая дочерним компонентом в качестве аргумента события-обработчика.
Нилобарп
2

Приведенный ниже пример говорит сам за себя. где ссылки и события могут использоваться для вызова функции от и к родителю и потомку.

// PARENT
<template>
  <parent>
    <child
      @onChange="childCallBack"
      ref="childRef"
      :data="moduleData"
    />
    <button @click="callChild">Call Method in child</button>
  </parent>
</template>

<script>
export default {
  methods: {
    callChild() {
      this.$refs.childRef.childMethod('Hi from parent');
    },
    childCallBack(message) {
      console.log('message from child', message);
    }
  }
};
</script>

// CHILD
<template>
  <child>
    <button @click="callParent">Call Parent</button>
  </child>
</template>

<script>
export default {
  methods: {
    callParent() {
      this.$emit('onChange', 'hi from child');
    },
    childMethod(message) {
      console.log('message from parent', message);
    }
  }
}
</script>
Mukundhan
источник
1

Я думаю, что мы должны рассмотреть вопрос о необходимости использования родителями методов ребенка. Фактически, родителям не нужно относиться к методу child, но они могут рассматривать дочерний компонент как компонент FSA (конечный автомат) .Parents контролировать состояние дочернего компонента. Так что для решения достаточно посмотреть изменение статуса или просто использовать функцию вычисления

user10097040
источник
2
Если вы говорите, что «родители никогда не должны заботиться о контроле над детьми», есть случаи, когда это необходимо. Рассмотрим компонент таймера обратного отсчета. Родитель может захотеть сбросить таймер, чтобы начать все сначала. Простого использования реквизита недостаточно, потому что переход от времени к времени 60 не изменит опору. Таймер должен предоставлять функцию «сброса», которую родитель может вызвать соответствующим образом.
TBM
0

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

<component :is="child1" :filter="filter" :key="componentKey"></component>

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

reloadData() {            
   this.filter = ['filter1','filter2']
   this.componentKey += 1;  
},

и использовать фильтр, чтобы вызвать функцию

Ардиан Зак
источник