- TypeScript Guide
Type เบื้องต้น มี 3 type ได้แก่
boolean
: เป็น true หรือ falsenumber
: จำนวนทั้งหมด เป็นได้ทั้ง int หรือ floatstring
: text ทุกแบบ
TypeScript เวอร์ชั่นหลังๆจะมีเพิ่มอีก 2 type คือ
bigint
: คล้ายnumber
แต่ใหญ่กว่าsymbol
: ใช้ประกาศค่า global
มี 2 แบบ
คือการประกาศ type มาเลย
let firstName: string = "Dylan";
คือการไม่ประกาศ type อะไรเลย แล้วให้ TypeScript เดาเอาเอง (** เราเรียกการเดาของ ts ว่า infer)
let firstName = "Dylan";
console.log(typeof firstName); // string
จะ error ถ้า define ตัวแปรไม่ตรงกับ type
let firstName: string = "Dylan";
firstName = 33; // attempts to re-assign the value to a different type
console.log(firstName); // Type 'number' is not assignable to type 'string'
หรือ ต่อให้ implicit ก็ไม่รอด
let firstName = "Dylan"; // inferred to type string
firstName = 33; // attempts to re-assign the value to a different type
console.log(firstName); // Type 'number' is not assignable to type 'string'
บางครั้ง ts ก็ infer ไม่ได้ว่าเป็น type อะไร อย่างในตัวอย่าง มันจะถือว่าเป็น any
type แทน
// Implicit any as JSON.parse doesn't know what type of data it returns so it can be "any" thing...
const json = JSON.parse("55");
// Most expect json to be an object, but it can be a string or a number like this example
console.log(typeof json); // number
จะไม่มีการ check type และสามารถประกาศค่าเป็น type ไหนก็ได้
let u = true;
u = "string";
console.log(Math.round(u)); // Error: Argument of type 'boolean' is not assignable to parameter of type 'number'
ตัวแปร u ไม่มีการประกาศ any
ทำให้โดน infer ว่าเป็น boolean
let v: any = true;
v = "string";
console.log(Math.round(v)); // no error as it can be "any" type
ส่วน v มีการประกาศ any
ทำให้สามารถเปลี่ยนค่าเป็น type อะไรก็ได้
คล้ายๆกับ any
แต่ปลอดภัยกว่า
let w: unknown = 1;
w = "string"; // no error
w = {
runANonExistentMethod: () => {
console.log("I think therefore I am");
}
} as { runANonExistentMethod: () => void }
// w.runANonExistentMethod(); // Error: Object is of type 'unknown'.
if(typeof w === 'object' && w !== null) {
(w as { runANonExistentMethod: Function }).runANonExistentMethod();
} // I think therefore I am
จากตัวอย่าง เราสามารถประกาศ type unknown
ก่อน ถ้าไม่ทราบว่าเป็น type อะไร แล้วค่อยประกาศทีหลัง โดยใช้ as
keyword
ประกาศค่าอะไรก็ error หมด (ไม่ค่อยมีใช้)
let x: never = true; // Error: Type 'boolean' is not assignable to type 'never'.
x: never = 1; // Error: Type 'number' is not assignable to type 'never'.
x: never = '1'; // Error: Type 'string' is not assignable to type 'never'.
let y: undefined = undefined;
let z: null = null;
console.log(typeof y); // undefined
console.log(typeof z); // null
y = 1; // Error: Type '1' is not assignable to type 'undefined'
รูปแบบทั่วไป ประกาศ type อะไร --> value ทั้งหมดต้องเป็น type นั้น
const names: string[] = [];
names.push("Dylan"); // no error
// names.push(3); // Error: Argument of type 'number' is not assignable to parameter of type 'string'.
ไม่สามารถเปลี่ยนค่าใน array ได้
const names: readonly string[] = ["Dylan"];
names.push("Jack"); // Error: Property 'push' does not exist on type 'readonly string[]'
ts สามารถ infer type ของ array ได้ เหมือนตัวแปร
const numbers = [1, 2, 3]; // inferred to type number[]
numbers.push(4);
numbers.push("2"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
let head: number = numbers[0]; // no error
คือ array ที่ประกาศ type ของ values ได้มากกว่า 1 type โดยเรียงตาม index
// define our tuple
let ourTuple: [number, boolean, string];
// initialize correctly
ourTuple = [5, false, 'Coding God was here']; // correct
ourTuple = [false, 'Coding God was mistaken', 5]; // error
บางที เราอาจจะเพิ่ม value เกินกว่า type ทำให้ value นั้น ไม่มี type safety
// define our tuple
let ourTuple: [number, boolean, string];
// initialize correctly
ourTuple = [5, false, 'Coding God was here'];
// We have no type safety in our tuple for indexes 3+
ourTuple.push('Something new and wrong');
console.log(ourTuple); // [5, false, 'Coding God was here', 'Something new and wrong']
ถ้าไม่ให้เพิ่ม สามารถใช้ readonly
เหมือน array ได้
// define our readonly tuple
const ourReadonlyTuple: readonly [number, boolean, string] = [5, true, 'The Real Coding God'];
// throws error as it is readonly.
ourReadonlyTuple.push('Coding God took a day off');
สามารถเพิ่ม context (ชื่อหรือคำอธิบาย) ให้กับ values ใน tuple ได้
const graph: [x: number, y: number] = [55.2, 41.3];
console.log(graph); // [ 55.2, 41.3 ]
console.log(graph[0]); // 55.2
console.log(graph[x]); // Cannot find name 'x'
console.log(x); // Cannot find name 'x'
สามารถประกาศตัวแปรแบบ destructuring ได้
const graph: [number, number] = [55.2, 41.3];
const [x, y] = graph;
console.log(x); // 55.2
ประกาศตัวแปรแบบ object
const car: { type: string, model: string, year: number } = {
type: "Toyota",
model: "Corolla",
year: 2009
};
console.log(car); // { type: 'Toyota', model: 'Corolla', year: 2009 }
infer type ได้
const car = {
type: "Toyota",
};
car.type = "Ford"; // no error
car.type = 2; // Error: Type 'number' is not assignable to type 'string'
การประกาศ type ของ property ข้างใน จำเป็นต้อง define ทุกอัน ไม่งั้น error
const car: { type: string, mileage: number } = {
type: "Toyota",
};
car.mileage = 2000;
console.log(car);
// Error: Property 'mileage' is missing in type '{ type: string; }' but required in type '{ type: string; mileage: number; }'
ทางแก้ --> ใช้ (?
) ข้างหลัง key กลายเป็น optional property
const car: { type: string, mileage?: number } = {
type: "Toyota"
};
car.mileage = 2000;
console.log(car); // { type: 'Toyota', mileage: 2000 }
ใช้ประกาศ type โดยที่ไม่ต้องมี list ของ property
const nameAgeMap: { [index: string]: number } = {};
nameAgeMap.Jack = 25; // no error
nameAgeMap.Mark = "Fifty"; // Error: Type 'string' is not assignable to type 'number'.
Index Signatures สามารถใช้งานได้เหมือนกับ Utility Types
(Record<string, number>
)
เป็น special "class" ที่ property เป็นค่าคงที่ ซึ่งมี 2 แบบคือ
string
numeric
property จะเป็น number
property จะมีค่าเริ่มจาก 0 และเพิ่มทีละ 1 ตามลำดับ
enum CardinalDirections {
North,
East,
South,
West
}
console.log(CardinalDirections.North); // 0
console.log(CardinalDirections.East); // 1
console.log(CardinalDirections.South); // 2
console.log(CardinalDirections.West); // 3
คล้าย default แต่สามารถ set ค่าเริ่มต้นได้
enum CardinalDirections {
North = 1,
East,
South,
West
}
console.log(CardinalDirections.North); // 1
console.log(CardinalDirections.West); // 4
set ค่าให้ทั้งหมด
enum StatusCodes {
NotFound = 404,
Success = 200,
Accepted = 202,
BadRequest = 400
}
console.log(StatusCodes.NotFound); // 404
enum ที่เป็น string
enum CardinalDirections {
North = 'North',
East = "East",
South = "South",
West = "West"
};
console.log(CardinalDirections.North); // North
จริงๆแล้ว สามารถผสม string กับ numeric ใน enum เดียวกันได้ แต่ไม่แนะนำ
ใน ts เราสามารถประกาศ type แยกจากตัวแปรได้ แล้วค่อยเอามาใช้ทีหลัง มี 2 แบบคือ
ประกาศ type แบบให้ชื่อใหม่
type Point = {
x: number;
y: number;
};
const point: Point = {
x: 5,
y: 10
};
console.log(point); // { x: 5, y: 10 }
ประกาศ type แบบให้ชื่อใหม่ และสามารถ extend ได้ ใช้สำหรับ object เท่านั้น
interface Point {
x: number;
y: number;
}
const point: Point = {
x: 5,
y: 10
};
console.log(point); // { x: 5, y: 10 }
ตัวอย่างการ extend
interface Point {
x: number;
y: number;
}
interface Point3D extends Point {
z: number;
}
const point3D: Point3D = {
x: 5,
y: 10,
z: 20
};
console.log(point3D); // { x: 5, y: 10, z: 20 }
หรือใช้ extends
กับ type alias ได้เหมือนกัน
type Point = {
x: number;
y: number;
};
type Point3D = Point & { z: number };
const point3D: Point3D = {
x: 5,
y: 10,
z: 20
};
console.log(point3D); // { x: 5, y: 10, z: 20 }
ประกาศ type ที่เป็นไปได้หลาย type
let union: string | number;
union = "string";
union = 3;
union = true; // Error: Type 'boolean' is not assignable to type 'string | number'.
สามารถใช้กับ object ได้
let union: { name: string } | { age: number };
union = { name: "Dylan" };
union = { age: 25 };
union = { name: "Dylan", age: 25 }; // Error: Object literal may only specify known properties, and 'age' does not exist in type '{ name: string; } | { age: number; }'.
function printStatusCode(code: string | number) {
console.log(`My status code is ${code}.`)
}
printStatusCode(404);
printStatusCode('404');
printStatusCode(true); // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
เมื่อใช้ union type แล้ว ถ้าเราใช้ method หรือ property ที่ไม่ได้อยู่ในทั้ง 2 type ที่เป็นไปได้ ก็จะ error
let union: string | number;
union = "string";
union = 3;
console.log(union.length); // Error: Property 'length' does not exist on type 'string | number'.
ประกาศ type ของ function
ประกาศ return type ของ function
function add(a: number, b: number): number {
return a + b;
}
console.log(add(5, 10)); // 15
ถ้าไม่ประกาศ return type จะ infer ได้
function add(a: number, b: number) {
return a + b;
}
console.log(add(5, 10)); // 15
ถ้าไม่มีการ return ค่าอะไร ให้ประกาศ return type เป็น void
function log(message: string): void {
console.log(message);
}
log("Hello, world!"); // Hello, world!
ประกาศ type ของ parameter ได้
function log(message: string, userId?: string) {
console.log(message, userId);
}
log("Hello, world!"); // Hello, world! undefined
log("Hello, world!", "Dylan"); // Hello, world! Dylan
ถ้าไม่มีการประกาศ type ของ parameter จะเป็น any
จนกว่าจะใส่ type ให้
ประกาศ parameter ให้เป็น optional ได้
function log(message: string, userId?: string) {
console.log(message, userId);
}
log("Hello, world!"); // Hello, world! undefined
log("Hello, world!", "Dylan"); // Hello, world! Dylan
ประกาศ parameter ให้มีค่าเริ่มต้นได้
function log(message: string, userId = "Not signed in") {
console.log(message, userId);
}
log("Hello, world!"); // Hello, world! Not signed in
log("Hello, world!", "Dylan"); // Hello, world! Dylan
ประกาศ parameter ให้มีชื่อได้ โดยใช้ object
function divide({ dividend, divisor }: { dividend: number, divisor: number }) {
return dividend / divisor;
}
console.log(divide({dividend: 10, divisor: 2})); // 5
ประกาศ parameter ให้เป็น rest ได้
function add(a: number, b: number, ...rest: number[]) {
return a + b + rest.reduce((p, c) => p + c, 0);
}
console.log(add(10,10,10,10,10)); // 50
ประกาศ type ของ function แล้วเอามาใช้
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b;
console.log(add(5, 10)); // 15
การเปลี่ยน type ของตัวแปร โดยใช้ as
keyword หรือ <>
ในการ cast
บางครั้งในการทำงาน เราจำเป็นต้องเปลี่ยน type ของตัวแปร เช่นการรับค่าที่ type ไม่ถูกต้อง มาจาก Library
การใช้ as
keyword ในการ cast
let value: any = "Hello, world!";
let length: number = (value as string).length;
console.log(length); // 13
let x: unknown = 'hello';
console.log((x as string).length); // 5
console.log(typeof x); // string
เราไม่สามารถ cast ได้เสมอไป ถ้าไม่เป็น type ที่ถูกต้อง ก็จะ error
let x: unknown = 4;
console.log((x as string).length); // prints undefined since numbers don't have a length
console.log(typeof x); // number
การใช้ <>
ในการ cast
let value: any = "Hello, world!";
let length: number = (<string>value).length;
console.log(length); // 13
let x: unknown = 'hello';
console.log((<string>x).length); // 5
console.log(typeof x); // string
การ cast แบบนี้ไม่สามารถทำงาน บน TSX
ได้
ตัองการ override type ของตัวแปร ที่ error โดยเปลี่ยนเป็น unknown
ก่อน แล้วค่อย cast กลับเป็น type ที่ต้องการ
let x = 'hello';
console.log(((x as unknown) as number).length); // x is not actually a number so this will return undefined