다다의 개발일지 6v6

[TS] JavaScript + type 체크 = TypeScript 에 대해 알아보자. 본문

Frontend/TypeScript

[TS] JavaScript + type 체크 = TypeScript 에 대해 알아보자.

dev6v6 2025. 2. 24. 19:18

JavaScript + 타입 체크 = TypeScript

js 안에 특수한 ts라고 생각하면 됨. ts ⊂ js

따라서 ts에서는 js가 돌아가지만 js에서는 ts가 돌아가지 않는다고 볼 수 있다.

 

TypeScript는 변수의 데이터 타입을 명확하게 지정해주어 안정성을 높여준다. → 이게 Ts의 제일 큰 특징

 

 

코드 연습 간단하게 할 수 있는 사이트

https://www.typescriptlang.org/play/?#code/Q

 

TS Playground - An online editor for exploring TypeScript and JavaScript

The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.

www.typescriptlang.org

 

 

js의 문제점

let myName = 'dada';

myName = 1; // 타입이 바뀌어 버림. 나중에 버그가 생길 수 있음.

 

ts의 개선방안

(오류를 발생시켜 코딩할 때 버그를 잡을 수 있도록 도와줌)

let myName: string = 'dada';

myName = 1; // 컴파일 에러

 

데이터 타입

기본 데이터 타입

  • number: 숫자 타입으로, 정수와 실수를 포함.
  • string: 문자열 타입.
  • boolean: 참(true)과 거짓(false)을 나타내는 불리언 타입.
  • null: 값이 없음을 나타내는 타입.
  • undefined: 값이 할당되지 않은 변수의 기본값인 타입.

객체 타입

  • object: 객체를 나타내는 타입. { }
    const user: {name: string, age: number, greet(): void} = {
      name: "John",
      age: 25,
      greet() { // 메서드 정의할 때는 : function을 빼야함.
        console.log(`Hello, my name is ${this.name}`);
      }
    };
    
    user.greet(); // Hello, my name is John
    
  • array: 동일한 타입의 요소를 가진 가변적인 길이의 배열을 나타내는 타입. [ ] 
    // 첫번째 방법 :타입 []
    let arr1: number[] = [1, 2, 3];
    // 두번쨰 방법 :Array<타입>
    let arr2: Array<number> = [1, 2, 3];
  • tuple: 각 요소가 다른 타입을 가질 수 있는 고정된 길이의 배열을 나타내는 타입. [ ]  (TS 전용)
    let tuple: [string, number, boolean] = ["Hello", 42, true];
    // 길이가 3이고 각각 string, number, boolean 타입으로 고정된 배열

 

특수 타입

1. any: 어떠한 타입이든 할당될 수 있는 타입입니다. (TS 전용)

  • 용도: any는 모든 타입을 허용하므로, 타입 검사를 무시하고 자유롭게 값을 할당할 수 있다. any를 사용하면 타입 안정성이 없어져 가능한 오류를 감지할 수 없으므로, 최소한으로 사용하는 것이 좋다.
  • 사용 예시: 타입을 알 수 없거나 임시로 타입 검사를 피하고 싶을 때 사용.
let value: any = 5;
value = "Hello"; // 타입 검사 없이 문자열로 재할당 가능
value = true; // 타입 검사 없이 boolean으로 재할당 가능

function logValue(input: any) {
  console.log(input); // 어떤 타입이든 받을 수 있음
}
logValue(42);
logValue("Test");

 

 

2. unknown: 타입을 미리 알 수 없는 경우에 사용되는 타입입니다. 이 타입은 안전한 타입 검사를 위해 사용됩니다. any보다 안전한 타입입니다. (TS 전용)

  • 용도: unknown은 타입을 미리 알 수 없을 때 사용하며, any와 달리 안전한 타입 검사를 강제합니다. unknown으로 선언된 변수에 대해 특정 타입의 작업을 수행하려면 타입 확인을 거쳐야 합니다.
  • 사용 예시: 외부에서 제공되는 데이터나, 여러 타입을 허용하는 값에 타입 검사를 강제하고 싶을 때 사용.
let input: unknown;

input = "Hello World";
input = 42;

// 직접 사용하려면 타입 검사가 필요합니다.
if (typeof input === "string") {
  console.log(input.toUpperCase()); // 타입이 string일 때만 메서드를 사용
}

function processValue(value: unknown) {
  if (typeof value === "number") {
    console.log(value + 10); // 타입이 number일 때만 덧셈 가능
  }
}
  • any vs unknown: any는 모든 타입 연산을 허용하는 반면, unknown은 타입 확인을 요구하여 보다 안전합니다.

