Was ist der beste Weg, um ein Änderungsereignis in Reaktion js auszulösen?

111

Wir verwenden das Backbone + ReactJS-Bundle, um eine clientseitige App zu erstellen. valueLinkWir verlassen uns stark auf berüchtigte Daten und geben sie über einen eigenen Wrapper, der die ReactJS-Schnittstelle für die bidirektionale Bindung unterstützt, direkt an das Modell weiter.

Jetzt standen wir vor dem Problem:

Wir haben ein jquery.mask.jsPlugin, das den Eingabewert programmgesteuert formatiert, damit keine React-Ereignisse ausgelöst werden. All dies führt zu einer Situation, in der das Modell unformatierte Werte von Benutzereingaben empfängt und formatierte vom Plugin verfehlt .

Es scheint, dass React je nach Browser viele Strategien zur Ereignisbehandlung hat. Gibt es eine übliche Möglichkeit, ein Änderungsereignis für ein bestimmtes DOM-Element auszulösen, damit React es hört?

wallice
quelle

Antworten:

172

Für Reaktion 16 und Reaktion> = 15,6

Setter .value=funktioniert nicht wie gewünscht, da die React-Bibliothek den Eingabewerte-Setter überschreibt, aber wir können die Funktion direkt im inputas-Kontext aufrufen .

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

Für TextArea- Elemente sollten Sie prototypevon HTMLTextAreaElementKlasse.

Neues Codepen-Beispiel .

Alle Credits für diesen Mitwirkenden und seine Lösung

Veraltete Antwort nur für React <= 15.5

Mit können react-dom ^15.6.0Sie das simulatedFlag für das Ereignisobjekt verwenden, damit das Ereignis durchlaufen wird

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

Ich habe einen Codepen mit einem Beispiel gemacht

Um zu verstehen, warum eine neue Flagge benötigt wird, fand ich diesen Kommentar sehr hilfreich:

Die Eingabelogik in React dedupliziert jetzt die Änderungsereignisse, sodass sie nicht mehr als einmal pro Wert ausgelöst werden. Es wartet sowohl auf onChange / onInput-Ereignisse des Browsers als auch auf die Werte für den DOM-Knotenwert (wenn Sie den Wert über Javascript aktualisieren). Dies hat den Nebeneffekt, dass, wenn Sie den Wert der Eingabe manuell aktualisieren. Input.value = 'foo', ein ChangeEvent mit {target: input} auslösen. React registriert sowohl die Menge als auch das Ereignis und sieht, dass der Wert immer noch '' foo ist ', betrachte es als doppeltes Ereignis und schlucke es.

Dies funktioniert im Normalfall einwandfrei, da ein "echtes" vom Browser initiiertes Ereignis keine Mengen für den element.value auslöst. Sie können diese Logik heimlich verlassen, indem Sie das von Ihnen ausgelöste Ereignis mit einem simulierten Flag markieren und reagieren, um das Ereignis immer auszulösen. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

Grinsen
quelle
1
Danke, das hat ab reag-dom ^ 15.6.0 funktioniert. Aber es scheint, dass dies nach React 16.0 nicht mehr funktioniert. Haben Sie eine Idee zur Alternative zur Verwendung eines simulierten Flags zum Auslösen der Änderungsereignisse?
Qwerty
Glauben Sie, dass es hier einen Punkt gibt, der den Hinweis geben würde: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Irgendeine Idee, was es sein könnte?
Qwerty
@ Qwerty Ich habe meine Antwort aktualisiert, vielleicht funktioniert es für Sie
Grinsen
und was ist mit dem Button onClick? Was sollte getan werden, um diese Art von Ereignis auszulösen?
Bender
@Bender Sie rufen nur nativer Klick - Methode auf Element
Grin
63

Zumindest bei Texteingaben scheint es, dass onChangeauf Eingabeereignisse gewartet wird:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Michael
quelle
Hängt von der Browserversion ab. IE8 unterstützt kein Eingabeereignis. Und ie9 löst kein Eingabeereignis aus, wenn Sie Zeichen aus dem Textfeld entfernen. developer.mozilla.org/en-US/docs/Web/Events/input
wallice
Eventwird nicht unterstützt IE https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
Joyful
1
IE8 wird von React nicht mehr unterstützt . Für IE9 können Sie möglicherweise so etwas verwenden var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, aber ich habe keine IE9-VM zur Hand.
Michael
@Michael Ich versuche Ihren Code auf IE11 und er funktioniert nicht in den Eingabefeldern von reactjs. Es funktioniert mit normalen HTML-Eingaben. Es funktioniert auch auf Edge. var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);
Bodosko
19

Ich weiß, dass diese Antwort etwas spät kommt, aber ich hatte kürzlich ein ähnliches Problem. Ich wollte ein Ereignis für eine verschachtelte Komponente auslösen. Ich hatte eine Liste mit Widgets vom Typ Radio und Kontrollkästchen (es handelte sich um Divs, die sich wie Kontrollkästchen und / oder Optionsfelder verhielten), und an einer anderen Stelle in der Anwendung musste ich eines deaktivieren, wenn jemand eine Toolbox schloss.

Ich habe eine ziemlich einfache Lösung gefunden, nicht sicher, ob dies die beste Vorgehensweise ist, aber sie funktioniert.

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

Dies löste das Klickereignis auf dem domNode aus und mein über reag angehängter Handler wurde tatsächlich aufgerufen, sodass er sich so verhält, wie ich es erwarten würde, wenn jemand auf das Element klickt. Ich habe onChange noch nicht getestet, aber es sollte funktionieren, und ich bin mir nicht sicher, wie dies in wirklich alten IE-Versionen funktionieren wird, aber ich glaube, dass das MouseEvent in mindestens IE9 und höher unterstützt wird.

