ผลต่างระหว่างรุ่นของ "Se63/typescript/started"
Jittat (คุย | มีส่วนร่วม) (สร้างหน้าด้วย "สิ่งที่ TypeScript เพิ่มให้กับ javascript คือระบบ type (type system) ที่ยืดหยุ่น...") |
Jittat (คุย | มีส่วนร่วม) (→Class) |
||
(ไม่แสดง 9 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 1: | แถว 1: | ||
+ | : ''หมายเหตุ ใน Typescript Playground จะมี tab size เป็น 4 เราจะคงไว้ในเอกสารนี้ แต่หลัง ๆ ในโค้ดของเราจะใช้ tab size เป็น 2'' | ||
+ | |||
สิ่งที่ TypeScript เพิ่มให้กับ javascript คือระบบ type (type system) ที่ยืดหยุ่น และช่วยในการตรวจสอบโปรแกรม | สิ่งที่ TypeScript เพิ่มให้กับ javascript คือระบบ type (type system) ที่ยืดหยุ่น และช่วยในการตรวจสอบโปรแกรม | ||
แถว 4: | แถว 6: | ||
<syntaxhighlight lang="typescript"> | <syntaxhighlight lang="typescript"> | ||
− | function | + | function insideCircle(circle, point) { |
− | + | let dx = (circle.x - point.x); | |
− | + | let dy = (circle.y - point.y); | |
− | + | return (circle.r * circle.r) >= (dx * dx + dy * dy); | |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | ทดลองนำไปใส่ใน typescript playground จะเห็นการเตือนว่าทั้ง circle และ point มี type เป็น <tt>any</tt> (นั่นคือเป็นอะไรก็ได้) | + | ทดลองนำไปใส่ใน [https://www.typescriptlang.org/play/ typescript playground] จะเห็นการเตือนว่าทั้ง circle และ point มี type เป็น <tt>any</tt> (นั่นคือเป็นอะไรก็ได้) |
เพิ่มบรรทัดต่อไปนี้ | เพิ่มบรรทัดต่อไปนี้ | ||
<syntaxhighlight lang="typescript"> | <syntaxhighlight lang="typescript"> | ||
− | console.log( | + | console.log(insideCircle({ r: 20 }, {})); |
</syntaxhighlight> | </syntaxhighlight> | ||
แล้วลองเรียกใช้งาน จะพบผลลัพธ์ที่ console ของ browser (ให้ลองหาวิธีแสดง console ดู อาจจะกด inspect element ก่อน แล้วค่อยเลือก console ก็ได้) | แล้วลองเรียกใช้งาน จะพบผลลัพธ์ที่ console ของ browser (ให้ลองหาวิธีแสดง console ดู อาจจะกด inspect element ก่อน แล้วค่อยเลือก console ก็ได้) | ||
+ | |||
+ | สังเกตว่าโปรแกรมสามารถทำงานได้และมีผลลัพธ์ แต่นี่เป็นสิ่งที่เราต้องการหรือเปล่า? จากตัวอย่างนี้ อาจจะพอเห็นได้ว่ามีการส่งข้อมูลผิดพลาด แต่ถ้าโค้ดส่วนนี้อยู่ในโปรแกรมใหญ่ ๆ เราจะไม่ทราบเลยว่าอาจจะมีความผิดพลาดมาจากการส่งค่าจากที่อื่น เช่น ใช้ตัวแปรผิดตัว (หรือพิมพ์ชื่อผิด) เป็นต้น | ||
+ | |||
+ | == Interface == | ||
+ | |||
+ | เราสามารถระบุว่า object ที่จะส่งให้ฟังก์ชัน หรือตัวแปรต่าง ๆ ต้องมี '''interface''' ตามที่ต้องการได้โดยประกาศ <tt>interface</tt> (อ่านเพิ่มได้ที่ [https://www.typescriptlang.org/docs/handbook/interfaces.html handbook]) ดังนี้ | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | interface Circle { | ||
+ | x: number; | ||
+ | y: number; | ||
+ | r: number; | ||
+ | } | ||
+ | |||
+ | interface Point { | ||
+ | x: number; | ||
+ | y: number; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | และเพิ่ม type ให้กับพารามิเตอร์ของ <tt>insideCircle</tt> ให้ทดลองเพิ่ม type ให้กับทีละพารามิเตอร์แล้วสังเกต error ที่ระบบระบุในบรรทัดที่เรียกใช้ <tt>insideCircle</tt> | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | function insideCircle(circle: Circle, point: Point) { | ||
+ | // ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === optional และ readonly === | ||
+ | เราสามารถระบุว่า property บางอันจะมีหรือไม่มีก็ได้ โดยตามด้วยเครื่องหมาย ? เช่น | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | interface Circle { | ||
+ | x: number; | ||
+ | y: number; | ||
+ | r: number; | ||
+ | color?: number; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | สามารถระบุว่าอ่านอย่างเดียวได้ด้วย keyword <tt>readonly</tt> | ||
+ | |||
+ | การตรวจสอบว่าข้อมูลที่ส่งตรงตาม interface มีรายละเอียดมากกว่านี้มาก สามารถอ่านเพิ่มที่ [https://www.typescriptlang.org/docs/handbook/interfaces.html handbook] | ||
+ | |||
+ | == Class == | ||
+ | |||
+ | สังเกตว่าฟังก์ชัน <tt>insideCircle</tt> ทำงานกับวงกลมโดยเฉพาะ เราสามารถนำฟังก์ชันนี้ไปใส่รวมกับ '''สิ่งที่ควรจะเป็น <tt>Circle</tt>''' ในกรณีนี้ interface จะไม่ใช่สิ่งที่เราต้องการเท่าใดนัก เพราะว่า interface ใช้ระบุ "หน้าตา" ว่า type ควรทำอะไรได้ ไม่ใช่ระบุการทำงานจริง ๆ เราจะเปลี่ยน interface Circle เป็น class Circle ดังนี้ | ||
+ | |||
+ | ในการทดลองเขียน ให้ป้อนโดยละ constructor ไว้ก่อน แล้วดู error ที่ระบบแสดงออกมา (จะอยู่ที่บรรทัด x,y,r) | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | interface Point { | ||
+ | x: number; | ||
+ | y: number; | ||
+ | } | ||
+ | |||
+ | class Circle { | ||
+ | x: number; | ||
+ | y: number; | ||
+ | r: number; | ||
+ | constructor(xx: number, yy: number, rr: number) { | ||
+ | this.x = xx; | ||
+ | this.y = yy; | ||
+ | this.r = rr; | ||
+ | } | ||
+ | |||
+ | inside(point: Point): boolean { | ||
+ | let dx = (this.x - point.x); | ||
+ | let dy = (this.y - point.y); | ||
+ | return (this.r * this.r) >= (dx * dx + dy * dy); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | let c = new Circle(10, 20, 30); | ||
+ | |||
+ | console.log('Circle is at', c.x, c.y); | ||
+ | console.log(c.inside({ x: 10, y: 40 })); | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ในคลาส เราจะประกาศเมทอด <tt>inside</tt> (ซึ่งถูกเรียกในตอนท้าย) และเมทอด constructor ที่ทำหน้าที่หลักคือกำหนดค่าให้กับ property ของวัตถุเมื่อมีการสร้างขึ้น เราจะสร้าง object ด้วย <tt>new</tt> พร้อมกับส่งพารามิเตอร์กำหนดค่าเริ่มต้น | ||
+ | |||
+ | สังเกตว่า property x, y, r สามารถอ่านค่าได้จาก object c | ||
+ | |||
+ | การใช้ constructor ในการกำหนดค่าเริ่มต้นทำบ่อยจนกระทั่ง typescript ให้เราเขียนแบบนี้ได้ | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | class Circle { | ||
+ | constructor(public x: number, public y: number, public r: number) {} | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | keyword <tt>public</tt> ทำให้เรานิยาม property และให้นำค่าเริ่มต้นมาจากพารามิเตอร์ได้เลย | ||
+ | |||
+ | === private และ protected === | ||
+ | |||
+ | เราสามารถปิดการเข้าถึง property ได้โดยระบุ keyword <tt>private</tt> แทน <tt>public</tt> ที่ property ถ้าแก้บรรทัด constructor เป็น | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | class Circle { | ||
+ | constructor(private x: number, private y: number, private r: number) {} | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | บรรทัดที่พิมพ์ <tt>c.x</tt> จะขึ้น error | ||
+ | |||
+ | == Inheritance == | ||
+ | |||
+ | เราสามารถขยายความสามารถของคลาสโดยการ extend เช่น | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | class ExpandableCircle extends Circle { | ||
+ | expand(s: number) { | ||
+ | this.r += s; | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | object ที่เป็น instance ของ ExpandableCircle จะเป็น Circle อยู่ และสามารถขยายได้ โดยสั่งเช่น obj.expand(10) | ||
+ | |||
+ | เราจะได้เห็นการใช้งานในตัวอย่าง zombie ที่จะเขียนถัดไป | ||
+ | |||
+ | == เรื่องอื่น ๆ == | ||
+ | |||
+ | === Type alias === | ||
+ | แทนที่จะสร้าง interface เราสามารถกำหนดชื่อให้กับ type ได้โดยตรง เช่น | ||
+ | |||
+ | <syntaxhighlight lang="typescript"> | ||
+ | type Name = string; | ||
+ | type Person = { | ||
+ | firstName: string; | ||
+ | lastName: string; | ||
+ | }; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ลักษณะการใช้งานจะคล้ายกับ interface แต่มีความแตกต่างอยู่ เมื่อทำงานกับ react เราจะใช้ type alias เวลาระบุ type ของ props |
รุ่นแก้ไขปัจจุบันเมื่อ 02:57, 13 กรกฎาคม 2563
- หมายเหตุ ใน Typescript Playground จะมี tab size เป็น 4 เราจะคงไว้ในเอกสารนี้ แต่หลัง ๆ ในโค้ดของเราจะใช้ tab size เป็น 2
สิ่งที่ TypeScript เพิ่มให้กับ javascript คือระบบ type (type system) ที่ยืดหยุ่น และช่วยในการตรวจสอบโปรแกรม
ลองดูตัวอย่างฟังก์ชันที่ตรวจสอบว่าจุดอยู่ในวงกลมหรือไม่
function insideCircle(circle, point) {
let dx = (circle.x - point.x);
let dy = (circle.y - point.y);
return (circle.r * circle.r) >= (dx * dx + dy * dy);
}
ทดลองนำไปใส่ใน typescript playground จะเห็นการเตือนว่าทั้ง circle และ point มี type เป็น any (นั่นคือเป็นอะไรก็ได้)
เพิ่มบรรทัดต่อไปนี้
console.log(insideCircle({ r: 20 }, {}));
แล้วลองเรียกใช้งาน จะพบผลลัพธ์ที่ console ของ browser (ให้ลองหาวิธีแสดง console ดู อาจจะกด inspect element ก่อน แล้วค่อยเลือก console ก็ได้)
สังเกตว่าโปรแกรมสามารถทำงานได้และมีผลลัพธ์ แต่นี่เป็นสิ่งที่เราต้องการหรือเปล่า? จากตัวอย่างนี้ อาจจะพอเห็นได้ว่ามีการส่งข้อมูลผิดพลาด แต่ถ้าโค้ดส่วนนี้อยู่ในโปรแกรมใหญ่ ๆ เราจะไม่ทราบเลยว่าอาจจะมีความผิดพลาดมาจากการส่งค่าจากที่อื่น เช่น ใช้ตัวแปรผิดตัว (หรือพิมพ์ชื่อผิด) เป็นต้น
เนื้อหา
Interface
เราสามารถระบุว่า object ที่จะส่งให้ฟังก์ชัน หรือตัวแปรต่าง ๆ ต้องมี interface ตามที่ต้องการได้โดยประกาศ interface (อ่านเพิ่มได้ที่ handbook) ดังนี้
interface Circle {
x: number;
y: number;
r: number;
}
interface Point {
x: number;
y: number;
}
และเพิ่ม type ให้กับพารามิเตอร์ของ insideCircle ให้ทดลองเพิ่ม type ให้กับทีละพารามิเตอร์แล้วสังเกต error ที่ระบบระบุในบรรทัดที่เรียกใช้ insideCircle
function insideCircle(circle: Circle, point: Point) {
// ...
}
optional และ readonly
เราสามารถระบุว่า property บางอันจะมีหรือไม่มีก็ได้ โดยตามด้วยเครื่องหมาย ? เช่น
interface Circle {
x: number;
y: number;
r: number;
color?: number;
}
สามารถระบุว่าอ่านอย่างเดียวได้ด้วย keyword readonly
การตรวจสอบว่าข้อมูลที่ส่งตรงตาม interface มีรายละเอียดมากกว่านี้มาก สามารถอ่านเพิ่มที่ handbook
Class
สังเกตว่าฟังก์ชัน insideCircle ทำงานกับวงกลมโดยเฉพาะ เราสามารถนำฟังก์ชันนี้ไปใส่รวมกับ สิ่งที่ควรจะเป็น Circle ในกรณีนี้ interface จะไม่ใช่สิ่งที่เราต้องการเท่าใดนัก เพราะว่า interface ใช้ระบุ "หน้าตา" ว่า type ควรทำอะไรได้ ไม่ใช่ระบุการทำงานจริง ๆ เราจะเปลี่ยน interface Circle เป็น class Circle ดังนี้
ในการทดลองเขียน ให้ป้อนโดยละ constructor ไว้ก่อน แล้วดู error ที่ระบบแสดงออกมา (จะอยู่ที่บรรทัด x,y,r)
interface Point {
x: number;
y: number;
}
class Circle {
x: number;
y: number;
r: number;
constructor(xx: number, yy: number, rr: number) {
this.x = xx;
this.y = yy;
this.r = rr;
}
inside(point: Point): boolean {
let dx = (this.x - point.x);
let dy = (this.y - point.y);
return (this.r * this.r) >= (dx * dx + dy * dy);
}
}
let c = new Circle(10, 20, 30);
console.log('Circle is at', c.x, c.y);
console.log(c.inside({ x: 10, y: 40 }));
ในคลาส เราจะประกาศเมทอด inside (ซึ่งถูกเรียกในตอนท้าย) และเมทอด constructor ที่ทำหน้าที่หลักคือกำหนดค่าให้กับ property ของวัตถุเมื่อมีการสร้างขึ้น เราจะสร้าง object ด้วย new พร้อมกับส่งพารามิเตอร์กำหนดค่าเริ่มต้น
สังเกตว่า property x, y, r สามารถอ่านค่าได้จาก object c
การใช้ constructor ในการกำหนดค่าเริ่มต้นทำบ่อยจนกระทั่ง typescript ให้เราเขียนแบบนี้ได้
class Circle {
constructor(public x: number, public y: number, public r: number) {}
// ...
}
keyword public ทำให้เรานิยาม property และให้นำค่าเริ่มต้นมาจากพารามิเตอร์ได้เลย
private และ protected
เราสามารถปิดการเข้าถึง property ได้โดยระบุ keyword private แทน public ที่ property ถ้าแก้บรรทัด constructor เป็น
class Circle {
constructor(private x: number, private y: number, private r: number) {}
// ...
}
บรรทัดที่พิมพ์ c.x จะขึ้น error
Inheritance
เราสามารถขยายความสามารถของคลาสโดยการ extend เช่น
class ExpandableCircle extends Circle {
expand(s: number) {
this.r += s;
}
}
object ที่เป็น instance ของ ExpandableCircle จะเป็น Circle อยู่ และสามารถขยายได้ โดยสั่งเช่น obj.expand(10)
เราจะได้เห็นการใช้งานในตัวอย่าง zombie ที่จะเขียนถัดไป
เรื่องอื่น ๆ
Type alias
แทนที่จะสร้าง interface เราสามารถกำหนดชื่อให้กับ type ได้โดยตรง เช่น
type Name = string;
type Person = {
firstName: string;
lastName: string;
};
ลักษณะการใช้งานจะคล้ายกับ interface แต่มีความแตกต่างอยู่ เมื่อทำงานกับ react เราจะใช้ type alias เวลาระบุ type ของ props