2025년 3월 4일
함수를 매개변수의 개수나 타입에 따라 여러가지 버전으로 정의하는 방법
자바스크립트에서는 안되고, 타입스크립트에서만 지원
// 매개변수 없음
void func() {
printf('매개변수 없음');
}
// 매개변수 1개
void func(int a) {
printf(a + 20);
}
// 매개변수 2개
void func(int a, int a) {
printf(i + j);
}
func();
func1(1);
func2(1, 2);
함수 오버로딩을 구현하려면 가장 먼저 버전들을 알려줘야 함
그리고 구현할 부분을 써줌
오버로드 시그니처를 만들어두면, 실제 구현부의 매개변수의 타입들은 호출할 때 큰 영향을 미치지 않음 -> 버전을 여러개 만들어서 버전에 따라 호출하게 만들 수 있음
/**
* 함수 오버로딩
* 하나의 함수를 매개변수의 개수나 타입에 따라
* 여러가지 버전으로 만드는 문법
* -> 하나의 함수 func
* -> 모든 매개변수의 타입 number
* -> Ver1. 매개변수 1개 -> 이 매개변수에 20을 곱한 값 출력
* -> Ver2. 매개변수 3개 -> 이 매개변수들을다 더한 값을 출력
*/
// 버전들 -> 오버로드 시그니처
function func(a: number): void;
function func(a: number, b: number, c: number): void;
// 실제 구현부 -> 구현 시그니처
function func() {}
// func(); 오류
func(1);
// func(1, 2); 오류
func(1, 2, 3);
오버로드 시그니처의 매개변수의 개수가 다르다면, 구현 시그니처의 매개변수를 선택적 매개변수로 설정해 모든 오버로드 시그니처가 구현 시그니처에 해당할 수 있게 만들어줘야함
// 버전들 -> 오버로드 시그니처 (함수의 구현부 없이 선언식만 써놓은 것)
function func(a: number): void;
function func(a: number, b: number, c: number): void;
// 실제 구현부 -> 구현 시그니처
function func(a: number, b?: number, c?: number) {
if (typeof b === 'number' && typeof c === 'number') {
console.log(a + b + c);
} else {
console.log(a * 20);
}
}
func(1);
func(1, 2, 3);
프로퍼티 이름을 기준으로 타입을 좁히면, 직관적으로 좋지 않고 프로퍼티가 중간에 이름이 바뀌면 이상한 타입으로 추론됨
type Dog = {
name: string;
isBarked: boolean;
}
type Cat = {
name: string;
isScratch: boolean;
}
type Animal = Dog | Cat;
function warning(animal: Animal) {
if('isBark' in animal) {
animal // Animal & Record<'isBark', unknown>
} else if('isScratch' in animal) {
animal
}
}
이럴 때 사용하면 좋은게 -> '사용자 정의 타입 가드'
type Dog = {
name: string;
isBark: boolean;
}
type Cat = {
name: string;
isScratch: boolean;
}
type Animal = Dog | Cat;
function isDog(animal: Animal) {
return (animal as Dog).isBark !== undefined
}
function warning(animal: Animal) {
if(isDog(animal)) {
animal // animal -> 타입 좁히기 X
} else if('isScratch' in animal) {
// 고양이
}
}
함수 자체를 타입 가드의 역할을 하도록 만들어줘야함
function isDog(animal: Animal): animal is Dog {
return (animal as Dog).isBark !== undefined
}
function isCat(animal: Animal): animal is Cat {
return (animal as Cat).isScratch !== undefined
}
function warning(animal: Animal) {
if(isDog(animal)) {
animal // Dog
} else if('isScratch' in animal) {
animal // Cat
}
}
타입에 이름을 지어주는 또 다른 문법
// 타입 별칭
type A = {
a : string;
b : number;
}
// 인터페이스
interface A {
a : string;
b : string;
}
인터페이스 : 상호 간에 약속된 규칙
객체의 구조를 정의하는데 특화된 문법
타입 별칭과 문법만 다를 뿐, 기본적인 기능은 같음
= 타입 별칭처럼 기본적으로 사용하면 됨
메서드 프로퍼티의 타입을 정의할 때는 함수 표현식이나 호출 시그니처 둘 다 사용해도 됨
호출 시그니처를 사용할 때는 메서드의 이름이 소괄호 앞에 붙는 다는 것 기억해야함!
type Func = {
(): void;
};
따라서, 메서드의 타입도 소괄호 앞에 이름을 붙여주지 않고, 호출 시그니처만 정의해주면 타입 자체를 함수 타입으로 인식 해서 sayHi와 같은 메서드 타입을 정의할 때는 이름을 붙여줘야함!!
interface Person {
readonly name: string;
age?: number;
// 메서드 타입 정의
sayHi: () => void; // 함수 타입 표현식
// sayHi(): void; 호출 시그니처를 사용해도 됨
// 메서드의 이름이 소괄호 앞에 붙음!!
}
const person: Person = {
name: '이정환',
sayHi: function () {
console.log('Hi');
}
}
// person.name = '홍길동' 오류
함수 오버 로딩을 메서드에 구현하고 싶을 때는 함수 타입 표현식을 쓰지 않고, 호출 시그니처를 사용해야 함!
interface Person {
readonly name: string;
age?: number;
sayHi(): void;
sayHi(a: number, b: number): void;
}
const person: Person = {
name: '이정환',
sayHi: function () {
console.log('Hi');
}
}
// person.name = '홍길동' 오류
person.sayHi();
person.sayHi(1, 2);
타입 별칭과 몇 가지 차이점이 있음
type Type1 = number | string | Person;
type Type2 = number & string | Person;
const person: Person | number = {
name: '이정환',
sayHi: function () {
console.log('Hi');
},
};
보통 인터페이스의 이름을 정의할 때, 'I'를 붙여서 정의하기도 함 (헝가리안 표기법) / 강사님은 안붙이는 걸 개인적으로 선호한다고함 -> 팀 따라가기
서브타입을 보면 중복된 프로퍼티가 반복 되는 것을 볼 수 있고, 수정하기라도 한다면 하나씩 다 수정해줘야함
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;
}
이럴 때 사용하는 것 -> 인터 페이스 확장
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal{
isBark: boolean;
}
const dog: Dog = {
name: '',
age: 1,
isBark: true
}
interface Cat extends Animal{
isScratch: boolean;
}
interface Chicken extends Animal{
isFly: boolean;
}
확장 -> 상속
상속을 받는 인터페이스에서 동일한 프로퍼티 타입을 다시 정의할 수 있음
Dog 타입의 name 프로퍼티를 string literal 타입으로 수정하면 변수 dog의 name 값이 오류가 발생하는 것을 볼 수 있음
그런데, 아무 타입으로 다시 정의할 수는 없음 -> 원본 타입의 서브 타입이어야함 (Animal의 name이 string 타입이라서 Dog의 name을 '돌돌이'라고 정의 할 수 있음)
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
name: number;
isBark: boolean;
}
const dog: Dog = {
name: '', // 오류 발생
age: 1,
isBark: true
}
인터페이스로 만든 객체 타입 말고, 타입 별칭으로 확장할 수도 있음
type Animal = {
name: string;
age: number;
}
interface Dog extends Animal {
isBark: boolean;
}
다중 확장
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
isBark: boolean;
}
interface Cat extends Animal {
isScratch: boolean;
}
interface DogCat extends Dog, Cat {}
const dogCat: DogCat = {
name: '',
age: 1,
isBark: true,
isScratch: true
}
선언 합침 (declaration merging)
동일한 이름으로 타입 별칭을 사용하면 오류가 발생하지만, 인터페이스는 오류가 발생하지 않음
type Person = {
name: string;
} // 오류 발생
type Person = {
age: number;
} // 오류 발생
interface Person2 {
name: string;
}
interface Person2 {
age: number;
}
동일한 이름으로 정의한 인터페이스는 결국 다 합쳐지기 때문!
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = {
name: '',
age: 27
}
but! 동일한 프로퍼티를 중복 정의하는데, 타입을 다르게 정의하는 경우에는 충돌이 발생함 (인터페이스의 프로퍼티의 타입을 서로 동일하게 적용해줘야함)
interface Person {
name: string;
}
interface Person {
name: string;
age: number;
}
inteface Developer extends Person {
name: 'hello';
}
const person: Person = {
name: '',
age: 27
}
보통 선언 합침은 라이브러리의 타입 정의가 부실한 경우, '모듈 보강' 이라는 작업을할 때 사용함
interface Lib {
a: number;
b: number;
}
interface Lib {
c: string;
}
const lib = {
a: 1,
b: 2,
c: 'hello'
}