onebite TS - 4강. 함수와 타입

박하늘·2025년 10월 21일

1️⃣ 함수 타입의 정의

// ▪️ 일반 함수
function func(a: number, b: number) {
    return a + b
}

// ▪️ 화살표 함수
const add = (a: number, b: number) => a + b

✴︎ 함수를 설명하는 가장 좋은 방법

  • 어떤 [타입]의 매개변수를 받고, 어떤 [타입]의 결과값을 반환하는 지


▪️ 함수의 매개변수

한 개의 매개변수

function introduce( name = "neul") {
    console.log(`name : ${name}`)
}

introduce(1) // ❌ - 위에서 추론된 매개변수 타입과 다른 타입을 넣음
  • 매개변수에 타입 정의 하지 않고 ( name = "neul") 이렇게 해주면 name 이라는 값은 string 으로 타입 추론 됨
  • ( name: number = "neul" ) 은 불가능

두 개의 매개변수

function introduce2( name = " neul", tall: number) {
    console.log(`name : ${name}`)
    console.log(`tall : ${tall}`)
}

introduce2("neul",170)
introduce2("neul") // ❌ - 매개변수에 두 개가 설정되어 있으면 두 개 모두 다 넣어줘야 함
  • 한 개는 선택적으로 하고 싶다면 매개 변수를 선택적 매개변수로 ( name = " neul", tall?: number) 이렇게 해주면 됨

매개변수에 대하여 숫자 덧셈할 때

[ 잘못된 예시 ]

function introduce3( name = " neul", tall?: number) {
    console.log(`name : ${name}`)
    console.log(`tall : ${tall + 10}`)
}
  • 여기서 tall 값은 선택적 매개변수 → 즉, 이에 대해 위처럼 조건 없이 더해주기만 했을 때 tall은 선택적 매개변수라 입력하지 않으면 undefined 일 수 있기 때문에 조건을 걸어주어야 함

[ 올바른 예시 ]

function introduce4( name = " neul", tall?: number) {
    console.log(`name : ${name}`)
    if ( typeof tall === "number") {
        console.log(`tall : ${tall + 10}`)
    }
}

필수 매개변수

필수 매개변수는 선택적 매개변수 뒤에 올 수 없음

// ❌
function introduce5( name = " neul", tall?: number, gender: string) {}

// ⭕️
function introduce5( name = " neul", gender: string, tall?: number) {}

rest 파라미터

  • ...rest 는 자바스크립트의 문법 가변적 길이의 인수를 전달하면 이걸 배열로 묶어서 모아줌
  • rest 매개변수는 나머지 매개변수라고 하며, 스프레드 연산자처럼 기호 ‘...’으로 표기
  • 스프레드 연산자는 배열이나 객체처럼 반복 가능한 값을 개별 요소로 분리하지만, rest 매개변수는 반대로 개별 요소를 배열로 묶습니다.

function getSum (...rest: number[]) {
    let sum = 0
    rest.forEach((it) => sum += it)

    return sum
}

getSum(1, 2, 3)
getSum(1, 2, 3, 4, 5)



// 만약 3개 숫자라는 배열 길이를 지정해둘거면 ...
function getSum (...rest: [number, number, number]) {}

getSum(1, 2, 3); // ✅ 정상
getSum(1, 2, 3, 4); // ❌ 오류 - 인자가 너무 많음
getSum(1, 2); // ❌ 오류 - 인자가 부족함

⚠️ rest 와 spread 차이

rest:
   (1, 2, 3)   --->   [1, 2, 3]

spread:
   [1, 2, 3]   --->   (1, 2, 3)

예제

function add(a: number, b: number, c: number) {
  return a + b + c;
}

const nums = [1, 2, 3];

// spread: 배열을 풀어서 전달
console.log(add(...nums)); // add(1, 2, 3)

// rest: 받은 인자를 배열로 묶음
function sum(...numbers: number[]) {
  return numbers.reduce((acc, n) => acc + n, 0);
}

console.log(sum(1, 2, 3)); // [1, 2, 3] → 합산



2️⃣ 함수 타입 표현식과 호출 시그니처

▪️ 함수 타입 표현식

타입 별칭을 이용하여 함수의 타입을 별도로 정의하는 문법

✴︎ 타입 별칭을 이용한 함수 타입 정의

type Operation = (a:number, b:number) => number

const add: Operation = (a, b) => a + b
// 함수: 타입별칭 => 함수 타입 표현식

const sub: Operation = (a, b) => a - b
const mlt: Operation = (a, b) => a * b
// 이런 비슷한 케이스의 함수들에 대하여 타입 별칭을 사용하여 표현할 수 있다

✴︎ 타입 별칭 없이 함수에 직접 타입 정의

const add1: (a:number, b:number) => number = (a, b) => a + b
// 이렇게 타입 별칭이 아닌 직접 타입을 지정해줄 수도 있음

const add22: (a:number, b:number) => number = (a, b, c) => a + b // ❌ 오류 - 인자가 너무 많음 ( c는 받지 못함 )

▪️ 호출 시그니처 (콜 시그니처)

type Operation2 = {
    (a: number, b: number): number
}

const add2: Operation2 = (a, b) => a + b
const sub2: Operation2 = (a, b) => a - b
const mlt2: Operation2 = (a, b) => a * b
// 이렇게도 가능


