คอมโพเนนท์และพรอพส์

เราสามารถมองและแบ่งส่วนติดต่อผู้ใช้งานออกเป็นส่วนย่อยๆที่ไม่มีความเกี่ยวข้องกันและสามารถนำไปใช้ซ้ำในที่อื่นๆได้ด้วยคอมโพเนนท์ ในหัวข้อนี้เราจะมาเริ่มต้นทำความรู้จักกับแนวคิดของคอมโพเนนท์กัน ในส่วนของรายละเอียดการใช้งาน API ของคอมโพเนนท์สามารถดูได้ที่นี่

โดยแนวคิดแล้วคอมโพเนนท์นั้นคล้ายกับฟังก์ชัน JavaScript โดยที่มันรับข้อมูล (เรียกว่า “พรอพส์”) เข้ามาแล้วตอบกลับด้วย React element ซึ่งเป็นสิ่งที่ถูกนำไปแสดงบนหน้าจอ

ฟังก์ชันและคลาสคอมโพเนนท์

วิธีที่ง่ายที่สุดในการสร้างคอมโพเนนท์คือการเขียนขึ้นมาด้วย JavaScript ฟังก์ชัน

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

ฟังก์ชันนี้ถือเป็น React คอมโพเนนท์เพราะมันรับข้อมูล “พรอพส์” (มาจากภาษาอังกฤษคำว่า Properties) อ็อปเจคและตอบกลับไปด้วย React element เราเรียกคอมโพเนนท์ลักษณะนี้ว่า “ฟังก์ชันคอมโพเนนท์” เพราะรูปแบบการเขียนของมันเป็นแบบฟังก์ชันของ JavaScript

เรายังสามารถสร้างคอมโพเนนท์โดยใช้ คลาส ES6

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

ในมุมมองของ React แล้วทั้งสองคอมโพเนนท์ด้านบนนั้นเหมือนกัน

ฟังก์ชันและคลาสมีคุณสมบัติอื่นๆ เพิ่มเติม ซึ่งจะพูดถึงในหัวข้อถัดไป

การแสดงผลของคอมโพเนนท์

ก่อนหน้านี้เราได้เห็น React element ที่สร้างขึ้นมาจากแทก DOM

const element = <div />;

แต่นอกจากนั้นแล้ว React element ยังสามารถจากคอมโพเนนท์ที่สร้างขึ้นเองได้อีกด้วย

const element = <Welcome name="Sara" />;

เมื่อ React พบว่ามี element ที่เป็นคอมโพเนนท์แบบที่สร้างขึ้นมาเอง มันจะทำการแปลงแอตทริบิวต์ของ JSX และลูกๆ ของมันให้เป็นอ็อปเจคที่เรียกว่า “พรอพส์” แล้วส่งค่าเข้าไปให้กับคอมโพเนนท์

จาก Code ตัวอย่างนี้ หน้าจอจะทำการแสดงผลคำว่า “Hello, Sara” ออกมา

function Welcome(props) {  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;ReactDOM.render(
  element,
  document.getElementById('root')
);

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

เรามาลองดูกันว่ามีอะไรเกิดขึ้นบ้างในตัวอย่างนี้

  1. เราเรียก ReactDOM.render() ด้วย <Welcome name="Sara" />
  2. React เรียกดูคอมโพเนนท์ Welcome ด้วยพรอพส์ {name: 'Sara'}
  3. คอมโพเนนท์ Welcome ของเรานั้นตอบกลับด้วย element <h1>Hello, Sara</h1>
  4. React DOM ทำการแก้ไข DOM ให้กลายเป็น <h1>Hello, Sara</h1>.

สังเกต: การตั้งชื่อคอมโพเนนท์ให้ขึ้นต้นด้วยภาษาอังกฤษตัวพิมพ์ใหญ่เสมอ

React มองคอมโพเนนท์ที่ชื่อขึ้นต้นด้วยภาษาอังกฤษตัวพิมพ์เล็กว่าเป็นแทกของ DOM. เช่น React มอง <div /> นั้นเป็น HTML div แทก แต่มอง <Welcome /> ว่าเป็นคอมโพเนนท์ และการจะแสดงผลได้นั้นจะต้องมี Welcome อยู่ภายในสโคป

เหตุผลเบื้องหลังของการตั้งข้อกำหนดนี้สามารถเรียนรู้เพิ่มเติมได้จาก เจาะลึก JSX

ส่วนประกอบของคอมโพเนนท์

คอมโพเนนท์สามารถอ้างอิงและใช้งานคอมโพเนนท์อื่นๆได้ ซึ่งทำให้เราสามารถใช้งานคอมโพเนนท์ได้ในทุกระดับเช่น ปุ่ม, ฟอร์ม, กล่องข้อความ หรือแม้กระทั่งทั้งหน้าจอ ทุกสิ่งทุกอย่างใน React นั้นถูกแสดงผ่านทางคอมโพเนนท์

ยกตัวอย่างเช่น เราสามารถสร้างคอมโพเนนท์​ App ที่แสดงผลคอมโพเนนท์ Welcome หลายๆครั้งได้

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />      <Welcome name="Cahal" />      <Welcome name="Edite" />    </div>
  );
}

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

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

โดยทั่วไปแล้ว แอพที่เขียนด้วย React นั้นจะมีคอมโพเนนท์ App อยู่บนสุด แต่อย่างไรก็ตาม มีบางกรณี เราอาจกำลังทำการผสาน React เข้าไปในแอพเดิมที่มีอยู่แล้ว ในกรณีเช่นนี้เราอาจจะเริ่มจากการเขียนคอมโพเนนท์เล็กๆเช่น Button ก่อน แล้วค่อยๆขยายการใช้งานเพิ่มขึ้นไปเรื่อยๆแบบล่างขึ้นบน

