-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28 from mildronize/typescript-path
- Loading branch information
Showing
24 changed files
with
1,098 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
paths/typescript/cookbook/append-prefix-object-property-key.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_">>' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
36
paths/typescript/cookbook/filter-some-property-of-record.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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">>' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
Oops, something went wrong.