การจัดการเหตุการณ์

การจัดการเหตุการณ์ที่เกิดบน React elements นั้นมีความคล้ายคลึงกับการจัดการเหตุการณ์บน DOM elements มาก มีเพียงไวยากรณ์ที่ต่างกันเล็กน้อย

  • เหตุการณ์ที่เกิดบน React ใช้วิธีเขียนชื่อแบบ camelCase แทนที่จะเป็นภาษาอังกฤษตัวพิมพ์เล็ก
  • เราส่งฟังก์ชั่นเข้ามาเป็นตัวจัดการเหตุการณ์ใน JSX เลย แทนที่จะเป็น string

ยกตัวอย่างเช่น ในกรณีที่เป็น HTML:

<button onclick="activateLasers()">
  Activate Lasers
</button>

จะเห็นว่าแตกต่างจาก React เพียงเล็กน้อย

<button onClick={activateLasers}>  Activate Lasers
</button>

สิ่งที่แตกต่างอีกอย่างใน React คือเราไม่สามารถใช้การคืนค่า false ในการป้องกันสิ่งที่จะเกิดขึ้นหลังจากเหตุการณ์นั้น เราต้องเรียก preventDefault เอง ยกตัวอย่างเช่นถ้าต้องการจะป้องกันการเปิดหน้าใหม่หลังจากที่กดลิงค์ ในกรณีที่เป็น HTML นั้นสามารถเขียนได้ในลักษณะนี้

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

ส่วนใน React นั้นจะเขียนแบบนี้

function ActionLink() {
  function handleClick(e) {    e.preventDefault();    console.log('The link was clicked.');  }
  return (
    <a href="#" onClick={handleClick}>      Click me
    </a>
  );
}

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

ในการเขียน React นั้น มักจะไม่จำเป็นที่ต้องเรียก addEventListener เพื่อเพิ่มข้อมูลการรับฟัง (listener) บน DOM element หลังจากที่มันถูกสร้าง เราเพียงแค่ให้ข้อมูลการรับฟัง(listener) เมื่อ element นั้นถูกนำไปแสดงผลในครั้งแรก

เมื่อเขียนคอมโพเนนท์ขึ้นมาด้วย คลาส ES6 ตัวจัดการเหตุการณ์โดยทั่วไปนั้นคือ method ของคลาส ยกตัวอย่างเช่นคอมโพเนนท์ Toggle ที่มีการแสดงสถานะ “เปิด” (ON) และ “ปิด” (OFF) โดยผู้ใช้สามารถสลับค่าไปมาได้

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // การ bind นี้เป็นสิ่งที่จำเป็นและทำให้ `this` ใช้งานได้ใน callback    this.handleClick = this.handleClick.bind(this);  }

  handleClick() {    this.setState(state => ({      isToggleOn: !state.isToggleOn    }));  }
  render() {
    return (
      <button onClick={this.handleClick}>        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

ทดลองเขียนบน CodePen

เราควรจะต้องระมัดระวังในค่า this ที่มากับ callback ของ JSX โดยในคลาส JavaScript นั้น method จะยังไม่ได้ถูก bind มาให้ ดังนั้นถ้าเราลืม bind this.handleClick แล้วส่งมันเข้าไปใน onClick เมื่อฟังก์ชันนี้ถูกเรียก ค่าของ this ในที่นี้จะกลายเป็น undefined ทันที

นี้ไม่ใช่สิ่งที่พบเจอได้แค่ใน React แต่มันเป็นวิธีการทำงานของฟังก์ชั่น JavaScritp ทั่วไป นั้นคือโดยทั่วไปแล้วถ้าเราอ้างถึงฟังก์ชั่นโดยที่ไม่มี () ต่อท้ายเช่น onClick={this.handleClick} เราควร bind method นั้น

ถ้าการเรียก bind นั้นดูไม่น่าใช้ ยังมีอีกสองวิธีที่สามารถทำได้ โดยใช้ ไวยากรณ์ class fields ที่ยังอยู่ในช่วงทดลองนั้นจัดการ bind ให้กับ callback

class LoggingButton extends React.Component {
  // ไวยากรณ์แบบนี้จะให้ทำให้ `this` นั้นถูก bind ภายใน handleClick  // ระวัง: ไวยากรณ์แบบนี้ยังอยู่ในช่วง *ทดลอง*  handleClick = () => {    console.log('this is:', this);  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

ไวยากรณ์ในรูปแบบนี้เปิดให้ใช้งานเป็นค่าเริ่มต้นแล้วใน Create React App

แต่ถ้าไม่ได้ใช้ไวยากรณ์ class fields เราก็สามารถใช้ฟังก์ชั่นแบบลูกศร ใน callback ได้

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // ไวยากรณ์แบบนี้จะให้ทำให้ `this` นั้นถูก bind ภายใน handleClick    return (      <button onClick={() => this.handleClick()}>        Click me
      </button>
    );
  }
}

ปัญหาของการเขียนแบบนี้คือจะมีการสร้าง callback ใหม่ทุกครั้งที่มีการแสดงผล LogginButton ซึ่งโดยทั่วไปแล้วสามารถทำได้ แต่ถ้า callback นี้ถูกส่งเข้าเป็น prop ของคอมโพเนนท์ที่อยู่ภายในอีก อาจจะส่งผลให้คอมโพเนนท์เหล่านั้นมีการสร้างการแสดงผลใหม่อย่างไม่จำเป็น เราจึงแนะนำให้ทำการ bind ที่ส่วน constructor หรือใช้ไวยากรณ์ class fields เพื่อหลีกเลี่ยงปัญหาด้านประสิทธิภาพ

การส่งค่าเข้าไปยังการจัดการเหตุการณ์

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

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

การเขียนทั้งสองบรรทัดบนนั้นเหมือนกัน โดยใช้ฟังก์ชั่นแบบลูกศร และ Function.prototype.bind ตามลำดับ

โดยทั้งสองแบบนั้นจะได้รับ e ซึ่งเป็นข้อมูลเหตุการณ์ของ React โดยมันจะถูกส่งเข้าไปลำดับที่สองต่อจาก ID ถ้าเขียนแบบฟังก์ชั่นลูกศร เราต้องเขียน e ลงไปเองให้ชัดเจนเลย แต่ในแบบ bind ค่าของ e นั้นจะถูกส่งต่อเข้าไปเองโดยอัตโนมัติ

Is this page useful?Edit this page