[TS] TypeScript 개론 (2)

kysung95·2021년 4월 1일
2

TypeScript 개론

목록 보기
2/5
post-thumbnail

지난 포스팅에서 TypeScript에 대해 간략히 알아보는 시간을 가졌습니다.
이번 포스팅에서는 타입 추론, 타입 명시, 인터페이스 등에 대해 설명하도록 하겠습니다.

TypeScript 타입 추론(type inference)

TypeScript는 JavaScript에서 기반한 언어이기에 JavaScript에서 유효한 코드는 대개 TypeScript에서도 유효합니다. 그렇지만 미묘하게 다른 부분이 존재합니다. 바로 타입 추론 관련한 것인데요.

app.ts 파일에 다음의 코드를 입력해보겠습니다.

//app.ts

let a=1;
a='hello' // a 변수를 string type으로 재할당

해당 코드는 JavaScript에서는 문제 없이 동작합니다. 그렇지만 TypeScript에서 이를 컴파일 할 시에는

Type ${string} is not assignable to type 'number'

위와 같은 에러가 발생하게 됩니다.
이는 재할당하려는 변수값이 숫자가 아니기 때문에 a 변수에 할당되지 못하기 때문입니다.
이러한 현상의 이유가 바로 TypeScript의 타입추론 특성입니다.

타입스크립트에서는 타입 표기가 없는 경우 코드를 읽고 분석하여 타입을 유추해낼 수 있습니다.

이러한 특성은 다음과 같은 경우에도 작동합니다.

//app.ts

let student={
    name: 'YSKim',
    course: 'introduction of TypeScript',
    code: function () {
        console.log('김용성입니다.')
    }
}

student.name=1 // 에러 발생

Type ${number} is not assignable to type 'string'

student.name 이라는 변수의 type값을 명시해주지 않아도, 타입스크립트는 똑똑하게 해당 변수의 type이 string type이라는 것을 추론하고 이러한 에러를 발생시켜줍니다.

TypeScript 타입 명시(type annotations)

지난 포스팅에서도 이에 대해 알아보았지만, 다시 한번 짚고 넘어가도록 하겠습니다.

변수 타입 명시

//app.ts

let name:string = 'YSKim' ; // 작동 코드
let studentId:number = '201511796' ; // 타입 에러 발생
let name: 201511796; // 타입 에러 발생

위 코드처럼 명시해준 type이 아닌 type의 값을 변수로 할당해줄 경우 에러가 발생합니다.
한번 타입이 명시된 변수는 항상 그 type의 값을 가져야 합니다.

함수 타입 명시

// app.ts

let name:string = 'YSKim';
let studentId:number = 201511796;
let group:string = 'H&B'

function getStudentInfo1(studentId: number) 
: object {   // 객체를 반환한다고 타입 명시 (문제는 생기지 않지만 타입명시를 더 해주는 것이 좋음)
    return null
}

function getStudentInfo2(studentId: number) : { 
   name:string;
   studentId:number;
   group:string;
}{   // 정확히 어떤 객체를 반환하는 지에 대해 구체적인 타입 명시
    return null;
}

object로 함수의 type을 명시할 경우 코드 자체는 잘못된 것은 없지만 TypeScript로 코드를 짤 때는 타입에 대한 더 많은 정보를 주는 것이 좋습니다.

getStudentInfo1() 의 경우 컴파일 상 문제는 없습니다. 그렇지만 TypeScript로 함수의 반환 type을 명시해줄 때는 object라고 대강 명시를 해놓는 것보다 정확히 객체가 가진 타입들을 명시해주는 것이 좋습니다.

getStudentInfo2() 는 반환되는 객체가 가진 타입들에 대해 정확히 명시한 함수입니다.

(반환하는 값이 없는 함수의 경우, void로 type을 명시해줍니다.)

하지만 함수를 선언할 때마다 getStudentInfo2() 와 같이 여러줄 type 명시를 하게 될 경우 상당히 귀찮아지지 않을까요? 그래서 나온 것이 바로 제가 다음에 설명드릴 Interface입니다.

인터페이스 (Interface)

흔히 대학교 교수님들은 Interface를 설명할 때, 붕어빵 틀을 사용하지 않나요?
저는 이 설명이 굉장히 마음에 들었습니다 :)

인터페이스 기본 개념

자, 이제 Interface를 통해 getStudentInfo2() 를 더 간단하게 만드는 작업을 진행해보도록 하겠습니다.

//let name:string = 'YSKim';
//let studentId:number = 201511796;
//let group:string = 'H&B'

interface Student { // 인터페이스 선언 (대문자로 시작)
   name:string;  
   studentId:number;
   group:string;
}

