개인공부/TypeScript

#TypeScript (1)

BTSBRINGMEHERE 2022. 8. 15. 16:42

TypeScript란?

TypeScript은 JavaScript 위에 레이어로서 자리잡고 있는데, JavaScript의 기능들을 제공하면서 그위에 자체 레이러를 추가합니다. 이 레이어가 TypeScript 타입 시스템입니다.

 

JavaScript는 string, number, object, undefined 같은 원시 타입을 갖고 있지만, 전체 코드베이스에 일관되게 할당되었는지 미리 확인해 주지 않습니다. TypeScript는 이 레이어로서 동작합니다.

 

TypeScript의 타입 검사기는 사용자가 생각한 일과 JavaScript가 실제로 하는 일 사이의 불일치를 강조할 수 있습니다.

TypeScript는 JavaScript 언어를 당연히 알고 있으며 대부분의 경우 타입을 생성해줄 것입니다. 

 

간단하게 TypeScript는 JavaScript에 타입 구문이 있는 것입니다.

 

타입 정의하기

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

const user: User = {
  name: "TypeScript",
  id: 0,
};

 

이 객체의 형태를 명시적으로 나타내기 위해서는 interface 로 선언합니다.

이제 변수 선언 뒤에 : TypeName 의 구문을 사용해 JavaScript 객체가 새로운 interface의 형태를 따르고 있음을 선언할 수 있습니다. 만약 해당 인터페이스에 맞지 않는 객체를 생성하면 TypeScript는 경고를 줄 것입니다. 또한, interface와 다른 타입을 사용한다면 역시 TypeScript는 경고를 합니다.

 

인터페이스는 Class로도 선언 가능합니다.

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

class UserAccount {
  name: string;
  id: number;
  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}
const user: User = new UserAccount("TypeScript", 100);

 

기본 타입 보기

// 원시값
const str: string = "TypeScript"
const num: number = 12345

// Objet(객체)
const obj: {
  str: string;
  num: number;
  objChild: {
    str: string;
    bool: boolean;
  };
} = {
  str: "String",
  num: 999,
  objChild: {
    str: "ChildString",
    bool: false,
  },
}

// Function (함수)
function func1(str: string, num: number): void {
  console.log(str, num)
}

const func2 = (obj: {str: string, num: number}) => {
  return `${obj.str}의 나이는 ${obj.num}`
}

// Array (배열) 타입에 맞지 않는 요소를 삽입시 에러
const strArr: Array<string> = ['str1', 'str2']

const numArr: number[] = [10, 20, 30, 40] 

// unknown 새로운 최상위 타입, any처럼 모든 값을 허용하지만 엄격 / 개발자가 직접 명시 / assertion혹은 타입 가드와 함께사용
let num: unknown = 100

//에러에 잡히지 않는다
if(typeof num === 'string'){
  num.trim();
}

 

Class Definition

TypeScript 클래스는 클래스 몸체에 클래스 프로퍼티를 사전 선언하여야 합니다.

class Person {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  func() {
    console.log(`${this.name} is ClassName`);
  }
}

const person = new Person('Kim')
person.func() // Kim is ClassName

TypeScript 클래스는 접근 제한자 public, private, protected를 지원하며 의미 또한 기본적으로 동일합니다.

TypeScript의 경우, 접근 제한자를 생략한 클래스 프로퍼티와 메소드는 암묵적으로 public으로 선언됩니다.

 

접근 제한자 public protected private
클래스 내부 ⭕️ ⭕️ ⭕️
자식 클래스 내부 ⭕️ ⭕️
클래스 인스턴스 ⭕️

 

class Foo {
  public pub: string;
  protected pro: string;
  private pri: string;
  
  constructor(pub: string, pro: string, pri: string) {
    // 모두 클래스 내부에서 참조 가능
    this.pub = pub;
    this.pro = pro;
    this.pri = pri;
  }
}

class Bar extends Foo {
  constructor(pub: string, pro: string. pri: string) {
  super(pub, pro, pri);
  
  this.pub = pub // public 접근 제어자는 자식 클래스 내부 참조 가능
  
  this.pro = pro // protected 접근 제어자는 자식 클래스 내부에서 참조 가능
  
  this.pri = pri // privated 접근 제어자는 자식 클래스 내부에서 참조 불가능
  }
}

const foo = new Foo('public', 'protected', 'privated');

console.log(foo) // 참조가 안되는 프로퍼티가 존재하므로 에러

