อะไรคือวิธีที่ดีที่สุดในการเรียกใช้เหตุการณ์ onchange ใน react js


112

เราใช้ Backbone + ReactJS บันเดิลเพื่อสร้างแอปฝั่งไคลเอ็นต์ การใช้ความฉาวโฉ่อย่างมากทำให้valueLinkเราเผยแพร่ค่าไปยังโมเดลโดยตรงผ่านทาง wrapper ของตัวเองที่รองรับอินเทอร์เฟซ ReactJS สำหรับการผูกสองทาง

ตอนนี้เราประสบปัญหา:

เรามีjquery.mask.jsปลั๊กอินที่จัดรูปแบบค่าอินพุตโดยทางโปรแกรมดังนั้นจึงไม่เริ่มเหตุการณ์ React ทั้งหมดนี้นำไปสู่สถานการณ์เมื่อโมเดลได้รับค่าที่ไม่ได้จัดรูปแบบจากอินพุตของผู้ใช้และพลาดค่าที่จัดรูปแบบจากปลั๊กอิน

ดูเหมือนว่า React มีกลยุทธ์การจัดการเหตุการณ์มากมายขึ้นอยู่กับเบราว์เซอร์ มีวิธีทั่วไปในการทริกเกอร์เหตุการณ์การเปลี่ยนแปลงสำหรับองค์ประกอบ DOM เฉพาะเพื่อให้ React ได้ยินหรือไม่


คำตอบ:


175

สำหรับ React 16 และ React> = 15.6

Setter .value=ไม่ทำงานตามที่เราต้องการเนื่องจากไลบรารี React แทนที่ตัวกำหนดค่าอินพุต แต่เราสามารถเรียกใช้ฟังก์ชันได้โดยตรงในinputas context

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ระดับ

ใหม่ตัวอย่างเช่น codepen

เครดิตทั้งหมดให้กับผู้ร่วมให้ข้อมูลนี้และแนวทางแก้ไขของเขา

คำตอบที่ล้าสมัยสำหรับการตอบสนอง <= 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);

ฉันสร้างcodepen ด้วยตัวอย่าง

เพื่อให้เข้าใจว่าเหตุใดจึงต้องตั้งค่าสถานะใหม่ฉันพบว่าความคิดเห็นนี้มีประโยชน์มาก:

ตรรกะการป้อนข้อมูลใน React ตอนนี้อนุมานเหตุการณ์การเปลี่ยนแปลงดังนั้นจึงไม่เริ่มทำงานมากกว่าหนึ่งครั้งต่อค่า มันรับฟังทั้งเหตุการณ์ onChange / onInput ของเบราว์เซอร์รวมทั้งชุดบน DOM node value prop (เมื่อคุณอัปเดตค่าผ่าน javascript) สิ่งนี้มีผลข้างเคียงที่หมายความว่าหากคุณอัปเดตค่าอินพุตด้วยตนเอง input.value = 'foo' จากนั้นส่ง ChangeEvent ด้วย {target: input} การตอบสนองจะลงทะเบียนทั้งชุดและเหตุการณ์ดูว่าค่านั้นยังคงเป็น `` 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 ฉันอัปเดตคำตอบแล้วอาจจะใช้ได้กับคุณ
Grin

และปุ่มอะไรบนคลิก? จะทำยังไงให้เกิดเหตุการณ์แบบนี้
Bender

@Bender คุณเรียกวิธีการคลิกแบบเนทีฟบนองค์ประกอบ
Grin

64

อย่างน้อยในอินพุตข้อความดูเหมือนว่าonChangeกำลังฟังเหตุการณ์อินพุต:

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

ขึ้นอยู่กับเวอร์ชันของเบราว์เซอร์ IE8 ไม่รองรับเหตุการณ์อินพุต และ ie9 ไม่เริ่มเหตุการณ์อินพุตเมื่อคุณลบอักขระออกจากกล่องข้อความ developer.mozilla.org/en-US/docs/Web/Events/input
wallice


1
React ไม่รองรับ IE8 อีกต่อไป สำหรับ IE9 คุณอาจจะใช้บางอย่างได้var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });แต่ฉันไม่มี IE9 VM ที่มีประโยชน์
Michael

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

