---
# System prepended metadata

title: typescript challenge
tags: [typescript]

---

## 11.tupletoObject

```typescript
let tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type TupleToObject<T extends ReadonlyArray<string>> = {
  [K in T[number]]: K
}
type result = TupleToObject<typeof tuple>

// expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
```

## as const
首先我們先訂一組 `const route` 記錄所有會使用到的 `const`。

```typescript
const route = {
  home: "/",
  admin: "/admin",
  users: "/users"
}
```

接著我們定義 `goToRoute function` 然後提供 `route params` 去指定 `route` 有哪些 `type` 可以使用，當然你可以單純寫死，`route` 的 `type` ，這會有一個問題，如果 `route` 越多你的 `type` 也要額外加，甚至可能會忘記。

```typescript
const goToRoute = (route: "/" | "/admin" | "/users") => { }
```

使用上來說也沒問題，可以直接用。

![截圖 2024-06-12 上午1.21.21](https://hackmd.io/_uploads/ByZvY-IHA.png)

但這時如果我們 `goToRoute` 帶進去的 `params` 他是一個 `reference` 會如何 ?你會看到 `route.admin` 的型別被定義成 `string` 所以導致` type error`，儘管 `route.admin` 的值是正確的。

![截圖 2024-06-12 上午1.22.59](https://hackmd.io/_uploads/SJdDKbUH0.png)



在沒有用 `as const` 之前 `const object` 對 `ts` 來說對於 `object` 的 `value` 會 `type infer` 成 `Basic Types` 而不是對應的 `value`。

![截圖 2024-06-12 上午1.24.49](https://hackmd.io/_uploads/rkbtYWUSR.png)
 
這是因為 `const object` 對 `ts` 來說他是 `mutable` 的所以 `ts` 並無法 `type infer` `object` 中實際的 `value` 是什麼。

```typescript
const route = {
  home: "/",
  admin: "/admin",
  users: "/users"
}
route.admin = '/another'
```

那我們要如何讓 `typescript` 知道 `object value` 的 `type` 實際是什麼，這時候 `as const` 就登場了。 

```typescript
const route = {
  home: "/",
  admin: "/admin",
  users: "/users"
} as const
route.admin = '/another'
```

這時候你會看到 `route.admin` 變成 `readonly` 所以不能再重新賦值給 `route`。

![截圖 2024-06-12 上午1.35.24](https://hackmd.io/_uploads/SJegiWIBA.png)

然後使用 `reference` 也沒有 `type error`。

```typescript
goToRoute(route.admin)
```
因為 `route.admin` 已經成功 `type infer` 成 `/admin`。

![截圖 2024-06-12 上午1.34.19](https://hackmd.io/_uploads/S1OA9b8r0.png)

**小結：** `as const` 解決了 
* typescript 無法正確 `type infer` 成實際的 `value`。
* 阻止 `const object` `mutable` 特性。

與 `as const` 類似的有 `Object.freeze` 同樣也可以做到 `as const` 的事情，但缺點就是 `Object.freeze` 無法做到 `deep freeze` 的事情而 `as const` 卻可以。

```typescript
const route = Object.freeze({
  home: "/",
  admin: "/admin",
  users: "/users"
})
route.admin = '/another'
```

接著我們要解決 `route` 的型別定義問題，總不可能要一個一個寫所有的 `const type` 吧ＸＤ

```typescript
const goToRoute = (route: "/" | "/admin" | "/users") => { }
```

我們的目標很明確希望拿到 `route` 中所有的 `value type` ，所以首先我們需要定義 `route object type` 是什麼。

在 `ts` 中我們可以使用 `typeof` 去 `infer` 對應變數的型別是什麼，因為我們的 `route` 有使用 `as const` 所以 `ObjectType` 會自動 `infer` 成 `readOnly` 的 `type`。

![截圖 2024-06-12 上午1.48.51](https://hackmd.io/_uploads/rydf0bLBR.png)

如果加上 `keyof` 就可以 `infer` 對應的 `object key type` 是什麼了。

![截圖 2024-06-12 上午1.50.45](https://hackmd.io/_uploads/ByLFA-LrR.png)

但我們的需求是 `infer` 出 `route` 的 `value type` ，這邊我們使用 `ts` 的 `mapping type` 功能，結合上述範例在 `Objecttype` 中找到 `infer readonly object type` 再搭配 `ObjectKey` ，這時 `ts` 就會很巧妙的 `infer` 出 `Route` 的所有 `type` 了。

![截圖 2024-06-12 上午1.53.56](https://hackmd.io/_uploads/HydrkfLHC.png)

這時如果新增新的 `value`

```typescript
const route = {
  home: "/",
  admin: "/admin",
  users: "/users",
  newUser:'/newUser'
} as const
```

`type Route` 就自動加上去了～

![截圖 2024-06-12 上午1.57.21](https://hackmd.io/_uploads/SylzlMISA.png)

最後的 `demo` 如下

```typescript
const route = {
  home: "/",
  admin: "/admin",
  users: "/users",
  newUser: '/newUser'
} as const

type Route = (typeof route)[keyof typeof route]

// dynamic type for route value
const goToRoute = (route: Route) => { }

// auth type suggestion
goToRoute('/')

// 可以 reference

goToRoute(route.admin)



```

## mapping
```typescript
import { type ValueOf } from 'type-fest';
import { z } from 'zod';

export const BONUS_CRITERIA_FIELD_TYPE = {
    SELECT: 0,
    MULTI_SELECT: 1,
    RANGE_PICKER: 2,
    INPUT_NUMBER: 3,
    AFFILIATE_LIST: 4,
    PRODUCT_VENDORS: 5,
    SLIDER: 6,
} as const;

export type BonusCriteriaFieldType = ValueOf<typeof BONUS_CRITERIA_FIELD_TYPE>;

type CriteriaFieldReturnTypeMap = {
    [BONUS_CRITERIA_FIELD_TYPE.SELECT]: {
        return: number[] | undefined;
        value: TargetBonusCriteriaOptions['value'];
    };
    [BONUS_CRITERIA_FIELD_TYPE.MULTI_SELECT]: {
        return: number[] | undefined;
        value: TargetBonusCriteriaOptions['value'];
    };
    [BONUS_CRITERIA_FIELD_TYPE.RANGE_PICKER]: {
        return: { from: number; to: number } | undefined;
        value: TargetBonusCriteriaDateRange['value'];
    };
    [BONUS_CRITERIA_FIELD_TYPE.AFFILIATE_LIST]: {
        return: string[] | undefined;
        value: TargetBonusCriteriaAffiliateCode['value'];
    };
    [BONUS_CRITERIA_FIELD_TYPE.PRODUCT_VENDORS]: {
        return: string[] | undefined;
        value: TargetBonusCriteriaProductVendors['value'];
    };
    [BONUS_CRITERIA_FIELD_TYPE.INPUT_NUMBER]: {
        return: number | undefined;
        value: TargetBonusCriteriaInput['value'];
    };
    [BONUS_CRITERIA_FIELD_TYPE.SLIDER]: {
        return: number[] | undefined;
        value: TargetBonusCriteriaOptions['value'];
    };
};

type CriteriaFieldInitialValueMapType = {
    [K in BonusCriteriaFieldType]: (value?: CriteriaFieldReturnTypeMap[K]['value']) => CriteriaFieldReturnTypeMap[K]['return'];
};

export const CRITERIA_FIELD_INITIAL_VALUE_MAP: CriteriaFieldInitialValueMapType = {
    [BONUS_CRITERIA_FIELD_TYPE.SELECT]: (value) =>
        toArray(value?.map(({ id }) => id)),
    [BONUS_CRITERIA_FIELD_TYPE.MULTI_SELECT]: (value) =>
        value?.map(({ id }) => id),
    [BONUS_CRITERIA_FIELD_TYPE.RANGE_PICKER]: (value) =>
        value && isNotNil(value.startAt) && isNotNil(value.endAt)
            ? {
                from: value.startAt,
                to: value.endAt,
            }
            : undefined,
    [BONUS_CRITERIA_FIELD_TYPE.AFFILIATE_LIST]: (value) => value?.map(({ affiliateCode }) => affiliateCode),
    [BONUS_CRITERIA_FIELD_TYPE.PRODUCT_VENDORS]: (value) => value?.flatMap(({ id, vendors }) => vendors.map(vendor => `${id}-${vendor.id}`)),
    [BONUS_CRITERIA_FIELD_TYPE.INPUT_NUMBER]: (value) => value,
    [BONUS_CRITERIA_FIELD_TYPE.SLIDER]: (value) => value?.map(({ id }) => id),
};

```