본문 바로가기
언어/JavaScript

(JavaScript)ES6와 TypeScript의 공통점과 차이점 - 차이점편

by 흥부와놀자 2021. 9. 1.

이번에는 저번에 이어 차이점에 대해 써보려고 한다. 사실 TypeScript안에 ES6가 포함되어 있으므로 차이점이란 그냥 TypeScript의 특징으로 봐도 될것 같다. 타입스크립트가 가진 기능들을 서술하고 각 기능들이 ES6에 대하여 어떤 의의가 있는지 서술하고자 한다.

 

정적 타입체크 기능

자바스크립트
타입스크립트

위의 코드는 string을 함수로 호출하려는 코드이다. 이런 코드에 대해 기존 자바스크립트에서는 어떠한 에러도 발생시키지 않은채 컴파일이 되고 런타임상에서 에러를 뱉는다. 하지만 타입스크립트의 경우 컴파일 이전에 에러를 뱉는것을 볼 수 있다. 이러한 기능을 하는 도구가 static type-checker이다.  

 

그외의 에러체킹 기능

만약 존재하지 않는 프로퍼티에 접근하려 할때 기존 자바스크립트는 undefiend를 반환시킨다. 하지만 타입스크립트는 조금 더 명확한 에러 메시지를 보여준다.

const user = {
	name: "Daniel",
	age: 26
};

user.location;

또한 이외에도 소문자 대문자 같은 철자, 함수에 소괄호 작성 됬는지, 분기문에서의 로직 에러들을 정확히 잡아내 준다.

 

자동완성 기능

타입스크립트의 type-checker는 내가 사용하고자 하는 프로퍼티를 미리 제안하는 기능도 가지고 있다.

IDE에서 자동완성 기능을 통해 코드를 제안하고 에러 메시지를 표시할 수 있다. 또한 단순히 에러 메시지를 발생시키는 것 이상으로 퀵 픽스를 통해 에러 가 발생하는 부분을 빠르게 고쳐줄 수도 있다. 또한 변수의 정의로 갈 수 있는 탐색기능과 해당 변수의 모든 참조들도 찾아주는 기능이 있다. 

import express from 'express';
const app = express();

app.get('/', function (req, res) {
	res.sen (... sen으로 시작하는 코드에 대한 코드 제안 컨텍스트 메뉴)
});

 

TypeScript Compiler (TSC)

tsc는 타입스크립트의 컴파일러이다.  작동법은 이렇다.

 

1. npm install -g typescript

-g옵션을 줘서 전역으로 설치를 하는 명령어 이며 아마 앞에 sudo를 붙여야 할 수도 있다.

 

2. ts파일을 만들고 타입스크립트 프로그램을 작성한다.

 

3. tsc 프로그램이름.ts

- tsc명령어를 실행한다.

 

처음 tsc를 실행하면 해당 ts파일이 js파일로 바뀐것을 볼 수가 있다. 만약 에러가 있는 코드를 컴파일하면 이렇게 터미널에 나온다.

한가지 이상한 점은 기존의 ts파일이 그대로 하나의 js파일로 바뀌어서 따로 생겼다는 것인데, 이것을 통해 타입스크립트를 컴파일 하면 자바스크립트로 다시 바뀌는것을 볼 수가 있다. (이러한 과정을 트랜스 파일이라고 하는데, 컴파일이 완전 다른언어로 바뀌는 것에 비해 트랜스파일은 해당 언어와 비슷하거나 좀 더 추상화된 언어로 바뀌는 것이다.)

이러한 것은 타입스크립트가 기존의 자바스크립트와 완전히 다른 언어가 아닌 호환이 가능한 언어라는 점을 말해준다고 생각한다. 현재 js파일이 사용되는 곳들은 타입스크립트로 다시 변환시키고 호환시키기 쉬울 것이다. 만약 작성해놓은 js코드가 문법적 에러가 나는지 확인해보고 싶다면 해당 js코드를 그대로 ts로 붙여넣은 뒤 체킹되는 에러를 수정하고 js로 트랜스파일링 하면 될것이다.

 

타입 명시

타입스크립트의 강력한 기능중 하나가 바로 이 타입을 명시해 놓을 수 있단거라고 생각한다.

이렇게 타입스크립트에서는 변수에 :와 타입을 명시해준다. 만약 변수 이름이 명확해서 굳이 명시를 안해줘도 된다면 타입스크립트에서 알아서 해당 타입을 추론해주니 안써도 상관은 없다. 하지만  조금 더 정확한 개발을 위해서 존재하는 타입스크립트의 중요한 기능이다. 또한 이 ts파일이 트랜스파일 되어 js파일로 바뀔때, 기존 자바스크립트에선 지원하지 않기에 이러한 타입명시코드는 사라진다.  

 