19

ฉันรู้ว่าคำตอบนี้มาช้าเล็กน้อย แต่ฉันเพิ่งประสบปัญหาที่คล้ายกัน ฉันต้องการทริกเกอร์เหตุการณ์บนส่วนประกอบที่ซ้อนกัน ฉันมีรายการที่มีวิดเจ็ตประเภทวิทยุและช่องทำเครื่องหมาย (เป็น div ที่ทำงานเหมือนช่องทำเครื่องหมายและ / หรือปุ่มตัวเลือก) และในที่อื่น ๆ ในแอปพลิเคชันหากมีคนปิดกล่องเครื่องมือฉันต้องยกเลิกการเลือก

ฉันพบวิธีแก้ปัญหาที่ค่อนข้างง่ายไม่แน่ใจว่านี่เป็นแนวทางปฏิบัติที่ดีที่สุด แต่ได้ผล

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

สิ่งนี้ทำให้เกิดเหตุการณ์การคลิกบน domNode และตัวจัดการของฉันที่แนบมาผ่านการตอบสนองถูกเรียกอย่างแท้จริงดังนั้นมันจึงทำงานอย่างที่ฉันคาดหวังหากมีคนคลิกที่องค์ประกอบ ฉันไม่ได้ทดสอบ onChange แต่มันควรจะใช้งานได้และไม่แน่ใจว่ามันจะยุติธรรมแค่ไหนใน IE เวอร์ชันเก่าจริงๆ แต่ฉันเชื่อว่า MouseEvent ได้รับการสนับสนุนอย่างน้อย IE9 ขึ้นไป

ในที่สุดฉันก็ย้ายออกจากสิ่งนี้สำหรับกรณีการใช้งานเฉพาะของฉันเนื่องจากส่วนประกอบของฉันมีขนาดเล็กมาก (มีเพียงส่วนหนึ่งของแอปพลิเคชันของฉันที่ใช้การตอบสนองตั้งแต่ฉันยังเรียนรู้อยู่) และฉันสามารถบรรลุสิ่งเดียวกันได้อีกทางหนึ่งโดยไม่ต้องรับการอ้างอิงถึงโหนดโดม

อัพเดท:

ตามที่คนอื่น ๆ ระบุไว้ในความคิดเห็นควรใช้this.refs.refnameเพื่ออ้างอิงถึงโหนดโหนด ในกรณีนี้ refname <MyComponent ref='refname' />เป็นโทษที่คุณติดอยู่กับองค์ประกอบของคุณผ่านทาง


1
แทนที่จะเป็น ID คุณสามารถใช้React.findDOMNodeฟังก์ชัน goo.gl/RqccrA
m93a

2
> แทน ID คุณสามารถใช้ฟังก์ชัน React.findDOMNode หรือเพิ่มrefองค์ประกอบของคุณจากนั้นใช้this.ref.refName.dispatchEvent
silkAdmin

Framework มีการเปลี่ยนแปลงมากที่สุดนับตั้งแต่ this.ref s .refname
nclord

1
การอ้างอิงสตริงถือเป็นมรดกตกทอดในตอนนี้และจะเลิกใช้งานในอนาคต การอ้างอิงการโทรกลับเป็นวิธีการที่ต้องการในการติดตามโหนด DOM
dzv3

9

คุณสามารถจำลองเหตุการณ์โดยใช้ReactTestUtilsแต่ออกแบบมาสำหรับการทดสอบหน่วย

ฉันไม่แนะนำให้ใช้ valueLink สำหรับกรณีนี้และเพียงแค่ฟังเหตุการณ์การเปลี่ยนแปลงที่เกิดจากปลั๊กอินและอัปเดตสถานะของอินพุตเพื่อตอบสนอง ยูทิลิตี้การรวมสองทางเป็นตัวอย่างมากกว่าสิ่งอื่นใด พวกเขารวมอยู่ในส่วนเสริมเพื่อเน้นความจริงที่ว่าการเชื่อมแบบสองทางที่บริสุทธิ์ไม่เหมาะสมสำหรับแอปพลิเคชันส่วนใหญ่และโดยปกติคุณจะต้องใช้ตรรกะของแอปพลิเคชันเพิ่มเติมเพื่ออธิบายการโต้ตอบในแอปของคุณ


