## 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") => { }
```
使用上來說也沒問題,可以直接用。

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

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

這是因為 `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`。

然後使用 `reference` 也沒有 `type error`。
```typescript
goToRoute(route.admin)
```
因為 `route.admin` 已經成功 `type infer` 成 `/admin`。

**小結:** `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` 吧XD
```typescript
const goToRoute = (route: "/" | "/admin" | "/users") => { }
```
我們的目標很明確希望拿到 `route` 中所有的 `value type` ,所以首先我們需要定義 `route object type` 是什麼。
在 `ts` 中我們可以使用 `typeof` 去 `infer` 對應變數的型別是什麼,因為我們的 `route` 有使用 `as const` 所以 `ObjectType` 會自動 `infer` 成 `readOnly` 的 `type`。

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

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

這時如果新增新的 `value`
```typescript
const route = {
home: "/",
admin: "/admin",
users: "/users",
newUser:'/newUser'
} as const
```
`type Route` 就自動加上去了~

最後的 `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),
};
```