React js'de onchange olayını tetiklemenin en iyi yolu nedir


112

İstemci tarafı bir uygulama oluşturmak için Backbone + ReactJS paketini kullanıyoruz. Ünlülere büyük ölçüde güvenerek, valueLinkiki yönlü bağlama için ReactJS arayüzünü destekleyen kendi sarmalayıcı aracılığıyla değerleri doğrudan modele yayıyoruz.

Şimdi sorunla karşılaştık:

Biz jquery.mask.jsprogramlı böylece yangın olaylarını tepki vermez girdi değerine biçimlendirir eklentisi. Tüm bunlar, modelin kullanıcı girdisinden biçimlendirilmemiş değerler aldığı ve eklentiden biçimlendirilmiş olanları kaçırdığı duruma yol açar .

Görünüşe göre React, tarayıcıya bağlı olarak birçok olay işleme stratejisine sahip. React'in bunu duyması için belirli bir DOM öğesi için değişiklik olayını tetiklemenin ortak bir yolu var mı?


Yanıtlar:


175

React 16 ve React> = 15.6 için

Setter .value=istediğimiz gibi çalışmıyor çünkü React kütüphanesi girdi değeri ayarlayıcısını geçersiz kılıyor, ancak işlevi doğrudan inputbağlamda çağırabiliriz .

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 elemanı prototypeiçin HTMLTextAreaElementclass kullanmanız gerekir .

Yeni codepen örneği .

Bu katılımcının ve çözümünün tüm kredileri

Yalnızca React <= 15.5 için eski yanıt

İle , olayın geçmesi için olay nesnesinde bayrak react-dom ^15.6.0kullanabilirsiniz.simulated

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

Bir örnekle bir codepen yaptım

Neden yeni bayrağın gerekli olduğunu anlamak için bu yorumu çok yararlı buldum :

React'teki giriş mantığı artık tekilleştirmenin değişiklik olaylarını, değer başına birden fazla tetiklememeleri için. Hem tarayıcıdaki onChange / onInput olaylarını hem de DOM düğüm değeri propundaki kümeleri dinler (değeri javascript aracılığıyla güncellediğinizde). Bu, girdinin değerini elle input.value = 'foo' olarak güncellerseniz, {target: input} ile bir ChangeEvent gönderirseniz, React hem seti hem de olayı kaydeder, değerinin hala `` foo '' olduğuna bakın. ', bunu yinelenen bir olay olarak kabul edin ve yutun.

Bu, normal durumlarda iyi çalışır çünkü tarayıcı tarafından başlatılan "gerçek" bir olay element.value üzerindeki kümeleri tetiklemez. Tetiklediğiniz olayı simüle edilmiş bir bayrakla etiketleyerek bu mantıktan gizlice kurtulabilirsiniz ve tepki her zaman olayı tetikler. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128


1
Teşekkürler, bu react-dom ^ 15.6.0'dan çalıştı. Ama görünen o ki React 16.0 sonrası bu çalışmayı durdurdu. Değişiklik olaylarını tetiklemek için simüle edilmiş bayrak kullanmanın alternatifi hakkında herhangi bir fikriniz var mı?
Qwerty

Burada ipucu verecek bir nokta olduğuna inanın: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Ne olabileceğine dair bir fikriniz var mı?
Qwerty

@Qwerty Cevabımı güncelledim, sizin için işe yarayabilir mi
Grin

ve onClick ne yapmalı? Bu tür bir olayı tetiklemek için ne yapılmalıdır?
Bender

Sadece yerli çağrı @Bender tıklama elemana yöntemi
Grin

64

En azından metin girişlerinde, onChangegiriş olaylarını dinleyen görünür :

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

Tarayıcı sürümüne bağlıdır. IE8, giriş olayını desteklemez. Ve ie9, metin kutusundan karakterleri kaldırdığınızda girdi olayını tetiklemez. developer.mozilla.org/en-US/docs/Web/Events/input
wallice


1
IE8 artık React tarafından desteklenmemektedir . IE9 için, gibi bir şey kullanabilirsiniz var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, ancak elimde bir IE9 VM yok.
Michael

@Michael Kodunuzu IE11'de deniyorum ve reactjs giriş alanlarında çalışmıyor. Normal HTML girişlerinde çalışır. Edge'de de çalışıyor. var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);
Bodosko

19

Bu cevabın biraz geç geldiğini biliyorum ama son zamanlarda benzer bir sorunla karşılaştım. Yuvalanmış bir bileşende bir olay tetiklemek istedim. Radyo ve onay kutusu tipi widget'ları içeren bir listem vardı (bunlar onay kutuları ve / veya radyo düğmeleri gibi davranan div'lerdi) ve uygulamanın başka bir yerinde, birisi bir araç kutusunu kapatırsa, birinin işaretini kaldırmam gerekiyordu.

Oldukça basit bir çözüm buldum, bunun en iyi uygulama olup olmadığından emin değilim ama işe yarıyor.

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

Bu, domNode üzerindeki tıklama olayını tetikledi ve react aracılığıyla bağlanan işleyicim gerçekten çağrıldı, bu nedenle birisi öğeye tıkladığında bekleyeceğim gibi davranıyor. Değişim üzerinde test etmedim ama işe yaramalı ve bunun gerçekten eski IE sürümlerinde nasıl adil olacağından emin değilim, ancak MouseEvent'in en azından IE9 ve üzeri sürümlerde desteklendiğine inanıyorum.

