คอมโพเนนท์และพรอพส์
เราสามารถมองและแบ่งส่วนติดต่อผู้ใช้งานออกเป็นส่วนย่อยๆที่ไม่มีความเกี่ยวข้องกันและสามารถนำไปใช้ซ้ำในที่อื่นๆได้ด้วยคอมโพเนนท์ ในหัวข้อนี้เราจะมาเริ่มต้นทำความรู้จักกับแนวคิดของคอมโพเนนท์กัน ในส่วนของรายละเอียดการใช้งาน 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')
);
เรามาลองดูกันว่ามีอะไรเกิดขึ้นบ้างในตัวอย่างนี้
- เราเรียก
ReactDOM.render()
ด้วย<Welcome name="Sara" />
- React เรียกดูคอมโพเนนท์
Welcome
ด้วยพรอพส์{name: 'Sara'}
- คอมโพเนนท์
Welcome
ของเรานั้นตอบกลับด้วย element<h1>Hello, Sara</h1>
- 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')
);
โดยทั่วไปแล้ว แอพที่เขียนด้วย 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>
);
}
คอมโพเนนท์นี้รับ 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>
);
}
<<<<<<< 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 หรือเหตุการณ์ใดๆโดยที่ไม่ผิดกฎที่สำคัญข้างต้นได้