2024-05-27.md
π‘DIL: μ΄νν°λΈ νμ μ€ν¬λ¦½νΈ
μ€ν°λ: μκ° CS, https://github.com/monthly-cs/2024-05-effective-typescript
μμ±μΌ: 2024-05-27
μμ±μ: dusunax
4μ₯ νμ μ€κ³
νμ κ³Ό λ‘μ§
- νλ λ λΈλ£©μ€
- μ½λμ νμ : ν μ΄λΈ
- μ½λμ λ‘μ§: μμλ
- νμ
κ³Ό λ‘μ§
- μ°μ°μ΄ μ΄λ£¨μ΄μ§λ λ°μ΄ν°λ λ°μ΄ν° νμ μ μ μ μλ€λ©΄ μ½λλ₯Ό μ΄ν΄ν μ μλ€
- λ°μ΄ν° νμ μ λͺ ννκ² ν΄, μ½λλ₯Ό μ΄ν΄νκΈ° μ½κ² νλ€
μμ΄ν 28: μ ν¨ν μνλ§ νννλ νμ μ μ§ν₯νκΈ° Prefer Types That Always Represent Valid States
- μ ν¨ν μνλ§ ννν μ μλ νμ
μ λ§λλ κ²μ΄ μ€μνλ€
- μ ν¨νμ§ μμ νμ μ΄ μ‘΄μ¬νλ κ²½μ° => νμ μ€κ³κ° μλͺ»λ κ²½μ°
λ¬Έμ μ½λ μμ
async function changePage(state: State, newPage: string) {
state.isLoading = true;
try {
const response = await fetch(getUrlForPage(newPage));
if (!response.ok) {
throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
}
const text = await response.text();
state.isLoading = false;
state.pageText = text;
} catch (e) {
state.error = "" + e;
// isLoadingμ falseλ‘ λ°κΎΈλ λ‘μ§μ΄ λΉ μ Έμμ
// μ€ν μ, μλ¬ μ΄κΈ°νκ° μμ
}
}
| κ²½μ° | μμ | | ------------------------------------------- | -------------------------------------------------- | | μν κ°μ λ κ°μ§ μμ±μ΄ λμμ μ λ³΄κ° λΆμ‘± | μμ²μ΄ μ€ν¨ν κ²μΈμ§ μ¬μ ν λ‘λ© μ€μΈμ§ μ μ μλ€ | | λ κ°μ§ μμ±μ΄ μΆ©λ | μ€λ₯μ΄κ±°λ, λ‘λ© μ€μΌ μ μλ€ |
μνλ₯Ό λͺ μμ μΌλ‘ λͺ¨λΈλ§νλ νκ·Έλ μ λμ¨ μ¬μ©
interface RequestPending {
state: "pending";
}
interface RequestError {
state: "error";
error: string;
}
interface RequestSuccess {
state: "ok";
pageText: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess;
interface State {
currentPage: string;
requests: { [page: string]: RequestState };
}
- μνμ λͺ¨νΈν¨
// renderPageλ΄ μν κ΅¬λΆ κ°μ
function renderPage(state: State) {
const { currentPage } = state;
const requestState = state.requests[currentPage];
switch (requestState.state) {
case "pending":
return `Loading ${currentPage}...`;
case "error":
return `Error! Unable to load ${currentPage}: ${requestState.error}`;
case "ok":
return `<h1>${currentPage}</h1>\n${requestState.pageText}`;
}
}
async function changePage(state: State, newPage: string) {
state.requests[newPage] = { state: "pending" };
state.currentPage = newPage;
try {
const response = await fetch(getUrlForPage(newPage));
if (!response.ok) {
throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
}
const pageText = await response.text();
state.requests[newPage] = { state: "ok", pageText };
} catch (e) {
state.requests[newPage] = { state: "error", error: "" + e };
}
}
- μμ: μ μ μ‘°μ’
μ ν곡기μ κΈ°μ₯ vs λΆκΈ°μ₯ μ€ν± State
- μ μ μ‘°μ’ μ μ€ν±μ κ²½μ°, μ€ν±μ κ°λλ₯Ό νκ· λ΄λ ν¨μλ₯Ό μ¬μ©νλ€
- κΈ°κ³μ μΌλ‘ μ°κ²°λμλ€λ©΄? μν ννμ angleλ§ μ‘΄μ¬νλ©΄ λ¨
- κ²°λ‘ : μ½λμ λ‘μ§μ λΆλͺ
νκ² νμ
- νμ μ μ€κ³ν λ, μ΄λ€ κ°μ ν¬ν¨νκ³ μ΄λ€ κ°μ μ μΈν μ§ μ μ€νκ² μκ°νκΈ°
- μ ν¨ν μν vaid stateλ₯Ό νννλ κ°λ§ νμ©νκΈ°
Things to Remember
- Types that represent both valid and invalid states are likely to lead to confusing and error-prone code.
- μ ν¨ν κ° & μ ν¨νμ§ μμ κ°μ ν¨κ» λνλ΄λ μνκ°μ νΌλκ³Ό μλ¬λ₯Ό λ§λ λ€.
- Prefer types that only represent valid states. Even if they are longer or harder to express, they will save you time and pain in the end!
- μ½λκ° λ κΈΈκ±°λ μ΄λ €μμ§λλΌλ, μ ν¨ν κ°λ§μ λνλ΄λ μ½λλ₯Ό μμ±νλ€.
μμ΄ν 29 μ¬μ©ν λλ λκ·Έλ½κ², μμ±ν λλ μ격νκ² Be Liberal in What You Accept and Strict in What You Produce
- κ²¬κ³ μ± μμΉ robustness principle / ν¬μ€ν
μ λ²μΉ Postel's Law
- be conservative in what you do, be liberal in what you accept from others
- TCP, μ‘΄ ν¬μ€ν Jon Postel
- ν¨μ νΈμΆμ μ½κ² νκΈ°
- ex) μ΅μ λ 맀κ°λ³μ
- λ΄λΆ λ‘μ§μ? μ λμ¨ νμ μ μμλ³ μ½λ λΆκΈ°
- μ¬μ©νκΈ° νΈλ¦¬ν api => λ°ν νμ μ΄ μ격!
μλ°μ€ν¬λ¦½νΈ κ΄λ‘ *-like
- ex) array, array-like
interface LngLat {
lng: number;
lat: number;
}
type LngLatLike = LngLat | { lon: number; lat: number } | [number, number];
interface Camera {
center: LngLat;
zoom: number;
bearing: number;
pitch: number;
}
interface CameraOptions extends Omit<Partial<Camera>, "center"> {
center?: LngLatLike; // centerλ₯Ό LngLatLikeλ‘ λ³κ²½ν Option μμ±
}
type LngLatBounds =
| { northeast: LngLatLike; southwest: LngLatLike }
| [LngLatLike, LngLatLike]
| [number, number, number, number];
// μ’μ μ€κ³κ° μλ
// νμ§λ§ λ€μν νμ
μ νμ©ν΄μΌνλ λΌμ΄λΈλ¬λ¦¬μΌ μ μλ€
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): Camera;
- 맀κ°λ³μμλ λμ¨ν νμ + λ¦¬ν΄ νμ μλ μ κ·ν νμ
function createUser(data: PartialUser): User {
return {
id: generateUniqueId(),
name: data.name || "Unknown",
email: data.email || "unknown@example.com",
age: data.age || 0,
};
}
function updateUser(id: string, data: PartialUser): User {
const existingUser = getUserById(id);
if (!existingUser) {
throw new Error("User not found");
}
return {
...existingUser,
...data,
};
}
const newUser = createUser({ name: "Alice" });
console.log(newUser);
const updatedUser = updateUser("123", { email: "alice@example.com" });
console.log(updatedUser);
Iterable<T>
over T[]
- λ°°μ΄ λμ λ°λ³΅ κ°λ₯ν κ°μ²΄λ₯Ό λ°μ μ μμ΅λλ€.
- iterable: Set, Map, String...
function processItems<T>(items: Iterable<T>): void {
for (const item of items) {
console.log(item);
}
}
// λ°°μ΄
const numberArray: number[] = [1, 2, 3, 4, 5];
processItems(numberArray);
// μ§ν©
const numberSet: Set<number> = new Set([1, 2, 3, 4, 5]);
processItems(numberSet);
// λ¬Έμμ΄
const text: string = "hello";
processItems(text);
// 맡 (key-value μμ λ°°μ΄μ λ°λ³΅ κ°λ₯)
const map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
processItems(map);
Things to Remember
- Input types tend to be broader than output types. Optional properties and union types are more common in parameter types than return types.
- λ체μ μΌλ‘ Input νμ μ΄ Output νμ λ³΄λ€ λλ€. μ΅μ λ μμ±κ³Ό μ λμ¨ νμ μ λ¦¬ν΄ νμ μλ μ¬μ©λμ§ μκ³ λ³΄ν΅ λ§€κ°λ³μλ‘ μ¬μ©λλ€
- Avoid broad return types since these will be awkward for clients to use.
- λ¦¬ν΄ νμ μ λμ νμ X
- To reuse types between parameters and return types, introduce a canonical form (for return types) and a looser form (for parameters).
- 맀κ°λ³μμ λ°ν νμ μ μ¬μ¬μ©μ μν΄μ => 맀κ°λ³μμλ λμ¨ν νν + λ°ν νμ μλ κΈ°λ³Έ νμ
- Use Iterable instead of T[] if you only need to iterate over your function parameter.
- iterableν 맀κ°λ³μλ₯Ό μν΄μ T[] λμ Iterableλ₯Ό μ¬μ©ν μ μλ€.
μμ΄ν 30: λ¬Έμμ νμ μ 보 μ°μ§ μκΈ° Donβt Repeat Type Information in Documentation
- μ½λμ μ£Όμμ΄ λ§μ§ μμΌλ©΄? λ λ€ νλ¦Ό
- νμ
ꡬ문 μμ€ν
μ?
- κ°κ²°νκ³ , ꡬ체μ μ΄λ©°, μ½κ² μ½μ μ μλ€.
- νΉμ 맀κ°λ³μλ₯Ό μ€λͺ
νκΈ° μν΄ JSDocsμ
@param
ꡬ문μ μ¬μ©ν μ μμ
| κ΅¬λΆ | λμ | | --------- | --------------------------------------------- | | μ£Όμ | ꡬν체μμ μ ν©μ±μ΄ μ΄κΈλ μ μλ€ | | νμ ꡬ문 | νμ μ²΄μ»€κ° νμ μ 보λ₯Ό λκΈ°ννλλ‘ κ°μ νλ€ |
νμ μΌλ‘ μ€λͺ νκΈ°
// μ£ΌμμΌλ‘ μ€λͺ
νλ κ²½μ°
/** Sort the strings by numeric value (i.e. "2" < "10"). Does not modify nums. */
function sortNumerically(nums: string[]): string[] {
return nums.sort((a, b) => Number(a) - Number(b));
}
// νμ
μΌλ‘ μ€λͺ
νλ κ²½μ°: κ·μΉμ κ°μ νλ€
function sortNumerically(nums: readonly string[]): string[] {
return nums.sort((a, b) => Number(a) - Number(b));
// ~~~~ ~ ~ Property 'sort' does not exist on 'readonly string[]'
// κ·μΉμ λ§μ§ μκ² λλ¬Έμ μλ¬κ° λ°μνλ€
}
- νμ μ 보λ₯Ό λ³μλͺ μ μ¬μ©νμ§ μλλ‘ νλ€. (λ¨μκ° μλ κ²½μ°λ₯Ό μ μΈ)
| λ³μλͺ | λ΄μ© | | ------ | -------------------------------------------------------- | | ageNum | λ³μλͺ μ ageλ‘ νκ³ νμ μ΄ numberμμ λͺ μνλ κ²μ΄ μ’λ€ | | timeMs | λ³μλͺ μ΄ timeμΌ λλ λ¨μμ λν μ 보λ₯Ό μ μ μλ€ |
Things to Remember
- Avoid repeating type information in comments and variable names. In the best case it is duplicative of type declarations, and in the worst case it will lead to conflicting information.
- μ£Όμκ³Ό νμ μ ν¨κ» λͺ μνλ κ²½μ° => μ€λ³΅ μ 보μ΄κ±°λ, μ ν©μ±μ΄ μ΄κΈλ μ λ³΄μΌ μ μλ€
- νμ μ 보μ λͺ¨μμ΄ λ°μ
- Declare parameters readonly rather than saying that you don't mutate them.
readonly
μ κ²½μ°, μ£ΌμμΌλ‘ λͺ μνμ§ λ§κ³ νμ μΌλ‘ κ°μ νλλ‘ νμ
- Consider including units in variable names if they aren't clear from the type (e.g., timeMs or temperatureC).
- νμ μ΄ λͺ ννμ§ μμ κ²½μ° λ¨μλ₯Ό λ³μλͺ μ ν¬ν¨νλ κ²μ κ³ λ €νλ€