function func(a: number) : void {}
// 🟰
type Operation3 = {
    (a: number) : void
}
// func 와 Operation3 동일한 의미

type Calculator = {
  add(a: number, b: number): number;
  sub(a: number, b: number): number;
};

// 이 문법은 “객체 안에 함수가 여러 개 들어갈 때” 유용



3️⃣ 함수 타입의 호환성

함수 타입의 호환성이란 특정 함수 타입을 다른 함수 타입으로 괜찮은지 판단하는 것을 의미

  • 다음 2가지 기준으로 함수 타입의 호환성을 판단

    - 두 함수의 반환값 타입이 호환되는가?
     - 두 함수의 매개변수의 타입이 호환되는가?

▪️ 기준 1. 두 함수의 반환값 타입이 호환되는가?

type A = () => number;
type B = () => 10;

let a: A = () => 10;
let b: B = () => 10;

a = b; // ✅ - 호환 가능
b = a; // ❌`- 호환 불가능
  • A의 반환값 타입은 Number, B의 반환값 타입은 Number Literal
  • 변수 a에 b를 할당하는 것은 가능하나 반대(다운캐스팅)로는 불가능
  • 업 캐스팅 가능 / 다운 캐스팅 불가

▪️ 기준 2. 매개변수의 타입이 호환되는가?

✴︎ 2-1. 매개변수의 개수가 같을 때

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를 할당하는 것은 불가능
  • dogFunc의 매개변수 타입이 animalFunc 매개변수 타입보다 작은 서브타입이기 때문

⛓️‍💥 animalFunc = dogFunc; 의 예시

let animalFunc = (animal: Animal) => {
  console.log(animal.name);  // ✅
  console.log(animal.color); // ❌
};
// 
  • animalFunc 타입의 매개변수 타입은 Animal 타입
  • dogFunc 함수 내부에서는 name과 color 프로퍼티에 접근합니다.
  • 따라서 이렇게 할당이 이루어지게 되면 animal.color처럼 존재할거라고 보장할 수 없는 프로퍼티에 접근

⛓️‍💥 dogFunc = animalFunc; 의 예시

let dogFunc = (dog: Dog) => {
  console.log(dog.name);
};
  • 다운 캐스팅 가능
  • animal 타입에 가지고 있는 객체를 dog 타입은 전부 다 갖고 있음

✴︎ 2-2. 매개변수의 개수가 다를 때

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; // ❌
  • 매개변수의 타입이 다르면 당연히 안 되지만 타입이 같은 매개변수에서 더 많은 매개변수를 가진 함수가 더 적은 매개변수를 가진 함수에 대입되는 건 불가능



4️⃣ 함수 오버로딩

하나의 함수 이름으로 여러 가지 매개변수 조합(개수·타입)을 처리하는 방법

하나의 함수 - func

- 모든 매개변수의 타입 number
- 매개변수 1개 : 매개변수 * 20 출력값
- 매개변수 3개 : 각 매개변수 전체 합

✴︎ 오버로드 시그니처

가능한 호출 타입만 선언

function func(a: number): void;
function func(a: number, b: number, c: number): void;
  • 위에 함수 func 에 대한 설명처럼 매개변수1개와 3개 받을 수 있는 타입 선언

✴︎ 함수의 실제 구현부 만들기

실제 동작은 “옵셔널 파라미터를 가진 하나의 구현부”에서 처리

function func() {}

function func() {}
// 이렇게 하기만 해도 오버로드 시그니처에 나타나는 에러가 사라짐. 즉, 이를 구현부로 인식

func() // ❌
func(1); // ✅ 1버전과 맞음
func(1, 2); // ❌
func(1, 2, 3); // ✅ 2버전과 맞음 
  • 직관적으로 맨 첫번째만 적용이 되어야 하는데 매개변수가 1개인 거랑 3개인 것만 에러가 없음
  • 이는 오버로드 시그니처를 만들어두었기 때문

옵셔널 구현부

function func(a: number, b: number, c: number) {} // ❌
// 위처럼 모든 매개변수 필수로 할당 시 버전 1(매개변수 1개)이 적용이 안 되므로 b 와 c 에는 옵셔널을 줘야 함

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);



5️⃣ 사용자 정의 타입 가드

참 또는 거짓을 반환하는 함수를 이용해 우리 입맛대로 타입 가드를 만들 수 있도록 도와주는 타입스크립트의 문법

✴︎ in 연산자

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 연산자를 이용해 타입을 좁히는 방식은 좋지 않음
  • 예를 들어 만약 Dog 타입의 프로퍼티가 다음과 같이 중간에 이름이 수정되거나 추가 또는 삭제될 경우에는 타입 가드가 제대로 동작하지 않을 수도 있음

✴︎ 추가 확인 함수 추가

// 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 warning1(animal: Animal) {
  if (isDog(animal)) {
    console.log(animal.isBark ? "짖습니다" : "안짖어요");
  } else {
    console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
  }
} 
  • isDog 함수는 매개변수로 받은 값이 Dog 타입이라면 true 아니라면 false를 반환
  • 이때 반환값의 타입으로 animal is Dog 를 정의하면 이 함수가 true를 반환하면 조건문 내부에서는 이 값이 Dog 타입임을 보장한다는 의미
  • warning 함수에서 isDog 함수를 호출해 매개변수의 값이 Dog 타입인지 확인하고 타입을 좁힘

0개의 댓글