code-along session
เก็บข้อมูล ตัดสินใจ แล้วสั่งให้ทำซ้ำ — สามอย่างนี้คือเกือบทั้งหมดของการเขียนโปรแกรม

today
Variables & Declaration
กล่องที่มีชื่อ let / const และการติดป้ายชนิดด้วย :
Function
ห่อโค้ด ตั้งชื่อ เรียกใช้ซ้ำ — รับอะไร คืนอะไร
Control flow — if / else
ให้โปรแกรมเลือกทางเดินเองจากคำถามที่ตอบ true / false
Loop
ทำงานซ้ำโดยไม่ต้องก๊อปโค้ดเดิม และแพตเทิร์นบวกสะสม
Array
ลิสต์ที่เข้าถึงด้วยตำแหน่ง เริ่มนับที่ศูนย์
Object
หนึ่งสิ่งที่มีหลายส่วน เข้าถึงด้วยชื่อ property
Nested object
object ซ้อน object และลิสต์ที่อยู่ใน object
Array of object
รูปทรงของข้อมูลจริงเกือบทุกอย่างที่มาจาก API
Array helper function
map / filter / find / reduce — เลิกเขียนลูปเอง
guess the result — 1 / 4
1 + "1" = ?
บวกเลขกับข้อความ ใครชนะ
guess the result — 2 / 4
"10" - 1 = ?
คราวนี้เปลี่ยนจากบวกเป็นลบ
guess the result — 3 / 4
[] + [] = ?
อาร์เรย์ว่างสองอันบวกกัน
guess the result — 4 / 4
"5" * 2 = ?
ข้อความคูณตัวเลข
ground rules
จอไม่ตรงกัน บอกทันที
typo ตอนนี้ = งงสิบนาทีข้างหน้า
error คือบทเรียน
เราจะพิมพ์ผิดให้ดูบนเวทีด้วย
ไม่ต้องติดตั้งอะไร
เปิด typescriptlang.org/play ก็พอ
Section 01
กล่องที่มีชื่อ let / const และการติดป้ายชนิดด้วย :
type this
1console.log("Hello, TypeScript!");วงเล็บ = สิ่งที่อยากโชว์ / เครื่องหมายคำพูด = ข้อความ / เซมิโคลอน = จบประโยค
anatomy
let nameStudent : string = "Ada"
│ │ │ │
│ │ │ └─ ค่า
│ │ └─ ชนิด (annotation)
│ └─ ชื่อ
└─ เปลี่ยนค่าได้ทีหลัง: บอกชนิด · = ให้ค่า — คนละหน้าที่กัน
syntax
1let nameStudent: string = "Ada";2let age: number = 30;3let isStudent: boolean = true;string ข้อความ / number ตัวเลข / boolean จริงหรือเท็จ
live
1console.log("Name: " + nameStudent);2console.log("Age: " + age);3console.log("Is student: " + isStudent);+ ต่อข้อความกับค่าในตัวแปรเข้าด้วยกัน
let / const
1let score: number = 10;2score = 25; // ได้ — let เปลี่ยนค่าได้3 4const birthYear: number = 1815;5birthYear = 1816; // ไม่ได้ — const ผูกไว้กับค่านั้นแล้วใช้ const เป็นค่าเริ่มต้น เปลี่ยนไปใช้ let เมื่อรู้ว่าค่าต้องเปลี่ยนจริง ๆ
break it on purpose
1birthYear = 1816;2// Cannot assign to 'birthYear' because it is a constant.3 4age = "thirty";5// Type 'string' is not assignable to type 'number'.พิมพ์ให้พัง → hover อ่าน error → npm run check → แล้วลบทิ้ง
know this from the first error
มันลบ type ทิ้งแล้วรันเลย — โค้ดที่ผิดชนิดจึง “ยังรันได้” อยู่ดี error จะโผล่แค่สองที่เท่านั้น
เส้นแดงใน VS Code
ที่หลัก — เอาเมาส์ชี้เพื่ออ่านข้อความ
npm run check
ในเทอร์มินัล ใช้ตอนอยากให้ error ดัง
checkpoint
Section 02
ห่อโค้ด ตั้งชื่อ เรียกใช้ซ้ำ — รับอะไร คืนอะไร
signature
function add ( a: number, b: number ) : number { … }
│ │
│ └─ คืนอะไรกลับไป
└─ รับอะไรเข้ามาvoid = ไม่คืนค่าอะไรกลับไป ทำงานแล้วจบ เช่นแค่ console.log
live
1export function sayHi(name: string): void {2 console.log("Hello, " + name + "!");3}4 5export function add(a: number, b: number): number {6 return a + b;7}ลอง return “5” ในฟังก์ชันที่ประกาศ : number ดู — return type ก็ถูกเช็คเหมือนกัน
live
1const multiply = (x: number, y: number): number => {2 return x * y;3};4 5const calVat = (price: number): number => price * 1.07;6 7console.log(calVat(100)); // 107.00000000000001บรรทัดเดียวไม่ต้องมีปีกกาและไม่ต้องเขียน return · เลขท้าย ๆ นั่นคือเรื่องทศนิยมของคอมพิวเตอร์
callback ⭐ gateway to array helpers
sayHello("Ada", ─────────────────► ฟังก์ชันข้างนอกสร้าง message
(m) => log(m) ───────► แล้วเรียกโค้ดที่เราส่งเข้าไปให้ทำงานชนิดของมันเขียนว่า (message: string) => void อ่านว่า “ฟังก์ชันที่รับ string แล้วไม่คืนค่า”
live
1export function sayHello(2 name: string,3 callback: (message: string) => void4): void {5 const message = "Hi " + name;6 callback(message);7}8 9sayHello("Ada", (message) => console.log(message));ถ้าเข้าใจสไลด์นี้ .map() จะไม่ใช่ของใหม่เลย — มันคือรูปแบบเดียวกันเป๊ะ
checkpoint
Section 03
ให้โปรแกรมเลือกทางเดินเองจากคำถามที่ตอบ true / false
type this
1let a = 5;2let b = 8;3 4console.log(a < b); // true5console.log(a > b); // false6console.log(a === b); // false — เท่ากันเป๊ะไหม7console.log(a !== b); // true — ไม่เท่ากันใช่ไหมเห็น == สองตัวในบทเรียนอื่น ให้ใช้ === สามตัวไว้ก่อน มันเดาผลได้ตรงกว่า
remember these two
=
ใส่ค่าลงกล่อง
x = 5
===
ถามว่าเท่ากันไหม
x === 5
how it runs
score >= 90 ? ──yes──► "Excellent" │no score >= 80 ? ──yes──► "Good" │no └────────────────► "Keep practising"
ลำดับจึงสำคัญ — ถ้าสลับ >= 80 ขึ้นก่อน >= 90 คนได้ 95 จะได้ “Good”
syntax
1if (condition) {2 // ทำอันนี้3} else if (anotherCondition) {4 // ไม่งั้นทำอันนี้5} else {6 // นอกนั้นทำอันนี้7}เงื่อนไขในวงเล็บต้องตอบได้ว่าจริงหรือเท็จเท่านั้น
live
1const score: number = 85;2 3if (score >= 90) {4 console.log("Excellent");5} else if (score >= 80) {6 console.log("Good");7} else {8 console.log("Keep practising");9}85 ไม่ผ่านเงื่อนไขแรก แต่ผ่านอันที่สอง — บล็อกที่สามจึงไม่ถูกแตะเลย
live
score = 95
จะได้อะไร
score = 70
แล้วอันนี้ล่ะ
watch out
ลืมวงเล็บปีกกา
คอมพิวเตอร์ดูปีกกา ไม่ได้ดูการย่อหน้า — ย่อหน้าไว้ให้คนอ่าน
ใช้ = แทน ===
if (temp = 30) ไม่ได้ถาม แต่กำลังยัดค่าใส่กล่อง
ใส่เซมิโคลอนหลังวงเล็บ
if (x > 3); ทำให้บล็อกทำงานทุกครั้งแบบเงียบ ๆ
narrowing
input: number | string │ ├── if (typeof input === "number") ──► ในบล็อกนี้เป็น number แน่นอน │ └── else ──────────────────────────► ในบล็อกนี้เป็น string แน่นอน
เครื่องหมาย | อ่านว่า “หรือ”
live
1let input: number | string = 15;2 3if (typeof input === "number") {4 console.log("เป็นตัวเลข: " + input);5} else {6 console.log("เป็นข้อความ: " + input);7}เปลี่ยน input เป็น "TypeScript" แล้วรันใหม่ — คนละบล็อกทำงาน
checkpoint
Section 04
ถ้าจับได้ว่ากำลังก๊อปบรรทัดเดิมห้าครั้ง แปลว่ามีลูปซ่อนอยู่
anatomy
for ( let i = 1 ; i <= 5 ; i++ )
│ │ │
│ │ └─ หลังจบแต่ละรอบ (บวกหนึ่ง)
│ └─ ทำต่อเมื่อยังจริง
└─ เริ่มที่นี่ ทำครั้งเดียวไล่ด้วยปากทีละรอบ: i เป็น 1, 1 ≤ 5 จริง, พิมพ์ 1, i เป็น 2…
live
1for (let i = 1; i <= 5; i++) {2 console.log(i);3}4// 1 2 3 4 5พอ i เป็น 6 เงื่อนไขเป็นเท็จ ลูปจึงหยุด
accumulate pattern ⭐
ประกาศก่อนลูป → let total = 0; บวกข้างในลูป → total += i; print หลังลูป → console.log(total);
ถ้าประกาศ total ไว้ข้างในลูป มันจะถูกรีเซ็ตทุกรอบ แล้วได้ค่าผิด
live
1let total = 0;2 3for (let i = 1; i <= 5; i++) {4 total += i;5}6 7console.log(total); // 15total += i คือรูปย่อของ total = total + i
live
1for (let i = 5; i >= 1; i--) {2 console.log(i);3}4 5for (let i = 1; i <= 10; i++) {6 if (i % 2 === 0) {7 console.log(i);8 }9}% คือเศษจากการหาร — i % 2 === 0 แปลว่าเลขคู่
off-by-one
i <= 5
i < 5
exercise
น้อยกว่า 4 = small / 4 ถึง 7 = medium / ตั้งแต่ 8 ขึ้นไป = large ลองเองก่อนห้านาที แล้วเราค่อยเขียนด้วยกัน
solution
1for (let n = 1; n <= 10; n++) {2 if (n < 4) {3 console.log(n + " is small");4 } else if (n <= 7) {5 console.log(n + " is medium");6 } else {7 console.log(n + " is large");8 }9}ลูปไล่ทีละตัว เปรียบเทียบเพื่อตัดสิน if เลือกป้าย แล้ว + ต่อข้อความ
checkpoint
Section 05
ลิสต์ที่เข้าถึงด้วยตำแหน่ง เริ่มนับที่ศูนย์
syntax
1const fruits: string[] = ["apple", "banana", "cherry"];2const scores: number[] = [90, 85, 100, 72];3 4// string[] = ลิสต์ของข้อความ5// number[] = ลิสต์ของตัวเลข6// boolean[] = ลิสต์ของ true/falsehover ที่ scores ดู — editor บอกเองว่าเป็น number[] แม้ไม่ได้พิมพ์ annotation
break it on purpose
1scores.push("oops");2// Argument of type 'string' is not assignable3// to parameter of type 'number'.npm run check จะแดงแบบตั้งใจ อ่าน error แล้วลบทิ้ง
index
index: 0 1 2
+--------+--------+--------+
fruits: | apple | banana | cherry | length = 3
+--------+--------+--------+
fruits[3] -> undefined (ไม่พัง แค่ไม่มีอะไรตรงนั้น)ตำแหน่งสุดท้ายคือ length - 1 เสมอ
live
1fruits[0]; // "apple"2fruits[5]; // undefined3fruits.length; // 34fruits[fruits.length - 1]; // "cherry" — ตัวสุดท้ายโดยไม่ hardcodeทายก่อนว่า fruits[5] จะได้อะไร แล้วค่อยกดรัน
the difference is the parentheses
fact — ไม่มีวงเล็บ
action — มีวงเล็บ
live
1fruits.push("durian"); // ต่อท้าย → คืนความยาวใหม่2fruits[1] = "blueberry"; // เหลี่ยมอยู่ซ้ายของ = คือเขียนทับ3const removed = fruits.pop(); // ตัดตัวท้าย → คืนตัวที่ถูกตัดเหลี่ยมอยู่ขวาของ = คืออ่าน อยู่ซ้ายของ = คือเขียนทับ
⚠️ the most confusing point
const colors ──► [ "red", "green" ]
│ ▲
│ └─ ข้างในเปลี่ยนได้ (push / pop / แทนที่)
└─ ลูกศรนี้เปลี่ยนไม่ได้ (ชี้ลิสต์ก้อนอื่นไม่ได้)colors.push('blue') ได้ · colors = [...] ไม่ได้
break it on purpose
1const colors = ["red", "green"];2 3colors.push("blue"); // ได้4colors = ["totally", "new"]; // Cannot assign to 'colors'5 // because it is a constant.แก้ของข้างในกล่องได้ แต่เปลี่ยนกล่องไม่ได้
live
1for (let i = 0; i < scores.length; i++) {2 console.log(scores[i]);3}4 5let total = 0;6for (let i = 0; i < scores.length; i++) {7 total += scores[i];8}9console.log(total / scores.length); // 86.75ใช้ < ไม่ใช่ <= — ถ้าใช้ <= จะเกินไปหนึ่งตำแหน่ง แล้วได้ undefined โผล่ท้าย
live
1for (const fruit of fruits) {2 console.log(fruit);3}ต้องรู้ตำแหน่ง ใช้ for แบบมี i / ต้องการแค่ตัวของ ใช้ for...of
checkpoint
Section 06
หนึ่งสิ่งที่มีหลายส่วน เข้าถึงด้วยชื่อ property
two different tools
[ ] array
{ } object
live
1const person = { name: "Ada", age: 30, isStudent: true };2 3console.log(person.name); // อ่าน4person.age = 31; // เขียน — จุดเดิม แต่ย้ายไปซ้ายของ =ข้างใน object ใช้ : ไม่ใช่ = · ป้ายชื่อไม่ต้องมีเครื่องหมายคำพูด
⭐ the key beat of this topic
JavaScript เปล่า ๆ : person.naem → undefined → ไล่บั๊กเป็นชั่วโมง TypeScript : person.naem → เส้นแดงทันทีตอนพิมพ์
TypeScript รู้จัก object ของคุณ เพราะคุณสร้างมันขึ้นมาเอง — ไม่ต้องประกาศ type แยกเลย
break it on purpose
1console.log(person.naem);2// Property 'naem' does not exist on type '{ name: string; ... }'.3// Did you mean 'name'?4 5person.age = "thirty";6// Type 'string' is not assignable to type 'number'.พิมพ์ person. แล้วหยุด — editor ขึ้นรายชื่อ property ให้เลือกเอง
live
1const book = { title: "Dune", pages: 412, available: true };2 3const dog = {4 dogName: "Rex",5 weight: 32,6 color: "brown",7 breed: "Labrador",8 burglarBiter: true,9};10console.log(dog); // print ทั้งก้อนได้เลยลองสร้าง object ของตัวเองหนึ่งก้อน ธีมอะไรก็ได้
checkpoint
Section 07
object ซ้อน object และลิสต์ที่อยู่ใน object
nested object
company
├── companyName "Jitta"
├── yearOfEstablishment 2012
└── address ─────────────┐
├── street
├── city ← company.address.city
└── statecompany → .address (ได้ object) → .city (ได้ข้อความ)
live
1const company = {2 companyName: "Jitta",3 address: {4 street: "Sukhumvit",5 city: "Bangkok",6 },7};8 9console.log(company.address.city); // "Bangkok"10company.address.city = "Chiang Mai";พิมพ์ company.address. แล้วหยุด — editor รู้จัก property ชั้นในให้ด้วย
live
1const companyActivities = {2 companyName: "Jitta",3 activities: ["research", "investing", "education"],4};5 6companyActivities.activities.length; // 37companyActivities.activities[0]; // "research"จุดเข้าไปหาลิสต์ก่อน แล้วค่อยใช้เหลี่ยมหยิบตัวในลิสต์
checkpoint
Section 08
รูปทรงของข้อมูลจริงเกือบทุกอย่างที่มาจาก API
array of object ⭐
students [ {name:"Ada", grade:90}, {name:"Grace", grade:85}, ... ]
^ ^
| +--- .name ชื่อ (ชั้นใน)
+------- [0] ตำแหน่ง (ชั้นนอก)
students[0].name -> หยิบตัวที่ 0 ออกมา แล้วอ่านป้ายชื่อ namestudents.name ไม่มี — ลิสต์ไม่มีชื่อ มีแต่ของแต่ละตัวในลิสต์
live
1const students = [2 { name: "Ada", grade: 90 },3 { name: "Grace", grade: 85 },4 { name: "Alan", grade: 78 },5];6 7students.length; // 38students[0].name; // "Ada"9students[1].grade; // 85ตำแหน่งก่อน ชื่อทีหลัง — อ่านจากซ้ายไปขวาเสมอ
live
1for (const student of students) {2 console.log(student.name + ": " + student.grade);3}4 5for (const student of students) {6 if (student.grade >= 85) {7 console.log(student.name + " passed with honors");8 }9}Alan ได้ 78 บรรทัดของเขาจึงไม่ถูก print
the dataset for the rest of the deck
1const products = [2 { name: "Notebook", price: 25 },3 { name: "Headphones", price: 80 },4 { name: "Pen", price: 5 },5 { name: "Backpack", price: 60 },6];ทุกตัวอย่างของ array helper ในหัวข้อ 09 จะกลับมาใช้ลิสต์ก้อนนี้
live
1let total = 0;2 3for (const product of products) {4 console.log(product.name + ": " + product.price);5 total += product.price;6 if (product.price > 50) {7 console.log(" ↑ ของแพง");8 }9}10console.log(total); // 170print แต่ละแถว / สะสมยอดรวม / กรองด้วย if — แพตเทิร์นบวกสะสมครั้งที่สาม
max pattern ⭐
แชมป์ = ตัวแรก ├─ เจอตัวที่มากกว่า? ──yes──► เปลี่ยนแชมป์เป็นตัวนี้ └─ ไม่มากกว่า ─────────────► แชมป์เดิมอยู่ต่อ จบลูป → ตัวที่ถืออยู่คือคำตอบ
ใช้ได้กับ มากสุด / น้อยสุด / ดีสุด — แค่พลิกเครื่องหมาย
live
1let priciestName = products[0].name;2let priciestPrice = products[0].price;3 4for (const product of products) {5 if (product.price > priciestPrice) {6 priciestName = product.name;7 priciestPrice = product.price;8 }9}10console.log(priciestName); // "Headphones"ตัวแปรสองตัวเดินไปด้วยกัน ต้องเปลี่ยนพร้อมกันเสมอ
checkpoint
homework
print ทุก title
print เฉพาะเรื่องที่ rating > 8
หา rating เฉลี่ย
ใช้ของที่เรียนไปแล้วทั้งหมด ไม่ต้องใช้ของใหม่
Section 09
map / filter / find / reduce — เลิกเขียนลูปเอง
same job, two ways to write it
เดิม
ใหม่
decision map
map
[100, 200, 500] ──.map(p => p * 1.07)──► [107, 214, 535]
ยาว 3 ยาว 3map ไม่ทิ้งใคร แค่เปลี่ยนหน้าตาแต่ละตัว และคืนลิสต์ใหม่ ลิสต์เดิมไม่ถูกแตะ
live
1const prices: number[] = [100, 200, 500, 1000];2 3const pricesWithVat = prices.map((price: number): number => price * 1.07);4console.log(prices); // ไม่เปลี่ยน5console.log(pricesWithVat); // ลิสต์ใหม่6 7const names = products.map((product) => product.name);8// ["Notebook", "Headphones", "Pen", "Backpack"]แยกฟังก์ชันออกมาตั้งชื่อก่อน แล้วส่งชื่อเข้าไปก็ได้ — ย้อนกลับไปหัวข้อ callback
do not mix these up
map
filter
live
1const expensive = products.filter((product) => product.price > 50);2 3const query = "pen";4const results = products.filter((product) =>5 product.name.toLowerCase().includes(query.toLowerCase())6);includes ตอบว่ามีข้อความนั้นอยู่ข้างในไหม · toLowerCase ทั้งสองฝั่ง “pen” จึงเจอ “Pen”
⭐ the heart of the whole course
found = products.find(…)
│
├─ เจอ → object
└─ ไม่เจอ → undefined
└─► found.price จะพังตอนรัน → TypeScript เลยฟ้องก่อนTypeScript ไม่ได้จู้จี้ มันบังคับให้เราคิดเผื่อกรณีไม่เจอ — เคสที่โปรแกรมจริงพังบ่อยที่สุด
live
1const found = products.find((product) => product.name === "Pen");2 3console.log(found.price);4// 'found' is possibly 'undefined'.5 6if (found) {7 console.log(found.price); // ในบล็อกนี้ TypeScript รู้แล้วว่ามีของแน่8}narrowing แบบเดียวกับ typeof ในหัวข้อ if / else — ถามก่อน แล้วชนิดจะแคบลงเอง
live
1products.findIndex((product) => product.name === "Backpack"); // 32products.findIndex((product) => product.name === "Laptop"); // -13 4products.some((product) => product.price > 70); // true5products.every((product) => product.price > 1); // trueไม่เจอคือ -1 ไม่ใช่ undefined · เช็คผลต้องเขียน if (i !== -1) เพราะ index 0 ก็ falsy
reduce ⭐
let total = 0; ← ค่าตั้งต้น = อาร์กิวเมนต์ตัวที่สอง
for (const p of products) { ← การวน = reduce วนให้
total = total + p.price; ← ตัวฟังก์ชัน = (sum, p) => sum + p.price
}ไม่ใช่ของใหม่เลย — เปิดสไลด์รายงานสินค้าค้างไว้ข้าง ๆ แล้วเทียบดู
live
1const total = products.reduce((sum, product) => sum + product.price, 0);2 3console.log(total); // 170 — เท่ากับลูปในหัวข้อ 08 เป๊ะ แต่สั้นกว่าเลข 0 ท้ายสุดคือค่าตั้งต้น อย่าลืมใส่
⚠️ new vs mutate
สร้างของใหม่
แก้ลิสต์ตัวเดิม
live
1const sorted = [...prices].sort((a, b) => a - b); // น้อย → มาก2console.log(prices); // ไม่เปลี่ยน เพราะก๊อปก่อน3 4const byPriceDesc = [...products].sort((a, b) => b.price - a.price);5 6const index = products.findIndex((product) => product.name === "Pen");7if (index !== -1) {8 products.splice(index, 1); // splice(ตำแหน่ง, จำนวนที่จะลบ)9}ฟังก์ชันเปรียบเทียบ: ติดลบ = a มาก่อน / บวก = b มาก่อน
checkpoint
wrap up
three lines to remember
npm run check คือสิ่งเดียวกับที่ CI ของโปรเจกต์จริงรัน — ถ้ามันเขียว โค้ดของคุณก็ผ่านด่านเดียวกับงานจริง และ array helper ในหัวข้อสุดท้ายคือโค้ดที่เจอในงานจริงราว 80%
TypeScript