[TS] Snippets
ENUM
假設我們的 enum 是這樣:
enum GENDER {
MALE = 'male',
FEMALE = 'female',
}
把 enum 的 values 變成 union type(values of enum to type )
Typescript: string literal union type from enum @ StackOverflow
在 Template Literal Types 出來之後,要把 enum 的 values 變成 union type 非常簡單:
// get type from values of enum
type valueAsUnion = `${GENDER}`; // "male" | "female"
把 enum 的 keys 變成 union type (keys of enum to type)
// get type from keys of enum
type keyAsUnion = keyof typeof GENDER; // "MALE" | "FEMALE"
之所以可以這樣使用,是因為 typeof GENDER
會是該 enum 的物件形式:
const gender: typeof GENDER = {
MALE: GENDER.MALE,
FEMALE: GENDER.FEMALE,
};
把 enum 的 values 變成 object key(values of enum to object key)
keywords: enum value as key
可以使用 Record
:
enum GENDER {
MALE = 'male',
FEMALE = 'female',
}
type Gender = Record<GENDER, string>;
// type Gender = {
// male: string;
// female: string;
// }
也可以使用 Mapped Type:
enum GENDER {
MALE = 'male',
FEMALE = 'female',
}
type Gender = { [P in GENDER]: string };
// type Gender = {
// male: string;
// female: string;
// }
不使用 ENUM
延伸閱讀
Why it is not good to use enums? @ StackOverflow
使用 Array 搭配 literal String
const brands = ['Apple', 'Samsung', 'Sony', 'Xiaomi'] as const;
type Brand = typeof brands[number];
使用 const object
const BRAND = Object.freeze({
APPLE: 'Apple',
SAMSUNG: 'Samsung',
XIAOMI: 'Xiaomi',
SONY: 'Sony',
} as const);
type Brand = typeof BRAND[keyof typeof BRAND];
錯誤處理
- 把
err
定義成unknown
- 搭配
err instanceof Error
這個 type guard
function foobar() {}
try {
foobar();
} catch (err: unknown) {
if (err instanceof Error) {
console.log(err.stack);
} else {
console.log(err);
}
}
解構賦值後搭配 as
const allPostsData = fileNames.map((fileName) => {
// ...
return {
id,
...(matterResult.data as { date: string; title: string }),
};
});
JSON type
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| {
[k: string]: JSONValue;
};
const result: JSONValue = {
name: 'Aaron',
languages: ['zh-TW', 'en-US'],
};
帶有預設欄位的 function options
interface Person {
fullName: string;
}
// GetPerson 這個 type 可以接收 options,預設定好的 options 包括 `firstName` 和 `lastName`
// 但可以透過 T 自行添加額外的 option
// 若不給 T 的話,新添加的 options value 都會是 unknown
type GetPerson<T extends Record<string, unknown> = Record<string, unknown>> = (
options: { firstName: string; lastName: string } & T,
) => Person & T;
const getPerson: GetPerson<{ age: number }> = (options) => {
const { firstName, lastName, age } = options;
return {
fullName: `${firstName} ${lastName}`,
age: age,
};
};
const person = getPerson({ firstName: 'Aaron', lastName: 'Chen', age: 32 });
console.log(person.age);
帶有預設欄位的 object
keywords: object
, dictionary
, indexed signatures
在下面的例子中,home
和 office
會是必填的屬性,但因為使用 indexed signature / dictionary,所以可以在該物件中添加額外的屬性
/* Index Signatures or Dictionary Type */
interface PhoneNumberDict {
// 因為物件的 key 可能沒有對應的 value,因此要記得加上 undefined
[numberName: string]:
| undefined
| {
areaCode: number;
num: number;
};
// 搭配 indexed signatures 使用時,表示這兩個屬性必須存在
home: {
areaCode: number;
num: number;
};
office: {
areaCode: number;
num: number;
};
}
const phoneDict: PhoneNumberDict = {
// home: {areaCode: 321, num: 333}, // 可以避免 typo
home: { areaCode: 123, num: 456 },
office: { areaCode: 321, num: 456 },
// 因為有定義 indexed signatures,所以可以添加額外的屬性
registered: { areaCode: 666, num: 333 },
};
isDefined
const isDefined = <T>(x: T | undefined): x is T => {
return typeof x !== 'undefined';
};
const foo = ['a', 'b', undefined];
const stringArray = foo.filter(isDefined);
Case Convert
幾個值得參考的 library:
幾個可以使用的 utility type:
CamelCase
透過 ToCamelCase
可以把字串型別的內容轉成 camel case:
// CamelCase: a type utility to transform a string into a camelCase
type ToCamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
: Lowercase<S>;
// 使用 CamelCase utility type
type FooBar = CamelCase<'foo_bar'>; // type FooBar = "fooBar"
更精簡的寫法:
type ToCamelCase<S extends string> = S extends `${infer Head}_${infer Tail}`
? `${Uncapitalize<Head>}${Capitalize<ToCamel<Tail>>}`
: Uncapitalize<S>;
KeysToCamelCase
KeysToCamelCase
可以把物件的 key 轉成 camel-case:
// 需要搭配上面的 ToCamelCase
type Dict = { [key: string]: any };
type KeysToCamelCase<T extends Dict | readonly any[]> = T extends readonly any[]
? {
[P in keyof T]: KeysToCamelCase<T[P]>;
}
: T extends Dict
? {
[P in keyof T as `${ToCamelCase<string & P>}`]: T[P] extends Dict
? KeysToCamelCase<T[P]>
: T[P];
}
: T;
使用 KeysToCamelCase
:
type Obj = KeysToCamelCase<{
major_name: string;
contact_person: {
first_name: string;
last_name: string;
};
phone_books: {
first_name: string;
last_name: string;
}[];
}>;
// 會變成
type Obj = {
majorName: string;
contactPerson: {
firstName: string;
lastName: string;
};
phoneBooks: {
firstName: string;
lastName: string;
}[];
};