FileEditViewTerminal
IT Issue Bootcamp - Visual Studio Code
day-2-hour-3.mdx

Day 2 / Hour 3

TypeScript Data Model

Issue, IssueStatus, mock data, mapping arrays, and data-driven rendering.

60 minutes
The issue list renders from typed mock data.

TypeScript Data Model

Day 2 - ชั่วโมงที่ 3: เปลี่ยน Static Rows ให้สร้างจากข้อมูล

เป้าหมายของชั่วโมงนี้

หลังจบชั่วโมงนี้ ผู้เรียนจะสามารถ:

  1. อธิบายได้ว่าทำไมเราควรแยกข้อมูลออกจาก JSX
  2. สร้าง Type สำหรับข้อมูล Issue ได้
  3. ใช้ Union Type จำกัดค่าของ Status ได้
  4. สร้าง Mock Data แบบ Issue[] ได้
  5. ใช้ .map() สร้าง Table Rows จากข้อมูลได้
  6. แสดง Status Badge ตามค่าที่อยู่ในข้อมูลได้

ไฟล์ที่แก้

Hour นี้แก้เพียงไฟล์เดียว:

app/page.tsx

เราจะวาง Type, Mock Data และ Function ช่วยไว้ในไฟล์นี้ก่อน แล้วค่อยแยกเป็นไฟล์จริงใน Hour 4


โครงสร้างเวลา 60 นาที

เวลาหัวข้อรูปแบบ
0-10 นาทีจาก Static Rows สู่ข้อมูลแบบ Arrayอธิบายและเปรียบเทียบ
10-25 นาทีสร้าง IssueStatus และ Issueทำทีละขั้นตอน
25-40 นาทีสร้าง Mock Data Issue[]ทำทีละขั้นตอน
40-52 นาทีแสดง Rows ด้วย .map()ทำทีละขั้นตอน
52-60 นาทีคืน Status Badge และตรวจผลลัพธ์ตรวจความพร้อม

Slide 1: จาก Static Rows ไปสู่ข้อมูล

Hour 2 เราสร้าง Table ที่มีข้อมูลเขียนอยู่ใน JSX โดยตรง:

<tr>
  <td>#001</td>
  <td>Login เข้าระบบไม่ได้</td>
  <td>Anan</td>
  <td><span className="status status-open">OPEN</span></td>
</tr>

วิธีนี้แสดงผลได้ แต่ข้อมูลกับหน้าตาของ Table ปนกันอยู่ หากต้องเพิ่มหรือแก้รายการ เราต้องแก้ <tr> ทีละแถว

Hour นี้เราจะย้ายข้อมูล Issue ออกมาไว้ใน Array แล้วให้ JSX สร้าง Rows จากข้อมูลนั้น


Slide 2: แยกข้อมูลออกจากโครงสร้าง Table

Hour นี้ไม่ได้เปลี่ยนหน้าตาของ Table แต่เปลี่ยนวิธีสร้างแต่ละแถว:

ส่วนก่อนปรับหลังปรับ
ข้อมูลปัญหาเขียนอยู่ใน <td> ของแต่ละแถวเก็บรวมไว้ใน Array issues
โครงสร้างแถวCopy <tr> พร้อมข้อมูลซ้ำทุกครั้งเขียน <tr> เป็นแบบสำหรับสร้างทุกแถว
เมื่อต้องเพิ่มปัญหาCopy JSX แล้วแก้ข้อความเพิ่มข้อมูลหนึ่งรายการใน issues
issues (ข้อมูล) → .map() → <tr> (หน้าตาที่แสดง)

.map() จึงเป็นตัวนำข้อมูลแต่ละรายการจาก issues มาสร้างเป็นหนึ่งแถว โดยรายละเอียดการเขียน Code จะเริ่มหลังจากเรากำหนดรูปแบบข้อมูลด้วย TypeScript


Slide 3: TypeScript ช่วยตรวจชนิดของข้อมูล

TypeScript ทำให้เราระบุได้ว่าค่าแต่ละตัวควรเป็นชนิดใด:

const title: string = "Login เข้าระบบไม่ได้";
const total: number = 3;

ถ้าใส่ค่าผิดชนิด TypeScript จะแจ้งเตือนตั้งแต่ตอนเขียน Code:

const total: number = "สาม";