Sonunda özel kullanım durumum için bundan uzaklaştım çünkü bileşenim çok küçüktü (uygulamamın yalnızca bir kısmı hala öğrenmeye başladığım için tepki veriyordu) ve aynı şeyi dom düğümlerine referans almadan başka bir yolla elde edebildim.

GÜNCELLEME:

Diğerlerinin yorumlarda belirttiği gibi, this.refs.refnamebir dom düğümüne referans almak için kullanmak daha iyidir . Bu durumda refname, bileşeninize aracılığıyla eklediğiniz referansdır <MyComponent ref='refname' />.


1
Kimlik yerine React.findDOMNodeişlevi kullanabilirsiniz . goo.gl/RqccrA
m93a

2
> ID yerine React.findDOMNode fonksiyonunu kullanabilirsiniz. Veya refelementinize bir ekleyin ve sonra şunu kullanınthis.ref.refName.dispatchEvent
silkAdmin

Framework, this.ref s .refname
nclord

1
Dize referansları artık eski kabul ediliyor ve gelecekte kullanımdan kaldırılacak. Geri arama referansları, DOM düğümünü takip etmek için tercih edilen yöntemdir.
dzv3

9

ReactTestUtils kullanarak olayları simüle edebilirsiniz, ancak bu, birim testi için tasarlanmıştır.

Bu durumda valueLink'i kullanmamanızı ve eklenti tarafından tetiklenen olayları değiştirmeyi dinlemenizi ve yanıt olarak girdinin durumunu güncellemeyi öneririm. İki yönlü bağlama, her şeyden çok bir demo işlevi görür; Yalnızca saf iki yönlü bağlamanın çoğu uygulama için uygun olmadığını ve uygulamanızdaki etkileşimleri tanımlamak için genellikle daha fazla uygulama mantığına ihtiyaç duyduğunuzu vurgulamak için eklentilere dahil edilirler.


Cevabın böyle bir şey olacağından korkuyordum (. Sorun şu ki, React gönderme / alma iş akışında çok katıdır. Özellikle, kullanıcı girdisine tepki verebilmek için onChange işleyicisini belirtmek gerekir, başka türlü değil, kontrolsüz durumdur. . me.In benim durumumda yanına Demirbaş I alakalı girdi jquery tarafından tetiklenen Onchange olay gelecek olurken, kuralları takip etmek, sadece bu işleyici beyan etmelidir tepki gerçekten gönderme uzanan fikrini yoksun / kullanıcı ile bitiş noktaları kodu ilan alma
wallice

1
Ve ... ReactTestUtils'i kullanmak istemeniz durumunda ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin

8

Grin / Dan Abramov'un cevabını genişleterek, bu, birden çok giriş türünde çalışır. React> = 15.5'te test edildi

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
Seçili öğeler için çalışmaz. Etkinlik için "giriş" yerine "değişiklik" yapmanız gerekir.
styks

3

Rastgele öğeler üzerinde değişiklik olaylarının tetiklenmesi, akıl yürütmesi zor olan bileşenler arasında bağımlılıklar yaratır. React'in tek yönlü veri akışına bağlı kalmak daha iyidir.

React'in değişiklik olayını tetikleyecek basit bir pasaj yoktur. Mantık, ChangeEventPlugin.js'de uygulanır ve farklı girdi türleri ve tarayıcılar için farklı kod dalları vardır. Dahası, uygulama ayrıntıları React'in sürümlerine göre değişir.

Ben yerleşik -tetik-değişim tepki şeyi yapar, ama değildi bir üretim bağımlılık olarak, test için kullanılacak amaçlanmıştır:

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

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

CodePen


1

bir onchange olayını işlemek için işlevleri kullandığımızdan, bunu şu şekilde yapabiliriz:

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

Bunu React'in Github sorunlarında buldum: Bir cazibe gibi çalışıyor (v15.6.2)

Bir Metin girişine şu şekilde uyguladım:

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

Metin kutusu değeri setNativeValue'ye geçirdiğiniz değerle aynıysa bu işe yaramaz
Arun

0

İçin HTMLSelectElement, yani<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

Olay türü inputbenim için işe yaramadı <select>ama değiştirmeyi changeişler

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

-1

Backbone ve React kullanıyorsanız, aşağıdakilerden birini tavsiye ederim,

Her ikisi de Backbone modellerini ve koleksiyonlarını React görünümleriyle entegre etmeye yardımcı olur. Backbone olaylarını Backbone görünümlerinde yaptığınız gibi kullanabilirsiniz. Her iki müzikleri ettik ve biri mixin ve diğer değişiklikler olması dışında pek bir fark görmedim React.createClassiçin React.createBackboneClass.


Lütfen react ve omurga arasındaki "olay temelli" köprülere dikkat edin İlk eklenti, bileşen bağlama düzeyini dikkate almayan setProps kullanır. Bu bir hata. İkincisi, forceUpdate'e büyük ölçüde güveniyor ve bir bileşen için yalnızca bir modelin atanabileceğini düşünüyor ki bu yaygın bir durum değil. Dahası, modeli react bileşenleriyle karmaşık kullanıcı arayüzünde paylaşırsanız ve forceUpdate'e neden olan gereksiz güncelleme olaylarına aboneliğinizi iptal etmeyi unuttuysanız, yinelemeli oluşturmaya düşebilirsiniz, bunun farkında olun.
wallice
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.