Как лучше всего запустить событие onchange в React JS

112

Мы используем пакет Backbone + ReactJS для создания клиентского приложения. Сильно полагаясь на пресловутые valueLinkмы распространяем значения напрямую в модель через собственную оболочку, поддерживающую интерфейс ReactJS для двусторонней привязки.

Теперь мы столкнулись с проблемой:

У нас есть jquery.mask.jsплагин, который программно форматирует входное значение, поэтому он не запускает события React. Все это приводит к тому, что модель получает неформатированные значения от пользовательского ввода и пропускает отформатированные от плагина.

Кажется, что у React есть множество стратегий обработки событий в зависимости от браузера. Есть ли какой-нибудь общий способ инициировать событие изменения для определенного элемента DOM, чтобы React его услышал?

Wallice
источник

Ответы:

175

Для React 16 и React> = 15,6

Сеттер .value=не работает так, как мы хотели, потому что библиотека React переопределяет установщик входных значений, но мы можем вызвать функцию непосредственно inputв контексте as.

var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');

var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);

Для TEXTAREA элемента следует использовать prototypeв HTMLTextAreaElementклассе.

Новый пример кода .

Все кредиты этому участнику и его решению

Устаревший ответ только для React <= 15.5

С помощью react-dom ^15.6.0вы можете использовать simulatedфлаг на объекте события, чтобы событие прошло через

var ev = new Event('input', { bubbles: true});
ev.simulated = true;
element.value = 'Something new';
element.dispatchEvent(ev);

Я сделал код с примером

Чтобы понять, зачем нужен новый флаг, я нашел этот комментарий очень полезным:

Логика ввода в React теперь выводит события изменения, поэтому они не срабатывают более одного раза для каждого значения. Он прослушивает как события браузера onChange / onInput, так и устанавливает в опоре значения узла DOM (когда вы обновляете значение через javascript). Это имеет побочный эффект, означающий, что если вы обновляете значение ввода вручную input.value = 'foo', затем отправляете ChangeEvent с {target: input} React зарегистрирует как набор, так и событие, его значение по-прежнему 'foo ', считайте это повторяющимся событием и проглатывайте его.

Это нормально работает в обычных случаях, потому что «реальное» событие, инициированное браузером, не запускает наборы для element.value. Вы можете избавиться от этой логики тайно, пометив событие, которое вы запускаете, симулированным флагом, и реакция всегда будет запускать событие. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

Усмешка
источник
1
Спасибо, что это сработало из response-dom ^ 15.6.0. Но, похоже, после React 16.0 это перестало работать. Любая идея об альтернативе использованию симулированного флага для запуска событий изменения?
Qwerty
Поверьте, здесь есть момент, который может дать подсказку: responsejs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Есть идеи, что это может быть?
Qwerty
@Qwerty Я обновил свой ответ, может быть, это сработает для вас
Grin
а что насчет кнопки onClick? что можно сделать, чтобы вызвать такого рода событие?
Бендер
@Bender вы просто вызываете собственный метод щелчка по элементу
Grin
64

По крайней мере, onChangeпри вводе текста кажется, что он прослушивает события ввода:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Майкл
источник
Зависит от версии браузера. IE8 не поддерживает событие ввода. И ie9 не запускает событие ввода, когда u удаляет символы из текстового поля. developer.mozilla.org/en-US/docs/Web/Events/input
wallice,
Eventне поддерживает IE https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
Joyful
1
IE8 больше не поддерживается React. Для IE9 вы можете использовать что-то вроде var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, но у меня нет виртуальной машины IE9 под рукой.
Michael
@Michael Я пробую ваш код в IE11, и он не работает с полями ввода responsejs. Он работает с обычным вводом HTML. Он также работает на Edge. var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);
Бодоско
19

Я знаю, что этот ответ приходит немного поздно, но недавно я столкнулся с аналогичной проблемой. Я хотел вызвать событие для вложенного компонента. У меня был список с виджетами типа радио и флажка (это были div, которые вели себя как флажки и / или переключатели), а в другом месте приложения, если кто-то закрыл панель инструментов, мне нужно было снять флажок.

Я нашел довольно простое решение, не уверен, что это лучшая практика, но оно работает.

var event = new MouseEvent('click', {
 'view': window, 
 'bubbles': true, 
 'cancelable': false
});
var node = document.getElementById('nodeMyComponentsEventIsConnectedTo');
node.dispatchEvent(event);

Это вызвало событие щелчка на domNode, и мой обработчик, подключенный через response, действительно был вызван, поэтому он ведет себя так, как я ожидал, если кто-то щелкнет элемент. Я не тестировал onChange, но он должен работать, и не уверен, насколько это будет справедливо в действительно старых версиях IE, но я считаю, что MouseEvent поддерживается как минимум в IE9 и выше.

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

ОБНОВИТЬ:

Как отмечали другие в комментариях, лучше использовать this.refs.refnameдля получения ссылки на узел dom. В этом случае refname - это ссылка, которую вы прикрепили к своему компоненту через <MyComponent ref='refname' />.

Роберт-В
источник
1
Вместо ID можно использовать React.findDOMNodeфункцию. goo.gl/RqccrA
m93a 03
2
> Вместо ID вы можете использовать функцию React.findDOMNode. Или добавьте refк своему элементу, затем используйтеthis.ref.refName.dispatchEvent
silkAdmin
С тех пор фреймворк, скорее всего, изменился, его this.ref s .refname
nclord
1
Строковые ссылки сейчас считаются устаревшими и будут исключены в будущем. Ссылки обратного вызова - предпочтительный метод для отслеживания узла DOM.
dzv3 07
9

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