Ich habe mich schließlich für meinen speziellen Anwendungsfall davon entfernt, weil meine Komponente sehr klein war (nur ein Teil meiner verwendeten Anwendung reagiert, da ich sie noch lerne) und ich das Gleiche auf andere Weise erreichen konnte, ohne Verweise auf Dom-Knoten zu erhalten.

AKTUALISIEREN:

Wie andere in den Kommentaren angegeben haben, ist es besser this.refs.refname, einen Verweis auf einen Dom-Knoten zu erhalten. In diesem Fall ist refname die ref, über die Sie Ihre Komponente angehängt haben <MyComponent ref='refname' />.

Robert-W
quelle
1
Anstelle von ID können Sie die React.findDOMNodeFunktion verwenden. goo.gl/RqccrA
m93a
2
> Anstelle von ID können Sie die Funktion React.findDOMNode verwenden. Oder fügen Sie refIhrem Element ein hinzu und verwenden Sie dannthis.ref.refName.dispatchEvent
soapAdmin
Framework hat sich höchstwahrscheinlich seitdem geändert, sein this.ref s .refname
nclord
1
String-Refs gelten jetzt als Legacy und werden in Zukunft veraltet sein. Rückrufreferenzen sind die bevorzugte Methode, um den DOM-Knoten zu verfolgen.
dzv3
9

Sie können Ereignisse mit ReactTestUtils simulieren, dies ist jedoch für Unit-Tests vorgesehen.

Ich würde empfehlen, valueLink für diesen Fall nicht zu verwenden und einfach die vom Plugin ausgelösten Änderungsereignisse abzuhören und den Status der Eingabe als Antwort zu aktualisieren. Die Zwei-Wege-Bindung dient eher als Demo als alles andere. Sie sind nur in Addons enthalten, um die Tatsache hervorzuheben, dass eine reine bidirektionale Bindung für die meisten Anwendungen nicht geeignet ist und dass Sie normalerweise mehr Anwendungslogik benötigen, um die Interaktionen in Ihrer App zu beschreiben.

Sophie Alpert
quelle
Ich hatte Angst, dass die Antwort so etwas sein wird (. Das Problem ist, dass React mit dem Sende- / Empfangs-Workflow zu streng ist. Insbesondere muss man den onChange-Handler angeben, um auf Benutzereingaben reagieren zu können, außer auf unkontrollierten Zustand, der freundlich ist In meinem Fall sollte ich diesen Handler nur deklarieren, um die Regeln zu befolgen, während relevante Eingaben von einem durch jquery ausgelösten Wechselereignis stammen. React fehlt wirklich die Idee, Sende- / Empfangsendpunkte mit vom Benutzer deklariertem Code zu erweitern
wallice
1
Und ... falls Sie ReactTestUtils verwenden möchten ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
Colllin
8

Dies erweitert die Antwort von Grin / Dan Abramov und funktioniert über mehrere Eingabetypen hinweg. Getestet in Reaktion> = 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
quelle
5
Funktioniert nicht für ausgewählte Elemente. Sie benötigen 'change' anstelle von 'input' für das Ereignis.
Styks
3

Durch das Auslösen von Änderungsereignissen für beliebige Elemente entstehen Abhängigkeiten zwischen Komponenten, über die nur schwer nachzudenken ist. Es ist besser, sich an den One-Way-Datenfluss von React zu halten.

Es gibt kein einfaches Snippet, um das Änderungsereignis von React auszulösen. Die Logik ist in ChangeEventPlugin.js implementiert und es gibt verschiedene Codezweige für verschiedene Eingabetypen und Browser. Darüber hinaus variieren die Implementierungsdetails je nach Version von React.

Ich habe eine React-Trigger-Änderung erstellt , die das Ding macht, aber sie soll zum Testen verwendet werden, nicht als Produktionsabhängigkeit:

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

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

CodePen

Vitaly Kuznetsov
quelle
1

Nun, da wir Funktionen verwenden, um ein Onchange-Ereignis zu behandeln, können wir dies folgendermaßen tun:

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)} />
  );
 }
}
Vier-Augen-04-04
quelle
1

Ich fand dies bei React's Github-Problemen: Funktioniert wie ein Zauber (v15.6.2)

So habe ich eine Texteingabe implementiert:

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
quelle
Dies funktioniert nicht, wenn der Textfeldwert mit dem Wert übereinstimmt, den Sie an setNativeValue
Arun
0

Für HTMLSelectElement, dh<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);
Oktoeder
quelle
-1

Wenn Sie Backbone und React verwenden, würde ich eine der folgenden Empfehlungen empfehlen:

Beide helfen bei der Integration von Backbone-Modellen und -Sammlungen in React-Ansichten. Sie können Backbone-Ereignisse genauso verwenden wie Backbone-Ansichten. Ich habe in beiden dabbled und nicht viel Unterschied sehen , außer man ist ein mixin und die anderen Änderungen React.createClasszu React.createBackboneClass.

Blaine Hatab
quelle
Seien Sie bitte vorsichtig mit diesen "ereignisgesteuerten" Brücken zwischen React und Backbone. Das erste Plugin verwendet setProps, ohne Berücksichtigung der Komponenten-Mount-Ebene. Es ist ein Fehler. Zweitens hängt ForceUpdate stark davon ab, dass nur ein Modell für eine Komponente zugewiesen werden kann, was nicht üblich ist. Wenn Sie das Modell über eine komplexe Benutzeroberfläche hinweg mit Reaktionskomponenten teilen und vergessen haben, sich von nutzlosen Aktualisierungsereignissen abzumelden, die forceUpdate verursachen, können Sie in rekursives Rendern verfallen. Beachten Sie dies.
Wallice