ในตัวอย่างนี้ total ต้องเป็น number แต่ค่าที่ใส่เป็น string เราจึงเห็นปัญหาก่อนเปิดหน้าเว็บ


Slide 4: Issue หนึ่งรายการมีข้อมูลอะไรบ้าง

ข้อมูลบางส่วนมาจาก Form และบางส่วนระบบเป็นผู้กำหนด:

มาจาก Formระบบกำหนดหรืออัปเดต
reporterNameid
reporterEmailstatus
titlecreatedAt
descriptionadminComment

เราจะสร้าง Type ชื่อ Issue เพื่อกำหนดว่า Issue ทุกตัวต้องมี Field เหล่านี้ในรูปแบบเดียวกัน


Slide 5: จำกัด Status ด้วย Union Type

ระบบของเรารองรับ Status เพียงสามค่า:

type IssueStatus = "OPEN" | "IN_PROGRESS" | "DONE";

เครื่องหมาย | อ่านว่า “หรือ” ดังนั้น IssueStatus จึงรับได้เฉพาะ:

  • "OPEN"
  • "IN_PROGRESS"
  • "DONE"

หากใส่ค่าอื่น เช่น "FINISHED" TypeScript จะแจ้งเตือนก่อนเปิดหน้าเว็บ


Slide 6: สร้าง Type Issue

Issue กำหนดว่า Issue หนึ่งรายการต้องมี Property ใดและแต่ละค่าต้องเป็นชนิดอะไร

type Issue = {  id: string;  reporterName: string;  reporterEmail: string;  title: string;  description: string;  status: IssueStatus;  adminComment?: string;  createdAt: string;};
  • status รับได้เฉพาะค่าจาก IssueStatus
  • adminComment? เป็น Optional Property เพราะ Issue ใหม่อาจยังไม่มีความเห็นจาก Admin

Slide 7: สร้าง Mock Data รายการแรก

const issues: Issue[] = [  {    id: "001",    reporterName: "Anan",    reporterEmail: "anan@example.com",    title: "Login เข้าระบบไม่ได้",    description: "ไม่สามารถเข้าสู่ระบบด้วยบัญชีเดิมได้",    status: "OPEN",    createdAt: "2026-05-08",  },];

Issue[] หมายถึง Array ที่สมาชิกทุกตัวต้องมีโครงสร้างตรงกับ Type Issue

ข้อมูลชุดนี้เป็น Mock Data สำหรับฝึกเขียน UI ยังไม่ได้มาจาก Form หรือ Database


Slide 8: เพิ่ม Mock Data รายการที่สอง

{  id: "002",  reporterName: "Mali",  reporterEmail: "mali@example.com",  title: "ส่งแบบฟอร์มไม่ได้",  description: "กดส่งข้อมูลแล้วระบบขึ้น error",  status: "IN_PROGRESS",  createdAt: "2026-05-08",},

รายการนี้ใช้ status: "IN_PROGRESS" ซึ่ง TypeScript ตรวจว่าเป็นค่าที่ IssueStatus รองรับ


Slide 9: เพิ่มรายการที่สามและ Optional Property

{  id: "003",  reporterName: "Kanda",  reporterEmail: "kanda@example.com",  title: "ขอสิทธิ์เข้า Dashboard",  description: "ต้องการสิทธิ์สำหรับตรวจสอบข้อมูลหลังบ้าน",  status: "DONE",  adminComment: "อนุมัติสิทธิ์และแจ้งผู้ใช้เรียบร้อยแล้ว",  createdAt: "2026-05-08",},

รายการที่สามมี adminComment ส่วนสองรายการแรกไม่มี ซึ่งทำได้เพราะ Property นี้เป็น Optional Property


Slide 10: Type ช่วยตรวจ Mock Data ทุกตัว

เพราะเราประกาศ Array เป็น Issue[] TypeScript จะตรวจทุก Object ภายใน Array

ตัวอย่างนี้ลืม title:

const issues: Issue[] = [
  {
    id: "001",
    reporterName: "Anan",
    reporterEmail: "anan@example.com",
    description: "ไม่สามารถเข้าสู่ระบบได้",
    status: "OPEN",
    createdAt: "2026-05-08",
  },
];

TypeScript จะแจ้งว่า Property title หายไป แต่ถ้าไม่ใส่ adminComment จะไม่ Error เพราะเรากำหนดไว้ว่าเป็น Optional