낮은 버전으로 트랜스파일

타입스크립트는 js로 바뀔때 낮은 버전의 js로도 바뀔수 있다.  

이러한 타입스크립트의 코드가

`Hello ${person}, today is ${date.toDateString()}!`;
"Hello " + person + ", today is " + date.toDateString() + "!";

이러한 자바스크립트코드로 바뀌었다. 처음 코드는 ES6의 스트링 템플릿 기능이지만 타입스크립트는 기본적으로 ES3나 ES5로 트랜스파일링 되기 때문에 아래의 코드로 바뀐것이다. 

tsc --target es2015 tsTest.ts 명령어로 해당 타입스크립트를 ES6로 트랜스파일할 수 있다. 

 

타입체킹 옵션

타입스크립트엔 코드를 얼마나 엄격하게 검증할지 옵션들을 제공한다. 알아볼 옵션 2가지는 noImplicitAny와 strictNullChecks 옵션이다. 만약 이러한 옵션을 키고 끄려면 tsconfig.json에서 해당 옵션의 프로퍼티를 true, false 해주면 된다.

 

- noImplicitAny

타입스크립트에는 모든 타입을 받을 수 있는 Any타입이 존재한다.  만약 사용자가 변수에 타입명시를 써놓지 않았을땐  Any타입으로 간주해버릴 수도 있는데, noImplicitAny는 이렇게 암묵적으로 Any타입으로 추론된느 변수에 대해서 에러를 발생시킨다. 모든 타입을 구체적으로 명시하여 더 정확한 코드를 원할때 이러한 옵션을 사용하면 될것 같다.

 

- strictNullChecks

타입스크립트에서 null과 undefiend도 하나의 타입으로 간주된다.

let nullable:null = null;
let undefinedable:undefined = undefined;
// [오류]
// [ts] 'undefined' 형식은 'null' 형식에 할당할 수 없습니다.
// let nullable: null
nullable = undefined;

만약 strictNullChecks 옵션이 켜져 있는 상태에서 위와같이 널타입의 변수에 널이 아닌값을 넣게되면 에러를 뱉는다. 

 

여러타입들

primitive 타입

타입스크립트도 자바스크립트와 같이 string, number, boolean의 프리미티브 타입을 지원한다.

 

 Arrays

let a1:number[]
let a2:Array<number>

이렇게 number[]로 사용가능하고 Array<number>처럼 제네릭 타입과 같이 사용가능 하다.

 

Any

어떤 변수를 Any타입으로 명시할 시 함수가 됬던, 어떤 타입의 값을 넣던, 어떤 프로퍼티에 접근하던 에러를 발생시키지 않는다.

let obj: any = { x: 0 };
// 아래의 모든 코드 줄은 타입 체크 컴파일 에러를 발생시키지 않는다.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

타입체킹옵션인 noImplicitAny를 통해 이러한 암묵적인 Any의 오류를 막을 수 있다.

 

함수

함수를 정의할때 매개변수에 타입과 리턴되는 타입도 명시 할 수 있다. 

function getFavoriteNumber(name: string): number {
	return 26;
}

name 매개변수는 string으로, 리턴값은 number로 명시해 놓은것을 볼 수 있다.

또한 함수의 호출 주체를 추론하여 체크할수도 있는데,

// 타입 주석이 없지만, 타입스크립트는 버그를 잡아낼 수 있다.
const names = ['Alice', 'Bob', 'Eve'];

