Skip to content

Commit

Permalink
Merge pull request #28 from mildronize/typescript-path
Browse files Browse the repository at this point in the history
  • Loading branch information
ponggun authored Aug 6, 2024
2 parents 3f1d39c + 8bee34c commit f2f001b
Show file tree
Hide file tree
Showing 24 changed files with 1,098 additions and 9 deletions.
70 changes: 61 additions & 9 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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" }
]
},
]
}
]
Expand All @@ -452,4 +453,55 @@ function sidebarSoftwareArchitecture(): DefaultTheme.SidebarItem[] {
]
}
]
}
}

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" },
]
}
],
},
];
}
3 changes: 3 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions paths/typescript/cookbook/append-prefix-object-property-key.md
Original file line number Diff line number Diff line change
@@ -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<T, U extends string> = {
[K in keyof T as `${U}${K & string}`]: T[K]
};

function appendPrefix<T extends Record<string, any>, U extends string>(obj: T, prefix: U) {
const result: Partial<AppendPrefix<T, U>> = {};
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<AppendPrefix<Person, "old_">>'
```
35 changes: 35 additions & 0 deletions paths/typescript/cookbook/create-own-enum-string.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# สร้าง enum ใช้เอง แบบที่ Key กับ Value เหมือกัน

```ts
type TupleToObject<T extends readonly (keyof any)[]> = {
[K in T[number]]: K
}

type Enum<T> = 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<T extends readonly (keyof any)[]>(data: T): TupleToObject<T>{
return data.reduce((result: any, v: keyof any) => {
result[v] = v;
return result;
}, {});
}

const GenState = tupleToObject(state);
console.log(GenState);

const myState1: Enum<typeof GenState> = GenState.completed;
const myState2: Enum<typeof GenState> = 'completed';
const myState3: Enum<typeof GenState> = 'hold'; // ❌ Error: Type '"hold"' is not assignable to type 'EnumGenState'.

// or
const myState4: keyof typeof GenState = 'completed';
```
11 changes: 11 additions & 0 deletions paths/typescript/cookbook/create-union-type-from-array.md
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions paths/typescript/cookbook/filter-some-property-of-record.md
Original file line number Diff line number Diff line change
@@ -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<T, U extends keyof T>(obj: T, keys: U[]) {
// สาเหตุที่เลือกใช้ Partial ของ Pick จะทำให้สร้าง Object ว่างๆ ได้นั่นเอง แล้วค่อยเลือก Property ทีหลัง
const result: Partial<Pick<T, U>> = {};
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<FilterRecord<IPost, "id">>'
```
84 changes: 84 additions & 0 deletions paths/typescript/cookbook/type-safe-builder-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# สร้าง Builder Pattern แบบ Type-Safe

## Basic

```typescript
class Inventory<Items extends Record<string, unknown> = {}> {

items: Items = {} as Items;

add<NewItem extends Record<string, unknown>>(value: NewItem) {
this.items = {
...value as unknown as Items
}
return this as Inventory<Items & NewItem>;
}
}

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<Items extends Record<string, unknown> = {}> {

constructor(private readonly jsonObject: Items) { }

add<K extends string, V>(key: K, value: V) {
const nextPart = { [key]: value } as Record<K, V>;
return new ObjectBuilder({ ...this.jsonObject, ...nextPart }) as
ObjectBuilder<{ [Key in keyof (Items & Record<K, V>)]: (Items & Record<K, V>)[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
61 changes: 61 additions & 0 deletions paths/typescript/cookbook/zod-typescript-integration.md
Original file line number Diff line number Diff line change
@@ -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<T>` ได้เพื่อระบุ Type ของ Schema ได้

```ts
import { z, ZodFormattedError } from 'zod';

export function parseEnv<T extends z.ZodTypeAny>(schema: T, input: Record<string, string>): z.infer<T> {
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<Map<string, string>, 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<typeof appSchema>;
// จะได้ 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
```
Loading

0 comments on commit f2f001b

Please sign in to comment.