2024-05-25.md
π‘DIL: μ΄νν°λΈ νμ μ€ν¬λ¦½νΈ
μ€ν°λ: μκ° CS, https://github.com/monthly-cs/2024-05-effective-typescript
μμ±μΌ: 2024-05-25
μμ±μ: dusunax
μμ΄ν 26: νμ μΆλ‘ μ λ¬Έλ§₯μ΄ μ΄λ»κ² μ¬μ©λλμ§ μ΄ν΄νκΈ° Understand How Context Is Used in Type Inference
νμ μΆλ‘ Type Inference
- νμ μ μΆλ‘ ν λ => κ° λΏλ§ μλλΌ, κ°μ΄ μ‘΄μ¬νλ κ³³μ λ¬Έλ§₯κΉμ§ μ΄νλ€
λ³μ μ μΈμ 리ν°λ΄ νμ vs κΈ°λ³Έν νμ => λ κ°μ§ ν΄κ²° λ°©λ²
type Language = "JavaScript" | "TypeScript" | "Python";
function setLanguage(language: Language) {} // λ¬Έμμ΄ λ¦¬ν°λ΄ νμ
μ μ λμ¨μΌλ‘ νμ
νΉμ
setLanguage("JavaScript"); // β
let language = "JavaScript"; // νμ
μ μΈ μ, languageμ νμ
μ? string!
setLanguage(language); // β
// ~~~~~~~~ Argument of type 'string' is not assignable
// to parameter of type 'Language'
1. νμ μ μΈμμ λ³μμ κ°λ₯ν κ°μ μ ννλ€.
- νμ΄ν μ€λ₯λ λ°©μ§ν μ μμ
let language: Language = "JavaScript";
setLanguage(language); // β
2. const ν€μλλ₯Ό μ¬μ©νλ€
- νμ 체컀μκ² κ°μ΄ λ³κ²½λμ§ μμμ μλ €μ€λ€. => μ νν νμ μΈ λ¬Έμμ΄ λ¦¬ν°λ΄λ‘ μΆλ‘ ν μ μλ€.
const language = "JavaScript"; // "JavaScript"
setLanguage(language); // β
νν μ¬μ© μ
function panTo(where: [number, number]) {}
panTo([10, 20]);
const loc = [10, 20];
// constλ‘ μ μΈνμ§λ§, number[]λ‘ μΆλ‘ λμλ€
panTo(loc);
// ~~~ Argument of type 'number[]' is not assignable to
// parameter of type '[number, number]'
1. νμ μ μΈμ μ 곡
const loc: [number, number] = [10, 20];
panTo(loc);
2. μμ λ¬Έλ§₯μ μ 곡
- as constλ₯Ό μ¬μ©νμ¬ νμ
체컀μκ² κΉμ μμμμ μλ¦¬κ³ , νμ
μκ·Έλμ²μ readonlyλ₯Ό μΆκ°ν μ μλ€.
- λ§μ½ νμ μκ·Έλμ²λ₯Ό μμ ν μ μλ κ²½μ°λΌλ©΄ νμ ꡬ문μ μ¬μ©ν΄μΌ ν©λλ€.
const shallowLoc = [10, 20]; // μμ μμ
const deeplyLoc = [10, 20] as const; // κΉμ μμ
// ^? const loc: readonly [10, 20]
panTo(loc);
// ~~~ The type 'readonly [10, 20]' is 'readonly'
// and cannot be assigned to the mutable type '[number, number]'
// νμ
μκ·Έλμ²μ readonlyλ₯Ό μΆκ°ν μ μλ€
function panTo(where: readonly [number, number]) {
/* ... */
}
const loc = [10, 20] as const;
panTo(loc); // OK
- λ¨μ : νμ μ μ μ€λ₯κ° νΈμΆλλ κ³³μμ λ°μν¨
const loc = [10, 20, 30] as const; // νμ
μ μ μ€λ₯ μμΉ
panTo(loc); // νμ
μλ¬ λ°μ μμΉ
// ~~~ Argument of type 'readonly [10, 20, 30]' is not assignable to
// parameter of type 'readonly [number, number]'
// Source has 3 element(s) but target allows only 2.
κ°μ²΄λ λ§μ°¬κ°μ§
type Language = "JavaScript" | "TypeScript" | "Python";
interface GovernedLanguage {
language: Language;
organization: string;
}
function complain(language: GovernedLanguage) {}
complain({ language: "TypeScript", organization: "Microsoft" }); // β
const ts = {
language: "TypeScript",
organization: "Microsoft",
};
complain(ts);
// ~~ Argument of type '{ language: string; organization: string; }'
// is not assignable to parameter of type 'GovernedLanguage'
// Types of property 'language' are incompatible
// Type 'string' is not assignable to type 'Language'
// νμ
μ μΈ μΆκ°
const ts: Language = {
language: "TypeScript",
organization: "Microsoft",
};
// μμ λ¨μΈ μ¬μ©
const ts = {
language: "TypeScript",
organization: "Microsoft",
} as const;
μ½λ°± μ¬μ© μ μ£Όμμ
- μ½λ°± ν¨μλ₯Ό λ€λ₯Έ ν¨μλ‘ μ λ¬ν λ, μ½λ°±μ 맀κ°λ³μ νμ μ μΆλ‘ νκΈ° μν΄ λ¬Έλ§₯μ μ¬μ©νλ€
function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
fn(Math.random(), Math.random());
}
callWithRandomNumbers((a, b) => {
// μ½λ°± ν¨μλ₯Ό μΆλ‘ ν μ μμ!
// ^? (parameter) a: number
console.log(a + b);
// ^? (parameter) b: number
});
- κ·Έλ°λ° μ½λ°±μ μμλ‘ λ½μλ΄λ©΄? λ¬Έλ§₯μ΄ μμ€λλ€
- λ§€κ° λ³μμ νμ ꡬ문 μΆκ°νκΈ°
- κ°λ₯ν κ²½μ°, μ 체 ν¨μ ννμμ νμ μ μΈ μ μ©
Things to Remember
- Be aware of how context is used in type inference.
- νμ μΆλ‘ μμ λ¬Έλ§₯μ΄ μ΄λ»κ² μ°μ΄λ μ§ μ£Όμν΄μ μ΄ν΄λ³΄κΈ°
- If factoring out a variable introduces a type error, maybe add a type annotation.
- λ³μλ₯Ό λ½μμ μ μΈνμ λ μ€λ₯κ° λ°μνλ€λ©΄? νμ μ μΈμ μΆκ°νκΈ°
- If the variable is truly a constant, use a const assertion (as const). But be aware that this may result in errors surfacing at use, rather than definition.
- λ³μκ° μ λ§λ‘ μμλΌλ©΄ μμ λ¨μΈ μ¬μ©νκΈ°
- μμ λ¨μΈμ μ¬μ©νλ©΄ μ μν κ³³μ΄ μλλΌ, μ¬μ©ν κ³³μμ μ€λ₯κ° λ°μνλ―λ‘ μ£ΌμνκΈ°
- Prefer inlining values where it's practical to reduce the need for type annotations.
- νμ μ μΈμ μ€μ΄κΈ° μν΄μ, μΈλΌμΈνλ₯Ό νμ. (κ°μ λ³μμ ν λΉν λ, κ°λ₯ν ν κ°μ μ§μ ν λΉνμ¬ νμ μ€ν¬λ¦½νΈκ° μλμΌλ‘ νμ μ μΆλ‘ νλλ‘ νκΈ°)
μμ΄ν 27: ν¨μν κΈ°λ²κ³Ό λΌμ΄λΈλ¬λ¦¬λ‘ νμ νλ¦ μ μ§νκΈ° Use Functional Constructs and Libraries to Help Types Flow
import _ from "lodash";
const rows = rawRows
.slice(1)
.map((rowStr) => _.zipObject(headers, rowStr.split(",")));
- νμ μ€ν¬λ¦½νΈμμλ μλνν° λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν λ, νμ μ 보λ₯Ό μ°Έκ³ νκΈ° λλ¬Έμ μλ°μ€ν¬λ¦½νΈλ³΄λ€ ν° μ΄μ μ΄ μλ€. (μκ° λ¨μΆ)
- λ°μ΄ν°μ κ°κ³΅μ΄ μ κ΅ν μλ‘ μ₯μ μ΄ λΆλͺ
μμ
interface BasketballPlayer {
name: string;
team: string;
salary: number;
}
declare const rosters: { [team: string]: BasketballPlayer[] };
// μ΄λκ°μ rostersκ° μμ!
// νμ
μ μΈ & concat
let allPlayers: BasketballPlayer[] = []; // λ³μλ₯Ό μ μΈν λ, νμ
μ μΈ νμ (noImplicitAny)
for (const players of Object.values(rosters)) {
allPlayers = allPlayers.concat(players); // rosterλ₯Ό BasketballPlayer[]λ‘ λ³κ²½ μ€
}
// ννν flatten
// κ°κ²°νκ³ νμ
ꡬ문λ νμμμ
const allPlayersFlat = Object.values(rosters).flat();
// Loadash
const bestPaid = _(allPlayers) // 1. allPlayers λ°°μ΄μ λ‘λμ 체μΈμΌλ‘ λνν©λλ€.
.groupBy((player) => player.team) // 2. μ μλ€μ νλ³λ‘ κ·Έλ£Ήνν©λλ€.
.mapValues((players) => _.maxBy(players, (p) => p.salary)!) // 3. κ° νμμ κ°μ₯ λμ μ°λ΄μ λ°λ μ μλ₯Ό μ°Ύμ΅λλ€.
.values() // 4. κ°μ²΄μ κ°μ λ°°μ΄λ‘ λ³νν©λλ€.
.sortBy((p) => -p.salary) // 5. μ°λ΄μ κΈ°μ€μΌλ‘ λ΄λ¦Όμ°¨μμΌλ‘ μ λ ¬ν©λλ€.
.value(); // 6. λ‘λμ 체μΈμ ν΄μ νκ³ μ΅μ’
κ²°κ³Όλ₯Ό λ°°μ΄λ‘ λ°νν©λλ€.
- λ΄μ₯λ ν¨μν κΈ°λ²λ€κ³Ό Loadsh λΌμ΄λΈλ¬λ¦¬ (νμ μ λ³΄κ° μ μ μ§λ¨)
- ν¨μ νΈμΆ μ, μ λ¬λ 맀κ°λ³μ κ°μ 건λλ¦¬μ§ μκ³ μλ‘μ΄ κ° λ°ν (체μΈ)
- μλ‘μ΄ νμ μΌλ‘ μμ νκ² λ°ν
- νμ
μ€ν¬λ¦½νΈλ μλ°μ€ν¬λ¦½νΈμ λμμ μ νν λͺ¨λΈλ§νλ € νλ€.
- λΌμ΄λΈλ¬λ¦¬μ νμ μ λ³΄κ° μ μ μ§λλ μ μ νμ©νμ
Things to Remember
- Use built-in functional constructs and those in utility libraries like Lodash instead of hand-rolled constructs to improve type flow, increase legibility, and reduce the need for explicit type annotations.
- λ΄μ₯λ ν¨μν κΈ°λ²κ³Ό μ νΈλ¦¬ν° λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°μ
-
- νμ νλ¦μ κ°μ
-
- κ°λ μ± λμ΄κΈ°
-
- λͺ μμ νμ ꡬ문μ νμμ± μ€μ΄κΈ°
-
// english
zipping μ·¨ν©
munging κ°κ³΅
νμ κ°μ μμ
// λ°νμμ userId is null μ€λ₯ λ°μ!
export const loadUserProfile = (queryClient: QueryClient) => async () => {
const userId = localStorage.getItem("userId");
const preloadUserProfile = await queryClient.ensureQueryData(
getUserProfile(userId || "")
);
// eslint-disable-next-line
// return type λ¨μΈλλ¬Έμ eslint disable μ¬μ©νμ΅λλ€
let groupProfile: AxiosResponse<any, any> | null = null;
const groupId = preloadUserProfile.data.group;
if (groupId) {
groupProfile = await queryClient.ensureQueryData(
getGroupProfile(preloadUserProfile.data.organizationId, groupId)
);
}
return { preloadUserProfile, groupProfile };
};
// νμ
κ°μ
export const loadUserProfile = (queryClient: QueryClient) => async () => {
const userId = localStorage.getItem("userId");
if (!userId) {
logout();
throw new Error("userId is not found in localStorage");
}
const preloadUserProfile = await queryClient.ensureQueryData(
getUserProfile(userId)
);
let preloadGroupProfile = null;
const groupId = preloadUserProfile.data.group;
if (groupId) {
preloadGroupProfile = await queryClient.ensureQueryData(
getGroupProfile(preloadUserProfile.data.organizationId, groupId)
);
}
return { preloadUserProfile, preloadGroupProfile };
};
-
μλ¬ μμΉ
const getUserProfile = async (userId: string) => { if (!userId) return Promise.reject("userId is null"); return await api.get(`users/${userId}`); };
-
μ»΄ν¬λνΈλ¨ μμΈ μΆκ°
export default function MyPage() { const { preloadUserProfile, preloadGroupProfile } = useLoaderData(); if (!preloadUserProfile.data.id) logoutWithError(); const { data: { data: userProfile }, } = useQuery({ ...getUserProfile(preloadUserProfile.data.id || ""), initialData: preloadUserProfile, }); if (!userProfile.data.id) logoutWithError(); }