// 함수를 위한 컨텍스츄얼 타이핑
names.forEach(function (s) {
	console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

// 컨텍스츄얼 타이핑은 화살표 함수에도 적용된다.
names.forEach((s) => {
	console.log((s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

위와 같이 names가 배열타입이란걸 명시하지 않아도 알아서 배열타입으로 추론하여 forEach해주고, 그안의 요소들이 string타입이란것을 추론하여 사용자가 쓴 메소드의 이름이 정확한지 체킹할 수 있다.

 

Object타입

// 매개변수의 타입이 오브젝트 타입이다.
function printCoord(pt: { x: number; y: number }) {
	console.log(`The coordinate's x value is ${pt.x}`);
	console.log(`The coordinate's y value is ${pt.y}`) 
}
printCoord({ x: 3, y: 7 });

위와 같이 pt라는 변수의 타입을 하나의 객체로 명시한 것이다. {}안에 해당 변수의 타입이 가질 프로퍼티 이름과 타입들을 명시해 주면된다.

굳이 class나 객체를 따로 만들지 않고 저렇게 리터럴객체로 명시해 준뒤 넣어 줌으로써 더 빠르고 정확한 개발이 가능할 것이다. 

 

Optional 프로퍼티

function printName(obj: { first: string; last?: string }) {
	// ...
}
// 둘 다 OK
printName({ first: 'Bob' });
printName({ first: 'Alice', last: 'Alisson' });

Optional프로퍼티는 이렇게 객체의 프로퍼티뒤에 ?를 붙임으로써 해당 프로퍼티가 굳이 존재하지 않아도 에러를 뱉지 않게 해준다.

만약 printName({ first: 'Bob' }); 이후 printName함수에서 last프로퍼티에 접근하려 한다면 undefiend를 뱉기 때문에 만약 해당 함수 내부에서 last프로퍼티를 사용하려한다면 해당 프로퍼티가 undefiend인지를 확인해 줘야 한다.

 

Union 타입

function printId(id: number | string) {
	console.log('Your ID is: ' + id);
}
// OK
printId(101);
// OK
printId('202');
// 에러
printId({ myId: 22342 });
// Argument of type '{ myId: nubmer; }'
// is not assignable to parameter of type
// 'string | number'.

Union타입의 경우 타입명시 할때 여러 타입 사이에 | 를 넣음으로써 여러 타입을 모두 사용할 수 있는 변수가 된다.  prindId의 매개변수 id가 숫자와 문자 둘다 받을 수 있음을 볼 수 있다.  만약 해당 함수 내부에서 특정한 타입이 들어왔을때 분기처리하려면 typeof와 같은 타입체크 연산잘를 사용해야 한다.

 

Type Alliases

오브젝트 타입이나 유니온 타입같은 타입들을 직접 변수 하나하나에 명시 할 수도 있지만 미리 지정해놓고 사용하면 편할것이다.

type Point = {
    x: number;
    y: number;
};


function printCoord(pt: Point) {
    console.log(`The coordinate's x value is ${pt.x}`);
  console.log(`The coordinate's y value is ${pt.y}`);
}


printCoord({ x: 100, y: 100 });

이렇게 Point타입을 따로 지정하여 사용하는것을 볼 수 있다.

 

 

Interface

Type Aliases와 비슷하게 오브젝트 타입에 이름을 붙이는 방식이다.

interface Point {
    x: number;
    y: number;
}


function printCoord(pt: Point) {
    console.log(`The coordinate's x value is ${pt.x}`);
  console.log(`The coordinate's y value is ${pt.y}`);
}


printCoord({ x: 100, y: 100 });

이렇게 class에서 상속받아 사용할 수 있다.

interface itest{
    func ():number;
    age:number
}




class CTest implements itest{
    age:number
    constructor(){
    
    this.age = 10  
    }
    func(){
        return 1;
    }

 

 

Type Alliases와 Interface의 차이

인터페이스와 타입 앨리어스 차이는  같은 이름을 사용하여 확장하는건 인터페이스만 가능하단 것입니다.

type Windows = {
    title: string
};


type Windows = {
    ts: TypeScriptAPI
}


// Error: Duplicate identifier: 'Windows'.

타입 앨리어스의 경우 위와 같이 중복됬을때 에러가 납니다.

interface Windows {
    title: string
}


interface Windows {
    ts: TypeScriptAPI
}

하지만 Interface의 경우 title에 이어 ts프로퍼티까지 새롭게 추가되게 됩니다.

 

Narrowing

Narrowing은 함수의 매개변수로 유니온 타입을 설정했을때 해당 매개변수에 들어갈 수 있는 타입들을 추론하여 런타임에서 나올 수 있는 값들을 분석하는 방법이다.

분기문, 참거짓 체크, typeof, instanceof와 같은 방법들이 존재한다.

function logValue(x: Date | string) {
    if (x instanceof Date) {
        console.log(x.toUTCString());
        // (parameter) x: Date
    } else {
        console.log(x.toUpperCase());
        // (parameter) x: string
    }
}

이것은 하나인 instanceof를 사용하는 코드이다. Date타입의 객체인 x는 Date의 인스턴스이므로 분기문과 x instanceof Date를 통해 x가 Date타입으로 들어왔을때를 분석할 수 있다. 

 

Generic

TypeScript를 통해 Generic을 사용할 수 있다. 

function identity<Type>(arg: Type): Type {
    return arg;
}

기존 Java에서의 제네릭과 동일하게 보면되고 제네릭 타입을 지정해주면 런타임때 자동으로 타입을 추론해 준다.