
// 함수를 설명하는 가장 좋은 방법
// 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명
function func(a, b) {
return a + b;
}
// ▼
function func(a: number, b: number): number {
return a + b;
}
참고로 함수의 반환값 타입은 자동으로 추론되기 때문에 다음과 같이 생략 가능하다.
function func(a: number, b: number) {
return a + b;
}
const add = (a: number, b: number): number => a + b;
// 화살표 함수 역시 반환값의 타입은 자동으로 추론
const add = (a: number, b: number) => a + b;
매개변수에 기본값이 설정되어있으면 타입이 자동으로 추론 → 타입 생략 가능
function introduce(name = "채영") {
console.log(`name : ${name}`);
}
기본값과 다른 타입으로 매개변수의 타입을 정의하면 오류가 발생
function introduce(name:number = "채영") {
console.log(`name : ${name}`);
}
기본값과 다른 타입의 값을 인수로 전달해도 오류가 발생
function introduce(name = "채영") {
console.log(`name : ${name}`);
}
introduce(1); // ❌
매개변수의 이름뒤에 물음표(?)를 붙여주면 선택적 매개변수가 되어 생략이 가능
function introduce(name = "채영", tall?: number) {
console.log(`name : ${name}`);
console.log(`tall : ${tall}`);
}
introduce("채영", 158);
introduce("채영");
선택적 매개변수의 타입은 자동으로 undefined와 유니온 된 타입으로 추론
따라서 tall의 타입은 현재 number | undefined이 된다.
그러므로 이 값을 number로 사용하려면 다음과 같이 타입 좁히기가 필요하다.
function introduce(name = "채영", tall?: number) {
console.log(`name : ${name}`);
if (typeof tall === "number") {
console.log(`tall : ${tall + 10}`);
}
}
함수 타입을 타입 별칭과 함께 별도로 정의할 수 있다.
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b;
객체를 정의하듯 함수의 타입을 별도로 정의할 수 있다.
type Operation2 = {
(a: number, b: number): number;
};
const add2: Operation2 = (a, b) => a + b;
const sub2: Operation2 = (a, b) => a - b;
const multiply2: Operation2 = (a, b) => a * b;
const divide2: Operation2 = (a, b) => a / b;
다음과 같이 호출 시그니쳐 아래에 프로퍼티를 추가 정의하는 것도 가능하다.
이렇게 할 경우 함수이자 일반 객체를 의미하는 타입으로 정의되며
이를 하이브리드 타입이라고 부른다.
type Operation2 = {
(a: number, b: number): number;
name: string;
};
const add2: Operation2 = (a, b) => a + b;
(...)
add2(1, 2);
add2.name;
특정 함수 타입을 다른 함수 타입으로 취급해도 괜찮은가를 판단
A와 B 함수 타입이 있다고 가정할 때 A 반환값 타입이 B 반환값 타입의 슈퍼타입이라면 두 타입은 호환
업캐스팅되는 상황에서는 호환가능/ 다운캐스팅되는 상황에서는 호환불가
type A = () => number;
type B = () => 10;
let a: A = () => 10;
let b: B = () => 10;
a = b; // ✅ 업캐스팅
b = a; // ❌ 다운캐스팅
두번째 기준인 매개변수의 타입이 호환되는지 판단할 때에는 두 함수의 매개변수의 개수가 같은지 다른지에 따라 두가지 유형으로 나뉘게 됩니다.
C 매개변수의 타입이 D 매개변수 타입의 서브 타입일 때에 호환
업캐스팅일때는 호환X / 다운캐스팅일때는 호환 가능 (일반적인 타입과 다름)
(이를 전문 용어로 반공변성이라고 한다)
type C = (value: number) => void;
type D = (value: 10) => void;
let c: C = (value) => {};
let d: D = (value) => {};
c = d; // ❌
d = c; // ✅
type Animal = {
name: string;
};
type Dog = {
name: string;
color: string;
};
let animalFunc = (animal: Animal) => {
console.log(animal.name);
};
let dogFunc = (dog: Dog) => {
console.log(dog.name);
console.log(dog.color);
};
animalFunc = dogFunc; // ❌
dogFunc = animalFunc; // ✅