console.log(foo.pub) // public으로 잘 출력

console.log(foo.pro) // protected는 클래스 내부와 자식 클래스 내부에서만 참조된다.

console.log(foo.pri) // private는 클래스 내부에서만 참조된다.

 

Class에 readonly가 선언된 클래스 프로퍼티는 선언 시 또는 생성자 내부에서만 값을 할당할 수 있다. 그 외의 경우 불가능하며 오직 읽기만 가능한 상태가 된다. 상수 선언에 사용하면 좋다.

 

TypeScript에서는 static 키워드를 사용할 수 있는데 클래스 속성과 메서드를 new로 인스턴스화 하지 않고 바로 호출할 수 있다. 접근 제어자를 활용할 수 있고 몇가지 정적 이름을 사용할 수 없다. ex) function.name / class

 

추상 클래스(abstract class)는 하나 이상의 추상 메소드를 포함하며 일반 메소드도 포함할 수 있다. 추상 메소드는 내용이 없이 메소드 이름과 타입만이 선언된 메소드를 말하며 선언할 때 abstract 키워드를 사용한다. 추상 클래스를 정의할 때는 abstract 키워드를 사용하며, 직접 인스턴스를 생성할 수 없고 상속만을 위해 사용된다. 추상 클래스를 상속한 클래스는 추상 클래스의 추상 메소드를 반드시 구현하여야 한다.

 

Interface

JavaScript에는 존재하지 않습니다. 인터페이스는 일반적으로 타입 체크를 위해 사용되며 변수, 함수, 클래스에 사용 가능합니다. 여러가지 타입을 갖는 프로퍼티로 이루어진 새로운 타입을 정의하는 것과 유사하고 객체의 타입을 정의하고 생김새를 갖도록 합니다. 일부 기능은 TypeScript에서만 존재하는 고유 문법으로 컴파일 후에 사라집니다.

 

interface Memo {
  content: string;
  id: number;
  checked: boolean;
}

const memo: Memo = {
  content: "TypeScript",
  id: 82938479128759234980,
  checked: true,
}

인터페이스를 사용하여 함수 파라미터를 선언할 수 있고, 이때 해당 함수에는 함수 파라미터의 이름 그리고 타입으로 지정한 인터페이스를 준수하는 인수를 잘 전달해야합니다.

 

// 함수와 인터페이스
interface FuncInterface {
  (num: number): number;
}

const FuncInterface: FuncInterface = (num: number) => {
  return num * num;
}

console.log(FuncInterface(9)); // 81

// 클래스와 인터페이스
interface CreateIClass {
  str: string;
  num: number;
}

class CreateClass implements CreateIClass {
  constructor (
    public str: string,
    public num: number,
  ) { }
}

const createclass = new CreateClass('TypeScript', 2022)

// 클래스는 2개의 interface를 받을 수 도 있다

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

interface Address {
  address: string;
}

class Human implements Person, Address {
  constructor(
    public name: string
    public age: number
    public address: string
    ) { super() }
}

 

Type Guard?

TypeScript에서 유니언 타입을 사용하면 다양한 타입 지정이 가능합니다. 하지만, 다양한 타입들 중 사용할 타입을 확실하게 정해야 합니다. 

const choicetype : string | number // 2개 타입 가능

위와 같은 함수가 존재할 때 확실하게 선택을 해줘야 합니다.

typeof   |   instanceOf   |   in   |   사용자 정의

// typeof
function mununu(value: number | string) {
  if(typeof value === 'number'){
    return `${value}의 타입은 number`
  }
  if(typeof value === 'string'){
    return `${value}의 타입은 string`
  }
  
  return value
}

console.log(mununu(10)) // 10의 타입은 number

//in 객체가 특정 속성을 갖고 있는지 검사를 불리언으로 반환
interface Dog {
  name: string;
  bark(): 'bowbow';
}

interface Cat {
  name: string;
  meow(): 'meow~~~';
}

function whatIsYourAnimal(animal: Dog | Cat) {
  if('bark' in animal) {
    animal.bark()
    animal.name
  }
  
  if('meow' in animal) {
    animal.meow()
    animal.name
  }
}

// instanceOf class경우 typeof 연산자를 이용해도 항상 object값을 얻기에 instanceof 함수 이용
class Student { }
class Teacher { }

function something(person: Student | Teacher) {
   if (person instanceof Student) { // 클래스 확인
    person.do();
  } else {
    person.do();
  }
}

