Aesthetic study notes about Vue 3 and TypeScript integration on an iPad screen

1. 🎯 ตอนที่ 32: Vue 3 + TypeScript คู่หูสุดแกร่งเพื่อแอปพลิเคชันระดับ Enterprise

2. 📖 เปิดฉาก (The Hook)

น้องๆ เคยเจอประสบการณ์ “โค้ดพังตอนรันจริง (Runtime Error)” ไหมครับ?

ลองจินตนาการว่าเรากำลังทำเว็บ E-commerce ขนาดใหญ่ มีการดึงข้อมูล product จาก API แล้วส่งต่อข้อมูลก้อนนี้ผ่าน Props ลงไปหา Component ลูกหลานลึกถึง 5 ชั้น วันดีคืนดี ทีม Backend ดันเปลี่ยนชื่อฟิลด์จาก product.title เป็น product.name สิ่งที่เกิดขึ้นใน JavaScript ธรรมดาคือ… หน้าเว็บขาวโพลน! ผู้ใช้กดซื้อของไม่ได้ กว่าเราจะรู้ตัวก็ตอนที่ลูกค้าโทรมาด่าแล้ว เพราะ JavaScript มันเป็นภาษาแบบ “ตามใจฉัน (Loosely typed)” มันจะไม่เตือนอะไรเราเลยจนกว่าโค้ดบรรทัดนั้นจะถูกรัน

เพื่อแก้ปัญหาฝันร้ายนี้ ในหนังสือ Large Scale Apps with Vue 3 and TypeScript ของคุณ Damiano Fusco ได้แนะนำให้เราจับคู่ Vue 3 เข้ากับ TypeScript ครับ

เปรียบเทียบง่ายๆ JavaScript เหมือนการจัดปาร์ตี้ที่ใครจะเดินเข้างานก็ได้ (แล้วค่อยไปมั่วเอาข้างใน) แต่ TypeScript คือ “การ์ดหน้าผับสุดโหด” ที่จะคอยตรวจบัตรเชิญ (Type Checking) ว่าคุณชื่อตรงไหม? อายุถึงไหม? ตั้งแต่ “ตอนที่เรากำลังพิมพ์โค้ด (Development time)” เลยล่ะครับ! วันนี้พี่จะพามาดูวิธีวางโครงสร้างข้อมูลและ Props ให้แข็งแกร่งดั่งหินผากันครับ

3. 🧠 แก่นวิชา (Core Concepts)

การนำ TypeScript มาใช้กับ Vue 3 ในระดับแอปพลิเคชันขนาดใหญ่ มีหัวใจสำคัญอยู่ 3 ประการครับ:

🛡️ 1. พลังแห่ง Type Checking (ดักจับบั๊กก่อนรันจริง)

ข้อดีสูงสุดของการใช้ TypeScript คือมันช่วยเปลี่ยน Runtime Errors (บั๊กที่เกิดตอนรันเว็บ) ให้กลายเป็น Compile-time Errors (บั๊กที่เตือนตั้งแต่ตอนเขียนโค้ด) ถ้าน้องส่งข้อมูลผิดประเภท (เช่น เค้าขอตัวเลข ดันส่งข้อความไป) หรือเรียกใช้ Property ที่ไม่มีอยู่จริง VS Code จะขีดเส้นใต้สีแดงด่าเราทันที! นอกจากนี้ยังช่วยให้ระบบ Autocomplete (IntelliSense) ทำงานได้อย่างสมบูรณ์แบบสุดๆ

📂 2. การจัดระเบียบ Models (Centralized Interfaces)

ในโปรเจกต์ขนาดใหญ่ เราจะไม่เขียน Type สะเปะสะปะไว้ใน Component ครับ แต่เราจะสร้างโฟลเดอร์ src/models/ ขึ้นมาเป็นศูนย์กลางเพื่อเก็บ “แบบแปลนข้อมูล (Interfaces)” ทั้งหมด และใช้ธรรมเนียมการตั้งชื่อไฟล์ด้วยนามสกุล .interface.ts (เช่น Item.interface.ts) เพื่อให้ทีมรู้ทันทีว่าไฟล์นี้คือแบบแปลน ไม่ใช่โค้ดที่ทำงานจริง