Я бы рекомендовал не использовать valueLink в этом случае, а просто прослушивать события изменения, запускаемые плагином, и обновлять состояние ввода в ответ. Двусторонняя привязка используется больше как демонстрация, чем что-либо еще; они включены в надстройки только для того, чтобы подчеркнуть тот факт, что чистая двусторонняя привязка не подходит для большинства приложений и что вам обычно требуется больше логики приложения для описания взаимодействий в вашем приложении.

Софи Альперт
источник
Я боялся, что ответ будет примерно таким (проблема в том, что React слишком строг с рабочим процессом отправки / получения. В частности, нужно указать обработчик onChange, чтобы иметь возможность реагировать на ввод пользователя, никаким другим способом, кроме неконтролируемого состояния, которое является добрым В моем случае я должен объявить этот обработчик только для того, чтобы следовать правилам, в то время как соответствующие входные данные будут поступать из события onchange, инициированного jquery. React действительно не имеет представления о расширении конечных точек отправки / получения с помощью кода, объявленного пользователем
wallice
1
И ... если вы хотите использовать ReactTestUtils ... ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin
8

Продолжая ответ Грина / Дэна Абрамова, это работает с несколькими типами ввода. Протестировано в React> = 15.5

const inputTypes = [
    window.HTMLInputElement,
    window.HTMLSelectElement,
    window.HTMLTextAreaElement,
];

export const triggerInputChange = (node, value = '') => {

    // only process the change on elements we know have a value setter in their constructor
    if ( inputTypes.indexOf(node.__proto__.constructor) >-1 ) {

        const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set;
        const event = new Event('input', { bubbles: true });

        setValue.call(node, value);
        node.dispatchEvent(event);

    }

};
cjpete
источник
5
Не работает для выбранных элементов. Вам нужно «изменить» вместо «ввода» для события.
Стикс
3

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

Нет простого фрагмента для запуска события изменения React. Логика реализована в ChangeEventPlugin.js, и есть разные ветки кода для разных типов ввода и браузеров. Более того, детали реализации различаются в зависимости от версии React.

Я создал response-trigger-change, который делает это, но он предназначен для использования для тестирования, а не как производственная зависимость:

let node;
ReactDOM.render(
  <input
    onChange={() => console.log('changed')}
    ref={(input) => { node = input; }}
  />,
  mountNode
);

reactTriggerChange(node); // 'changed' is logged

CodePen

Виталий Кузнецов
источник
1

ну, поскольку мы используем функции для обработки события onchange, мы можем сделать это следующим образом:

class Form extends Component {
 constructor(props) {
  super(props);
  this.handlePasswordChange = this.handlePasswordChange.bind(this);
  this.state = { password: '' }
 }

 aForceChange() {
  // something happened and a passwordChange
  // needs to be triggered!!

  // simple, just call the onChange handler
  this.handlePasswordChange('my password');
 }

 handlePasswordChange(value) {
 // do something
 }

 render() {
  return (
   <input type="text" value={this.state.password} onChange={changeEvent => this.handlePasswordChange(changeEvent.target.value)} />
  );
 }
}
очкарик-04-04
источник
1

Я нашел это в проблемах React Github: работает как шарм (v15.6.2)

Вот как я реализовал ввод текста:

changeInputValue = newValue => {

    const e = new Event('input', { bubbles: true })
    const input = document.querySelector('input[name=' + this.props.name + ']')
    console.log('input', input)
    this.setNativeValue(input, newValue)
    input.dispatchEvent(e)
  }

  setNativeValue (element, value) {
    const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
    const prototype = Object.getPrototypeOf(element)
    const prototypeValueSetter = Object.getOwnPropertyDescriptor(
      prototype,
      'value'
    ).set

    if (valueSetter && valueSetter !== prototypeValueSetter) {
      prototypeValueSetter.call(element, value)
    } else {
      valueSetter.call(element, value)
    }
  }
a2ron44
источник
Это не работает, если значение текстового поля такое же, как значение, которое вы передаете в setNativeValue
Арун,
0

Ибо HTMLSelectElement, т.е.<select>

var element = document.getElementById("element-id");
var trigger = Object.getOwnPropertyDescriptor(
  window.HTMLSelectElement.prototype,
  "value"
).set;
trigger.call(element, 4); // 4 is the select option's value we want to set
var event = new Event("change", { bubbles: true });
element.dispatchEvent(event);
октоэдр
источник
0

Тип события inputу меня не работал, <select>но изменил его на changeработает

useEffect(() => {
    var event = new Event('change', { bubbles: true });
    selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);
Шехар
источник
-1

Если вы используете Backbone и React, я бы порекомендовал одно из следующих:

Оба они помогают интегрировать модели и коллекции Backbone с представлениями React. Вы можете использовать события Backbone точно так же, как и представления Backbone. Я попробовал оба и не заметил особой разницы, за исключением того, что один является миксином, а другой изменяется React.createClassна React.createBackboneClass.

Блейн Хатаб
источник
Будьте осторожны с этими "управляемыми событиями" мостами между react и backbone. Первый плагин использует setProps без учета уровня монтирования компонента. Это ошибка. Второй во многом полагается на forceUpdate и на то, что для компонента может быть назначена только одна модель, что не является обычной ситуацией. Более того, если вы делитесь моделью в сложном пользовательском интерфейсе с реагирующими компонентами и забыли отписаться от бесполезных событий обновления, которые вызывают forceUpdate, тогда вы можете попасть в рекурсивный рендеринг, помните об этом.
wallice