Jaki jest najlepszy sposób na wyzwolenie zdarzenia onchange w reakcji js


112

Używamy pakietu Backbone + ReactJS do tworzenia aplikacji po stronie klienta. Mocno polegając na notorycznych valueLink, propagujemy wartości bezpośrednio do modelu za pośrednictwem własnego opakowania, które obsługuje interfejs ReactJS do dwukierunkowego wiązania.

Teraz stanęliśmy przed problemem:

Mamy jquery.mask.jswtyczkę, która programowo formatuje wartość wejściową, więc nie uruchamia zdarzeń React. Wszystko to prowadzi do sytuacji, w której model otrzymuje niesformatowane wartości z danych wejściowych użytkownika i pomija sformatowane z wtyczki.

Wygląda na to, że React ma wiele strategii obsługi zdarzeń w zależności od przeglądarki. Czy jest jakiś typowy sposób wywołania zdarzenia zmiany dla określonego elementu DOM, aby React je usłyszał?


Odpowiedzi:


175

Dla React 16 i React> = 15,6

Setter .value=nie działa tak, jak chcieliśmy, ponieważ biblioteka React przesłania ustawianie wartości wejściowych, ale możemy wywołać funkcję bezpośrednio w inputkontekście 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);

Dla elementu textarea należy skorzystać prototypez HTMLTextAreaElementklasy.

Nowy przykład z kodem .

Wszystkie kredyty dla tego współautora i jego rozwiązania

Nieaktualna odpowiedź tylko dla React <= 15,5

Dzięki react-dom ^15.6.0możesz użyć simulatedflagi na obiekcie zdarzenia, aby zdarzenie przeszło

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

Zrobiłem kodepen na przykładzie

Aby zrozumieć, dlaczego potrzebna jest nowa flaga, ten komentarz był bardzo pomocny:

Logika wejściowa w Reakcie teraz deduplikuje zdarzenia zmiany, aby nie były uruchamiane więcej niż raz na wartość. Nasłuchuje on zarówno zdarzeń onChange / onInput przeglądarki, jak i ustawia właściwość wartości węzła DOM (gdy aktualizujesz wartość za pomocą javascript). Ma to efekt uboczny polegający na tym, że jeśli ręcznie zaktualizujesz wartość wejścia input.value = 'foo', a następnie wywołasz ChangeEvent z {target: input} React zarejestruje zarówno zestaw, jak i zdarzenie, widzisz, że jego wartość to nadal `` foo ', potraktuj to jako zdarzenie zduplikowane i połknij je.

Działa to dobrze w normalnych przypadkach, ponieważ „prawdziwe” zdarzenie zainicjowane przez przeglądarkę nie wyzwala zestawów w elemencie.value. Możesz potajemnie wyjść z tej logiki, oznaczając zdarzenie, które wyzwalasz, symulowaną flagą, a reakcja zawsze spowoduje uruchomienie zdarzenia. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128


1
Dzięki temu zadziałało z respons-dom ^ 15.6.0. Ale wygląda na to, że po React 16.0 to przestało działać. Masz jakiś pomysł na alternatywę użycia symulowanej flagi do wywołania zdarzeń zmiany?
Qwerty,

Uwierz, że jest tu punkt, który dałby wskazówkę: actjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Masz pojęcie, co to może być?
Qwerty,

@Qwerty Zaktualizowałem moją odpowiedź, może to zadziała dla Ciebie
Grin

a co o przycisku onClick? co należy zrobić, aby wywołać tego rodzaju zdarzenie?
Bender

@Bender po prostu wywołaj metodę natywnego kliknięcia elementu
Grin

64

Przynajmniej przy wprowadzaniu tekstu wydaje się, że onChangenasłuchuje zdarzeń wejściowych:

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

Zależy od wersji przeglądarki. IE8 nie obsługuje zdarzeń wejściowych. Ie9 nie uruchamia zdarzenia wejścia, gdy usuniesz znaki z pola tekstowego. developer.mozilla.org/en-US/docs/Web/Events/input
wallice.


1
React nie obsługuje już IE8 . W przypadku IE9 możesz użyć czegoś podobnego var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, ale nie mam pod ręką maszyny wirtualnej IE9.
Michael,

@Michael Wypróbowuję Twój kod na IE11 i nie działa on na polach wejściowych reagjs. Działa na normalnych wejściach HTML. Działa również na 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

Wiem, że ta odpowiedź przychodzi trochę późno, ale ostatnio miałem podobny problem. Chciałem wyzwolić zdarzenie na zagnieżdżonym komponencie. Miałem listę z widżetami typu radio i checkbox (były to divy, które zachowywały się jak checkboxy i / lub radio button), aw innym miejscu w aplikacji, jak ktoś zamknął toolbox, musiałem odznaczyć jeden.

Znalazłem dość proste rozwiązanie, nie jestem pewien, czy to najlepsza praktyka, ale działa.

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

To wywołało zdarzenie click w domNode, a mój moduł obsługi dołączony za pośrednictwem reakcji został rzeczywiście wywołany, więc zachowuje się tak, jak bym się spodziewał, gdyby ktoś kliknął element. Nie testowałem onChange, ale powinno działać i nie jestem pewien, jak to będzie wyglądać w naprawdę starych wersjach IE, ale uważam, że MouseEvent jest obsługiwane przynajmniej w IE9 i nowszych.

