跳至主要内容

[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

使用 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

在下面的例子中,homeoffice 會是必填的屬性,但因為使用 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;
}[];
};