01204223/react-components
- หน้านี้เป็นส่วนหนึ่งของวิชา 01204223
ใน React app ที่เราเขียนขึ้น เราใส่ทุกอย่างบน UI ลงใน component App เพียงอย่างเดียว ทำให้โค้ดใน App.jsx มีขนาดใหญ่และซับซ้อนมาก & nbsp; ข้อสังเกตหนึ่งของความซับซ้อนก็คือการจัดการเกี่ยวกับฟอร์ม comment ของแต่ละ TodoItem ที่เราสร้าง state ที่มีความซับซ้อน
เราจะปรับโครงสร้างของ UI ใหม่ จากที่มี component เดียวดังรูปด้านล่าง
ให้แยกส่วนแสดง TodoItem ออกเป็นอีก component หนึ่ง ดังรูปต่อไปนี้
การออกแบบระบบดังกล่าวทำให้แต่ละ component เล็กลง แต่ก็แลกมากับการที่ต้องพิจารณาเรื่องการเก็บ state ของ UI และการเก็บ state ของข้อมูลของแอพทั้งหมด
เนื้อหา
แยก Component ในการแสดงผล
แผนการ
ในโค้ด App.jsx ส่วนที่จะถูกตัดออกเป็นจะเป็นส่วนด้านล่างนี้ที่อยู่ใน tag li
{todoList.map(todo => (
<!----------- จะตัดส่วนนี้ไปสร้างอีก component หนึ่ง ------------------->
<li key={todo.id}>
<span className={todo.done ? "done" : ""}>{todo.title}</span>
<button onClick={() => {toggleDone(todo.id)}}>Toggle</button>
<button onClick={() => {deleteTodo(todo.id)}}>❌</button>
{(todo.comments) && (todo.comments.length > 0) && (
// ละไว้
)}
<div className="new-comment-forms">
// ละไว้
</div>
</li>
<!------------------------------------------------------------>
))}เมื่อแยกไปแล้ว ส่วนที่เหลือใน App.jsx จะเป็นดังนี้ แสดงให้ดูเฉย ๆ ยังไม่ต้องแก้ตาม
{todoList.map(todo => (
<!---------------- โค้ดหลังใช้ component TodoItem ---------------->
<TodoItem
key={todo.id}
todo={todo}
toggleDone={toggleDone}
deleteTodo={deleteTodo}
addNewComment={addNewComment}
/>
<!------------------------------------------------------------>
))}การ refactor การแยกโค้ดออกไปเป็นส่วนใหม่ เป็นวิธีการปรับโค้ดเพื่อทำให้มีโครงสร้าง (หรือสถาปัตยกรรม) ดีขึ้น ดูแลรักษาง่ายขึ้น การปรับนี้ เราไม่ได้มีเป้าหมายในการเปลี่ยนแปลงพฤติกรรมของโค้ดเลย เราทำเพื่อปรับโครงสร้างเท่านั้น การดำเนินการนี้ เราจะเรียกว่า การ refactor โค้ด (อ่านเพิ่มใน wiki)
เรียก backend server และเรียก npm run dev
ในขั้นตอนถัดไป เราจะแก้โค้ดไปพร้อม ๆ กับเทส ดังนั้นให้ไปเรียก backend server รวมทั้งเปิด frontend dev ด้วย โดยเรียก
ใน backend ให้ activate virtual environment จากนั้นให้ตั้งค่า FLASK_APP ให้เรียบร้อย แล้วเรียก
flask run --debug
และ ใน frontend
npm run dev
ก่อนจะทำในขั้นถัด ๆ ไป
ตัดโค้ดไปสร้าง TodoItem.jsx
ในการสร้าง TodoItem component เราจะเริ่มจากไฟล์ TodoItem.jsx ในไดเร็กทอรี frontent/src โดยทำเป็นโครงว่าง ๆ ดังด้านล่าง
// ทำในไฟล์ TodoItem.jsx ในไดเร็กทอรี frontent/src/
import './App.css'
function TodoItem({todo}) {
return (
)
}
export default TodoItem
คำอธิบาย
- Component ใน react สมัยใหม่จะเป็น function ที่คืนค่าเป็นก้อน html function นี้จะรับ props (มาจากคำว่า properties) จาก UI component อื่น ๆ โดยในกรณีนี้เราจะรับข้อมูล todo มาจาก component App สังเกตว่าเราเขียน argument todo ภายในวงเล็บปีกกา (สำคัญมาก) ซึ่งจะทำให้เวลามีการส่ง props มาจะมีการแยก todo มาให้โดยอัตโนมัติ
- อย่าลืมบรรทัด export บรรทัดสุดท้าย จะทำให้เราสามารถนำ function นี้ไปใช้จากไฟล์อื่นได้
เราจะเริ่มโดยตัดโค้ดในส่วนของการแสดง TodoItem มาจาก App.jsx โดยโค้ดที่ตัดมาเราจะนำมาเพิ่มในส่วน return ของฟังก์ชัน TodoItem ดังแสดงตัวอย่างด้านล่าง
หมายเหตุ: ให้ตัดโค้ดของตัวเองมา ด้านล่างโค้ดแสดงไม่ครบ
function TodoItem({todo}) {
return (
<li key={todo.id}>
<span className={todo.done ? "done" : ""}>{todo.title}</span>
<button onClick={() => {toggleDone(todo.id)}}>Toggle</button>
<button onClick={() => {deleteTodo(todo.id)}}>❌</button>
{(todo.comments) && (todo.comments.length > 0) && (
<>
// ละไว้
</>
)}
<div className="new-comment-forms">
// ละไว้
<button onClick={() => {addNewComment(todo.id)}}>Add Comment</button>
</div>
</li>
)
}
จากนั้นเราจะไปแก้ App.jsx ให้เรียกใช้ component TodoItem ในการแสดง UI ส่วนนี้
หมายเหตุ: โค้ดที่เราจะแก้ต่อไปตอนแรกจะทำงานได้ไม่ครบ เราจะทยอยแก้ทีละจุดจนกระทั่งทำงานได้
เราจะ import TodoItem มาก่อน ให้เพิ่มบรรทัดด้านบนที่ตอนต้น App.jsx (ต่อท้ายบรรดา import ต่าง ๆ)
// ทำในไฟล์ App.jsx
import TodoItem from './TodoItem.jsx'
จากนั้นในส่วนที่แสดงรายการ Todo ให้แก้ส่วนที่เราตัดออกไปให้เป็น
{todoList.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
/>
))}ข้อสังเกต เราจะยังระบุ prop key ลงไปด้วย ซึ่ง property นี้สำคัญมากสำหรับ react ในการแสดงผล เพราะระบบจะใช้ในการตรวจสอบว่าหน้าจอมีส่วนใดเปลี่ยนแปลงบ้าง
เมื่อแก้เสร็จให้ save ทั้งหมดแล้วกลับไปดูหน้าจอ Todo app ของเรา
จะพบว่าหน้าจอพังไปแล้ว!
ให้ไป Inspect และเปิดดู error ใน console จะพบ error ประมาณด้านล่าง
Uncaught ReferenceError: newComments is not defined
at TodoItem (TodoItem.jsx:22:18)
เราจะทยอยแก้กันไปทีละส่วน
กลับมาแก้ TodoItem.jsx กันต่อ
จาก error เราจะพบว่าใน component เรามีการใช้ state newComments ซึ่งเดิมเราใช้เก็บข้อความในฟอร์ม comment ของทุก ๆ todoitem รวมกัน เนื่องจากตอนนี้เราจัดการแต่ละ component แยกกันแล้ว ดังนั้นเราจะย้าย state ส่วนนี้แยกออกมาไว้ใน component เองเลย การจัดการตรงนี้ทำให้ลดการขึ้นต่อกันระหว่าง component และ App และเป็นประโยชน์หลัก ๆ ของการแยก component ที่เราทำมาทั้งหมด
เราจะเพิ่ม state newComment ลงใน TodoItem และแก้โค้ดเดิมดังนี้
function TodoItem({todo}) {
// เพิ่มบรรทัด
const [newComment, setNewComment] = React.useState(""); // เพิ่ม state newComment
// .. ละส่วนอื่นไว้
<div className="new-comment-forms">
<input
type="text"
// แก้บรรทัดด้านล่าง
value={newComment} // ของเก่าเป็น value={newComments[todo.id] || ""}
onChange={(e) => {
const value = e.target.value;
// แก้บรรทัดด้านล่าง
setNewComment(value); // ของเดิม: setNewComments({ ...newComments, [todo.id]: value });
}}
/>
<button onClick={() => {addNewComment(todo.id)}}>Add Comment</button>
</div>
// .. ละส่วนอื่นไว้
}