ฉันกลัวว่าคำตอบจะเป็นแบบนั้น (ปัญหาคือการตอบสนองนั้นเข้มงวดเกินไปกับเวิร์กโฟลว์การส่ง / รับโดยเฉพาะอย่างยิ่งต้องระบุตัวจัดการ onChange เพื่อให้สามารถตอบสนองต่ออินพุตของผู้ใช้ได้ไม่มีวิธีอื่นใดนอกจากสถานะที่ไม่มีการควบคุมซึ่งเป็นชนิด มากกว่าแบบสำเร็จรูปสำหรับฉันในกรณีของฉันฉันควรประกาศตัวจัดการนี้เพื่อปฏิบัติตามกฎเท่านั้นในขณะที่อินพุตที่เกี่ยวข้องจะมาจากเหตุการณ์ onchange ที่เรียกโดย jquery การตอบสนองขาดความคิดในการขยายจุดสิ้นสุดการส่ง / รับด้วยรหัสที่ผู้ใช้ประกาศไว้
กำแพง

1
และ ... ในกรณีที่คุณต้องการใช้ ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin

8

การขยายคำตอบจาก Grin / Dan Abramov สิ่งนี้ใช้ได้กับอินพุตหลายประเภท ทดสอบใน 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
ใช้ไม่ได้กับองค์ประกอบที่เลือก คุณต้อง "เปลี่ยน" แทน "อินพุต" สำหรับเหตุการณ์
สไตล์

3

การทริกเกอร์เหตุการณ์การเปลี่ยนแปลงในองค์ประกอบตามอำเภอใจจะสร้างการอ้างอิงระหว่างส่วนประกอบซึ่งยากที่จะให้เหตุผล จะดีกว่าที่จะยึดติดกับการไหลของข้อมูลทางเดียวของ React

ไม่มีตัวอย่างง่ายๆที่จะทริกเกอร์เหตุการณ์การเปลี่ยนแปลงของ React ตรรกะถูกนำไปใช้ใน ChangeEventPlugin.js และมีสาขารหัสที่แตกต่างกันสำหรับประเภทอินพุตและเบราว์เซอร์ที่แตกต่างกัน นอกจากนี้รายละเอียดการใช้งานจะแตกต่างกันไปตามเวอร์ชันของ React

ฉันได้สร้างreact-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)} />
  );
 }
}

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

สิ่งนี้จะใช้ไม่ได้หากค่า textbox เหมือนกับค่าที่คุณส่งผ่านไปยัง 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 เข้ากับมุมมองการตอบสนอง คุณสามารถใช้เหตุการณ์ Backbone ได้เช่นเดียวกับที่คุณทำกับมุมมอง Backbone ผมเคยขลุกอยู่ในทั้งสองและไม่เห็นความแตกต่างมากยกเว้นหนึ่งเป็น mixin และการเปลี่ยนแปลงอื่น ๆที่จะReact.createClassReact.createBackboneClass


โปรดใช้ความระมัดระวังกับสะพาน "ที่ขับเคลื่อนด้วยเหตุการณ์" ระหว่างปลั๊กอิน react และ backbone First ใช้ setProps โดยไม่คำนึงถึงระดับเมาท์ของคอมโพเนนต์ มันเป็นความผิดพลาด ประการที่สองต้องอาศัย forceUpdate เป็นอย่างมากและคิดว่าสามารถกำหนดโมเดลได้เพียงแบบเดียวสำหรับส่วนประกอบซึ่งไม่ใช่สถานการณ์ทั่วไป ยิ่งไปกว่านั้นหากคุณแชร์โมเดลข้าม UI ที่ซับซ้อนพร้อมส่วนประกอบการตอบสนองและลืมที่จะยกเลิกการสมัครจากเหตุการณ์การอัปเดตที่ไร้ประโยชน์ซึ่งทำให้เกิด ForceUpdate คุณก็อาจตกอยู่ในการเรนเดอร์ซ้ำได้โปรดระวังเรื่องนั้น
wallice
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.