TypeScript 핵심 기능 설명

TypeScript 설치 및 실행

TypeScript를 설치하는 방법은 npm 또는 yarn을 사용하는 것입니다.

npm install typescript -g
yarn global add typescript
    

설치 후 TypeScript 파일을 JavaScript로 컴파일하거나 직접 실행할 수 있습니다.

tsc Demo1.ts
npm install -g ts-node
ts-node Demo1.ts
    

정적 타입

TypeScript의 핵심은 정적 타입 시스템입니다. 변수 선언 시 타입을 명시하면 해당 변수의 타입은 고정되며, 타입에 맞는 속성과 메서드만 사용할 수 있습니다.

const count: number = 1;
    

기본 및 객체 타입

TypeScript는 다양한 기본 타입을 지원합니다. 변수명 뒤에 콜론(:)을 붙여 타입을 명시할 수 있습니다.

  • 기본 타입: number, string, null, undefined, symbol, boolean, void
const count: number = 918;
    

객체 타입은 배열, 클래스, 함수 등 다양한 형태로 정의될 수 있습니다.

const arr: string[] = ["sds", "sdsd", "sdw"];
const func: () => string = () => { return "sdsd"; };
    

타입 주석과 타입 추론

TypeScript는 타입 주석(Type Annotation)을 통해 명시적으로 타입을 지정할 수 있으며, 타입 추론(Type Inference)을 통해 컴파일러가 타입을 자동으로 유추하도록 할 수도 있습니다.

function getTotal(one: number, two: number) {
  return one + two;
}
const total = getTotal(1, 2); // 'total'의 타입은 number로 자동 추론됩니다.
    

타입 추론이 가능한 경우 타입을 생략하여 코드를 간결하게 만들 수 있습니다. 명확하지 않은 경우 타입 주석을 사용하여 타입을 명확히 하는 것이 좋습니다.

함수 매개변수 및 반환 타입 정의

함수의 매개변수와 반환 값에 타입을 지정하여 함수의 안정성을 높일 수 있습니다.

// 반환 값이 있는 함수
function getTotal(one: number, two: number): number {
  return one + two;
}
const total = getTotal(1, 2);

// 반환 값이 없는 함수 (void)
function sayHello(): void {
  console.log('hello');
}

// never 타입: 함수가 정상적으로 종료되지 않음을 나타냅니다 (예: 오류 발생 또는 무한 루프).
function errFunc(): never {
  throw new Error();
  // console.log('hello~'); // 이 코드는 실행되지 않습니다.
}

function forNever(): never {
  while (true) {}
  // console.log('hl-'); // 이 코드는 실행되지 않습니다.
}

// 객체 구조 분해 할당을 매개변수로 사용
function add({ one, two }: { one: number, two: number }): number {
  return one + two;
}
const total = add({ one: 1, two: 2 });

function getNumber({ one }: { one: number }): number {
  return one;
}
const num = getNumber({ one: 8 });
    

배열 타입 정의

배열의 요소 타입을 지정하는 일반적인 방법과 객체 배열 타입을 정의하는 방법을 알아봅니다.

// 일반 배열 타입
const numberArr: number[] = [1, 2, 3];
const strArr: string[] = ['1', '2'];
const undefinedArr: undefined[] = [undefined, undefined];
const mixedArr: (number | string)[] = [1, '2', 3]; // 숫자 또는 문자열 배열

// 배열 내 객체 타입 정의
const objArr: { name: string, age: number }[] = [
  { name: 'ssd', age: 12 },
  { name: 'sdsd', age: 123 },
];

// 타입 별칭(Type Alias)을 사용하여 코드 재사용
type PersonItem = { name: string, age: number };
const arrWithTypeAlias: PersonItem[] = [
  { name: 'ds', age: 12 },
  { name: '233', age: 8 },
];

// 클래스를 배열 타입으로 사용
class PersonClass {
  name: string;
  age: number;
}
const arrWithClass: PersonClass[] = [
  { name: '12', age: 12 },
  { name: 'dfdf', age: 89 },
];
    

튜플 (Tuple)

튜플은 고정된 개수의 요소와 각 요소의 타입을 명확히 정의할 때 유용합니다. 일반 배열에서는 타입이 혼합될 때 유연성이 있지만, 튜플은 순서와 타입이 중요할 때 사용됩니다.

