Interface

5개의 게시글

TypeScript

[TypeScript] 인터페이스 구현하기 (implements)

November 27, 2025

타입스크립트의 인터페이스(Interface) 는 객체의 타입을 정의하는 용도뿐만 아니라, 클래스의 설계도(Blueprint) 역할도 수행할 수 있다. 쉽게 말해, "이 클래스는 적어도 이 프로퍼티와 이 메서드는 무조건 가지고 있어야 해!"라고 강제하는 것이다. 이때 사용하는 키워드가 바로 implements다. 1. 인터페이스로 설계도 만들기 먼저 게임 캐릭터를 만든다고 가정하고, 캐릭터라면 가져야 할 필수 요소를 인터페이스로 정의해보자. TypeScript interface CharacterInterface { name: string; // 이름 moveSpeed: number; // 이동 속도 move(): void; // 이동 메서드 } 이제 이 인터페이스는 하나의 약속(Contract) 이 된다. "누구든 CharacterInterface를 구현하려면 name, moveSpeed, move()를 반드시 가지고 있어야 한다"는 규칙이 생긴 것이다. --- 2. 클래스에서 구현하기 (implements) 클래스 이름 뒤에 implements 키워드와 인터페이스 이름을 적어주면 된다. TypeScript class Character implements CharacterInterface { // 생성자 매개변수에 접근 제어자를 붙여 필드 선언과 초기화를 한 번에 해결 constructor( public name: string, public moveSpeed: number, private extra: string // 인터페이스에 없는 건 마음대로 추가 가능 ) {} // 인터페이스에 정의된 메서드 구현 move(): void { console.log(${this.moveSpeed} 속도로 이동!); } } 이제 Character 클래스는 CharacterInterface의 규칙을 따르게 된다. 만약 인터페이스에 정의된 프로퍼티나 메서드를 하나라도 빼먹으면 에러가 발생한다. 구현 시 주의할 점 (접근 제어자) 인터페이스에 정의된 프로퍼티나 메서드는 클래스에서 구현할 때 무조건 public 이어야 한다. 인터페이스는 기본적으로 "외부에 노출되는 인터페이스(접점)"를 정의하는 것이기 때문에, 이를 private이나 protected로 숨길 수 없다. TypeScript interface CharacterInterface { name: string; } class Character implements CharacterInterface { // ❌ 에러: 인터페이스의 속성은 private일 수 없음 // private name: string; constructor(public name: string) {} // ✅ public은 가능 } > 참고: 인터페이스에 정의되지 않은 추가 필드(private extra 등)는 private이나 protected로 자유롭게 설정할 수 있다. > --- 3. 요약 1. 설계도 역할: 인터페이스를 사용해 클래스가 가져야 할 필드와 메서드의 구조를 정의할 수 있다. 2. implements: 클래스가 특정 인터페이스를 준수하도록 강제하는 키워드다. 3. 규칙: 인터페이스에 정의된 속성은 클래스에서 반드시 구현해야 하며, 접근 제어자는 public 이어야 한다.

TypeScriptStudyClassInterfaceOOP
TypeScript

[TypeScript] 인터페이스 선언 합침 (Declaration Merging)

November 27, 2025

타입스크립트에서 타입 별칭(Type Alias) 은 동일한 스코프 내에서 같은 이름으로 중복 선언하는 것이 불가능하다. TypeScript type Person = { name: string; }; // ❌ 에러 발생: 중복된 식별자 'Person' // type Person = { // age: number; // }; 하지만 인터페이스(Interface) 는 가능하다. 심지어 에러가 나지 않을 뿐만 아니라, 알아서 하나로 합쳐진다. 1. 선언 합침이란? 동일한 이름으로 정의된 여러 개의 인터페이스가 컴파일 시점에 자동으로 하나의 인터페이스로 합쳐지는 기능을 말한다. TypeScript // 첫 번째 선언 interface Person { name: string; } // 두 번째 선언 (에러 없음 ✅) interface Person { age: number; } 위 코드는 내부적으로 다음과 같이 하나의 인터페이스로 합쳐진다. TypeScript // 결과적으로 이렇게 됨 interface Person { name: string; age: number; } 따라서 Person 타입의 변수를 생성할 때는 두 인터페이스에 정의된 모든 프로퍼티를 구현해야 한다. TypeScript const person: Person = { name: "조태민", age: 25, }; 이 기능은 주로 외부 라이브러리의 기존 타입 정의에 내가 필요한 속성을 추가하고 싶을 때(예: Window 객체 확장 등) 유용하게 사용된다. --- 2. 주의할 점: 프로퍼티 충돌 인터페이스가 합쳐질 때, 만약 동일한 이름의 프로퍼티가 양쪽에 존재한다면 어떻게 될까? 1. 타입이 같은 경우: 문제 없다. (그냥 합쳐짐) 2. 타입이 다른 경우: ❌ 에러가 발생한다 (충돌). TypeScript interface Person { name: string; } interface Person { // ❌ 에러 발생! // 후속 속성 선언의 타입은 반드시 기존 선언의 타입과 같아야 한다. // 'string' 형식이어야 하는데 'number' 형식이다. name: number; age: number; } 첫 번째 Person에서는 name을 string으로, 두 번째 Person에서는 number로 정의했다. 이렇게 동일한 프로퍼티를 서로 다른 타입으로 정의하면 충돌로 간주되어 선언 합침이 허용되지 않는다. --- 요약 1. 타입 별칭은 중복 선언이 불가능하지만, 인터페이스는 가능하다. 2. 선언 합침: 같은 이름의 인터페이스는 자동으로 하나로 합쳐진다. 3. 충돌 주의: 합쳐지는 과정에서 동일한 프로퍼티 이름인데 타입이 다르면 에러가 발생한다.

TypeScriptStudyInterfaceDeclarationMerging
TypeScript

[TypeScript] 인터페이스 확장 (Interface Extension)

November 27, 2025

타입스크립트에서 인터페이스 확장이란, 하나의 인터페이스를 다른 인터페이스들이 상속받아 중복된 프로퍼티를 다시 정의하지 않도록 도와주는 문법이다. 객체지향 프로그래밍의 '상속' 개념과 매우 유사하다. 1. 왜 확장이 필요한가? (중복의 문제) 만약 확 기능을 사용하지 않고 여러 동물의 타입을 정의한다면 어떻게 될까? TypeScript interface Animal { name: string; age: number; } interface Dog { name: string; // 중복 age: number; // 중복 isBark: boolean; } interface Cat { name: string; // 중복 age: number; // 중복 isScratch: boolean; } interface Chicken { name: string; // 중복 age: number; // 중복 isFly: boolean; } Dog, Cat, Chicken 모두 Animal의 특징(name, age)을 가지고 있다. 이렇게 되면 코드가 중복될 뿐만 아니라, 유지보수가 매우 힘들어진다. 만약 Animal의 age를 ages로 수정해야 한다면? 나머지 3개의 인터페이스를 일일이 찾아다니며 모두 수정해야 하는 대참사가 일어난다. --- 2. 인터페이스 확장 사용하기 (extends) 이럴 때 extends 키워드를 사용하면 효율적으로 타입을 정의할 수 있다. TypeScript interface Animal { name: string; color: string; } // Animal을 확장(상속)받음 interface Dog extends Animal { breed: string; } interface Cat extends Animal { isScratch: boolean; } interface Chicken extends Animal { isFly: boolean; } 이제 Dog, Cat, Chicken은 Animal에 정의된 name과 color 프로퍼티를 자동으로 갖게 된다. TypeScript const dog: Dog = { name: "춘식이", // Animal에서 물려받음 color: "brown", // Animal에서 물려받음 breed: "리트리버", // Dog만의 프로퍼티 }; 이때 상속을 해주는 Animal은 슈퍼 타입(부모), 상속을 받는 Dog는 서브 타입(자식) 이 된다. --- 3. 프로퍼티 재정의 (Overriding) 확장과 동시에 부모에게 물려받은 프로퍼티의 타입을 재정의할 수도 있다. TypeScript interface Animal { name: string; color: string; } interface Dog extends Animal { name: "도로롱"; // string -> "도로롱" (Literal Type)으로 재정의 breed: string; } ⚠️ 주의할 점: 타입 호환성 규칙 프로퍼티를 재정의할 때는 반드시 원본 타입의 서브 타입으로만 재정의할 수 있다. TypeScript interface Animal { name: string; color: string; } interface Dog extends Animal { name: number; // ❌ 에러 발생! breed: string; } 왜 안 될까?Dog가 Animal을 확장한다는 것은 Dog가 Animal의 서브 타입(자식)이 된다는 뜻이다. 하지만 name을 number로 바꿔버리면, Dog는 더 이상 Animal(name이 string인 집합)에 포함될 수 없게 된다. 따라서 원본 타입의 범위를 벗어나는 재정의는 불가능하다. --- 4. 타입 별칭(Type Alias) 확장하기 재미있는 점은 인터페이스가 인터페이스뿐만 아니라 타입 별칭으로 정의된 객체도 확장할 수 있다는 것이다. TypeScript type Animal = { name: string; color: string; }; // 타입 별칭을 인터페이스가 확장 interface Dog extends Animal { breed: string; } --- 5. 다중 확장 (Multiple Extension) 여러 개의 인터페이스를 동시에 확장하는 것도 가능하다. 콤마(,)를 사용하면 된다. TypeScript interface Dog { name: string; isBark: boolean; } interface Cat { name: string; isScratch: boolean; } // 개이면서 동시에 고양이인 혼종(?) 타입 interface DogCat extends Dog, Cat {} const dogCat: DogCat = { name: "개냥이", isBark: true, isScratch: true, }; --- 요약 1. 인터페이스 확장 (extends): 중복 코드를 줄이고 유지보수성을 높여준다. 2. 프로퍼티 재정의: 가능하지만, 원본 타입의 서브 타입 범위 내에서만 가능하다. 3. 유연성: 타입 별칭(type)도 확장할 수 있으며, 여러 인터페이스를 한 번에 확장(다중 확장)할 수도 있다.

TypeScriptStudyInterfaceExtends
TypeScript

[TypeScript] 인터페이스(Interface)

November 27, 2025

타입스크립트에는 객체의 타입을 정의하는 방법이 크게 두 가지가 있다. 하나는 앞서 배운 타입 별칭(Type Alias) 이고, 다른 하나는 이번에 다룰 인터페이스(Interface) 이다. 인터페이스는 타입에 이름을 지어주는 또 다른 문법으로, 특히 객체의 구조를 정의하는 데 특화되어 있다. 1. 기본 문법 인터페이스는 interface 키워드를 사용하여 정의한다. 타입 별칭과 문법만 조금 다를 뿐, 기본적인 기능은 거의 같다. TypeScript // 인터페이스 정의 interface Person { name: string; age: number; } // 변수에 타입 주석으로 사용 const person: Person = { name: "조태민", age: 25, }; --- 2. 프로퍼티 설정 객체 리터럴 타입에서 배웠던 선택적 프로퍼티나 읽기 전용 프로퍼티도 동일하게 사용할 수 있다. 2-1. 선택적 프로퍼티 (Optional Property) 프로퍼티 이름 뒤에 ?를 붙여서, 해당 속성이 없어도 에러가 나지 않게 설정할 수 있다. TypeScript interface Person { name: string; age?: number; // 있어도 되고 없어도 됨 } const person: Person = { name: "조태민", // age는 생략 가능 }; 2-2. 읽기 전용 프로퍼티 (Readonly Property) 프로퍼티 이름 앞에 readonly를 붙여서, 초기화 이후 값을 수정할 수 없게 만든다. TypeScript interface Person { readonly name: string; // 읽기 전용 age?: number; } const person: Person = { name: "조태민", age: 25, }; // person.name = "홍길동"; // ❌ 에러 발생 (수정 불가) --- 3. 메서드 타입 정의와 오버로딩 인터페이스 내부에서 메서드의 타입을 정의하는 방법은 두 가지가 있다. 어떤 방식을 쓰느냐에 따라 오버로딩 가능 여부가 달라지므로 주의해야 한다. 3-1. 함수 타입 표현식 (화살표 함수 형태) 가장 일반적인 형태지만, 메서드 오버로딩을 구현할 수 없다는 단점이 있다. TypeScript interface Person { sayHi: () => void; // sayHi: (a: number) => void; // ❌ 오버로딩 불가능 (에러 발생) } 3-2. 호출 시그니처 (Call Signature) 메서드 이름 뒤에 괄호를 여는 방식이다. 이 방식을 사용하면 메서드 오버로딩을 구현할 수 있다. TypeScript interface Person { sayHi(): void; sayHi(a: number): void; // ✅ 오버로딩 가능 sayHi(a: number, b: number): void; // ✅ 오버로딩 가능 } --- 4. 하이브리드 타입 (Hybrid Type) 자바스크립트에서는 함수도 객체이므로, 함수이면서 동시에 객체(프로퍼티를 가짐) 인 형태를 만들 수 있다. 인터페이스로도 이를 정의할 수 있다. TypeScript interface Func2 { (a: number): string; // 호출 시그니처 (함수로서의 역할) b: boolean; // 프로퍼티 (객체로서의 역할) } const func: Func2 = (a) => "hello"; func.b = true; --- 5. 주의할 점 (타입 별칭과의 차이) 인터페이스는 타입 별칭(type)과 거의 비슷하게 동작하지만, Union(|)이나 Intersection(&) 타입을 직접 정의할 수는 없다. TypeScript // 타입 별칭은 가능 type Type1 = number | string; // 인터페이스는 불가능 (문법 오류) // interface Person { ... } | number // ❌ 만약 인터페이스와 유니온/인터섹션을 함께 사용하고 싶다면, 타입 별칭을 이용해 합치거나 타입 주석에서 직접 사용해야 한다. TypeScript interface Person { name: string; age: number; } // 타입 별칭을 이용해 결합 type Type1 = number | string | Person; // 변수 선언 시 결합 const person: Person & string = { name: "조태민", age: 25, // ... string 메서드 등 }; --- 요약 1. 인터페이스: 객체의 구조를 정의하는 문법이다 (interface Name { ... }). 2. 프로퍼티: 선택적(?), 읽기 전용(readonly) 설정이 가능하다. 3. 메서드: - func: () => void (함수 타입 표현식) -> 오버로딩 불가 ❌ - func(): void (호출 시그니처) -> 오버로딩 가능 ✅ 4. 한계: 인터페이스 자체적으로 Union이나 Intersection을 생성할 수 없다 (타입 별칭과 조합하여 해결).

TypeScriptStudyInterfaceObject
TypeScript

[TypeScript] 객체 타입의 호환성과 초과 프로퍼티 검사

November 27, 2025

지난 포스팅에서 "타입은 집합이다" 라는 개념을 통해 업캐스팅(자식→부모)은 가능하고, 다운캐스팅(부모→자식)은 불가능하다는 것을 배웠다. 이 원칙은 객체(Object) 타입에서도 똑같이 적용된다. 하지만 객체는 프로퍼티의 개수에 따라 부모-자식 관계가 조금 헷갈릴 수 있다. 이를 명확히 짚어보자. 1. 객체 타입의 호환성 간단한 예제로 시작해 보자. ts type Animal = { name: string; color: string; }; type Dog = { name: string; color: string; breed: string; // 추가된 프로퍼티 }; 여기서 Animal과 Dog 중 어느 것이 슈퍼 타입(부모) 이고, 어느 것이 서브 타입(자식) 일까? 정답은 Animal이 슈퍼 타입이다. 얼핏 보면 Dog가 프로퍼티가 더 많으니 슈퍼 타입 같지만, 집합의 관점에서 보면 반대다. - Animal: name과 color만 있으면 다 포함된다. (더 넓은 범위) - Dog: name, color에 breed까지 있어야 한다. (더 좁고 구체적인 범위) 따라서 Dog는 Animal의 부분 집합(서브 타입)이 된다. 업캐스팅 적용 ts let animal: Animal = { name: "기린", color: "yellow", }; let dog: Dog = { name: "돌돌이", color: "brown", breed: "진도", }; animal = dog; // ✅ OK (업캐스팅: Dog -> Animal) // dog = animal; // ❌ NO (다운캐스팅: Animal -> Dog) dog 변수는 Animal이 필요로 하는 name과 color를 모두 가지고 있다. 따라서 Animal 타입으로 취급해도 아무런 문제가 없다. 이것이 구조적 타이핑(Structural Typing) 의 핵심이다. --- 2. 또 다른 예시 (Book) 이해를 돕기 위해 예제 하나를 더 보자. ts type Book = { name: string; price: number; }; type ProgrammingBook = { name: string; price: number; skill: string; }; 여기서도 Book이 슈퍼 타입, ProgrammingBook이 서브 타입이다. ts let book: Book; let programmingBook: ProgrammingBook = { name: "한 입 크기로 잘라먹는 리액트", price: 33000, skill: "reactjs", }; book = programmingBook; // ✅ OK // programmingBook = book; // ❌ NO --- 3. 예외: 초과 프로퍼티 검사 (Excess Property Check) 그런데 타입스크립트에는 업캐스팅임에도 불구하고 에러를 발생시키는 특수한 규칙이 있다. 바로 초과 프로퍼티 검사다. 문제 상황 위의 Book 예제를 그대로 사용하여, 변수에 객체 리터럴을 직접 할당해 보자. ts let book2: Book = { name: "한 입 크기로 잘라먹는 리액트", price: 33000, skill: "reactjs", // ❌ 에러 발생! (Object literal may only specify known properties...) }; "어? skill이 있어도 name과 price가 있으니까 Book 타입에 들어갈 수 있는 거(업캐스팅) 아닌가?" 논리적으로는 맞다. 하지만 타입스크립트는 변수를 초기화할 때 '객체 리터럴'을 직접 사용하면, 타입에 정의되지 않은 초과된 프로퍼티가 있는지 엄격하게 검사한다. 이를 방지하기 위한 안전장치다. 해결 방법: 변수에 담아서 할당하기 이 검사는 객체 리터럴을 직접 대입할 때만 발동한다. 따라서 값을 다른 변수에 미리 담아둔 뒤 할당하면 검사를 피할 수 있다. ts // 미리 만들어둔 변수 (ProgrammingBook 타입으로 추론되거나 명시됨) let programmingBook = { name: "한 입 크기로 잘라먹는 리액트", price: 33000, skill: "reactjs", }; // 변수를 할당하면 초과 프로퍼티 검사가 발동하지 않음 let book3: Book = programmingBook; // ✅ OK 함수 인수의 경우 함수에 인수를 전달할 때도 동일하게 적용된다. ts function func(book: Book) {} // 1. 객체 리터럴 직접 전달 -> 검사 발동 (에러) func({ name: "리액트", price: 33000, // skill: "reactjs", // ❌ 에러 발생 }); // 2. 변수에 담아서 전달 -> 검사 회피 (성공) func(programmingBook); // ✅ OK --- 요약 1. 객체 타입 호환성: 프로퍼티가 더 적은 쪽이 슈퍼 타입(부모), 더 많은 쪽이 서브 타입(자식) 이다. 2. 구조적 타이핑: 서브 타입 객체를 슈퍼 타입 변수에 할당하는 것(업캐스팅)은 언제나 가능하다. 3. 초과 프로퍼티 검사: 단, 객체 리터럴을 직접 할당할 때는 타입에 없는 속성이 있으면 에러가 난다. 이를 피하려면 변수에 담아서 할당하면 된다.

TypeScriptFrontEndStudyTypeSystemInterface