3. never: 절대 반환 되지 않는 타입을 나타냅니다. 예를 들어, 함수가 항상 예외를 발생시키거나 무한 루프를 실행할 때 이 타입을 사용할 수 있습니다. (TS 전용)

  • 용도: never 타입은 아무 값도 반환하지 않는 함수에서 사용됩니다. 예를 들어, 항상 예외를 발생시키거나 무한 루프에 빠지는 함수는 never 타입을 반환합니다.
  • 사용 예시: 특정 함수가 정상적으로 종료되지 않을 때나, 발생하지 않아야 할 상황을 처리할 때 유용합니다.
// 항상 예외를 발생시키는 함수
function throwError(message: string): never {
  throw new Error(message); // 함수가 정상적으로 반환되지 않음
}

// 조건에서 절대 발생하지 않는 경우를 표현할 때
function checkType(value: string | number) {
  if (typeof value === "string") {
    console.log("It's a string");
  } else if (typeof value === "number") {
    console.log("It's a number");
  } else {
    const impossible: never = value; // value가 string이나 number가 아니면 오류 발생
  }
}

  • never와 void의 차이: void는 함수가 아무 값도 반환하지 않을 때 사용하는 반면, never는 반환되지 않음을 보장합니다.

 

타입 추론 기능

 

타입스크립트는 타입 추론 기능을 가지고 있어서 변수의 타입을 자동으로 판단할 수 있다. 만약 명시적으로 타입을 지정하지 않아도 컴파일러가 초기 할당값을 기준으로 변수의 타입을 추론한다.

→ 나중에 다른 타입을 할당하려고 하면 명시적으로 타입 설정해줬을 때처럼 오류 발생

let age = 27; // age는 number로 추론해서 자동으로 타입 설정해줌.
age = 'dada' // 컴파일 error

이 기능 덕분에 코드가 더 간결해지고, 필요한 경우에만 타입을 명시할 수 있다.

모호하거나 복잡한 로직에서는 타입을 꼭 명시해주는 것이 좋다.

 

타입을 전부 설정하는게 나은지, 특수한 경우에만 설정하는게 나은지? 

 

1. 일반적으로는 타입을 명시하는 것이 좋다.

  • 가독성: 타입을 명시하면 다른 개발자가 코드의 의도를 더 쉽게 이해할 수 있다.
  • 코드의 명확성: TypeScript가 추론하는 것보다 더 정확하고 상세한 타입 정보를 제공할 수 있다. 특히 함수의 매개변수나 반환값은 타입을 명시하는 것이 좋다.
  • 유지보수: 명시적인 타입 설정은 코드가 커지거나 팀 프로젝트에서 협업할 때 오류를 줄여준다.
function add(a: number, b: number): number {
  return a + b;
}

 

2. 타입 추론이 명확한 경우, 타입 명시를 생략해도 괜찮다.

  • 간결성: 타입 추론이 명확한 경우, 타입을 생략하면 코드가 더 짧아지고 읽기 쉬워진다.
  • 초기화가 명확한 변수: 변수에 기본값을 바로 할당하는 경우, 타입을 추론하도록 둬도 괜찮다.
let count = 10; // number로 자동 추론
const message = "Hello"; // string으로 자동 추론

 

3. 타입을 명시하는 것이 좋은 경우

  • 복잡한 객체나 배열: 복잡한 객체 구조나 배열의 경우, 타입 추론이 불분명할 수 있다. 이때는 인터페이스나 타입 별칭을 사용해 명시하는 것이 좋다.
  • any 사용 방지: 타입이 불분명한 경우 any로 추론될 수 있는데, 이를 방지하려면 명시적으로 타입을 지정하는 것이 좋다.
interface User {
  id: number;
  name: string;
  age?: number;
}

const user: User = { id: 1, name: "Alice" }; // 명시적으로 User 타입 사용

4. 타입 명시가 중요한 경우

  • API 데이터: 외부 API에서 받는 데이터나 동적으로 생성되는 객체는 타입이 불명확할 수 있으므로, 이럴 때는 명시적인 타입을 지정해 오류를 줄일 수 있다.
  • 함수 반환 타입: 함수의 반환 타입은 TypeScript가 잘못 추론할 수 있으므로, 명시적으로 설정하는 것이 좋다.

코드가 길어질수록 명시적인 타입 설정이 유지 보수와 협업에 큰 도움이 되므로, 기본적으로 타입을 설정하는 습관을 가지는 것이 좋다!

 

함수의 데이터 타입

기본 형태

// (매개변수): 반환값
function add(a: number, b: number): number {
  return a + b;
}

 

선택적 매개변수 사용하기 (?: 있어도 없어도 되는 매개변수)

function greet(name: string, **greeting?: string)**: string {
  if (greeting) { // 선택적이니까 있으면 여기
    return `${greeting}, ${name}!`;
  } else { // 없어도 오류 아님.
    return `Hello, ${name}!`; 
  }
}

 

함수의 시그니처 정의: 함수 타입을 변수로 정의할 수 있음.

type GreetFunction = (name: string) => string;
let greet: GreetFunction = (name) => `Hello, ${name}`;

 

interface (인터페이스) 

기본 구조와 사용법

interface Person {
  name: string;
  age: number;
}