// 튜플: string, string, number 순서로 타입을 정의
const tupleArr: [string, string, number] = ['ss', 'sdsd', 18];
    

인터페이스 (Interface)

인터페이스는 객체의 구조를 정의하고 타입을 규정하는 데 사용됩니다. 반복되는 타입 정의를 묶어 재사용성을 높입니다.

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

function getPersonInfo(person: Person) {
  if (person.age < 24 && person.height > 160) {
    console.log(person.name + ' is young and tall.');
  } else if (person.age > 24 && person.height < 160) {
    console.log(person.name + ' is older and short.');
  }
}

// 인터페이스와 타입 별칭의 차이점:
// - 타입 별칭은 `type AliasName = string;` 과 같이 기본 타입에도 사용할 수 있지만,
//   인터페이스는 객체 구조에만 사용 가능합니다.
// - 인터페이스는 선택적 속성(`?`)을 정의할 수 있습니다.
interface OptionalPerson {
  name: string;
  age: number;
  height?: number; // 선택적 속성
}

// 인터페이스는 추가 속성을 허용할 수 있습니다.
interface DynamicPerson {
  name: string;
  age: number;
  height?: number;
  [propName: string]: any; // 인덱스 시그니처: 문자열 키에 대해 어떤 타입이든 허용
}

const personWithExtra: DynamicPerson = {
  name: 'kk',
  age: 12,
  sex: 'female', // 추가 속성
};

// 인터페이스는 메서드도 정의할 수 있습니다.
interface MethodPerson {
  name: string;
  age: number;
  height?: number;
  [propName: string]: any;
  say(): string; // 메서드 정의
}

const personWithMethod: MethodPerson = {
  name: 'kk',
  age: 13,
  height: 190,
  say() {
    return 'hello~';
  }
};

// 인터페이스와 클래스: 구현(implements) 및 상속(extends)
class XY implements Person {
  name = 'k';
  age = 13; // 타입 오류: string은 number에 할당될 수 없습니다.
  height = 120;
  say() {
    return 'hello--';
  }
}

interface Teacher extends Person {
  teach(): string;
}

const personData: Teacher = {
  name: 's',
  age: 12,
  height: 190,
  say() {
    return 'kk';
  },
  teach() {
    return 'mm';
  }
};

const getResume = (person: Teacher) => {
  console.log('Resume for: ' + person.name);
};
    

클래스 (Class)

클래스는 객체 지향 프로그래밍의 기본 요소로, 속성과 메서드를 캡슐화합니다.

class Person {
  content = "hi";
  sayHi() {
    return this.content;
  }
}

const personInstance = new Person();
console.log(personInstance.sayHi());

// 클래스 오버라이딩: 자식 클래스가 부모 클래스의 메서드를 재정의
class XY extends Person {
  sayHi() {
    return 'hi, girl';
  }
  sayWorld() {
    return 'hello world';
  }
}

// super 키워드: 부모 클래스의 메서드 호출
class ExtendedPerson extends Person {
  sayHi() {
    return super.sayHi() + "---Hello World";
  }
}
    

클래스 접근 제어자

public, protected, private 키워드를 사용하여 클래스 멤버의 접근 범위를 제어할 수 있습니다.

// public: 클래스 내부 및 외부에서 접근 가능 (기본값)
class PublicPerson {
  public name: string;
  public sayHello() {
    console.log(this.name + ' says Hello');
  }
}
const pubPerson = new PublicPerson();
pubPerson.name = 'jspang.com';
pubPerson.sayHello();
console.log(pubPerson.name);

// private: 클래스 내부에서만 접근 가능
class PrivatePerson {
  private name: string;
  public sayHello() {
    console.log(this.name + ' says Hello'); // 내부 접근 가능
  }
}
const privPerson = new PrivatePerson();
// privPerson.name = 'jspang.com'; // 외부 접근 시 오류 발생
privPerson.sayHello();
// console.log(privPerson.name); // 외부 접근 시 오류 발생

// protected: 클래스 내부 및 상속받은 자식 클래스에서 접근 가능
class ProtectedPerson {
  protected name: string;
  public sayHello() {
    console.log(this.name + ' says Hello'); // 내부 접근 가능
  }
}

class Teacher extends ProtectedPerson {
  public sayBye() {
    this.name; // 자식 클래스 내부 접근 가능
  }
}
    