W końcu odszedłem od tego w moim konkretnym przypadku użycia, ponieważ mój komponent był bardzo mały (tylko część mojej aplikacji reaguje, ponieważ wciąż się go uczę) i mogłem osiągnąć to samo w inny sposób bez odwoływania się do węzłów dom.

AKTUALIZACJA:

Jak powiedzieli inni w komentarzach, lepiej jest użyć, this.refs.refnameaby uzyskać odniesienie do węzła DOM. W tym przypadku refname to odniesienie dołączone do komponentu za pośrednictwem <MyComponent ref='refname' />.


1
Zamiast ID możesz skorzystać z React.findDOMNodefunkcji. goo.gl/RqccrA
m93a

2
> Zamiast ID możesz użyć funkcji React.findDOMNode. Lub dodaj a refdo swojego elementu, a następnie użyjthis.ref.refName.dispatchEvent
silkAdmin,

Ramowy najprawdopodobniej zmieniło od jego this.ref s .refname
nclord

1
Odniesienia do ciągów są teraz uważane za starsze i zostaną wycofane w przyszłości. Odwołania zwrotne są preferowaną metodą śledzenia węzła DOM.
dzv3

9

Możesz symulować zdarzenia za pomocą ReactTestUtils, ale jest to przeznaczone do testowania jednostkowego.

Zalecałbym nie używać valueLink w tym przypadku i po prostu nasłuchiwać zdarzeń zmian uruchamianych przez wtyczkę i aktualizować stan wejścia w odpowiedzi. Dwukierunkowe wiązanie jest bardziej demonstracyjne niż cokolwiek innego; są one zawarte w dodatkach tylko po to, aby podkreślić fakt, że czyste dwukierunkowe wiązanie nie jest odpowiednie dla większości aplikacji i że zwykle potrzebujesz więcej logiki aplikacji, aby opisać interakcje w aplikacji.


Obawiałem się, że odpowiedź będzie podobna (. Problem polega na tym, że React jest zbyt rygorystyczny w przepływie pracy wysyłania / odbierania. W szczególności należy określić procedurę obsługi onChange, aby móc reagować na dane wejściowe użytkownika, nie ma innego sposobu poza niekontrolowanym stanem, który jest miły W moim przypadku powinienem zadeklarować tę procedurę obsługi tylko zgodnie z regułami, podczas gdy odpowiednie dane wejściowe będą pochodzić ze zdarzenia onchange wyzwalanego przez jquery. React naprawdę nie ma pomysłu na rozszerzenie punktów końcowych wysyłania / odbierania za pomocą kodu zadeklarowanego przez użytkownika
wallice

1
I ... jeśli chcesz użyć ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin

8

Rozszerzając odpowiedź Grina / Dana Abramowa, działa to na wiele typów danych wejściowych. Testowane w 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);

    }

};

5
Nie działa dla wybranych elementów. Potrzebujesz „zmiany” zamiast „wejścia” dla zdarzenia.
styks

3

Wyzwalanie zdarzeń zmian w dowolnych elementach tworzy zależności między komponentami, o których trudno jest wnioskować. Lepiej trzymać się jednokierunkowego przepływu danych w React.

Nie ma prostego fragmentu, który wywołałby zdarzenie zmiany w React. Logika jest zaimplementowana w ChangeEventPlugin.js i istnieją różne gałęzie kodu dla różnych typów danych wejściowych i przeglądarek. Co więcej, szczegóły implementacji różnią się w zależności od wersji React.

Zbudowałem reakcję-wyzwalacz-zmianę, która działa, ale jest przeznaczona do testowania, a nie jako zależność produkcyjna:

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

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

CodePen


1

cóż, ponieważ używamy funkcji do obsługi zdarzenia onchange, możemy to zrobić w następujący sposób:

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

Znalazłem to w problemach React na Github: Działa jak urok (wersja 15.6.2)

Oto jak zaimplementowałem do wprowadzania tekstu:

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

To nie działa, jeśli wartość pola tekstowego jest taka sama, jak wartość, którą przekazujesz do setNativeValue
Arun

0

Dla HTMLSelectElementtj<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

Typ zdarzenia inputnie zadziałał dla mnie, <select>ale zmiana go na changedziała

useEffect(() => {
    var event = new Event('change', { bubbles: true });
    selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);

-1

Jeśli używasz Backbone i React, polecam jedną z poniższych opcji,

Oba pomagają integrować modele i kolekcje Backbone z widokami React. Możesz używać zdarzeń Backbone tak samo jak w przypadku widoków Backbone. Mam parał się zarówno i nie widzi wielkiej różnicy z wyjątkiem jednego jest wstawek i inne zmiany React.createClassdo React.createBackboneClass.


Proszę, bądź ostrożny z tymi "zdarzeniowymi" mostami między reakcją a kręgosłupem Wtyczka First używa setProps bez względu na poziom montowania komponentów. To pomyłka. Po drugie, w dużym stopniu opiera się na forceUpdate i uważa, że ​​tylko jeden model można przypisać do komponentu, co nie jest powszechną sytuacją. Co więcej, jeśli udostępniasz model w złożonym interfejsie użytkownika z komponentami reagującymi i zapomniałeś zrezygnować z subskrypcji bezużytecznych zdarzeń aktualizacji, które powodują forceUpdate, możesz wpaść w renderowanie rekurencyjne, pamiętaj o tym.
Wallice
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.