diff --git a/.vitepress/config.mts b/.vitepress/config.mts index ba202a5..e0bd78e 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -15,6 +15,7 @@ export default defineConfig({ items: sidebarASPNetCore(), }, "/paths/devops": { base: "/paths/devops/", items: sidebarDevOps() }, + "/paths/typescript": { base: "/paths/typescript/", items: sidebarTypeScript() }, "/paths/java": { base: "/paths/java/", items: sidebarJava() }, "/paths/azure": { base: "/paths/azure/", items: sidebarAzure() }, "/paths/cloud-computing": { @@ -422,14 +423,14 @@ function sidebarPractices(): DefaultTheme.SidebarItem[] { { text: "Software Development Practices", items: [ - { - text: "Design Practices", - collapsed: true, - base: "/paths/practices/design/", - items: [ - { text: "Design Patterns", link: "design-patterns" } - ] - }, + { + text: "Design Practices", + collapsed: true, + base: "/paths/practices/design/", + items: [ + { text: "Design Patterns", link: "design-patterns" } + ] + }, ] } ] @@ -452,4 +453,55 @@ function sidebarSoftwareArchitecture(): DefaultTheme.SidebarItem[] { ] } ] -} \ No newline at end of file +} + +function sidebarTypeScript(): DefaultTheme.SidebarItem[] { + return [ + { + text: "TypeScript", + items: [ + { + text: "Types ที่ใช้งานเป็นประจำ", + collapsed: true, + base: "/paths/typescript/everyday-types/", + items: [ + { text: "Type คือข้อมูลชนิดหนึ่ง", link: "type-is-data" }, + { text: "การจัดการกับ Object", link: "object-manipulation" }, + { text: "Type Assertions", link: "runtime-type" }, + { text: "จัดการ Try-Catch Error", link: "catch-error" }, + { text: "จัดการ Runtime Type", link: "runtime-type" }, + ], + }, + { + text: "คู่มือ TypeScript", + collapsed: true, + base: "/paths/typescript/handbook/", + items: [ + { text: "Generics", link: "generics" }, + { text: "Utility Types", link: "utility-types" }, + { text: "conditional types (แบบมีเงื่อนไข)", link: "conditional-types" }, + { text: "Function Overload", link: "function-overload" }, + { text: "Enum", link: "enum" }, + { text: "Keyof", link: "keyof" }, + { text: "Narrowing Type", link: "narrowing-type" }, + { text: "Mapped Types", link: "mapped-types" }, + { text: "การปรับแต่ง Type", link: "type-manipulation" }, + ] + }, + { + text: "Cookbook", + collapsed: true, + base: "/paths/typescript/cookbook/", + items: [ + { text: "สร้าง enum ใช้เอง แบบที่ Key กับ Value เหมือกัน", link: "create-own-enum-string" }, + { text: "เลือกบาง Property จาก Record", link: "filter-some-property-of-record" }, + { text: "สร้าง Builder Pattern แบบ Type-Safe ", link: "type-safe-builder-pattern" }, + { text: "สร้าง union type from array", link: "create-union-type-from-array" }, + { text: "เพิ่ม Prefix ในแต่ละ Property Key ใน Object", link: "append-prefix-object-property-key" }, + { text: "Data Validation ด้วย Zod ใช้คู่กับ TypeScript ได้", link: "zod-typescript-integration" }, + ] + } + ], + }, + ]; +} diff --git a/index.md b/index.md index afa851a..203f29d 100644 --- a/index.md +++ b/index.md @@ -38,6 +38,9 @@ features: - title: Source Code Control details: แนวทางการดูแลและควบคุม Source Code ที่เราทำงานกันเป็นทีมได้อย่างมีประสิทธิภาพครับ link: /paths/sourcecodecontrol + - title: TypeScript + details: เนื้อหาที่เกี่ยวกับ TypeScript การใช้งาน Type ในชีวิตจริง และคู่มือการใช้งานและ Cookbook + link: /paths/typescript - title: ASP.Net Core details: ขั้นตอนแนะนำในการเตรียมความพร้อมเพื่อพัฒนา Website ด้วย ASP.NET Core ในปี 2024 link: /paths/aspnet-core diff --git a/paths/typescript/cookbook/append-prefix-object-property-key.md b/paths/typescript/cookbook/append-prefix-object-property-key.md new file mode 100644 index 0000000..04587c7 --- /dev/null +++ b/paths/typescript/cookbook/append-prefix-object-property-key.md @@ -0,0 +1,40 @@ +# เพิ่ม Prefix ในแต่ละ Property Key ใน Object + +Ref: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types + +Require: Version 4.1 + +```ts + +type AppendPrefix = { + [K in keyof T as `${U}${K & string}`]: T[K] +}; + +function appendPrefix, U extends string>(obj: T, prefix: U) { + const result: Partial> = {}; + for (const [key, value] of Object.entries(obj)) { + result[`${prefix}${key}`] = value; + } + return result; +} + +interface Person { + name: string; + age: number; + location: string; +} + +const person: Person = { + name: 'John', + age: 30, + location: 'Thailand' +} + +const oldPerson = appendPrefix(person, 'old_'); + +oldPerson.old_age; +oldPerson.old_location; +oldPerson.old_name; + +oldPerson.name; // ❌ Property 'name' does not exist on type 'Partial>' +``` \ No newline at end of file diff --git a/paths/typescript/cookbook/create-own-enum-string.md b/paths/typescript/cookbook/create-own-enum-string.md new file mode 100644 index 0000000..f3601c2 --- /dev/null +++ b/paths/typescript/cookbook/create-own-enum-string.md @@ -0,0 +1,35 @@ +# สร้าง enum ใช้เอง แบบที่ Key กับ Value เหมือกัน + +```ts +type TupleToObject = { + [K in T[number]]: K +} + +type Enum = T[keyof T]; + +const state = ['todo', 'in progress', 'completed'] as const; + +// What we want +// const stateA = { +// todo: 'todo', +// 'in progress': 'in progress', +// completed: 'completed' +// } + +function tupleToObject(data: T): TupleToObject{ + return data.reduce((result: any, v: keyof any) => { + result[v] = v; + return result; + }, {}); +} + +const GenState = tupleToObject(state); +console.log(GenState); + +const myState1: Enum = GenState.completed; +const myState2: Enum = 'completed'; +const myState3: Enum = 'hold'; // ❌ Error: Type '"hold"' is not assignable to type 'EnumGenState'. + +// or +const myState4: keyof typeof GenState = 'completed'; +``` \ No newline at end of file diff --git a/paths/typescript/cookbook/create-union-type-from-array.md b/paths/typescript/cookbook/create-union-type-from-array.md new file mode 100644 index 0000000..df6e9eb --- /dev/null +++ b/paths/typescript/cookbook/create-union-type-from-array.md @@ -0,0 +1,11 @@ +# สร้าง a Union type from an Array in TypeScript + +```typescript +// 👇️ const sizes: readonly ["small", "medium", "large"] +const sizes = ['small', 'medium', 'large'] as const; + +// 👇️ type SizesUnion = "small" | "medium" | "large" +type SizesUnion = typeof sizes[number]; +``` + +https://bobbyhadz.com/blog/typescript-create-union-type-from-array \ No newline at end of file diff --git a/paths/typescript/cookbook/filter-some-property-of-record.md b/paths/typescript/cookbook/filter-some-property-of-record.md new file mode 100644 index 0000000..a6b2e40 --- /dev/null +++ b/paths/typescript/cookbook/filter-some-property-of-record.md @@ -0,0 +1,36 @@ +# เลือกบาง Property จาก Record + +Ref: https://rossbulat.medium.com/typescript-typing-dynamic-objects-and-subsets-with-generics-44ba3988a91a + +## มาดูตัวอย่างเต็มๆ กันดีกว่า + +```typescript +/** + * Get only some keys and return the correct key + */ +export function pick(obj: T, keys: U[]) { + // สาเหตุที่เลือกใช้ Partial ของ Pick จะทำให้สร้าง Object ว่างๆ ได้นั่นเอง แล้วค่อยเลือก Property ทีหลัง + const result: Partial> = {}; + keys.forEach((key) => { + if (typeof obj[key] !== "undefined") { + result[key] = obj[key]; + } + }); + return result; +} + +interface IPost { + id: string; + title: string; +} + +const post: IPost = { + id: '1', + title: 'Go Lang' +} +const result = pick(post, ['id']); + + +console.log(result.id); +console.log(result.title); // ❌ Error: Property 'title' does not exist on type 'Partial>' +``` \ No newline at end of file diff --git a/paths/typescript/cookbook/type-safe-builder-pattern.md b/paths/typescript/cookbook/type-safe-builder-pattern.md new file mode 100644 index 0000000..a6ff726 --- /dev/null +++ b/paths/typescript/cookbook/type-safe-builder-pattern.md @@ -0,0 +1,84 @@ +# สร้าง Builder Pattern แบบ Type-Safe + +## Basic + +```typescript +class Inventory = {}> { + + items: Items = {} as Items; + + add>(value: NewItem) { + this.items = { + ...value as unknown as Items + } + return this as Inventory; + } +} + +const inventory = new Inventory() + .add({ + hello: 'world', + }).add({ + typescript: 5.1, + numbers: [23, '123'] + }); + +console.log(inventory.items.typescript) + + +type A = typeof inventory.items; + +// type A = { +// hello: string; +// } & { +// typescript: number; +// numbers: (string | number)[]; +// } +``` + +[playground](https://www.typescriptlang.org/play?#code/FAYwNghgzlAECSA7AbgU0QFwPYCcCeAPPBqgLZyoAeJiAJnAEqoi60FQY4CWiA5gDSwArogDWiLAHdEAPlgBeWAG8AvnKXBgsbbC4lyALgT64i1bGjGyUANyadF2mwByqScTKwqNerCYscNg5uPkERcSlZGQAKZAgwIVQjV3d9AEplLQcdDAALLigAOj1rBUzsithC6riE1As4cIlpBqtyLIqVDuycVAwhHERYPILWpDRMXEIPclgAMlgUmZk7By6u0CxEDl0UdGx8MsQ3BD3J-Gi07sKIJ2iNSthc1DAwLCMAcklcMFoP-m6KjSNzuD0qGDwAAdUFAQNxIRgjABWQoARgBj0QQlIACNUDgoEYANoAJgAzIIPqjyR8ALqAtJ2TbbLBgVCFN68aI8CYHPDFEyFCHQ2HwjBXTTC+oAQTKUqwADNdrypgLrHYAPQanQAPQA-EA) + +Ref: TypeScript Meetup Thailand July 2023 +https://www.facebook.com/phantipk/videos/289991566938840?idorvanity=1377224595918442 + +## More advanced + +```typescript +class ObjectBuilder = {}> { + + constructor(private readonly jsonObject: Items) { } + + add(key: K, value: V) { + const nextPart = { [key]: value } as Record; + return new ObjectBuilder({ ...this.jsonObject, ...nextPart }) as + ObjectBuilder<{ [Key in keyof (Items & Record)]: (Items & Record)[Key] }>; + } + + build(): Items { + return this.jsonObject; + } + + static create(){ + return new ObjectBuilder({}); + } + +} + +const json = ObjectBuilder.create() +.add('aString', 'some text') +.add('aNumber', 2) +.add('anArray', [1, 2, 3]) +.build(); + +type B = typeof json; + +// type B = { +// aString: string; +// aNumber: number; +// anArray: number[]; +// } +``` + +[playground](https://www.typescriptlang.org/play?#code/MYGwhgzhAEDyBGArApsALgIQK4EsQBNkAnAHgEk1kBbGZAD0oDt8YAlVAeyPxIjSJyMA5gBpoWRgGtGHAO6MAfNAC80AN4BfJWoBQO6AejAOjPkSzouACgAOAgG5hK0IsjD4TIAJ7REEEwgo6ABc0BTUEACU6tAaeobQ7jwA0tD0TCzQZoKi0ABqClaSyF6hyWKOIFjIoXnRugkJxqZo0IzpAApgRK2qatAA2sVeALqhldWxiWyc3CTl+QoA3PqNBq5oWESMbciycEiomLgExFb9AHRXaAAWOBAXfgGH6GJXF+0MXT2x0ZCrawSgSO2DwhFI-QGyRK0EE0GGHAAZtArOEaNAAGTQdjGOYLAqRMYotEwLE4rgpMQEqElEaxZYA2LxQzwE74KyRUIk9SMhIbLY7W73R7+RjA9ArBJxRl8Jw4YBGVxOZAchqA-nbXb7cXHMFnTSRSWGaXS5p8XyilQHIK605EC7AJWUDk6C5JKwAcjAAGV+DkPWIPf4qMhoJQGB7Iq73V6AHJYKjwYgB6AAJijbvw7K9jAAgkQiGAvCmBgBGMSpsQAZhGGdZYI5Kx0aC8NlDGCtLbbSItJibAHp+2HW+2rbpB40fX7hKFssIVhOEmB44niKFGAmk0QF0Ol3mC0X15viAMRjumXogA) + +Ref: https://medium.hexlabs.io/the-builder-pattern-with-typescript-using-advanced-types-e05a03ffc36e \ No newline at end of file diff --git a/paths/typescript/cookbook/zod-typescript-integration.md b/paths/typescript/cookbook/zod-typescript-integration.md new file mode 100644 index 0000000..2c83aa2 --- /dev/null +++ b/paths/typescript/cookbook/zod-typescript-integration.md @@ -0,0 +1,61 @@ +# Data Validation ด้วย Zod ใช้คู่กับ TypeScript ได้ + +หลายคนๆ อาจจะรู้จักกับ `Zod` ในการทำ Data Validation + +> TypeScript-first schema validation with static type inference + +## การเขียน Utility Function เพื่อทำ Data Validation + +โดยใน [Doc ของ zod](https://github.com/colinhacks/zod/blob/c617ad3edbaf42971485f00042751771c335f9aa/README.md#writing-generic-functions) พูดถึงการเขียน Function Generics สำหรับทำ Validation แต่ไม่ได้บอก Return Type ให้ เราสามารถใข้ `z.infer` ได้เพื่อระบุ Type ของ Schema ได้ + +```ts +import { z, ZodFormattedError } from 'zod'; + +export function parseEnv(schema: T, input: Record): z.infer { + const _functionEnv = schema.safeParse(input); + + if (_functionEnv.success === false) { + const message = 'Invalid input'; + throw new Error(zodPrettyError(message, _functionEnv.error.format())); + } + + return { ..._functionEnv.data }; +} + +export const zodPrettyError = (message: string, errors: ZodFormattedError, string>) => { + const ouput = Object.entries(errors) + .map(([name, value]) => { + if (value && '_errors' in value) return `${name}: ${value._errors.join(', ')}\n`; + }) + .filter(Boolean); + + return `❌ ${message}:\n ${ouput.join(', ')}`; +}; + +// +// ตัวอย่างการใช้งาน +// + +const appSchema = z.object({ + Webhook_URL: z.string().url(), + Is_Enable: z.boolean(), +}); + +type App = t.infer; +// จะได้ Type ของ TypeScript ออกมา +// type App = { +// Webhook_URL: string; +// Is_Enable: boolean; +// } + +const appData: App = { + Webhook_URL: 'https://typescrip-th.thadaw.com', + Is_Enable: true, +} + +// Validate it! +const parsingData = parseEnv(appSchema, data); + +parsingData.Webhook_URL // type => string +parsingData.Is_Enable // type => boolean +``` \ No newline at end of file diff --git a/paths/typescript/everyday-types/catch-error.md b/paths/typescript/everyday-types/catch-error.md new file mode 100644 index 0000000..e9bc987 --- /dev/null +++ b/paths/typescript/everyday-types/catch-error.md @@ -0,0 +1,59 @@ +--- +outline: deep +--- +# จัดการ Try-Catch Error + +Because JS can throw any type: + +```ts +throw 'What the!?' +throw 5 +throw {error: 'is this'} +throw null +throw new Promise(() => {}) +throw undefined +``` + +ดังนั้นเราสามารถใช้ Type Narrowing ในการจัดการ Type ได้อย่างถูกต้อง +```ts +try { + throw new Error('Oh no!'); +} catch (error) { + // error always unknown type + if (error instanceof Error) return error.message; + return String(error); +} +``` + +ตัวอย่างการใช้งานจริง ด้วย [zod](https://github.com/colinhacks/zod) +```ts +import { z } from 'zod'; + +const errorWithMessageSchema = z.object({ + message: z.string(), +}) + +type ErrorWithMessage = z.infer; + +function isErrorWithMessage(error: unknown): error is ErrorWithMessage { + return errorWithMessageSchema.safeParse(error).success; +} + +function toErrorWithMessage(maybeError: unknown): ErrorWithMessage { + if (isErrorWithMessage(maybeError)) return maybeError + + try { + return new Error(JSON.stringify(maybeError)) + } catch { + // fallback in case there's an error stringifying the maybeError + // like with circular references for example. + return new Error(String(maybeError)) + } +} + +function getErrorMessage(error: unknown) { + return toErrorWithMessage(error).message +} +``` + +Ref: https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript \ No newline at end of file diff --git a/paths/typescript/everyday-types/object-manipulation.md b/paths/typescript/everyday-types/object-manipulation.md new file mode 100644 index 0000000..f37efd1 --- /dev/null +++ b/paths/typescript/everyday-types/object-manipulation.md @@ -0,0 +1,84 @@ +--- +outline: deep +--- +# การจัดการกับ Object + +บางครั้งเราต้องการเพิ่มคุณสมบัติใหม่ให้กับวัตถุ แต่การกระทำนี้อาจส่งผลให้เกิดข้อผิดพลาด ดูตัวอย่างด้านล่าง: + +```ts +const nameRecord = {}; +nameRecord.john = 1; // ❌ Error: Property 'john' does not exist on type '{}'. +``` + +เราไม่สามารถเพิ่มคุณสมบัติ 'john' ให้กับ `nameRecord` ได้เนื่องจากวัตถุไม่มีคุณสมบัตินั้น + +ในการแก้ปัญหานี้ เราต้องใช้ [Mapped Types](../handbook/mapped-types) ในกรณีนี้คือ `Record` + +## Record + +`Record` ช่วยให้เราสามารถสร้างวัตถุที่สามารถเพิ่มคุณสมบัติใหม่ๆ ได้ ดูตัวอย่างด้านล่าง: + +```typescript +const nameRecord: Record = { + john: 1, + micky: 2, +}; + +nameRecord['Lilly'] = 5; // เราสามารถเพิ่มคุณสมบัติใหม่ให้กับวัตถุได้ เพราะ 'Lilly' เป็นชนิดข้อมูล string + +nameRecord[true] = 5; // ❌ ข้อผิดพลาด: ไม่สามารถใช้ชนิดข้อมูล 'true' เป็นคีย์ +``` + +ในตัวอย่างนี้เราสร้างตัวแปร `nameRecord` ที่มีชนิดข้อมูล `Record` ซึ่งอนุญาตให้เพิ่มคุณสมบัติที่มีชนิดข้อมูลเป็น `string` และค่าของคุณสมบัติเป็นชนิดข้อมูล `number` นอกจากนี้เรายังไม่สามารถใช้ค่าที่ไม่ใช่ `string` เป็นชนิดข้อมูลของคีย์ได้ + +### Record with Type Union (จำเป็นต้องมีคีย์ครบทั้งหมด) + +`Record` ต้องการให้กำหนดคีย์ของ Object ให้ครบถ้วน แต่ถ้าต้องการให้เป็นแบบ Partial (ไม่จำเป็นต้องมีคีย์ครบทั้งหมด) [ตอนนี้ TypeScript ยังไม่ได้ support](https://github.com/microsoft/TypeScript/issues/43918) + +```typescript +type Activities = 'Running' | 'Walking' | 'Swimming'; +const countActivities: Record = { + Running: 0, + Swimming: 1, + Walking: 5 +} + +// คีย์ของ Object `countActivities` ต้องตรงกับชนิด `Activities` +``` + +ในตัวอย่างนี้ เรากำหนดชนิด `Activities` +ที่ประกอบด้วยค่า 'Running', 'Walking', และ 'Swimming' แล้วเราสร้าง Object `countActivities` +ที่มีชนิดข้อมูล `Record` +สิ่งนี้หมายความว่าวัตถุ `countActivities` +ต้องมีคีย์ของคุณสมบัติตรงกับที่กำหนดในชนิด `Activities` +ดังนั้นเราจึงต้องกำหนดค่าให้กับคุณสมบัติ 'Running', 'Walking', และ 'Swimming' ทั้งสามคุณสมบัติเสมอ + +### Record with Type Union (ไม่จำเป็นต้องมีคีย์ครบทั้งหมด) + +ในกรณีที่ต้องการให้คุณสมบัติทั้งหมดเป็นตัวเลือก คุณสามารถใช้ `Partial` ร่วมกับ `Record` ดังตัวอย่าง: + +```ts +type Activities = 'Running' | 'Walking' | 'Swimming'; +const countActivities2: Partial> = { + Running: 0, +} + +// คีย์ของ Object `countActivities2` ต้องตรงกับชนิด `Activities` และไม่จำเป็นต้องใส่ครบ +``` + +หรือคุณสามารถกำหนด **`PartialRecord`** เพื่อใช้งานเอง: + +```typescript +// กำหนด PartialRecord เพื่อใช้งานเอง +type PartialRecord = Partial> + +type Activities = 'Running' | 'Walking' | 'Swimming'; +const countActivities: PartialRecord = { + Running: 0, +} + +countActivities.Walking = 5; // ใช้งานได้! +countActivities.Cycling = 4; // ❌ ข้อผิดพลาด: คุณสมบัติ 'Cycling' ไม่มีอยู่ในชนิด 'Partial>' +``` + +ในตัวอย่างนี้ เรากำหนดชนิด **`Activities`** ที่ประกอบด้วยค่า 'Running', 'Walking', และ 'Swimming' และสร้าง Object **`countActivities`** ที่มีชนิดข้อมูล **`PartialRecord`** ซึ่งอนุญาตให้คุณสมบัติทั้งหมดใน Object เป็นตัวเลือก ไม่จำเป็นต้องใส่ครบทุกคีย์ แต่คีย์ที่ใช้ยังคงต้องอยู่ในชนิด **`Activities`** diff --git a/paths/typescript/everyday-types/runtime-type.md b/paths/typescript/everyday-types/runtime-type.md new file mode 100644 index 0000000..73fc5e3 --- /dev/null +++ b/paths/typescript/everyday-types/runtime-type.md @@ -0,0 +1,61 @@ +--- +outline: deep +--- +# จัดการ Runtime Type + +เมื่อเราต้องจัดการกับชนิดข้อมูลในขณะรันโปรแกรม (Runtime Type) และไม่แน่ใจว่าจะได้รับค่าอะไรเข้ามา เช่น การเรียก API ต่าง ๆ ซึ่งเราสามารถใช้ [Zod](https://github.com/colinhacks/zod) ในการตรวจสอบและกำหนด Schema ของข้อมูลในขณะรันโปรแกรม เพื่อให้ตรงตามที่เราต้องการ + +กำหนด Schema ของข้อมูลที่ต้องการ ดังนี้: + +```ts +import { z } from 'zod'; + +// นิยาม Schema ที่ต้องการ +export const responseBodySchema = z.object({ + userId: z.number(), + id: z.number(), + title: z.string(), + completed: z.boolean(), +}); +``` + +หลังจากที่เรานิยาม schema ไปแล้ว ให้ใช้คำสั่ง `safeParse` เพื่อตรวจสอบและแปลงข้อมูลในขณะรันโปรแกรม นำไปใช้งานในโค้ดได้เลย: + +```ts +const url = 'https://jsonplaceholder.typicode.com/todos/1'; +export async function getUserData() { + const rawBody = await (await fetch(url)).json(); + // เอา data ที่เป็น any type ไป validate โดยใช้ zod schema + const parseBody = responseBodySchema.safeParse(rawBody); + if (!parseBody.success) { + const { error } = parseBody; + throw new Error(`Schema is not correct, ${JSON.stringify(error.errors)}`); + } + + return parseBody.data; + // ^? { userId: number; id: number; title: string; completed: boolean; } +} +``` + +[Zod](https://github.com/colinhacks/zod) เป็นพระเอกของเราในงานนี้เลย เพราะนอกจากจะเช็ค Schema ให้ตรงกับที่เราต้องการมั้ย + +เราสามารถแปลงเป็น Type ของ TypeScript ให้เราไปทำอย่างอื่นได้ต่อ อีก ดีงามมากกกก 😍 + +```ts +type Todo = z.infer; +// ^? type Todo = { userId: number; id: number; title: string; completed: boolean; } + +const myTodo: Todo = { + userId: 1, + id: 1, + title: 'delectus aut autem', + completed: false, +}; +``` + +ใครไม่อยากใช้ zod เพียวๆ ก็มีคนทำ library เอาไว้ใช้ +- [zodios](https://github.com/ecyrbe/zodios) = zod + axios +- [zod-fetch](https://github.com/mattpocock/zod-fetch) = zod + fetch + +ลองเล่น [Playground](https://www.typescriptlang.org/play?#code/PTAEAsBdIBwZwFwgOYEtLgK4CMB0BjAewFtgBTfATwCdszgAvQgE1ULlAF5QnnQBqUAEMAHmzgAoEBGjwkwNBhwESwYkNmEi+ANaMWAWgBmZSPnBceLAaBNnwEqWFQA7ZmRG5Ik1MRiFqSFAAbx5QAF9bahJQAHJeWIBuRw9-QNAiFzgg6jI4fyyyACEWSgBlczJ1SwZcQmwAKwpIAApgiVBQTDgyagBJZgQeXBdMYjpqFoBKABoO0FRB4dHx3um5zsh0ABsyIdrs6ldkdfmiP13IMiXa7EJCXaEXU-Cp5IlM7K7qbctYqFgiBADTghBcMG2QnwZHAD3c1C8lBgqCI7hUpEgLHYwAAjEkJKkAkEhHBKC58LZMOStmDQMhTABVHrUAAiGiE0xCZzBX2oQgA7iVmJRLAKhOhQC0xRK7OYWpgflMprgQWDpslOp8gjAhNQekKRdxcvkecVShVwFUhLg4EITAAFXU9Fp8wWlN7zVBGSUAQh1erNwptmHw0LgcCmXM6mp5QVCvWi1Ailn9+tKGujGGi-NALjIOYAotREy0AAYWq0LDguQhBIjF5ozUAAEmCACkygB5AByNsgRxcaCMlBaCYCuDHeqm4VLHs64UcnVykAVLlAqcDlFwzHZyQXTlABiPx5Pp7P54vl6v15vt7v96vjmkrncniu2S8Pj8RNA76CRmiYhQAAIiEAA3IRgOSZ9v3SUIGGTACYmA3goIPXw0jjOlGWZNlICEJtjQKNNhQrapIiQoDgNwYAXw8NCJEgJEyFAAAVLEalwVwTGoAAeJiYDIQhvSI00DTIoQAD5oLAaMAD0AH5HC1UBiEodjmEIIYNMISx2k6bpegGIYcQ2BYllM+YtkgXYhlidxdnwFcOCETBiTcqpYjM84IVMa4hiMIRth6OZwneP8WliP8vOEUlyUlSAhieShI04SSowyWMulw9lRX5cUgnpSAmV6PCOTnUBgAAKiq+ZQCq0BAAE4QAiOEASThQEAEThABQ4VrAEE4QBSOFawByOFAQBCOEAVjg2OYsbACY4HqhuauqauAbksgeMhcG2QgTlLQzWXZXA9uMltgj2srDuZAYZwqz51s27ayzOg6fMufyTqe-D0V8q5mGu95Nm3MgyBgAsAEdMCClo1J0psPqED1XkSIA) + diff --git a/paths/typescript/everyday-types/type-assertions.md b/paths/typescript/everyday-types/type-assertions.md new file mode 100644 index 0000000..457a495 --- /dev/null +++ b/paths/typescript/everyday-types/type-assertions.md @@ -0,0 +1,78 @@ +--- +outline: deep +--- + +# Type Assertions + +รู้มั้ย Typescript สามารถหลอก Type ได้ + +TypeScript เป็นภาษาที่แยกตัวออกจากโลกของ JavaScript อย่างชัดเจน +สิ่งที่ TypeScript พยายามทำคือ Type inference (คาดเดา) Type จากภาษา JavaScript. +ดังนั้นเราเขียน TypeScript, แต่ ณ Runtime เราทำงานด้วย JavaScript. + +แต่ในบางครั้งเรามักจะมีการใช้ any โดยตั้งใจ หรือไม่ตั้งใจ ก็ตาม +แต่ any สามารถบังคับให้ Type เป็นอะไรก็ได้ผ่าน return type ของ function, as, หรือ satisfies + +แนวทางการป้องกันความเข้าใจผิดเราอาจจะใช้ Eslint ที่ชื่อ +[@typescript-eslint/no-unsafe-return](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-unsafe-return.md) + +แต่อย่างไรก็ตาม ถ้าเราใช้ as หรือ satisfies เป็นการใช้ บังคับเปลี่ยน type ของ any โดยตั้งใจ TypeScript ถือว่า เรารับทราบปัญหาที่จะเกิดขึ้นในอนาคต + +หมายเหตุ: as หรือ satisfies มีประโยชน์ในหลายๆ เหตุการณ์ เลือกใช้ตามความเหมาะสม + +## ตัวอย่างการใช้ Fetch ที่อาจจะนำมาซึ่งปัญหา + +```ts +interface Person { + data: { + name: string; + }; +} +const url = "https://jsonplaceholder.typicode.com/todos/1"; +async function getUserData1(): Promise { + const result = (await fetch(url)).json(); + // ^? Promise + return result; +} + +async function getUserData2(){ + const result = (await fetch(url)).json(); + // ^? Promise + return result as Promise; // Same as `satisfies Promise` +} + +/** + * ไม่ได้ Error, ใน TypeScript, แต่ ❌ Error ที่ Runtime, TypeError: Cannot read properties of undefined (reading 'name') + **/ +console.log((await getUserData1()).data.name); +console.log((await getUserData2()).data.name); + +export {}; +``` + +ลองเล่น [Playground](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgArQM4HsTIN4BQyyAJnGHAFz5HHIhwC2E1GYUoA5gNy0C+vPgQQ42yAK5QANsgC8yAEQALMGAAOGSgHotAK2wg1UxBCVYpJaADowATzXARlqyMZawWElgxaAjAt44DFsQBGQYcVCwYBxkTggwAFUMaAARcjhfAAoASmpUKCxGYBSAHnQoAwA+GmIREDEoCAxxKTA5ZCy4AHc4YHaYBIQlLMkpHJyrfRxc3mIdOjoAPQB+NELisrgQWyraJrBJXCaWtsECAiCQsIiomNx4pJSodIoAJlza5HrG5tb2+RdXr9cJDEZjCZTAyzWgLRbIVbrIolCClba7fYJI7IE7-ZBBJGbVEVarceZaZAAZSYKAJAAMMOQSjBgM1CSjypgcFU6QQhAQtAAqQVEQXIQAicIBCOEAEnDiwAocIBJOGQAFEoIUoAAaZCAYThAJhwyAAKvYIJSEBw1GAtYBBOEAqHDS5CAGXIVWqsFBkIB0OEArHD2gBKkWizC1RrUEFV6uoAGFtiAsO0mnASMg1IUQ1BomysDAJCBLCyQBBE1l4yQuMgAOQMZhlnKiwVaS7BULhSIIaKxRh9ECfQh1UTmCBWKRYThZIF9dqPZJpDLZSFkChWSsQHJzb59qQDocjscgyfPV5wD5zjKLmkrvkXDugWZAA) + +# Const Assertions + +เราสามารถแปลง Object เป็น Readonly ผ่าน `as const` + +const assertions in Typescript +https://mainawycliffe.dev/blog/const-assertion-in-typescript/ + +```typescript +const person = { + name: "John Doe", + age: 25, +}; + +const person = { + name: "John Doe", + age: 25, +} as const; + +// (Note: This is not TypeScript Syntax) +// const person: { +// readonly name: "John Doe"; +// readonly age: 25; +// } +``` \ No newline at end of file diff --git a/paths/typescript/everyday-types/type-is-data.md b/paths/typescript/everyday-types/type-is-data.md new file mode 100644 index 0000000..4b2b82b --- /dev/null +++ b/paths/typescript/everyday-types/type-is-data.md @@ -0,0 +1,67 @@ +--- +outline: deep +--- + +# Type คือข้อมูลชนิดหนึ่ง + +## ประเภทของ Type + +1. Primitive types + + ```ts + type MyNumber = number; + type MyString = string; + type MyBoolean = boolean; + type MySymbol = symbol; + type MyBigInt = bigint; + type MyUndefined = undefined; + type MyNull = null; + ``` + +2. Literal types + + Literal types คือ Type ที่มีค่านั้น เพียงค่าเดียว + + ```ts + type MyNumberLiteral = 20; + type MyStringLiteral = 'Hello'; + type MyBooleanLiteral = true; + ``` + +3. Data structures (Object Types) + + ```ts + type MyObject = { + key1: boolean; + key2: number; + }; + type MyRecord = { + [key: string]: number; + }; + type MyTuple = [boolean, number]; + type MyArray = number[]; + ``` + +4. Unions + + ```ts + type MyUnion = 'Yes' | 'No'; + // Example + const myUnion1: MyUnion = 'Yes'; + const myUnion2: MyUnion = 'No'; + const myUnion3: MyUnion = 'Maybe'; // Error + ``` + +5. Intersections + + ```ts + type MyIntersection = { count: number } & { title: string }; + // Example + const myIntersection1: MyIntersection = { + count: 1, + title: 'Hello', + }; + ``` + + +อ้างอิง: https://type-level-typescript.com/types-are-just-data \ No newline at end of file diff --git a/paths/typescript/handbook/conditional-types.md b/paths/typescript/handbook/conditional-types.md new file mode 100644 index 0000000..3b2b3bf --- /dev/null +++ b/paths/typescript/handbook/conditional-types.md @@ -0,0 +1,27 @@ +# conditional types (แบบมีเงื่อนไข) + +อธิบาย keyword สำคัญๆ + +- `extends` เราสามารถคิดได้ว่าเป็นเงื่อนไขใน javacsript ก็ได้ คล้ายๆ กับการใช้ `===` เปรียบเทียบค่า +- `type` คิดว่าเหมือนเป็น function แต่เป็นสำหรับ type มันคือการใส่ input (ซึ่งก็คือ type เราเรียกว่า generics) แล้วก็มี output โดยที่ output ก็ขึ้นอยู่กับ "เงื่อนไข" ของ type นั้นๆ `extends` ก็เหมือนกับ block ของ logic เหมือนกับ if +- `infer` เปรียบเหมือนแว่นขยายใน TypeScript, ทำหน้าที่ในการ extract type ออกมาจาก type อื่นๆ + +## การ Extract property type ออกจาก object + +```typescript +type ObjectInfer = + // ถ้า Object T มี property ที่ชื่อ name อยู่ + T extends {name: infer A} + // ถ้ามี ให้ return type A (type ของ property name) + ? A + // ถ้าไม่มี ใ้ห return never + : never; + +const member = { name: 'John' }; + +type nameType1 = ObjectInfer; // จะได้ type เป็น string +type nameType2 = ObjectInfer; // จะได้ type เป็น never +``` + +## อ่านเพิ่มเด้อ +- [Learn Advanced TypeScript Types](https://medium.com/free-code-camp/typescript-curry-ramda-types-f747e99744ab) (3.7k claps) \ No newline at end of file diff --git a/paths/typescript/handbook/enum.md b/paths/typescript/handbook/enum.md new file mode 100644 index 0000000..35c23bf --- /dev/null +++ b/paths/typescript/handbook/enum.md @@ -0,0 +1,93 @@ +# Enum + +ถ้าเราใช้งาน enum เราไม่สามารถ assign string ตรงๆ ไปได้ เช่น เราไม่สามารถใส่ `DRAFT` ลงไปใน `PostState` ได้ + +```typescript +enum PostState { + Draft = "DRAFT", + Scheduled = "SCHEDULED", + Published = "PUBLISHED", +} + +const state1: PostState = PostState.Draft; +const state2: PostState = "IDEA"; // ❌ Error: Type '"IDEA"' is not assignable to type 'PostState' +const state3: PostState = "DRAFT"; // ❌ Error: Type '"DRAFT"' is not assignable to type 'PostState'. +``` + +บางทีเราสามารถใช้ `as` เพื่อ casting type ได้ แต่มันก็ไม่เกิดประโยชน์เท่าไหร่ เพราะความสามารถในการเช็ค Type หายไป + +```typescript +const state2: PostState = "IDEA" as PostState.Draft; // ตรงนี้มันควรจะ Error ใช่มั้ย +const state3: PostState = "DRAFT" as PostState.Draft; +``` + +ปัญหาอีกอย่าง ถ้าเราใช้ `enum` อยู่ใน Library แล้วเราไม่สามารถส่ง string `"DRAFT"` ที่มีใน `enum PostState` ได้เลย +เราอาจจะต้อง import enum เข้ามาใช้งาน และถ้าเป็นภาษา JS ละ ?? + +## เราสามารถใช้ const object แทน enum ใน TypeScript ได้ + +```typescript +const PostState = { + Draft : "DRAFT", + Scheduled : "SCHEDULED", + Published : "PUBLISHED", +} as const; +``` + +เมื่อเราใช้ `as const` แต่ละ property ของ object จะเป็น `readonly` ทันที + +![Enum Object Const](./images/enum-object-const.png) + +เราสามารถทำ Type Alias เพื่อมา check type ของ `PostState` ได้ + +```typescript +type PostStateType = PostState; // ❌ Error เราไม่สามารถเอา js variable มาเป็น type ได้ +type PostStateType = typeof PostState; //เราสามารถใช้ `typeof` เพื่อเอา type ของ js variable มาได้แทน +``` + +แบบนี้เราจะได้ Key ของ object `PostState` + +```typescript +type PostStateType = keyof typeof PostState; +// type PostStateType = "Draft" | "Scheduled" | "Published" +``` + +แบบนี้เราจะได้ Value ของ object `PostState` พอเป็นแบบนี้เราก็เอามา check type ได้แล้ว + +```typescript +type PostStateType = typeof PostState[keyof typeof PostState]; +// type PostStateType = "DRAFT" | "SCHEDULED" | "PUBLISHED" +``` + +## เรามาดูตัวอย่างแบบเต็มๆ กันดีกว่า + +```typescript +const PostState = { + Draft : "DRAFT", + Scheduled : "SCHEDULED", + Published : "PUBLISHED", +} as const; + +type PostStateType = typeof PostState[keyof typeof PostState]; +// type PostStateType = "DRAFT" | "SCHEDULED" | "PUBLISHED" + +const state1: PostStateType = PostState.Draft; // อันนี้ เราสามารใช้งานได้เหมือน enum เลย +const state2: PostStateType = "IDEA"; // อันนี้ ❌ Error: Type '"IDEA"' is not assignable to type 'PostStateType' +const state3: PostStateType = "DRAFT"; // เย้ๆ เราสามารถ passing string ที่ match กับ value ใน PostState ได้แล้ว +``` + +ขอบคุณตัวอย่างจากคุณ Andrew Burgess [How to use TypeScript Enums and why not to, maybe](https://www.youtube.com/watch?v=pWPClHdcvVg) + +## ทำ Type Utility ใช้งานเอง + +```typescript +// Type Utility +type Enum = T[keyof T]; + +type PostStateType = Enum; +``` + + +## อ่านเพิ่มน้าาา +- https://shaky.sh/ts-bit-flags/ +- [Number Enums as flags](https://basarat.gitbook.io/typescript/type-system/enums#number-enums-as-flags) \ No newline at end of file diff --git a/paths/typescript/handbook/function-overload.md b/paths/typescript/handbook/function-overload.md new file mode 100644 index 0000000..bda6f79 --- /dev/null +++ b/paths/typescript/handbook/function-overload.md @@ -0,0 +1,3 @@ +# Function Overloading + +https://dmitripavlutin.com/typescript-function-overloading/ \ No newline at end of file diff --git a/paths/typescript/handbook/generics.md b/paths/typescript/handbook/generics.md new file mode 100644 index 0000000..28b04e0 --- /dev/null +++ b/paths/typescript/handbook/generics.md @@ -0,0 +1,4 @@ + +# Generics + + \ No newline at end of file diff --git a/paths/typescript/handbook/images/enum-object-const.png b/paths/typescript/handbook/images/enum-object-const.png new file mode 100644 index 0000000..67901a3 Binary files /dev/null and b/paths/typescript/handbook/images/enum-object-const.png differ diff --git a/paths/typescript/handbook/keyof.md b/paths/typescript/handbook/keyof.md new file mode 100644 index 0000000..51f942d --- /dev/null +++ b/paths/typescript/handbook/keyof.md @@ -0,0 +1,51 @@ +# Keyof + +ในบทความนี้ ผมจะอธิบายละเอียดขั้นตอนในการสร้างฟังก์ชันที่สามารถดึงค่าจากคีย์ของ Object ใน TypeScript + +สมมติ เริ่มต้นด้วยการกำหนดโครงสร้างของ Object ใน Post ดังนี้: + +```ts +type Post = { + title: string; + date: Date; +}; + +const myPost: Post = { + title: "TypeScript is great!", + date: new Date(), +}; +``` +1. สมมติว่า เราต้องการสร้าง Function ที่ดึง Value ออกมาจาก Key ของ Object `myPost` นี้ + + ```ts + getPostByKey1("title", myPost); // TypeScript is great! + ``` +2. ถ้าเราพยายามใช้ key ในรูปแบบ string สำหรับฟังก์ชัน getPostByKey2 เราจะเจอข้อผิดพลาด เนื่องจากคีย์ต้องเป็น title หรือ date + ```ts + function getPostByKey2(key: string, postData: Post) { + return postData[key]; + // Error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Post'. + } + ``` +3. วิธีแก้ไขคือการกำหนดคีย์ให้เป็น union type แสดงว่า key ต้องเป็น union type ที่มีค่าเป็น `'title' | 'date'` + ```ts + function getPostByKey3(key: 'title' | 'date', postData: Post) { + return postData[key]; + } + ``` +4. ใช้คำสั่ง keyof เพื่อได้ union ของคีย์ของ Object ทำให้เราสามารถกำหนดคีย์ที่ถูกต้องและใช้งานได้ + ```ts + type KeyOfPost = keyof Post; // 'title' | 'date' + ``` + +5. เราสามารถสร้างฟังก์ชันที่เป็น generic และนำไปใช้กับข้อมูลใด ๆ ได้ ด้วยฟังก์ชัน `getObjectByKey` ซึ่งใช้ keyof ของตัวแปร T ตัวอย่างนี้ใช้ `'date'` จาก object `myPost` + ```ts + function getObjectByKey(key: keyof T, data: T) { + return data[key]; + } + getObjectByKey('date', myPost); + ``` + +จากตัวอย่าง เราสามารถสร้างฟังก์ชันที่สามารถดึงค่าจากคีย์ของ Object อย่างมีประสิทธิภาพและยืดหยุ่นใน TypeScript ได้ ฟังก์ชัน `getObjectByKey` ที่เป็น generic สามารถนำไปใช้กับ Object หรือโครงสร้างข้อมูลชนิดอื่น ๆ ได้ตามความต้องการของคุณ + +ลองเล่นใน [Playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwHMQMAFHAZwwCEBPAaRBoEYAKAa0YC5403UcA7qgA08AA4UM3MpQCU3XvyEBuAFCqMNMQhkZ4AXngBvVfHjYMEEN0owsqAmrPAoGa-AAirkGoC+a1TA8SngAWxpdaUkDY1NzLEt3ACIAFS0QAGUwOzE9LHJCOFcAQiThOJc3blQQAU9vFlly-1UAegAqdtN2+CYAOnhAVDhASThAWjhAcDhAQDhAJjhAYjhAKjhZ4enx+AAxNExcfEB0OEBWOEAJOEAUOEA2OFWANSgIZARR0cnAQjhpwAg4acn4Bhp4QCA4CfgAeQARgArcAYbqtVREUiSWifVhJCxWMphCKSWTKeCtVrwNLaLI5PIFAhFDDFNqdbrwABMA0AAnCzaaAETgHgd5tMnrNAGhwgGE4QBQcMN4BwvnTANhwgHE4QCYcPBbPYCPAmUdBXTAHhwjMADHAyjB2BwK1n8g7xRLwQDUcLNAOxwo3glRAENUKHQ2DwhGIujhjGp7C4Wp1BFEEkoXgwUCicliZmx8AAAhhyABaEAAD20mATMBgOBgcTgGGQMHwAYwQagAG1hQBdPwUrrwHoAZgGgEE4eZHcaAcjgDtMhYwhmNxmKpTxUNtzOkDAA+eAAciRICn8AAPtObVO7Q6ts7oW76Iw616aNxEQlkYv4EkbSjC8XQxhZOH4Dm8wXJMWy4xK6pfNWqQAWAbCnBEHgZ51UVQU0BHX5VmFH4-iBUFMDtTRtA+Rh-kQXQYgAoDdExSMZ2POdTynFd1A6GsegAVgGHZAGY4HkTUFdcnXwIgajsMB4CeSZvhOYZpXZTkuQfEBkHIKBASsBUlTXTYWJdDB4LBd0aAAHhScd924bDcVESoQ1xO8TDMR982tVxSwrKtoSUzAVJYEjvCnURwl0DEgA) \ No newline at end of file diff --git a/paths/typescript/handbook/mapped-types.md b/paths/typescript/handbook/mapped-types.md new file mode 100644 index 0000000..46ce0cd --- /dev/null +++ b/paths/typescript/handbook/mapped-types.md @@ -0,0 +1,4 @@ +# Mapped Types + +Ref: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html + diff --git a/paths/typescript/handbook/narrowing-type.md b/paths/typescript/handbook/narrowing-type.md new file mode 100644 index 0000000..91f42af --- /dev/null +++ b/paths/typescript/handbook/narrowing-type.md @@ -0,0 +1,54 @@ +# Narrowing Type + +คือการตัด case ความเป็นไปได้ของ Type ที่เกิดขึ้น เพื่อให้เราจัดการกับ ชนิดของ Object ได้ถูกต้องนั้นเอง + +- https://www.codecademy.com/learn/learn-typescript/modules/learn-typescript-type-narrowing/cheatsheet +- https://formidable.com/blog/2022/narrowing-types/ + +## การ narrow type ของ unknown + +```ts +/** + * A custom type guard function that determines whether + * `value` is an array that only contains numbers. + */`` +function isNumberArray(value: unknown): value is number[] { + return ( + Array.isArray(value) && value.every(element => typeof element === "number") + ); +} + +const unknownValue: unknown = [15, 23, 8, 4, 42, 16]; + +if (isNumberArray(unknownValue)) { + // Within this branch, `unknownValue` has type `number[]`, + // so we can spread the numbers as arguments to `Math.max` + const max = Math.max(...unknownValue); + console.log(max); +} +``` + +Ref: +- https://mariusschulz.com/blog/the-unknown-type-in-typescript +- https://mainawycliffe.dev/blog/type-guards-and-narrowing-in-typescript/ +- https://bobbyhadz.com/blog/typescript-type-unknown-is-not-assignable-to-type + +ทีนี้มาลอง Narrow เพื่อเช็ค Record Type กัน โดยใช้ lib data validation ที่ชื่อ [zod](https://github.com/colinhacks/zod) + +```ts +import { z } from 'zod'; + +function isRecord(object: unknown): object is Record { + const recordSchema = z.record(z.unknown()); + return recordSchema.safeParse(object).success; +} + +function findLength(object: unknown): number { + if (Array.isArray(object)) { + return console.log(object.length); + } else if (isRecord(object)) { + return Object.keys(object).length; + } + throw new Error(`Input object doesn't support type`); +} +``` \ No newline at end of file diff --git a/paths/typescript/handbook/type-manipulation.md b/paths/typescript/handbook/type-manipulation.md new file mode 100644 index 0000000..a519e81 --- /dev/null +++ b/paths/typescript/handbook/type-manipulation.md @@ -0,0 +1,19 @@ +# การปรับแต่ง Type + +## keyof + +```ts +type Person = { + name: string; + age: number; +} + +type keyOfPerson = keyof Person; +// Similar to type keyOfPerson = 'name' | 'age': +``` + +```ts +type keyOfAny = keyof any; +// Similar to type keyOfAny = string | number | symbol +``` + diff --git a/paths/typescript/handbook/utility-types.md b/paths/typescript/handbook/utility-types.md new file mode 100644 index 0000000..634dbd5 --- /dev/null +++ b/paths/typescript/handbook/utility-types.md @@ -0,0 +1,148 @@ +# Utility Types + +https://www.typescriptlang.org/docs/handbook/utility-types.html + + +## Awaited + +ใช้ `Awaited` สำหรับการถอด Promise Type ออกมา และสามารถถอดแบบ Recursive ลึกจนถึง Value ได้ + +```typescript +type A = Awaited>; +// type A = string + +type B = Awaited>>; +// type B = number + +type C = Awaited>; +// type C = boolean | number +``` + +## Partial + +ทำให้ทุก Property ของ Type เป็น optional value; + +```typescript +interface Todo { + title: string; + description: string; +} + +type PartialTodo = Partial; + +// type PartialTodo = { +// title?: string | undefined; +// description?: string | undefined; +// } +``` + +## Required + +ทำให้ทุก Property ของ Type เป็น required value + +```typescript +interface Todo { + title?: string; + description?: string; +} + +type RequiredTodo = Required; + +// type RequiredTodo = { +// title: string; +// description: string; +// } +``` + +## Readonly + +ทำให้ทุก Property ของ Type เป็น readonly (const) ไม่สามารถแก้ไขค่าได้ + +```typescript +interface Todo { + title: string; +} + +type ReadonlyTodo = Readonly; + +// type ReadonlyTodo = { +// readonly title: string; +// } + +// Example + +const todo: ReadonlyTodo = { + title: "Delete inactive users", +}; + +todo.title = "Hello"; // ❌ Error: Cannot assign to 'title' because it is a read-only property +``` + +เราสามารถใช้ `as const` เพื่อทำให้เป็น readonly ได้เหมือนกัน (Only Type Assertion) [อ่านเพิ่มที่](../everyday-types/type-assertions) + +ถ้าเราอยากให้ Error ที่ Run time แนะนำให้ใช้ `Object.freeze(obj)` เช่น + +```typescript +function freeze(obj: Type): Readonly { + return Object.freeze(obj); +} + +const todo = { + title: 'Running' +}; + +const freezedTodo = freeze(todo); + +freezedTodo.title = 'Lazy'; // ❌ Type Error and Error at run time +``` + +## Record + +```typescript +const nameRecord: Record = { + john: 1, + micky: 2, +}; + +nameRecord['Lilly'] = 5; // We can add new property in object becuase 'Lilly' is string + +nameRecord[true] = 5; // ❌ Error: Type 'true' cannot be used as an index type +``` + +## Pick + +เลือกบาง Property ออกมาจาก Object + +```typescript +interface Todo { + title: string; + description: string; + completed: boolean; +} + +type PickTodo = Pick; + +// type PickTodo = { +// title: string; +// completed: boolean; +// } +``` + +## Omit + +ลบบาง Property จาก Object + +```typescript +interface Todo { + title: string; + description: string; + completed: boolean; +} + +type OmitTodo = Omit; + +// type OmitTodo = { +// title: string; +// completed: boolean; +// } +``` diff --git a/paths/typescript/index.md b/paths/typescript/index.md new file mode 100644 index 0000000..6ad0224 --- /dev/null +++ b/paths/typescript/index.md @@ -0,0 +1,15 @@ +--- +outline: deep +title: 'TypeScript Roadmap' +description: ขั้นตอนแนะนำในการเตรียมความพร้อมในการเข้าสู่เส้นทาง TypeScript ในปี 2024 +--- + +# TypeScript + +TypeScript เป็นภาษาโปรแกรมที่ถูกพัฒนาโดย Microsoft และเป็นภาษาที่สร้างขึ้นบน JavaScript โดยมีการเพิ่มเติมความสามารถในการเขียนโปรแกรมให้มีความเป็นระเบียบมากขึ้น และเพิ่มความเป็นมืออาชีพในการพัฒนาโปรแกรม + +เนื้อหาส่วนหนึ่งจากหนังสือ [คู่มือ TypeScript สำหรับคนไทย](https://typescript-th.thadaw.com/) +และ [Official TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) + +สำหรับใครอยากเรียนรู้ TypeScript ขั้นสูง เพื่อสำหรับออกแบบ Type-safe Design Pattern ในหนังสือ [Type-safe Design Pattern +in Modern TypeScript](https://type-safe.thadaw.com/) \ No newline at end of file