🧩 3. การนิยาม Props ด้วย Generic Types

ในยุคของ Vue 3 <script setup> การรับ Props จะเปลี่ยนร่างไปอย่างสวยงาม เราสามารถนำ Interface ที่เราสร้างไว้ มาโยนใส่ฟังก์ชัน defineProps<...>() ได้เลย ทำให้ Component ของเรารู้ทันทีว่า “ฉันต้องการรับข้อมูลหน้าตาแบบนี้นะ ห้ามส่งอย่างอื่นมาเด็ดขาด!”

Diagram showing the architecture of a Vue 3 application using TypeScript Interfaces for Props and Data

4. 💻 ร่ายมนต์โค้ด (Show me the Code)

เรามาดูวิธีการสร้างระบบ “รายการสินค้า (Items List)” ที่มีการวางโครงสร้าง Type อย่างเป็นระเบียบกันครับ

Step 1: สร้างแบบแปลนข้อมูล (Interface) สร้างไฟล์ src/models/items/Item.interface.ts

// แยกไฟล์ Interface ออกมาเพื่อให้ Component อื่นๆ นำไปใช้ร่วมกันได้
export interface ItemInterface {
  id: string;
  name: string;
  selected: boolean; // บังคับว่าต้องมีสถานะการเลือก (true/false)
}

Step 2: สร้าง Component ลูกสำหรับแสดงผล (รับ Props) ไฟล์ src/components/items/ItemsList.component.vue

<template>
  <div class="items-list">
    <h3>รายการสนค:</h3>
    <ul>
      <!-- V-for จะม Autocomplete ใหใชงานไดอยางแมนยำ! -->
      <li v-for="item in items" :key="item.id">
        <!-- ถ้าเผลอพิมพ์ item.title ระบบจะแจ้งเตือน Error ทันที เพราะใน Interface มีแค่ name -->
        {{ item.name }} 
        <span v-if="item.selected"> (เลือกแล้ว)</span>
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
// 1. นำเข้าแบบแปลนที่เราสร้างไว้
import type { ItemInterface } from '@/models/items/Item.interface'

// 2. ใช้ defineProps พร้อมกับระบุ Type ผ่าน Generic <{...}>
// เป็นการบอกว่า Component นี้ขอรับ Prop ชื่อ 'items' ซึ่งต้องเป็น Array ของ ItemInterface เท่านั้น!
defineProps<{ 
  items: ItemInterface[] 
}>()
</script>

Step 3: สร้าง Component แม่ (คนส่ง Data) ไฟล์ src/App.vue

<template>
  <main>
    <h1>ระบบจดการสนคาดวย TypeScript</h1>
    <!-- งขอม items ลงไปให Component  -->
    <ItemsList :items="myItems" />
  </main>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import ItemsList from './components/items/ItemsList.component.vue'
import type { ItemInterface } from '@/models/items/Item.interface'

// 3. กำหนด Type ให้กับ State 
// ถ้าเราลืมใส่ `selected: false` ระบบ TS จะขีดแดงเตือนทันทีว่าข้อมูลไม่ครบตามแบบแปลน!
const myItems = ref<ItemInterface[]>([
  { id: '001', name: 'MacBook Pro', selected: false },
  { id: '002', name: 'iPad Air', selected: true },
  { id: '003', name: 'iPhone 15', selected: false }
])
</script>

5. 🛡️ เคล็ดลับจากคัมภีร์ลับ (Under the Hood / Pro-Tips)