Slide 11: เปลี่ยน Static Rows เป็น .map()

<tbody>  {issues.map((issue) => (    <tr key={issue.id}>      <td>#{issue.id}</td>      <td>{issue.title}</td>      <td>{issue.reporterName}</td>      <td>{issue.status}</td>    </tr>  ))}</tbody>

ข้อความภายใน { } คือ JavaScript Expression ที่ใช้ใน TSX ได้ เช่น {issue.title} จะนำค่า title ของ Issue ปัจจุบันมาแสดง

ตอนนี้ให้แสดง {issue.status} เป็นข้อความธรรมดาก่อน เพื่อดูการทำงานของ .map() ให้ชัดเจน ส่วน <span className="status ..."> จะนำกลับมาครอบ Status ใน Slide 14 หลังจากเราสร้าง Function สำหรับเลือก Class แล้ว

Table List ไม่จำเป็นต้องแสดงทุก Field ส่วน description และ adminComment จะนำไปใช้ในหน้ารายละเอียดภายหลัง


Slide 12: .map() ทำงานทีละรายการ

เมื่อ Array มีสามรายการ .map() จะทำงานสามรอบ:

issues[0] → issue → <tr> ของ Anan
issues[1] → issue → <tr> ของ Mali
issues[2] → issue → <tr> ของ Kanda
  • issues คือ Array ต้นทาง
  • issue คือข้อมูลหนึ่งรายการในรอบปัจจุบัน
  • <tr>...</tr> คือผลลัพธ์ที่สร้างจากรายการนั้น

เมื่อเพิ่ม Object ใหม่ใน issues Table จะมี Row ใหม่ตามข้อมูลโดยไม่ต้อง Copy JSX เพิ่ม


Slide 13: ทำไมแต่ละ Row ต้องมี key

จาก Code ใน Slide ก่อนหน้า:

<tr key={issue.id}>

React ใช้ key เพื่อแยกแต่ละรายการออกจากกัน เมื่อข้อมูลถูกเพิ่ม ลบ หรือเปลี่ยน React จะรู้ว่า Row ใดต้องอัปเดต

id เหมาะกับการเป็น key เพราะแต่ละ Issue มีค่าไม่ซ้ำกันและไม่เปลี่ยนตามลำดับของ Array

หากลืมใส่ key หน้าเว็บอาจยังแสดงได้ แต่ Browser Console จะแจ้ง Warning


Slide 14: แปลง Status เป็นชื่อ Class

function getStatusClass(status: IssueStatus): string {  if (status === "OPEN") {    return "status-open";  }   if (status === "IN_PROGRESS") {    return "status-progress";  }   return "status-done";}

Function รับ Status หนึ่งค่า แล้วคืน status-open, status-progress หรือ status-done ที่มีอยู่แล้วใน globals.css


Slide 15: คืน Status Badge ให้แต่ละ Row

<td>  <span className={`status ${getStatusClass(issue.status)}`}>    {issue.status}  </span></td>
  • status คือ Class พื้นฐานของ Badge
  • ${getStatusClass(issue.status)} เพิ่ม Class สีตาม Status
  • {issue.status} แสดงข้อความ Status

ตอนนี้ทั้งข้อความและสีของ Badge มาจากข้อมูล Issue แต่ละรายการ ไม่ต้องเขียน Badge ซ้ำทีละ Row


Slide 16: ตรวจว่า Table สร้างจากข้อมูลแล้ว

ตรวจผลลัพธ์ใน Browser และ app/page.tsx:

  • Form จาก Hour 2 ยังแสดงเหมือนเดิม
  • Table แสดงข้อมูลครบ 3 รายการและ Badge มีสีถูกต้อง
  • Terminal และ Browser ไม่มี Error หรือ Warning
  • IssueStatus, Issue, issues และ getStatusClass() อยู่ก่อน Home()
  • <tbody> ไม่มี Static Rows เดิมและใช้ issues.map(...)
  • ทุก <tr> มี key={issue.id}

ตอนนี้ UI สร้างจาก Mock Data แล้ว แต่ทุกอย่างยังอยู่ในไฟล์เดียว Hour ถัดไปเราจะแยก Type, Data, Form, List และ Badge ออกเป็นไฟล์ที่นำกลับมาใช้ซ้ำได้