클래스 생성자

생성자(constructor)는 클래스의 인스턴스가 생성될 때 자동으로 호출되는 메서드입니다. 인스턴스 초기화에 사용됩니다.

// 생성자를 이용한 속성 초기화
class PersonConstructor {
  public name: string;
  constructor(name: string) {
    this.name = name;
  }
}
const personInstance1 = new PersonConstructor('sds');
console.log(personInstance1.name);

// 접근 제어자를 생성자 매개변수에 사용하여 속성 자동 생성
class PersonShort {
  constructor(public name: string) {}
}
const personInstance2 = new PersonShort('kk');
console.log(personInstance2.name);

// 클래스 상속에서의 생성자
class ParentPerson {
  constructor(public name: string) {}
}
class ChildTeacher extends ParentPerson {
  constructor(public age: number) {
    super('js'); // 부모 클래스의 생성자 호출
  }
}
const teacherInstance = new ChildTeacher(19);
console.log(teacherInstance.age);
console.log(teacherInstance.name);

// 자식 클래스에서 생성자를 정의할 경우, 반드시 super()를 호출하여 부모 클래스의 생성자를 먼저 호출해야 합니다.
    

Getter/Setter/Static

gettersetter는 객체의 속성에 접근하거나 값을 변경할 때 특정 로직을 실행하도록 하는 메서드입니다. static 멤버는 클래스의 인스턴스가 아닌 클래스 자체에 속합니다.

클래스의 읽기 전용 속성 및 추상 클래스

readonly 키워드는 속성을 선언 시에만 초기화할 수 있도록 하여 변경을 방지합니다. abstract 클래스는 추상 메서드를 포함하며, 직접 인스턴스화할 수 없고 반드시 상속받아 구현해야 합니다.

// readonly 속성
class ReadonlyPerson {
  public readonly name: string;
  constructor(name: string) {
    this.name = name;
  }
}

// abstract 클래스
abstract class AbstractSkill {
  abstract skill(): void; // 추상 메서드
}

class SkillA extends AbstractSkill {
  skill() {
    console.log('Skill A implemented');
  }
}

class SkillB extends AbstractSkill {
  skill() {
    console.log('Skill B implemented');
  }
}

class SkillC extends AbstractSkill {
  skill() {
    console.log('Skill C implemented');
  }
}
    

tsconfig.json

tsconfig.json 파일은 TypeScript 컴파일러의 설정을 정의합니다. tsc --init 명령어로 생성할 수 있습니다.

  • include: 컴파일할 파일을 지정합니다.
  • exclude: 컴파일에서 제외할 파일을 지정합니다.
  • compilerOptions: JavaScript로 변환하는 방식 등 다양한 컴파일 옵션을 설정합니다. (예: outDir, rootDir, target, module 등)