const person1: Person = { name: "Alice", age: 25 }; // 정상
const person2: Person = { name: "Bob" }; // 오류: 'age' 속성이 누락됨

 

선택적 속성

interface Car {
  brand: string;
  model: string;
  year?: number; // 선택적 속성
}

const car1: Car = { brand: "Toyota", model: "Camry" }; // 정상
const car2: Car = { brand: "Honda", model: "Civic", year: 2020 }; // 정상

 

읽기 전용 속성 (Read-Only 변경 불가)

interface User {
  readonly id: number;
  name: string;
}

const user1: User = { id: 1, name: "Alice" };
user1.name = "Bob"; // 정상
user1.id = 2; // 오류: 'id'는 읽기 전용 속성입니다.

 

함수 타입 정의

interface Greeting {
  (name: string): string; // 함수 시그니처
}

const greet: Greeting = (name) => `Hello, ${name}!`;

 

Type Aliases (타입 별칭)

보통 맨 첫글자를 대문자로 씀.

원시 데이터 타입 별칭

type Age = number;
const myAge: Age = 30;

 

다양한 사례

// Array
type Names = string[];
const myFriends: Names = ['Alice', 'Bob', 'Charlie'];
 
// Tuple
type Coordinates = [number, number];
const myLocation: Coordinates = [37.7749, -122.4194];
 
// 객체
type User = {
  id: string;
  name: string;
  age: number;
};
const user: User = { id: '1', name: 'John Doe', age: 28 };
 
// 함수
type GreetingFunction = (name: string) => string;
const greet: GreetingFunction = (name) => `Hello, ${name}!`;

 

조금 더 복잡한 형태

type UserID = string;
type UserName = string;
type Age = number;
 
type User = {
  id: UserID;
  name: UserName;
  age: Age;
};
 
const user: User = { id: '1', name: 'John Doe', age: 28 };

 

type(확장 불가, 유니언타입)과 interface(확장 가능, 클래스)의 차이

 

interface와 type은 TypeScript에서 모두 타입을 정의하는데 사용되지만, 각기 다른 목적과 특징이 있다. 두 가지 모두 객체의 구조를 정의하거나, 함수 타입을 지정하는 데 유용하지만, 몇 가지 차이점이 있다.

 

주요 차이점: 확장성 (확장과 병합)

interface는 확장이 가능하다.

  • 다른 interface를 상속받거나, 같은 이름의 인터페이스를 여러 번 정의할 수 있으며, TypeScript는 자동으로 병합
  • 이를 통해 여러 번 정의된 속성들이 하나의 인터페이스로 합쳐질 수 있다.
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = { name: "Buddy", breed: "Labrador" };

  • 인터페이스 병합:
interface Person {
  name: string;
}

interface Person {
  age: number;
}

const person: Person = { name: "Alice", age: 30 }; // 두 인터페이스가 병합됨

 

type은 확장이 제한적이다.

  • 타입은 상속이나 병합을 지원하지 않는다. 대신 유니언 타입 등을 사용하여 조합할 수 있지만, interface만큼 유연하게 상속되지 않음.
  • 타입 별칭을 조합할 때는 유니언(|)이나 교차(&) 연산자를 사용해야 함.
  • 유니언(Union)과 교차(Intersection) 타입
    • 유니언 타입: 하나 이상의 타입을 허용할 수 있다.
      let value: string | number;
      value = "hello";
      value = 42;
    • 교차 타입: 두 개 이상의 타입을 합쳐서 새로운 타입을 만든다.
      type Person = { name: string };
      type Contact = { phone: string };
      type Employee = Person & Contact;
type Animal = {
  name: string;
};

type Dog = Animal & {
  breed: string;
};

const myDog: Dog = { name: "Buddy", breed: "Labrador" };

 

 

  interface type
확장성 다른 인터페이스 상속 및 병합 가능 상속 불가, 유니언 타입과 교차 타입으로 조합
사용 범위 주로 객체 타입 정의 유니언, 튜플, 객체, 함수 등 다양한 타입 정의 가능
유니언 및 교차 타입 지원하지 않음 지원
클래스 구현 클래스에서 구현 가능 클래스 구현에 직접 사용하지 않음
가독성과 코드 스타일 객체 구조 정의 시 가독성 높음 복잡한 타입 표현 시 유연하고 간결함

 

제네릭 (Generics)

  • 제네릭다양한 타입을 처리하는 함수나 클래스를 만들 때 사용.
  • 타입을 함수나 클래스가 호출되는 시점에 지정할 수 있도록 하여, 코드의 재사용성과 타입 안전성을 높인다.
  • 예를 들어 배열의 모든 항목을 출력하는 함수를 만들 때 number 배열, string 배열 모두 처리할 수 있도록 제네릭을 사용할 수 있다.
function identity<T>(arg: T): T {
  return arg;
}

const output = identity<string>("Hello"); // T가 string으로 설정됨