Який найкращий спосіб викликати подію onchange у реакції js


112

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

Тепер ми зіткнулися з проблемою:

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

Схоже, React має безліч стратегій обробки подій залежно від браузера. Чи існує якийсь загальний спосіб викликати подію зміни для конкретного елемента DOM, щоб React почув це?


Відповіді:


174

Для React 16 та React> = 15.6

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

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
Дякуючи, це працювало від react-dom ^ 15.6.0. Але, схоже, після React 16.0 це припинило роботу. Будь-яка ідея щодо альтернативи використанню імітованого прапора для запуску змін?
Qwerty

Повірте, тут є момент, який би дав підказку: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Будь-яка ідея, що це може бути?
Qwerty

@Qwerty Я оновив свою відповідь, можливо, це буде працювати для вас
Грін

і яка кнопка abou onClick? що можна зробити, щоб викликати подібні події?
Бендер

@Bender ви просто зателефонували до натисного методу натискання на елемент
Грін

63

Принаймні, на текстових вводах, здається, що onChangeслухає події введення:

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

Залежить від версії браузера. IE8 не підтримує події введення. І ie9 не запускає події введення, коли ви видаляєте символи з текстового поля. developer.mozilla.org/en-US/docs/Web/Events/input
wallice


1
IE8 більше не підтримується React. Для IE9 ви можете використовувати щось на кшталт var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, але я не маю під рукою IE9 VM.
Майкл

@Michael Я пробую ваш код на IE11, і він не працює над полями введення reactjs. Він працює на звичайних 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

Я знаю, що ця відповідь приходить трохи пізно, але я нещодавно зіткнувся з подібною проблемою. Я хотів викликати подію на вкладеному компоненті. У мене був список із віджетами типу радіо та прапорців (це були диви, які поводилися як прапорці та / або кнопки радіо) та в іншому місці програми, якщо хтось закрив панель інструментів, мені потрібно було зняти його.

Я знайшов досить просте рішення, не впевнений, чи це найкраща практика, але це працює.

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

Це викликало подію натискання на domNode, і мій обробник, приєднаний через react, справді викликався, тому він поводиться так, як я б очікував, якщо хтось натисне на елемент. Я не перевіряв onChange, але він повинен працювати, і не впевнений, як це відбудеться в справді старих версіях IE, але я вважаю, що MouseEvent підтримується принаймні IE9 і вище.

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

ОНОВЛЕННЯ:

Як зазначали інші в коментарях, краще використовувати this.refs.refnameдля отримання посилання на вузол купола. У цьому випадку refname - це посилання, яке ви додали до свого компонента через <MyComponent ref='refname' />.


1
Замість ідентифікатора ви можете використовувати React.findDOMNodeфункцію. goo.gl/RqccrA
m93a

2
> Замість ідентифікатора ви можете використовувати функцію React.findDOMNode. Або додайте refдо свого елемента, а потім використовуйтеthis.ref.refName.dispatchEvent
silkAdmin

Рамка, найімовірніше, змінилася з тих пір, її this.ref s .refname
nclord

1
Поточні відкликання вважаються застарілими і в майбутньому будуть застарілими. Відкликання зворотних дзвінків є кращим методом для відстеження вузла DOM.
dzv3

9

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

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


Я побоювався, що відповідь буде чимось подібним (. Проблема полягає в тому, що React занадто суворий з робочим процесом відправлення / отримання. Зокрема, потрібно вказати обробник onChange, щоб мати можливість реагувати на введення користувача, інакше, крім неконтрольованого стану, який є добрим . більш ніж шаблонний до me.In мого нагоди я повинен оголосити цей обробник тільки слідувати правилам, в той час як відповідний внесок буде надходити з OnChange події , ініційованого по JQuery Реагувати дійсно не вистачає ідей розширення відправки / отримання кінцевих точок з користувачем оголосили код
wallice

1
І ... якщо ви хочете використовувати ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin

8

Розширюючи відповідь Гріна / Дана Абрамова, це працює на декілька типів введення. Випробувано в Реакті> = 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);

    }

};

5
Не працює для виділених елементів. Вам потрібно "змінити" замість "введення" для події.
стикс

3

Ініціювання подій зміни на довільних елементах створює залежності між компонентами, про які важко пояснити. Краще дотримуватися однобічного потоку даних React.

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

Я створив реакцію-запуск-зміну, яка робить це, але вона призначена для тестування, а не як виробнича залежність:

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)} />
  );
 }
}

1

Я виявив це в питаннях Github React: працює як шарм (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)
    }
  }

Це не працює, якщо значення текстового поля збігається зі значенням, яке ви передаєте setNativeValue
Arun

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);

-1

Якщо ви використовуєте Backbone та React, я рекомендую одне з наступних:

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


Будь ласка, будьте обережні з тими мостами "керованими подіями" між реакцією та основою. Перший плагін використовує setProps, не враховуючи рівень монтажу компонентів. Це помилка. По-друге, в значній мірі покладається на силуUpdate і вважає, що для компонента може бути призначена лише одна модель, що не є звичайною ситуацією. Більше того, якщо ви поділите модель на складний інтерфейс з компонентами, що реагують, і забули скасувати підписку на непотрібні події оновлення, що викликають forceUpdate, то ви можете впасти в рекурсивну візуалізацію, пам’ятайте про це.
wallice
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.