การแบ่งส่วนของคอมโพเนนท์

อย่าลังเลที่จะแบ่งส่วนคอมโพเนนท์ใหญ่ให้ออกเป็นคอมโพเนนท์เล็กๆ ยกตัวอย่างการแบ่งคอมโพเนนท์ Comment ออกส่วนย่อยๆ

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

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

คอมโพเนนท์นี้รับ author (เป็นอ็อปเจค), text (เป็น string) และรับ date (เป็น date) เข้ามาเป็นพรอพส์แล้วทำการแสดงผลข้อความ Comment ขึ้นไปบนเว็บไซต์เครือข่ายสังคม

การแก้ไขคอมโพเนนท์นี้อาจทำได้ยากเนื่องจากถูกสร้างขึ้นมาแบบมี element หลายตัวที่ทับซ้อนกัน และการจะนำส่วนต่างๆแยกออกไปใช้นั้นยังทำได้ยาก เรามาลองแบ่งมันออกเป็นหลายๆคอมโพเนนท์กันดู

เริ่มจากคอมโพเนนท์ Avatar

function Avatar(props) {
  return (
    <img className="Avatar"      src={props.user.avatarUrl}      alt={props.user.name}    />  );
}

คอมโพเนนท์ Avatar ไม่จำเป็นต้องรู้ว่ามันจะถูกนำไปแสดงในคอมโพเนนท์ Comment ดังนั้นเราจึงตั้งชื่อพรอพส์ของมันกลางๆได้ว่า user แทนที่จะเป็น author

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

ตอนนี้คอมโพเนนท์ Comment ดูเรียบง่ายขึ้นมาอีกนิด

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

ต่อไปเราจะทำการแยกคอมโพเนนท์ UserInfo ออกมา โดยคอมโพเนนท์นี้แสดงผลคอมโพเนนท์ Avatar และชื่อของผู้ใช้

function UserInfo(props) {
  return (
    <div className="UserInfo">      <Avatar user={props.user} />      <div className="UserInfo-name">        {props.user.name}      </div>    </div>  );
}

คอมโพเนนท์ Comment ดูเรียบง่ายเพิ่มขึ้นมาอีกนิด

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

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

<<<<<<< HEAD การแบ่งส่วนคอมโพเนนท์อาจจะดูเป็นงานที่ดูจุกจิกในตอนแรก แต่การมีชุดของคอมโพเนนท์ที่สามารถนำไปใช้ซ้ำได้เยอะๆส่งผลดีต่อการพัฒนาในแอพที่ใหญ่ขึ้น หลักการง่ายๆคือถ้ามีส่วนของการแสดงผลที่นำไปใช้หลายๆครั้ง (เช่น Button, Panel, Avatar) หรือคอมโพเนนท์ที่ซับซ้อน (อย่างเช่น App, FeedStory, Comment) การสร้างมันให้เป็นคอมโพเนนท์ก็เป็นตัวเลือกดี ======= Extracting components might seem like grunt work at first, but having a palette of reusable components pays off in larger apps. A good rule of thumb is that if a part of your UI is used several times (Button, Panel, Avatar), or is complex enough on its own (App, FeedStory, Comment), it is a good candidate to be extracted to a separate component.

32e3c7a6f92cb6580eb38c047960805d5998c2ec

พรอพส์นั้นต้องไม่เปลี่ยนแปลง

ไม่ว่าเราจะสร้างคอมโพเนนท์แบบฟังก์ชันหรือคลาส ตัวคอมโพเนนท์นั้นจะต้องไม่มีการแก้ไขพรอพส์ภายในตัวมันเอง ลองพิจารณาจากฟังก์ชัน sum ดังต่อไปนี้

function sum(a, b) {
  return a + b;
}

เราเรียกฟังก์ชันแบบนี้ว่าเป็น “Pure ฟังก์ชัน” เพราะว่ามันไม่มีการแก้ไขค่าที่มันรับเข้ามา และถ้าค่าที่มันรับเข้าไปเป็นแบบเดิมมันก็จะส่งผลลัพธ์ที่เป็นแบบเดิมออกมาเสมอ

ในทางตรงกันข้าม ฟังก์ชันต่อไปนี้เป็นแบบ Impure เพราะว่ามันมีการเปลี่ยนแปลงค่าที่มันรับเข้าไป

function withdraw(account, amount) {
  account.total -= amount;
}

React นั้นค่อนข้างยืดหยุ่นมาก แต่มีกฎที่เข้มงวดอยู่ข้อเดียวคือ

ทุกคอมโพเนนท์ของ React จะต้องให้ความสำคัญกับพรอพส์ของมันแบบเดียวกับ Pure ฟังก์ชันเสมอ

เป็นเรื่องปกติที่ส่วนติดต่อผู้ใช้งานนั้นจะต้องมีการเปลี่ยนแปลงได้ ในหัวข้อถัดไป เราจะมาทำความรู้จักกับ “สเตท” โดยสเตทนั้นทำให้คอมโพเนนท์ของ React มีการเปลี่ยนแปลงตอบสนองกับการใช้งานของผู้ใช้, เมื่อได้รับข้อมูลจาก network หรือเหตุการณ์ใดๆโดยที่ไม่ผิดกฎที่สำคัญข้างต้นได้

Is this page useful?Edit this page