μΆμ² : https://toss.tech/article/typescript-type-compatibility
μλ νμΈμ! μ€λμ νμ μ€ν¬λ¦½νΈλ₯Ό μ¬μ©νλ©΄μ λ λ§μ£ΌμΉμ§λ§, λ§μ μ€λͺ νλ €λ©΄ ν·κ°λ¦¬λ 'νμ νΈνμ±(Type Compatibility)'μ λν΄ κΉμ΄ νκ³ λ€μ΄ λ³΄λ €κ³ ν©λλ€. λ§μΉ¨ ν μ€ κΈ°μ λΈλ‘κ·Έμ μ¬λΌμ¨ νμ μ€ν¬λ¦½νΈμ νμ νΈνμ±, μ λλ‘ μκ³ κ³μ κ°μ?λΌλ κΈμ μ½κ³ ν° λμμ λ°μμ, μ κ° κ³΅λΆνκ³ μ΄ν΄ν λ΄μ©μ λ°νμΌλ‘ λΈλ‘κ·Έμ μ λ¦¬ν΄ λ³΄κΈ°λ‘ νμ΅λλ€.

π€ νμ νΈνμ±μ΄λ? μ μ€μν κΉ?
νμ μ€ν¬λ¦½νΈμμ νμ νΈνμ±μ΄λ, 'νΉμ νμ μ κ°μ΄ λ€λ₯Έ νμ μ κ°μΌλ‘ μ·¨κΈλ μ μλκ°?'λ₯Ό νλ¨νλ κ·μΉμ λλ€. κ°λ¨ν λ§ν΄ AλΌλ νμ μ λ³μμ BλΌλ νμ μ κ°μ ν λΉν μ μλμ§ κ²°μ νλ κ²μ΄μ£ .
let name: string = "μ¨λ¦¬μ€";
let age: number = 30;
// name = age; // Error! Type 'number' is not assignable to type 'string'.
μ μμ μ²λΌ string νμ μ numberλ₯Ό ν λΉν μ μλ κ²μ λ무λ λΉμ°ν΄ 보μ λλ€. νμ§λ§ νμ μ΄ κ°μ²΄λ ν¨μμ²λΌ 볡μ‘ν΄μ§κΈ° μμνλ©΄, νμ μ€ν¬λ¦½νΈκ° μ΄λ€ κΈ°μ€μΌλ‘ νΈνμ±μ νλ¨νλμ§ λͺ νν μλ κ²μ΄ μ€μν΄μ§λλ€. μ΄ μ리λ₯Ό μ λλ‘ μ΄ν΄ν΄μΌ μμΈ‘ λΆκ°λ₯ν νμ μλ¬λ₯Ό μ€μ΄κ³ , νμ μ€ν¬λ¦½νΈμ κ°λ ₯ν νμ μΆλ‘ κΈ°λ₯μ 100% νμ©ν μ μμ΅λλ€.
π¦ ν΅μ¬ μμΉ: ꡬ쑰μ νμ΄ν (Structural Typing)
νμ μ€ν¬λ¦½νΈ νμ μμ€ν μ κ°μ₯ ν° νΉμ§μ ꡬ쑰μ νμ΄νμ λ°λ₯Έλ€λ μ μ λλ€. μ΄λ¦μ΄ μ€μν κ²μ΄ μλλΌ, κ°μ§κ³ μλ μμ±(ꡬ쑰)μ΄ μ€μνλ€λ μλ―Έμ λλ€.
μ΄λ νν 'λ νμ΄ν(Duck Typing)'μΌλ‘ λΉμ λ©λλ€. "μ€λ¦¬μ²λΌ κ±·κ³ , μ€λ¦¬μ²λΌ κ½₯κ½₯κ±°λ¦°λ€λ©΄, κ·Έκ²μ μ€λ¦¬μΌ κ²μ΄λ€."λΌλ λ§μ²λΌμ.
interface Person {
name: string;
}
interface Developer {
name: string;
skill: string;
}
let person: Person;
let developer: Developer = { name: "λ°₯", skill: "TypeScript" };
// κ°λ₯! Developerλ Personμ΄ μꡬνλ 'name' μμ±μ κ°μ§κ³ μμΌλ―λ‘.
person = developer;
// λΆκ°λ₯! Personμ Developerκ° μꡬνλ 'skill' μμ±μ΄ μμΌλ―λ‘.
// developer = person; // Error! Property 'skill' is missing in type 'Person'...
μ μ½λμμ Developerλ Person μΈν°νμ΄μ€λ₯Ό implements νμ§ μμμ΅λλ€. νμ§λ§ Developerλ Personμ΄ νμλ‘ νλ λͺ¨λ μμ±(name)μ κ°μ§κ³ μκ³ , μ¬μ§μ΄ skillμ΄λΌλ μμ±μ μΆκ°λ‘ λ κ°μ§κ³ μμ΅λλ€. λ°λΌμ νμ μ€ν¬λ¦½νΈλ Developerλ₯Ό Person νμ μ ν λΉν μ μλ€κ³ νλ¨ν©λλ€. λ λ§μ μμ±μ κ°μ§ νμ μ λ μ μ μμ±μ κ°μ§ νμ μ ν λΉνλ κ²μ μμ νκΈ° λλ¬Έμ΄μ£ .
β¨ μ κΉ! κ·Έλ°λ° μ΄κ±΄ μ μλ¬κ° λ κΉ? - μ΄κ³Ό μμ± κ²μ¬
ꡬ쑰μ νμ΄ν μμΉλ§ μκ°νλ©΄ μλ μ½λλ λ¬Έμ κ° μμ΄ λ³΄μ λλ€.
interface Person {
name: string;
}
function greet(person: Person) {
console.log(`Hello, ${person.name}`);
}
// μλ¬ λ°μ!
// Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'Person'.
// Object literal may only specify known properties, and 'age' does not exist in type 'Person'.
greet({ name: "찰리", age: 40 });
λΆλͺ ageλΌλ μ΄κ³Ό μμ±μ΄ μκΈ΄ νμ§λ§, Personμ΄ μꡬνλ name μμ±μ λ§μ‘±νλλ° μ μλ¬κ° λ κΉμ?
μ΄λ νμ μ€ν¬λ¦½νΈκ° κ°μ²΄ 리ν°λ΄ μ λν΄ μ΄κ³Ό μμ± κ²μ¬(Excess Property Checks)λΌλ νΉλ³ν κ²μ¬λ₯Ό μννκΈ° λλ¬Έμ λλ€. νμ μ€ν¬λ¦½νΈλ κ°λ°μκ° μλ§λ age μμ±μ μΆκ°νλ €λ κ² μλλΌ, μμ± μ΄λ¦μ name λμ naemμ²λΌ μ€νλ₯Ό λμ κ°λ₯μ±μ΄ λλ€κ³ νλ¨νκ³ μ΄λ₯Ό μλ¬λ‘ μλ €μ£Όλ κ²μ λλ€. μΌμ’ μ μμ μ₯μΉμΈ μ μ΄μ£ .
μ΄ κ²μ¬λ₯Ό νΌνκ³ μΆλ€λ©΄, κ°μ²΄ 리ν°λ΄μ λ³μμ λ¨Όμ ν λΉνλ©΄ λ©λλ€.
const charlie = { name: "찰리", age: 40 };
greet(charlie); // OK!
charlie λ³μμ ν λΉλλ μκ° 'μ μ ν(fresh)' κ°μ²΄ 리ν°λ΄μ΄ μλκ² λλ―λ‘, μ΄κ³Ό μμ± κ²μ¬λ₯Ό 건λλ°κ³ ꡬ쑰μ νμ΄ν κ·μΉλ§μΌλ‘ νμ μ κ²μ¬νκ² λ©λλ€.
π€― κ°μ₯ ν·κ°λ¦¬λ λΆλΆ: ν¨μμ νμ νΈνμ±
ν¨μμ νμ νΈνμ±μ κ°μ²΄λ³΄λ€ μ‘°κΈ λ 볡μ‘ν©λλ€. λ§€κ°λ³μμ λ°ν νμ , λ κ°μ§λ₯Ό λͺ¨λ κ³ λ €ν΄μΌ νκΈ° λλ¬Έμ΄μ£ .
1. λ°ν νμ (Return Type) - 곡λ³μ± (Covariance)
λ°ν νμ μ νΈνμ±μ κ°μ²΄μ λΉμ·ν΄μ μ§κ΄μ μ λλ€. ν λΉνλ €λ ν¨μμ λ°ν νμ μ΄ ν λΉλ ν¨μμ λ°ν νμ λ³΄λ€ λ ꡬ체μ μΈ(μμ) νμ μ΄μ΄μΌ ν©λλ€.
interface Person {
name: string;
}
interface Developer extends Person {
skill: string;
}
let personFactory: () => Person;
let developerFactory: () => Developer;
// κ°λ₯! Developerλ Personμ μλΈνμ
μ΄λ―λ‘.
personFactory = developerFactory;
// λΆκ°λ₯! Personμ Developerμ λͺ¨λ μμ±μ λ§μ‘±νμ§ λͺ»νλ―λ‘.
// developerFactory = personFactory;
personFactoryλ Personμ λ°ννλ ν¨μλ₯Ό λ΄μ λ³μμ λλ€. μ¬κΈ°μ Developerλ₯Ό λ°ννλ developerFactoryλ₯Ό ν λΉνλ κ²μ μμ ν©λλ€. Developerλ Personμ λͺ¨λ μμ±μ ν¬ν¨νκΈ° λλ¬Έμ, personFactoryλ₯Ό νΈμΆν μͺ½μμλ κΈ°λνλ name μμ±μ λ¬Έμ μμ΄ μ¬μ©ν μ μμ΅λλ€.
μ΄λ₯Ό 곡λ³μ±(Covariance)μ΄λΌκ³ ν©λλ€. νμ μ μμ κ΄κ³ λ°©ν₯κ³Ό ν λΉ κ°λ₯μ±μ λ°©ν₯μ΄ κ°λ€λ μλ―Έμ λλ€.
2. λ§€κ°λ³μ (Parameters) - λ°κ³΅λ³μ± (Contravariance)
λ§€κ°λ³μλ λ°ν νμ κ³Ό μ λ°λμ λλ€. ν λΉνλ €λ ν¨μμ λ§€κ°λ³μ νμ μ΄ ν λΉλ ν¨μμ λ§€κ°λ³μ νμ λ³΄λ€ λ μΆμμ μΈ(ν°) νμ μ΄μ΄μΌ ν©λλ€.
interface Person {
name: string;
}
interface Developer extends Person {
skill: string;
}
let greetPerson: (person: Person) => void;
let greetDeveloper: (dev: Developer) => void;
// λΆκ°λ₯!
// greetPerson = greetDeveloper;
// κ°λ₯!
greetDeveloper = greetPerson;
greetDeveloperλ Developer νμ μ μΈμλ‘ λ°λ ν¨μμ λλ€. μ¬κΈ°μ Person νμ μ μΈμλ‘ λ°λ greetPersonμ ν λΉνλ κ²μ΄ μ κ°λ₯ν κΉμ?
greetDeveloper λ³μμ greetPerson ν¨μλ₯Ό ν λΉνλ€κ³ κ°μ ν΄ λ΄ μλ€. μ΄μ greetDeveloperλ₯Ό νΈμΆνλ©΄ μ€μ λ‘λ greetPersonμ΄ μ€νλ©λλ€.
// greetDeveloper λ³μμλ greetPerson ν¨μκ° λ΄κ²¨μλ€.
greetDeveloper({ name: "λ°μ΄λΈ", skill: "React" });
greetDeveloperλ₯Ό νΈμΆνλ μͺ½μμλ μ½μλλ‘ Developer κ°μ²΄λ₯Ό μΈμλ‘ λκΈΈ κ²μ λλ€. μ΄ κ°μ²΄λ μ€μ μ€νλ greetPerson ν¨μλ‘ μ λ¬λ©λλ€. greetPerson ν¨μλ Person νμ μ μΈμλ₯Ό κΈ°λνλλ°, Developerλ Personμ΄ μꡬνλ name μμ±μ κ°μ§κ³ μμΌλ―λ‘ μ무 λ¬Έμ κ° μμ΅λλ€.
λ°λμ κ²½μ°λ₯Ό μκ°ν΄λ³΄λ©΄ μ μλλμ§ λͺ νν΄μ§λλ€. λ§μ½ greetPerson λ³μμ greetDeveloper ν¨μλ₯Ό ν λΉνλ€λ©΄?
// greetPerson λ³μμ greetDeveloper ν¨μκ° λ΄κ²¨μλ€κ³ κ°μ (μ€μ λ‘λ μλ¬)
greetPerson({ name: "μ΄λΈ" });
greetPersonμ νΈμΆνλ μͺ½μμλ Person κ°μ²΄λ₯Ό λκΈΈ κ²μ λλ€. μ΄ κ°μ²΄λ μ€μ μ€νλ greetDeveloper ν¨μλ‘ μ λ¬λ©λλ€. νμ§λ§ greetDeveloperλ skill μμ±μ κ°μ§ Developer κ°μ²΄λ₯Ό κΈ°λνλλ°, Person κ°μ²΄μλ skill μμ±μ΄ μμΌλ―λ‘ λ°νμ μλ¬κ° λ°μν μ μμ΅λλ€.
μ΄μ²λΌ λ§€κ°λ³μμ νμ νΈνμ±μ μμ κ΄κ³μ λ°©ν₯κ³Ό λ°λμ΄κΈ° λλ¬Έμ λ°κ³΅λ³μ±(Contravariance)μ΄λΌκ³ ν©λλ€.
π‘ μ κΉ! μ΄λ³μ±(Bivariance)λ μμ΄μ tsconfig.jsonμμ "strictFunctionTypes": false μ΅μ μ μ¬μ©νλ©΄ λ§€κ°λ³μμμ 곡λ³μ±κ³Ό λ°κ³΅λ³μ±μ λͺ¨λ νμ©νκ² λλλ°, μ΄λ₯Ό μ΄λ³μ±(Bivariance)μ΄λΌκ³ ν©λλ€. νμ μμ μ±μ μν΄ trueλ‘ μ€μ νλ κ²μ΄ κ°λ ₯νκ² κΆμ₯λ©λλ€.
βοΈ νμ νΈνμ± μ μ΄νκΈ°: Branded Type
λλ‘λ κ΅¬μ‘°κ° κ°λλΌλ μλμ μΌλ‘ νμ μ ꡬλΆνκ³ μΆμ λκ° μμ΅λλ€. μλ₯Ό λ€μ΄, UserIdμ PostIdλ λ λ€ number νμ μ΄μ§λ§, μλ‘ μ λ μμ¬μλ μ λλ κ°μ λλ€. μ΄λ Branded Type (λλ Nominal Type) κΈ°λ²μ μ¬μ©ν μ μμ΅λλ€.
type UserId = number & { readonly __brand: "UserId" };
type PostId = number & { readonly __brand: "PostId" };
function getUserId(id: number): UserId {
return id as UserId;
}
function getPostId(id: number): PostId {
return id as PostId;
}
function getUser(id: UserId) {
// ...
}
const userId = getUserId(1);
const postId = getPostId(1);
getUser(userId); // OK
// getUser(postId); // Error! Type 'PostId' is not assignable to type 'UserId'.
μ€μ λ‘λ μ‘΄μ¬νμ§ μλ __brand μμ±μ νμ μ μΆκ°ν¨μΌλ‘μ¨, κ°μ number νμ μ΄λΌλ ꡬ쑰μ μΌλ‘ λ€λ₯΄κ² λ§λ€μ΄ νμ μ€ν¬λ¦½νΈκ° λ νμ μ ꡬλΆνλλ‘ λ§λλ νΈλ¦μ λλ€.
κΈμ λ§μΉλ©°
νμ μ€ν¬λ¦½νΈμ νμ νΈνμ±, νΉν ν¨μμ 곡λ³μ±κ³Ό λ°κ³΅λ³μ±μ μ²μμλ λ¨Έλ¦Ώμμμ κ·Έλ¦Όμ΄ μ κ·Έλ €μ§μ§ μλ μ΄λ €μ΄ κ°λ μΌ μ μμ΅λλ€. νμ§λ§ μ΄ μ리λ₯Ό μ΄ν΄νκ³ λλ©΄ μ νμ μ€ν¬λ¦½νΈκ° νΉμ μν©μμ μλ¬λ₯Ό λ°μμν€λμ§, κ·Έλ¦¬κ³ μ΄λ»κ² νμ μ λ μμ νκ² μ€κ³ν μ μλμ§μ λν κΉμ ν΅μ°°μ μ»κ² λ©λλ€.
μ μ°ν¨κ³Ό μμ μ± μ¬μ΄μμ μ λ¬ν κ· νμ μ‘κ³ μλ νμ μ€ν¬λ¦½νΈμ μΈκ³, μ λ§ λ§€λ ₯μ μ΄μ§ μλμ? μ λ μ΄λ² κΈ°νμ νμ μμ€ν μ λν΄ λ κΉμ΄ 곡λΆν μ μμμ΅λλ€. μ΄ κΈμ΄ μ¬λ¬λΆμ νμ μ€ν¬λ¦½νΈ μ¬μ μ μμ λμμ΄ λμμΌλ©΄ μ’κ² μ΅λλ€.