function getStudentInfo2(studentId: number) : Student // 인터페이스를 통해 간단하게 타입 명시
{   
    return {
      name: 'YSKim',
      studentId: 201511796,
      group: 'H&B',    
    };  // 반환되는 값들은 type이 올발라야 합니다.
}

어떤가요? 확실히 Interface를 사용하니 함수의 type 명시 파트가 간략해지고 깔끔해졌습니다.

(TypeScript에서 Interface의 이름은 UpperCase로 시작해야합니다! 또한 다른 프로그래밍 언어와 다르게 앞에 'I'를 붙이지 않습니다.)

잠깐✋✋

만약 어떤 학생은 포함되어 있는 모임이 존재하지 않는데 이 경우에 해당 학생은 어떤 방법으로 함수에서 return시켜주어야 할까요?

인터페이스 optional

단순히 group 값을 return에 포함시키지 않는다면 어떻게 될까요?
아마 다음과 같은 메시지를 보게될겁니다.

Property 'group' is missing in type '{}' but required in type 'Student'.

이는 Student Interface에서 정의된 group property가 Student type의 반환값이 포함되지 않았기 때문에 발생한 문제입니다.
이러한 문제는 어떻게 해결해야할까요?

이런 경우에는 Interface 정의 부분을 변경시켜주면 됩니다. 상당히 간단한 방법인데요. 아래 코드를 살펴보겠습니다.

interface Student { // 인터페이스 선언 (대문자로 시작)
   name:string;  
   studentId:number;
   group?:string;   // group에 optional을 부여
}

인터페이스 응용

group property에 물음표를 붙여 optional을 부여함으로써 group이 존재하지 않는 학생의 정보도 컴파일 에러 없이 가져올 수가 있게 되었습니다. 이는 여러분들이 앞으로 많이 접하게 될 것입니다.

이제 학생 정보를 저장하는 함수를 만들어보도록 하겠습니다.

//app.ts


let student1:Student={   // 학생 생성
 name: 'SYKim',
 studentId: 20125,
 group: 'M&S', 
}

function saveStudentInfo (student:Student) //인터페이스를 사용하여 argument 선언이 편리해짐
:void {  //해당 함수는 반환값이 존재하지 않으므로 type은 void가 됩니다.
}

saveStudentInfo(student1) //생성한 학생을 saveStudentInfo의 argument로 대입

몇가지 짚고 넘어갈 것이 보입니다.
우선 saveStudentInfo() 의 경우 학생정보를 저장하는 기능을 가지고 있고 별다른 return값이 존재하지 않으므로 void type으로 지정해줍니다.
또한 해당 함수의 argument type은 미리 만들어 놓은 Student라는 Interface를 통해 손쉽게 지정한 것을 볼 수 있습니다.

인터페이스 method 선언

이번에는 Interface에 property 뿐만 아니라 객체 내에서 정의된 함수인 method를 추가시켜보는 작업을 진행해보겠습니다.

addEtc (etc:string) : string;
addEtc : (etc:string) => string;

보다시피 method는 두가지 방식으로 생성될 수 있습니다. 첫번째는 normal 형식, 두번째는 arrow function을 사용하는 것입니다.

필자는 개인적으로 두번째 방식을 통해 method를 생성하는 것을 추천합니다.

인터페이스 read-only property

다음은 Readonly(읽기전용) 속성입니다. Readonly로 할당된 property 값은 선언 후에 변경할 수가 없다는 특성을 가지고 있어 특정 상황에서 쓰이고는 합니다. 아래의 코드를 살펴보겠습니다.

interface Student { 
   name:string;  
   readonly studentId:number; //studentId를 readonly로 선언
   group:string;
}

function saveStudentInfo (student:Student) 
:void {  
    student.studentId= 100100100; //에러 발생
}

아래와 같은 메시지를 볼 수 있습니다.

Cannot assign to 'studentId' because it is a read-only property.

인터페이스 js 파일?

여러분들이 해당 코드들을 따라 입력을 하다가 js파일을 보면 의아한 부분이 있을겁니다.

'어? 내가만든 interface가 app.js 파일에는 존재하지 않네?'

실제로 Interface는 코드가 렌더링 될 때 아무런 영향력이 없기에 tsc가 TypeScript 코드를 JavaScript로 compile 하는 과정에서 지워버립니다.
작성중인 코드에 대한 더 많은 정보를 제공하기 위한 용도로 작성된 인터페이스는 이렇게 JavaScript 파일인 app.js파일에서는 보이지 않게 되는 것입니다.

마무리

이번 포스팅에서는 타입 추론, 타입 명시, 인터페이스에 대해 알아보았습니다.
다음 포스팅에서는 TypeScript의 열거형(enum)과 유니언에 대해 알아보도록 하겠습니다.

감사합니다 :)

profile
김용성입니다.

0개의 댓글