目錄
const斷言是什麼
(2024/4/30更新)
const斷言(assertion)是一種型別斷言,它可以告訴Typescript限縮變數的型別、把變數變成readonly
,寫immutable
code時會用到
使用as const
會把型別變成literal type
literal
adjective
The literal meaning of a word is its original, basic meaning.
literal type是一種鎖死
的型別,簡單說就是你詳細定義「這個字串(數值)是什麼」、「這個陣列內的元素是什麼」、「這個物件內有什麼屬性、屬性值是什麼」
const斷言和const宣告的差別在於並不是用於宣告變數,或者限縮型別
,兩者使用的場景並不完全一樣
// 型別是string;
let fruit = 'apple';
// 型別是apple;
let fruit2 = 'apple' as const;
const fruit3 = 'apple';
let arr = [10, 20]; // number[]
const arr2 = [10, 20] as const; // readonly [10, 20]
const arr3 = arr2; // readonly [10, 20]
arr.push(30);
// Property 'push' does not exist on type 'readonly [10, 20]'
arr2.push(30);
// Property 'push' does not exist on type 'readonly [10, 20]'
// 傳址,且其reference已經被as const鎖死型別,所以不能push
arr3.push(30);
const obj = {
a:1,
};
// {
// a: number;
// }
// 使用了as const以後物件的型別完全被鎖死,所有屬性變readonly(immutable)
const obj2 = {
a:1,
} as const;
// {
// readonly a: 1;
// }
// Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;
// 型別沒有被完全鎖死,所以可以修改屬性值
obj.a = 20;
Property 'b' does not exist on type '{ readonly a: 1; }'
obj2.b = 2;
Cannot assign to 'a' because it is a read-only property.
obj2.a = 20;
在一些需要enum的場景也可以使用const斷言代替
const mainDishes = {
beef:'steak',
pork:'pepper pork',
lamb:'roasted lamb',
fish:'sashimi',
shrimp:'curry shirmp',
carb:'butter crab',
} as const;
// {
// readonly beef: "steak";
// readonly pork: "pepper pork";
// readonly lamb: "roasted lamb";
// readonly fish: "sashimi";
// readonly shrimp: "curry shirmp";
// readonly carb: "butter crab";
// }
// Cannot assign to 'beef' because it is a read-only property.
mainDishes.beef = 'roasted beef';
const斷言限制
1.只能用於直接賦值宣告、簡單的表達式
```js
// bad
// A 'const' assertion can only be applied to a to a string, number, boolean, array, or object literal.
const num = 1 + 2 as const;
const day = new Date('2023-11-13') as const;
const set = new Set([1,2,3]) as const;
const maxNum = Math.max(...[10,20,30]) as const;
// ok
const num = 1 as const;
const weekends = ['Sunday', 'Saturday'] as const;
```
2.如果物件的屬性有reference
,那const斷言不
會對那個屬性生效
```js
let arr = ['chocolate']; // string[]
let arr2 = ['coffe'] as const; // let arr2 = ['coffe'] as const;
let person = {
name: "Derek",
favorite: arr,
hate: arr2,
hobby: ['gardening'],
} as const;
// {
// readonly name: "Derek";
// readonly favorite: string[];
// readonly hobby: readonly ["gardening"];
// }
// Property 'push' does not exist on type 'readonly ["gardening"]'.
person.hobby.push('swimming');
// 可以push的原因是arr並沒有被用as const鎖死型別,所以可以修改
person.favorite.push('strawberry');
// Cannot assign to 'favorite' because it is a read-only property.
// 不可以重新將favorite屬性賦值,因為person被用as const鎖死型別
person.favorite = ['apple'];
// Property 'push' does not exist on type 'readonly ["coffe"]'.
// 不可以push是因為arr2被用as const鎖死型別
person.hate.push('milk');
```
infer關鍵字是什麼
使用infer關鍵字(keyword)可以讓TS自動從推斷型別參數推斷出
一個型別,並傳給變數
,可以在限縮型別的同時讓型別變得更彈性
用於條件型別(conditional type),能解決深度巢狀的條件型別
(a? b : c? d : e? f : g)
enum Desserts {
Cake = 'chocolate cake',
Jelly = 'organge jelly',
IceCream = 'macha ice cream'
}
// 攤平陣列取得element的型別
type Flatten<Type> = Type extends (infer Item)[] ? Item : Type;
// Desserts[]是型別參數
// Type被extends限制必須要是陣列,如果Type是陣列,TS會從它推斷出其中的元素的型別,即Desserts,並將Desserts賦予給Item變數
type Sweets = Flatten<Desserts[]>; // Desserts
用於推斷return value型別、parameter型別
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never;
type Num = GetReturnType<() => number>;
// 相當於
type Num2 = ReturnType<() => number>;
type GetParameterType<Type> = Type extends (arg: infer Args) => any ? Args : Type;
type Args = GetParameterType<(a:number, b: string) => void>
// 相當於
type Params = Parameters<(a:number, b: string) => void>;
ComponentProps的實作
React也提供了util type,只要傳component
給它就可以取得其props型別
import {ComponentProps} from 'react';
type PropsOfMyButton = ComponentProps<typeof MyButton>; // MyButtonProps
type PropsOfButton = ComponentProps<"button">; // ClassAttributes<HTMLButtonElement> & ButtonHTMLAttributes<HTMLButtonElement>
type Try = ComponentProps<123>; // {}
// 原始碼
type ComponentProps<
T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> = T extends JSXElementConstructor<infer P> ? P :
T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : {};
JSX.IntrinsicElements是在 JSX 中可以使用的HTML 元素 (ex: button、span)
JSXElementConstructor則是在 JSX 中可以使用的 React 組件 (ex: function component、class component)
以上的程式碼的意思是,如果傳入的型別參數是組件,那就回傳其props。如果不是的話,檢查是否為HTML元素,是的話就回傳該HTML元素的所有屬性 (ex: style、type、name、value...),如果不是的話回傳{}
infer關鍵字限制
不是conditional type時
,infer不能寫在
extend後(型別參數的限制子句
)
// bad
// 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
type Item<T extends (infer U)[]> = U;
參考資料
TS - const assertions
TS - Inferring Within Conditional Types
TS - Literal Types
TS - readonly Tuple Types