// 사용자 정의 타입 가드
// 기존에 존재하는 키워드 대신 사용자가 직접 타입을 검증하는 함수를 작성하여 사용
// value is Type 형태의 반환 타입으로 작성
interface Student {
  study(): void
}

interface Teacher {
  teach(): void
}

// 사용자 정의 타입 가드
function isStudent(person: Student | Teacher): person is Student {
    return person.study !== undefined;
}

function something(person: Student | Teacher) {
    if (isStudent(person)){
    	person.study();
      	person.teach(); // 에러!, 여기는 Student 공간
    }
}

 

열거형

열거형으로 이름이 있는 상수드르이 집합을 정의할 수 있습니다. 열거형을 사용하면 의도를 문서화 하거나 구분되는 사례 집합을 더 쉽게 만들 수 있습니다. TypeScript는 숫자와 문자열 기반 열거형을 제공합니다.

 

숫자 열거형 (Numeric enums)

enum NumEnums {
  num1 = 1,
  num2,
  num3,
  num4,
}

//리버스 매핑이 가능하다.
const firstVal = NumEnums.num1
const keyOfFirstVal = NumEnums[firstVal]

num1이 1로 초기화된 숫자형 열거형을 선언했습니다. 그 아래로 부터는 자동으로 증가된 값을 갖습니다.

NumEnums.num1 은 1 num2 는 2 num3 은 3 num4 는 4를 갖습니다. 만약 초기 값을 설정하지 않는다면 0부터 자동으로 1이 증가 됩니다.

 

문자열 열거형 (String enums)

enum StringEnums {
  name = "GoldenHeart",
  address = "NEWYORK",
  value = "Values",
}

문자열 열거형은 수자 열거형처럼 자동으로 증가하는 기능은 없지만, 직렬화라는 이점이 있습니다. 문자열 열거형을 이용하면 코드를 실행할 때, 열거형 멤버에 지정된 이름과는 무관하게 의미있고 읽기 좋은 값을 이용하여 실행할 수 있습니다. 숫자형 열거와의 차이점은 리버스 매핑이 안됩니다.

 

이종 열거형 (Heterogeneous enums)

enum HeterogeneousEnums {
  nums = 100,
  str = 'String',
}

많은 이용자들이 웬만하면 이용하지 말라고함

 

const 사용

const enum Desk {
  Color = 'brown',
  Width = 1500,
}

Desk.Color
//Desk.Height
//Desk[Height]

기본적인 열거형은 불안전한 접근을 허용하지만 enum 앞에 const 키워드를 명시함으로써 안전한 열거가 가능합니다.

 

컴파일 시점에서 열거형

열거형이 런타임에 존재하는 실제 객체라도, keyof 키워드는 일반적인 객체에서 기대하는 동작과 다르게 동작합니다. 대신, keyof typeof를 사용하면 모든 열거형의 키를 문자열로 나타내는 타입을 가져옵니다.

const enum Language {
    TypeScript = 'TS',
    JavaScript = 'JS',
    Java = 'JAVA',
    Ruby = 'RB',
}

type LangCode = keyof typeof Language
// 위와 아래가 같다
type LangCode2 = "TypeScript" | "JavaScript" | "Java" | "Ruby"

function getLang(langCode: LangCode) {
    console.log(langCode)
}

 

논리 연산자 활용 타입

유니온 타입

// Union Type
const gender = 'M' | 'F'

const address = 'Seoul' | 'Washington, D.C.' | 'London'


// Intersection Type (교집합 형)
const userName = (name: string) => {
  return name;
}

const userAge = (age: number) => {
  return age;
}

const user = userName & userAge

 

참조 문헌


https://www.typescriptlang.org/docs/
https://typescript-kr.github.io/pages/the-handbook.html

1주차 느낀점


  1. 타입 설정하는게 생각보다 어렵다.
  2. 자바스크립트에서 지나치는 에러들을 미리 알 수 있어서 좋았다.
  3. interface 사용하여 미리 사용할 함수, 클래스의 구조를 미리 설정하는 것이 신기했다.
  4. 정해진 구조, 형식은 없지만 사용자들이 의식적으로 지양할 것 지향할 것을 구분한 점이 신기했다.
  5. 하지만 의식적으로 학습을 해도 아마 나중에 타입을 선언할 때 unknown 즉, any를 사용할 것 같아서 두렵다.
  6. 자동으로 추론하는게 앞으로 있을 팀 혹은 개인 프로젝트에서 더욱 쉽게 작용할 것 같다.