주의: tsconfig.json 파일 내에서는 큰따옴표(")를 사용해야 합니다.

유니언 타입과 타입 가드

유니언 타입(Union Types)은 여러 타입 중 하나일 수 있는 변수를 정의할 때 사용합니다. (예: string | number). 타입 가드(Type Guards)는 유니언 타입의 변수가 어떤 타입인지 런타임에 확인하여 안전하게 접근하도록 돕습니다.

주요 타입 가드 기법:

  • 타입 단언 (Type Assertion): as 키워드를 사용하여 개발자가 타입을 명시적으로 지정합니다.
  • `in` 연산자: 객체에 특정 속성이 있는지 확인합니다.
  • `typeof` 연산자: 변수의 타입을 문자열로 반환합니다. (primitive 타입에 주로 사용)
  • `instanceof` 연산자: 객체가 특정 클래스의 인스턴스인지 확인합니다.
interface Waiter {
  isWaiter: boolean;
  saySomething: () => {};
}

interface Teacher {
  isTeacher: boolean;
  teachSomething: () => {};
}

// 타입 단언 예시
function judgeWho(person: Waiter | Teacher) {
  if (person.isWaiter) {
    (person as Waiter).saySomething();
  } else {
    (person as Teacher).teachSomething();
  }
}

// 'in' 연산자 예시
function processItem(item: Waiter | Teacher) {
  if ('teachSomething' in item) {
    item.teachSomething();
  } else {
    item.saySomething();
  }
}

// typeof 연산자 예시
function concatenate(first: string | number, second: string | number): string | number {
  if (typeof first === 'string' || typeof second === 'string') {
    return `${first}${second}`;
  }
  return first + second;
}

// instanceof 연산자 예시
class NumObject {
  count: number;
  constructor(count: number) { this.count = count; }
}

function addObjects(first: object | NumObject, second: object | NumObject): number {
  if (first instanceof NumObject && second instanceof NumObject) {
    return first.count + second.count;
  }
  return 0;
}
    

열거형 (Enum)

열거형은 관련된 상수들의 집합을 정의할 때 사용합니다. 코드의 가독성을 높이고 오류를 줄이는 데 도움이 됩니다.

enum Status {
  Pending,   // 0
  Processing, // 1
  Completed,  // 2
  Failed      // 3
}

function getStatusMessage(status: Status): string {
  switch (status) {
    case Status.Pending:
      return 'Operation is pending.';
    case Status.Processing:
      return 'Operation is processing.';
    case Status.Completed:
      return 'Operation completed successfully.';
    case Status.Failed:
      return 'Operation failed.';
    default:
      return 'Unknown status.';
  }
}

const currentStatus = Status.Processing;
console.log(getStatusMessage(currentStatus));
    

제네릭 (Generics)

제네릭은 타입스크립트에서 재사용 가능한 컴포넌트를 만들 때 사용합니다. 다양한 타입에서 작동하는 유연하고 재사용 가능한 코드를 작성할 수 있게 해줍니다.

// 제네릭 함수
function identity<T>(arg: T): T {
  return arg;
}

const numResult = identity<number>(10);
const strResult = identity<string>("hello");

// 제네릭 배열
const numberArray: Array<number> = [1, 2, 3];
const stringArray: Array<string> = ["a", "b", "c"];

// 여러 개의 제네릭 타입 파라미터
function combine(first: T, second: U): string {
  return `${first}${second}`;
}
combine(1, "2");

// 제네릭 타입 제약 조건
class ConstrainedContainer<T extends number | string> {
  private item: T;
  constructor(item: T) {
    this.item = item;
  }
  getItem(): T {
    return this.item;
  }
}
const numContainer = new ConstrainedContainer<number>(100);
const strContainer = new ConstrainedContainer<string>("example");
// const boolContainer = new ConstrainedContainer<boolean>(true); // 오류 발생: boolean은 number 또는 string이 아님
    

TypeScript 개발 환경 설정

TypeScript 프로젝트를 설정하는 단계별 가이드입니다.

  1. 프로젝트 폴더를 생성하고 VSCode에서 엽니다.
  2. 터미널에서 npm init -y를 실행하여 package.json 파일을 생성합니다.
  3. tsc --init 명령어로 tsconfig.json 파일을 생성합니다.
  4. srcbuild 폴더를 생성하고, index.html 파일을 만듭니다.
  5. tsconfig.json 파일에서 outDir (컴파일 결과물 출력 디렉토리)과 rootDir (소스 파일 디렉토리)를 설정합니다.
  6. index.html 파일에 JavaScript 파일을 포함시킵니다.
  7. src 폴더에 TypeScript 파일(예: page.ts)을 작성합니다.
  8. 터미널에서 tsc 명령어를 실행하여 TypeScript 파일을 JavaScript로 컴파일합니다.
  9. index.html 파일을 브라우저에서 열고 개발자 도구(F12)를 통해 출력 결과를 확인합니다.

네임스페이스 (Namespace)

네임스페이스는 코드를 논리적으로 그룹화하고 전역 스코프 오염을 방지하는 데 사용됩니다. 모듈화된 개발을 위한 기본적인 구문을 제공합니다.

namespace MyModule {
  export class MyClass {
    // ... class implementation
  }
  export const myVariable: string = "Hello";
}

const instance = new MyModule.MyClass();
console.log(MyModule.myVariable);
    

Parcel을 이용한 TypeScript 번들링

Parcel은 설정이 간편한 모던 웹 애플리케이션 번들러로, TypeScript 프로젝트를 쉽게 빌드하는 데 사용할 수 있습니다.

yarn add --dev parcel@next
    

태그: TypeScript 정적 타입 타입스크립트 기본 함수 타입 인터페이스

8월 1일 22:30에 게시됨