๐ง 01. ํ์ ์คํฌ๋ฆฝํธ ๋ฉํ ๋ชจ๋ธ: JS์ TS์ ์์ฌ์์ฌํ ๊ท์น
๐ ๊ฐ์
๋ฐํ์๊ณผ ๋น๋ํ์ ๊ตฌ๋ถ, ๊ตฌ์กฐ์ ํ์ดํ ๋ฑ TS์ ํต์ฌ ๋์ ์๋ฆฌ ํ์ ํ๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 10๋ถ(์ ์ฒด) / ํต์ฌ ํํธ๋ง: 5๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[๋ฐํ์๊ณผ ๋น๋ํ์์ ์ดํด] โ [๊ตฌ์กฐ์ ํ์ดํ(Duck Typing)์ ๋น๋ฐ] โ [ํ์
์คํฌ๋ฆฝํธ์ ๋ฐฐ์ (any)]
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- ๋ธ๋ผ์ฐ์ ์์ ์คํ๋๋ ์ฝ๋์ TS ์ปดํ์ผ๋ฌ๊ฐ ๊ฒ์ฌํ๋ ์ฝ๋์ ์ฐจ์ด๋ฅผ ์ค๋ช ํ ์ ์๋ค.
- ๊ตฌ์กฐ์ ํ์ดํ์ด ์ ์ค๋ฌด์์ ์ ์ฉํ์ง (๊ทธ๋ฆฌ๊ณ ์ ๊ฐ๋ ์ํํ์ง) ์ดํดํ๋ค.
- ์์๋ค ์ปค๋ฎค๋ํฐ ์ฝ๋์์ ํํ ๋ฐ์ํ๋ ํ์ ๊ตฌ๋ฉ์ ์ด๋ป๊ฒ ๋ง๋์ง ์ฒด๋ํ๋ค.
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ ( ์ ์
): "๋ฆฌ๋ ๋! ๋ฐฉ๊ธ ๋ฐฐํฌํ ํ๋กํ ์์ ๊ธฐ๋ฅ์์
Cannot read properties of undefined (reading 'nickname')์๋ฌ ๋๋๋ฐ์?! ์ปดํ์ผํ ๋๋ ์๋ฌด ์๋ฌ ์์๋จ ๋ง์ด์์!" - ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "์์ฒ ๋, ํ์ ์คํฌ๋ฆฝํธ๋ ๊ฑฐ์ง๋ง์ ํ์ง ์์์. ๋ค๋ง ์์ฒ ๋์ด API ์๋ต๊ฐ์ ํ์ ์คํฌ๋ฆฝํธ์๊ฒ ๊ฑฐ์ง๋ง๋ก ์๋ ค์คฌ์ ๋ฟ์ด์ฃ . ๋น๋ํ์๊ณผ ๋ฐํ์์ ์ฐจ์ด์ , ์ค๋ ํ์คํ๊ฒ ์ง๊ณ ๋์ด๊ฐ์๋ค."
๐ค ์ ์์์ผ ํ๋๊ฐ: '์๋ฌ'๋ ๋๊ตฌ์ ์๋ชป์ธ๊ฐ?
์์ฒ ์ด์ ์ต์ธํจ์ ๋น์ฐํฉ๋๋ค. VScode์๋ ๋นจ๊ฐ์ค ํ๋ ์์๊ณ , npm run build๋ ์๋ฒฝํ ํต๊ณผํ์ผ๋๊น์. ํ์ง๋ง ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ์์๋ ์์ ์์ด ํ๋ฉด์ด ํ์๊ฒ ๋์ด ๊ฑธ๋ฆฝ๋๋ค. ๋๋์ฒด ๋ฌด์์ด ๋ฌธ์ ์์๊น์?
์ด ํ์์ ์ดํดํ๋ ค๋ฉด, ๋จผ์ ํ์ ์คํฌ๋ฆฝํธ๋ ๋ธ๋ผ์ฐ์ ์์ ์คํ๋๋ ์ง์ง ์ฝ๋๊ฐ ์๋๋ผ๋ ์ฌ์ค์ ๋ผ์ ๋ฆฌ๊ฒ ๋๊ปด์ผ ํฉ๋๋ค. ํ์ ์คํฌ๋ฆฝํธ๋ ๋จ์ง ์ปดํ์ผ๋ฌ(๊ฒ๋ฌธ์)์ผ ๋ฟ, ์ค์ ๋ก ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ฌ๋ฆฌ๋ ๊ฑด 100% ์์ํ ์๋ฐ์คํฌ๋ฆฝํธ์ ๋๋ค.
์ด **๋ฉํ ๋ชจ๋ธ(Mental Model)**์ ์ ๋๋ก ์ ์ฐฉ์ํค์ง ๋ชปํ๋ฉด, 5๋
์ฐจ๊ฐ ๋์ด๋ any์ as๋ฅผ ๋จ๋ฐํ๋ฉฐ ํ์
์คํฌ๋ฆฝํธ์ ๋งค์ผ ์ธ์ฐ๊ฒ ๋ฉ๋๋ค.
๐๏ธ 1. ๋น๋ํ์ vs ๋ฐํ์ (ํํ์ธ๊ณ์ ์ดํด)
๊ฐ์ฅ ํํ๊ฒ ์ฐฉ๊ฐํ๋ ๋ถ๋ถ์ ๋๋ค. ํ์ ํ ์คํธ๋ ์ค๋ก์ง ๊ฐ๋ฐ ํ๊ฒฝ(๋น๋ ํ์) ์๋ง ์กด์ฌํฉ๋๋ค.
๐ฃ ์์ฒ ์ด์ ์คํด ์ฝ๋
// ๐ฃ ์์ฒ : "์ ์ ์ ๋ณด๋ ๋ฌด์กฐ๊ฑด User ํ์
์ด๋๊น ์์ ํ๊ฒ ์ง!"
type User = {
id: number;
nickname: string;
};
// ๋ฐฑ์๋(์์)๊ฐ ๋ณด๋ด์ค ๋ฐ์ดํฐ๋ฅผ ๋ณ์์ ํ ๋น
async function fetchUser() {
const response = await fetch('/api/user/1');
const data: User = await response.json(); // ๐ฃ ์ง์ฅ์ ์์
return data;
}๐ฆ ์ํธ์ ํฉํญ
์ ์ฝ๋์์ const data: User๋ ๋ฐ์ดํฐ ๋ฐํ์ ๊ฒ์ฆ์ด ์๋๋๋ค. ์ด ๋ฌธ์ฅ์ ์ปดํ์ผ๋ฌ์๊ฒ ์ด๋ ๊ฒ ์์ญ์ด๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
"์ผ TS! ๋ด๊ฐ ์ฅ๋ดํ๋๋ฐ ์ API ์๋ต์ ๋ฌด์กฐ๊ฑด User ๋ชจ์์ด์ผ. ๋ฌป์ง๋ ๋ฐ์ง์ง๋ ๋ง๊ณ ํต๊ณผ์์ผ."
์ค์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๋ณํ๋ ์ฝ๋๋ ์ถฉ๊ฒฉ์ ์ผ ๋งํผ ํ๋ฌดํฉ๋๋ค.
// ํธ๋์คํ์ผ ํ์ ์์ JS ์ฝ๋
async function fetchUser() {
const response = await fetch('/api/user/1');
const data = await response.json(); // ํ์
์ด ํ์ ๋ ์์ด ์ฌ๋ผ์ง!
return data;
}๋ง์ฝ ๋ฐฑ์๋์ ์์ ๋์ด ์ค์๋ก nickname ๋์ name์ ๋๊ฒจ์ฃผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์?
- ๋น๋ํ์ (TS): ์์ฒ ์ด๊ฐ
User๋ผ๊ณ ์ฅ๋ดํ์ผ๋ ํต๊ณผ. - ๋ฐํ์ (๋ธ๋ผ์ฐ์ ):
data.nickname์ ์ ๊ทผํ๋ ์๊ฐundefinedํญํ ํฐ์ง!
๐ก TS์ ํ๊ณ: ํ์ ์คํฌ๋ฆฝํธ๋ ๋คํธ์ํฌ ํต์ ๊ฒฐ๊ณผ, ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฐ์ดํฐ, ์ฌ์ฉ์์ ์ ๋ ฅ๊ฐ ๊ฐ์ '๋ฐํ์'์ ๋์ ๋ฐ์ดํฐ๋ฅผ ์ค์ค๋ก ์ ์ ์์ต๋๋ค. ๊ฐ๋ฐ์๊ฐ ์ ํํ ๊ฐ์ด๋ํด์ฃผ๊ฑฐ๋,
Zod๊ฐ์ ๋ฐํ์ ๊ฒ์ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์จ์ผ ํฉ๋๋ค.
๐ฆ 2. ๊ตฌ์กฐ์ ํ์ดํ (Duck Typing): ๋ ์ด๋ป๊ฒ ์๊ฒผ๋?
C#์ด๋ Java ์ถ์ ์ ๊ฐ๋ฐ์๋ค์ด TS๋ฅผ ์ฒ์ ์ ํ ๋ ๊ฐ์ฅ ํท๊ฐ๋ฆฌ๋ ๋ถ๋ถ์
๋๋ค.
ํ์
์คํฌ๋ฆฝํธ๋ ๊ตฌ์กฐ์ ํ์ดํ(Structural Typing) ์ ์ฑํํ๊ณ ์์ต๋๋ค. ์ฝ๊ฒ ๋งํด "์ค๋ฆฌ์ฒ๋ผ ์๊ฒผ๊ณ , ์ค๋ฆฌ์ฒ๋ผ ๊ฝฅ๊ฝฅ๊ฑฐ๋ฆฌ๋ฉด, ๋ ์ค๋ฆฌ๋ค" ๋ผ๋ ๋
ผ๋ฆฌ์
๋๋ค.
์์๋ค ์ปค๋ฎค๋ํฐ ๊ถํ ๋ถ์ฌ ์์
type BoardAdmin = {
internalId: number;
manageBoard: () => void;
};
type AppSuperUser = {
internalId: number;
manageBoard: () => void;
deleteApp: () => void;
};
// ๊ฒ์ํ ๊ด๋ฆฌ์๋ฅผ ํ์ฉํ๋ ํจ์
function grantBoardAccess(admin: BoardAdmin) {
admin.manageBoard();
}
// ๐ฃ ์์ฒ ์ ์ง๋ฌธ
const superMan: AppSuperUser = {
internalId: 100,
manageBoard: () => console.log("๊ฒ์ํ ๊ถํ ํ๋"),
deleteApp: () => console.log("์ฑ ํญํ!"),
};
// "BoardAdmin ํ์
์ด ์๋๋ฐ ์ด๊ฒ ๋ค์ด๊ฐ์?"
grantBoardAccess(superMan); // โ
ํต๊ณผ! (์๋ฌ ์์)grantBoardAccess ํจ์๋ BoardAdmin์ ์๊ตฌํฉ๋๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ AppSuperUser๋ฅผ ๋ฃ์์ต๋๋ค.
๋ค๋ฅธ ์ธ์ด๋ผ๋ฉด ์์(extends) ๊ด๊ณ๊ฐ ๋ช
์๋์ง ์์ ์๋ฌ๋ฅผ ๋ฟ์๊ฒ ์ง๋ง, ํ์
์คํฌ๋ฆฝํธ๋ ํต๊ณผ์ํต๋๋ค.
์์ผ๊น์? superMan์ด ์ต์ํ BoardAdmin์ด ์๊ตฌํ๋ ๋ชจ๋ ์คํ(internalId, manageBoard) ์ ์๋ฒฝํ๊ฒ ๊ฐ์ถ๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ก๊ณ ๋ฌผ์ด ๋ ๋ฌป์ด์์(deleteApp) ๋ฟ์
๋๋ค.
์ด ์ ์ฐ์ฑ ๋๋ถ์ TS๋ ์๋ฐ์คํฌ๋ฆฝํธ์ ๋์ ์ด๊ณ ์์ ๋ถ๋ฐฉํ ๊ฐ์ฒด ํ ๋น ํจํด์ ๋ฌด๋ฆฌ ์์ด ํ์ด๋ผ ์ ์์ต๋๋ค.
๐ซ 3. any์ as: ์ ์ด๊ถ์ ํฌ๊ธฐํ๋ ํ์
์ํคํ
์ฒ ๊ด์ ์์, ํ์
์คํฌ๋ฆฝํธ์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๋ฌด๊ธฐ๋ any๋ ์๋๊ณ Interface๋ ์๋๋๋ค. ๋ฐ๋ก ํ์
์ถ๋ก (Type Inference) ์
๋๋ค.
โ ๋์ ์ (์์ฒ ์ด์ ํ์ ๊ฐ์ ์ฝ์ )
// ๐ฃ ์์ฒ : "๋ TS๊ฐ ๋ถ์ํ๋๊น ๋ชจ๋ ๋ณ์์ ํ์
์ ๋ฌ์์ค ๊ฑฐ์ผ!"
const message: string = "์๋
ํ์ธ์!";
const isLoading: boolean = true;
const userIds: number[] = [1, 2, 3];
const result = "๋น๋ฐ ๋ฌธ์" as any; // ๐ฃ ๊ธํ ๋ ์ฐ๋ ๋ง๋ฅ์ด์ โ ์ข์ ์ (์ํธ์ ์ฐ์ํ ์ถ๋ก )
// ๐ฆ ์ํธ: "TS๋ ์๊ฐ๋ณด๋ค ๋๋ํด์. ๋ช
๋ฐฑํ ๊ฑด ๋๋๊ณ , ํ์ํ ๊ณณ์๋ง ํ์ ์ฃผ์ฃ ."
const message = "์๋
ํ์ธ์!"; // TS๊ฐ ์ด๋ฏธ string์ผ๋ก ์
const isLoading = true; // boolean์ผ๋ก ์
const userIds = [1, 2, 3]; // number[]๋ก ์
// ๋ช
์์ ํ์
์ ์ธ์, ๋ฐํ๊ฐ, ๋ณต์กํ ๊ฐ์ฒด ์์ฑ ์์ ์ง์ค!
function createProfile(name: string, age: number): User {
return { id: 1, nickname: name };
}ํนํ as (Type Assertion)๋ "TS์ผ, ๋ ํ๋ ธ์ด. ๋ฅ์น๊ณ ๋ด๊ฐ ๋ง์ผ๋๊น ๊ฒ์ฌํ์ง ๋ง." ๋ผ๋ ์ค๋งํ ์ ์ธ์
๋๋ค. ์ฌ๋งํ๋ฉด as ์์ด ํ์
์ด ์ ์ฐํ๊ฒ ํ๋ฌ๊ฐ๋๋ก(Flow) ์ฝ๋๋ฅผ ์๊ฒ ์ชผ๊ฐ๋ ๊ฒ์ด ์ง์ง ์ค๋ ฅ์
๋๋ค.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์ธ๋ถ API์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ const data: User = await res.json()์ผ๋ก ํ์ดํํ๋ ๊ฒ์ ์ํ์ฑ์ ๋ฌด์์ธ๊ฐ?
โ ์ ๋ต: ๋น๋ํ์ ๊ฒ์ฌ๋ง ํต๊ณผํ ๋ฟ, ์ค์ ๋ฐํ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ณด์ฅํ์ง ๋ชปํจ.
๐ก ์์ธ ํด์ค:
res.json()์ ํ์ ์คํฌ๋ฆฝํธ ๊ด์ ์์ ๊ธฐ๋ณธ์ ์ผ๋กany๋ฅผ ๋ฐํํฉ๋๋ค.- ๋ณ์์
: User๋ฅผ ๋จ๋ค๊ณ ํด์ ๊ฐ์ฒด ๋ด์ฉ์ด ๋ณ๊ฒฝ๋๋ ๊ฒ์ด ์๋๋๋ค. ๋ฐํ์(๋ธ๋ผ์ฐ์ )์์ ์ค์ ๋กnicknameํ๋กํผํฐ๊ฐ ์์ผ๋ฉด ๋์ค์ ์ ๊ทผ ์ ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
Q2. ์๋ฐ์คํฌ๋ฆฝํธ๋ ๋ฐํ์ ์ธ์ด์ ๋๋ค. ํ์ ์คํฌ๋ฆฝํธ๋ ๋ฐํ์์ ์๋ฌ๋ฅผ ๋ง์์ฃผ๋ ๋ฐฉํจ ์ญํ ์ ์ํํ๋์?
โ ์ ๋ต: ์๋์. ํ์ ์คํฌ๋ฆฝํธ๋ ๋ธ๋ผ์ฐ์ (๋ฐํ์)์์ ์คํ๋์ง ์์ต๋๋ค.
๐ก ์์ธ ํด์ค:
- ํ์ ์คํฌ๋ฆฝํธ์ ๊ฒ์ฌ๋ ๊ฐ๋ฐ ํ๊ฒฝ(๋น๋ ํ์, VSCode ํธ์ง ์ค)๊น์ง๋ง ์ ํจํฉ๋๋ค.
- ์คํ๋๋ ์์ ์๋ ๋ชจ๋
type๊ณผinterface๊ป๋ฐ๊ธฐ ์ฝ๋๊ฐ ์ฆ๋ฐํ๊ณ ์์ํ ์๋ฐ์คํฌ๋ฆฝํธ๋ง ๋จ์ต๋๋ค. ์ด ํํ ์ธ๊ณ๋ฅผ ์ดํดํ๋ ๊ฒ์ด ๋ฉํ ๋ชจ๋ธ์ ์ฒซ ๋จ์ถ์ ๋๋ค.
Q3. [์์ฒ ์ด์ ํ
์คํธ ํ์: ์ค๋ฌด ๋๋ ๋ง] ์์ฒ ์ด๊ฐ ๋์์ธ ์์คํ
์ปดํฌ๋ํธ์ธ Button์ ๋ง๋ค๊ณ ์์ต๋๋ค. ๊ธฐ์กด HTML <button>์ ๋ชจ๋ ์์ฑ(onClick, disabled ๋ฑ)์ ๋ค ๋ฐ์์ค๋ฉด์ ์ปค์คํ
์์ฑ์ธ variant๋ฅผ ์ถ๊ฐํ๊ณ ์ถ์ด ํฉ๋๋ค. "์ด๊ฑฐ Props๋ฅผ ์ผ์ผ์ด ๋ค ์ ์ด์ค์ผ ํ๋์?"๋ผ๊ณ ๊ณ ๋ฏผํ ๋, ๊ตฌ์กฐ์ ํ์ดํ์ ์ดํดํ ์ํธ ๋ฆฌ๋๋ ์ด๋ค ํด๊ฒฐ์ฑ
(๊ฐ๋
)์ ์ ์ํ ๊น์?
โ
์ ๋ต: "์ค๋ฆฌ์ฒ๋ผ ์๊ธด ๊ฑด ๋ค ๋ฐ์์!" (extends๋ฅผ ํตํ HTML attributes ํ์ฅ)
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ๊ตฌ์กฐ์ ํ์ดํ์ ์ ์ฐํจ์ ํ์ฉํ๋ฉด,
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: 'primary' | 'secondary' }ํํ๋ก ์์ฝ๊ฒ HTML ๊ธฐ๋ณธ ์์ฑ์ ์น ๋ค ํก์ํ ์ ์์ต๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋,
onClick?: () => void๋ถํฐonMouseEnter๊น์ง 100๊ฐ๊ฐ ๋๋ ์ด๋ฒคํธ ์์ฑ์ ์์์ ์ผ๋ก ์ ๊ณ ์๋ ๊ฑด ์๋์ฃ ?" - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๋ด๊ฐ ํ์ํ ์คํ(variant)๋ง ์ ์ํ๊ณ , ๋๋จธ์ง๋ ๊ฒ์ฆ๋ ๋ฉ์ด๋ฆฌ(HTMLAttributes)์ ํฉ์น๋ฉด ๊ทธ๋ง์ด๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
๐ก "ํ์ ์คํฌ๋ฆฝํธ๋ ์ฒ์ฌ ๊ฐ์ ๊ฒฝ๋น์์ด์ง๋ง, ์ฑ๋ฌธ์ ํต๊ณผํ ํ์ ์ฌ๊ณ ๋ ์ฑ ์์ ธ ์ฃผ์ง ์๋๋ค."
์ค๋๋ ํ๋ฐํ ๋๋ฆฌ์๋ค. any์ as๋ฅผ ๋์ง๋์ง ๋ฐ๋ผ์ VScode์ ๋นจ๊ฐ ์ค๋ง ์ง์ฐ๋ฉด ๋์ธ ์ค ์์๋ ๋์ ์์ผํ ๊ณผ๊ฑฐ๋ฅผ ๋ฐ์ฑํ๊ฒ ๋ ํ๋ฃจ. ๋ง์น ์์ฃผ ์ธก์ ๋ ์ ํ๊ณ ๋ฌด์กฐ๊ฑด ํต๊ณผ์์ผ ์ฃผ๋ ๊ฒฝ์ฐฐ๊ด์ ์ข์ ์ฌ๋์ด๋ผ๊ณ ์ฐฉ๊ฐํ ๊ฒ๊ณผ ๊ฐ๋ฌ๊น?
๋ด์ผ๋ถํฐ๋ API ์๋ต๊ฐ์ ๋ค๋ฃฐ ๋ ์ ๋ ๋ ์ค์ค๋ก๋ฅผ ๋ฏฟ์ง ๋ง์์ผ๊ฒ ๋ค. ์ง์ ๊ฐ์ ๋ฐํ์ ์ ํจ์ฑ ๊ฒ์ฌํด ์ฃผ๋ Zod๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ข ์ฐพ์๋ณด๊ณ ์์ผ์ง. ์, ์ํธ ๋ฆฌ๋ ๋์ด ํด๊ทผ๊ธธ์ ๋์ ธ์ฃผ์ ๋ฐ๋๋ ์ฐ์ ๋ฌ๋ฌํ๋ค!