🚨 1. ศัตรูตัวร้ายที่ชื่อว่า any (The Evil ‘any’ Type) กฎเหล็กสูงสุดในการเขียน TypeScript คือ “หลีกเลี่ยงการใช้ any ให้มากที่สุดเท่าที่จะทำได้” ครับ! การกำหนดตัวแปรหรือ Props เป็นประเภท any (เช่น items: any[]) มันคือการบอก TypeScript ว่า “ไม่ต้องตรวจหรอก ปล่อยผ่านไปเลย” ซึ่งมันทำลายเป้าหมายหลักของการใช้ TS ทิ้งทั้งหมด! ถ้าใช้ any สู้กลับไปเขียน JavaScript ธรรมดาดีกว่าครับ

💡 2. การใช้ withDefaults สำหรับกำหนดค่าเริ่มต้นให้ Props เวลาเราใช้ <script setup lang="ts"> และระบุ Type ของ Props ผ่าน Generic (defineProps<{...}>()) เราจะไม่สามารถระบุค่า Default แบบเดิมได้ Vue 3 จึงเตรียม Helper อีกตัวมาให้ชื่อว่า withDefaults ครับ:

interface Props {
  title: string;
  items?: ItemInterface[]; // ใส่ ? เพื่อบอกว่าเป็น Optional (ไม่ต้องส่งมาก็ได้)
}

const props = withDefaults(defineProps<Props>(), {
  title: 'รายการสินค้าเริ่มต้น',
  items: () => [] // ถ้าไม่ส่ง items มา ให้ใช้ Array ว่างเป็นค่าเริ่มต้น
})

🎯 3. Type-Only Imports (import type) สังเกตในโค้ดพี่ไหมครับว่า ตอนอิมพอร์ตแบบแปลน พี่ใช้คำว่า import type { ItemInterface } การใส่คำว่า type ลงไปด้วยเป็น Best Practice ที่ช่วยบอกเครื่องมือ Build Tool (เช่น Vite) ว่า “ของชิ้นนี้เป็นแค่โครงสร้างนะ ตอนแปลงเป็น JavaScript ให้ตัดบรรทัดนี้ทิ้งไปเลยไม่ต้องเอาไปรวมให้หนักไฟล์” ทำให้แอปของเราเบาและเร็วขึ้นครับ!

6. 🏁 บทสรุป (To be continued…)

การนำ TypeScript เข้ามาผสมผสานกับ Vue 3 อาจจะดูเหมือนทำให้เราต้องเขียนโค้ดเพิ่มขึ้น (ต้องมานั่งสร้าง Interface) แต่ในความเป็นจริงแล้ว มันคือ “การลงทุนระยะยาว” ครับ สำหรับแอปพลิเคชันระดับ Enterprise ที่มีโค้ดเป็นหมื่นบรรทัดและมีนักพัฒนาทำงานร่วมกันหลายคน TypeScript จะเป็นเกราะป้องกันชั้นดีที่ช่วยลดบั๊ก เพิ่มความมั่นใจในการ Refactor โค้ด และทำให้โค้ดของเรา “อ่านตัวเองได้ (Self-documenting)” โดยไม่ต้องพึ่งคู่มือภายนอกเลยล่ะครับ

เมื่อเราได้โครงสร้างข้อมูลที่แข็งแกร่งแล้ว ในบทความถัดไป เราจะมาดูวิชาขั้นสูงเกี่ยวกับการจัดการข้อมูลเหล่านั้นเมื่อมันโตขึ้น ด้วยการใช้ Pinia State Management แบบแบ่งส่วน (Modular Store) เตรียมตัวรับมือกับความซับซ้อนอย่างมีศิลปะได้เลยครับ!


ต้องการที่ปรึกษาและพัฒนาระบบ Web Application หรือ Frontend Architecture ให้กับธุรกิจของคุณ? ทีมงาน WP Solution พร้อมให้บริการออกแบบและพัฒนาซอฟต์แวร์แบบครบวงจร ดูรายละเอียดบริการของเราได้ที่: www.wpsolution2017.com หรือพูดคุยปรึกษาเบื้องต้นได้ที่ Line: wisit.p