animalFunc = dogFunc를 코드로 표현하면 아래와 같이 할당이 불가하다
따라서 업캐스팅이 불가
let animalFunc = (animal: Animal) => {
console.log(animal.name); // ✅
console.log(animal.color); // ❌
};
type Func1 = (a: number, b: number) => void;
type Func2 = (a: number) => void;
let func1: Func1 = (a, b) => {};
let func2: Func2 = (a) => {};
func1 = func2; // ✅
func2 = func1; // ❌
같은 함수를 매개변수의 개수나 타입에 따라 여러가지 버전으로 만드는 문법
하나의 함수 func에서
// 버전들 -> **오버로드 시그니쳐**
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); // ✅ 버전 1 - 오버로드 시그니쳐
func(1, 2); // ❌
func(1, 2, 3); // ✅ 버전 3 - 오버로드 시그니쳐
함수 오버로딩이란?
사실 실무나 프로젝트를 진행하면서 함수 오버로딩을 쓴 적이 없어서… 오버로딩에 대해 거의 생소했다..
실질적으로 프론트 코딩만 한다면 자주 만날일이 없지만, 자바스크립트에서 지원하고 있고, 추후 내가 무슨 개발을 할지 모르기때문에 중간에 개념정리도 함께 해보려고 한다.
오버로딩이 사용되어야만 하는 순간은?
// 우리가 쓰는 코드 const div = document.createElement('div'); // div 변수는 HTMLDivElement 타입 const img = document.createElement('img'); // img 변수는 HTMLImageElement 타입 // 라이브러리(DOM) 내부 정의 (대략 이런 느낌) function createElement(tagName: "div"): HTMLDivElement; function createElement(tagName: "img"): HTMLImageElement; function createElement(tagName: "canvas"): HTMLCanvasElement; function createElement(tagName: string): HTMLElement; // 그 외실제로 createElement 를 보면
(method) Document.createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K] (+2 overloads)
라고 되어 있다 이 문구는 현재 보여지는 정의 외에 2개의 오버로드 시그니처가 더 존재한다는 뜻이다.
실제로 사용하려면 해당 함수에 한번 사용할때 보통 위에 선언하는 형식으로 사용한다.
보면서 궁금했던 점이 타입 별칭과 뭐가 다르지? 라는 의문이었는데 아래에서 비교해보고자 한다.
A. 오버로드 시그니처 (함수 선언문 자체)
function키워드로 함수를 직접 만들 때 쓴다. 이 시그니처는 이 함수(func)에 종속된다.// [오버로드 시그니처] : 이 함수(func) 전용 메뉴판 function func(a: number): void; function func(a: number, b: number): void; // [구현] function func(a: number, b?: number) { ... }B. 인터페이스 / 타입 별칭 (함수의 타입 정의)
함수의 모양(Type)만 미리 정의해두고, 나중에 변수나 매개변수에 갖다 쓴.
// [인터페이스] : "이런 모양의 함수를 'Calculator'라고 부르자" (설계도) interface Calculator { (a: number): void; (a: number, b: number): void; // 인터페이스 안에서도 오버로딩 표현 가능! } // [사용] : 이 설계도를 가져와서 변수에 적용 const myFunc: Calculator = function(a: number, b?: number) { ... }; const anotherFunc: Calculator = function(a: number, b?: number) { ... };
- 오버로드 시그니처:
function func(...)위에 바로 적어서 그 함수를 직접 정의한다.- 인터페이스/타입:
myFunc: Calculator처럼 변수에 타입을 입혀줄 때 사용한다.
프로젝트를 진행할때도 input에 따라 return이 달라지는 복잡한 유틸리티가 아니면 사용할 일이 많이 없지만 중간 개념 정리차…..

type Dog = {
name: string;
isBark: boolean;
};
type Cat = {
name: string;
isScratch: boolean;
};
type Animal = Dog | Cat;
function warning(animal: Animal) {
if ("isBark" in animal) {
console.log(animal.isBark ? "짖습니다" : "안짖어요");
} else if ("isScratch" in animal) {
console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
}
하지만 in 으로 타입을 가드하는건 좋지않다. 갑자기 isBark가 사라질 수도 있고, 직관적으로도 인지가 좋지않다.
// Dog 타입인지 확인하는 타입 가드
function isDog(animal: Animal): animal is Dog {
return (animal as Dog).isBark !== undefined;
}
// Cat 타입인지 확인하는 타입가드
function isCat(animal: Animal): animal is Cat {
return (animal as Cat).isScratch !== undefined;
}
function warning(animal: Animal) {
if (isDog(animal)) {
console.log(animal.isBark ? "짖습니다" : "안짖어요");
} else {
console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
}
}
만약 반환 타입을 단순히 boolean으로 하면, 타입스크립트는 내부 로직을 수행해도 이 변수가 Dog인지 확신하지 못한다.
: animal is Dog라고 써줌으로써 타입스크립트에게 확실하게 타입을 전달할 수 있다.
: animal is Dog이라는 문법도 결국 타입단언처럼 개발자가 타입스크립트에게 알려주는 상황이기때문에 확실하게 런타임에서 검증하고 싶으면 zod의 .safeParse() 를 사용하면 확실하게 타입을 검증할 수 있다.
import { z } from "zod";
// 1. Zod 스키마
const DogSchema = z.object({
name: z.string(),
isBark: z.boolean(),
});
const CatSchema = z.object({
name: z.string(),
isScratch: z.boolean(),
});
// 2. 타입 추출
type Dog = z.infer<typeof DogSchema>;
type Cat = z.infer<typeof CatSchema>;
type Animal = Dog | Cat;
// 3. 타입 가드
function isDog(animal: Animal): animal is Dog {
const result = DogSchema.safeParse(animal);
return result.success;
}
function isCat(animal: Animal): animal is Cat {
const result = CatSchema.safeParse(animal);
return result.success;
}
// 4. 사용
function warning(animal: Animal) {
if (isDog(animal)) {
// 이제 TS는 여기가 Dog 타입인 걸 확실히 알게된다
console.log(animal.isBark ? "짖습니다" : "안짖어요");
} else if (isCat(animal)) {
console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
}
}