Skip to content

สร้าง 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

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

Ref: https://medium.hexlabs.io/the-builder-pattern-with-typescript-using-advanced-types-e05a03ffc36e