자바스크립트
자바스크립트는 동적 타입 언어이다
그와 동시에 인터프리터 언어이다
인터프리터 언어란 코드를 한 줄씩 읽어들이며 해석하고 실행하는 프로그램을 말한다
JS는 타입에 제한을 받지 않기에 유연함을 자랑한다
하지만 그와 동시에 불안정한 부분도 존재한다
따라서 이는 비교적 작은 규모의 프로젝트를 진행하기에 적합하다
또한 JS는 웹브라우저에서 읽어들이기에 독립적으로 사용이 가능하다
타입스크립트
타입스크립트는 정적 타입 언어이다
그와 동시에 컴파일 언어이다
컴파일 언어란 코드 전체를 읽어서 기계어로 해석한 후 실행하는 프로그램을 말한다
TS는 타입에 제한을 걸 수 있다
이로 인해 일관성을 유지할 수 있으며 재사용성이 높다
또한 코드 해석에 대해 직관적이기에 간결하다
TS의 특징은 JS와 달리 존재하지 않는 property에 접근하면 undefined를 반환하는 것이 아닌 오류를 반환한다
따라서 이는 비교적 큰 규모의 프로젝트, 복잡한 프로젝트를 진행하기에 적합하다
웹 브라우저에서는 TS를 바로 해석하지 못해 JS로 컴파일 후 실행한다
따라서 의존적이다
(TS는 JS의 확장이다)
설치
설치는 npm을 통해 하며 명령어는 다음과 같다
npm i -g typescript
이를 통해 컴파일러도 동시에 설치된다
타입스크립트와 같은 패키지는 전역으로 설치하는 경우가 일반적이다
이젠 test.ts파일을 만들고 컴파일을 해보자
컴파일 명령어는 다음과 같다
tsc test.ts
정상적으로 main.js가 생성된 모습을 확인할 수 있다

다음으로는 TS의 환경설정을 살펴보자
환경설정
우선 환경설정을 위해서 tsconfig.json파일을 생성해야 한다
명령어는 다음과 같다
tsc --init
실행하면 json파일이 생기고 내부의 내용은 컴파일러 옵션이 모두 적혀있을 것이다
해당 내용을 모두 지우고 원하는 설정으로 초기화하자
컴파일 할 대상 폴더를 지정할 수 있다CompilerOptions의 내부 설정들을 살펴보자
outdir
컴파일 후 추출한 결과물을 담을 폴더명을 설정할 수 있다
target
컴파일 하는 버전을 설정할 수 있다
moduleResolution
node로 설정 시 import가 node를 가리키도록 지정할 수 있다
skipLibCheck
정의 파일의 타입 확인을 건너뛸 수 있다
module
모듈을 위한 코드 생성을 설정할 수 있다
moduleDirection
우선 TS는 기본적으로 동일한 네임 스페이스에 변수들이 모인다
따라서 전역으로 변수들을 모으는데 이 과정에서 다른 파일에서 사용한 변수도 제한을 받는다
이를 방지하기 위해 설정하는 옵션이다
strict
타입을 지정하도록 유도한다 - TS의 사용이유
stricNullCheck
변수에 null이나 undefined를 재할당이 불가능하도록 설정
noImplicitAny
암묵적으로 any타입을 지정할 지 설정
우선 TS는 타입을 기재하지 않으면 해당 변수에 타입을 추론한다
예를 들어
const a = 1;
이라고만 사용하면 a를 number type으로 추론한다는 의미이다
따라서 이러한 타입을 명시적으로 지정해주기 위해 타입 주석(type annotation)을 사용한다
const a:number = 1;
primitive type
원시형의 타입들의 설정은 간단하다
const num:number = 1;
const str:string = 'hello';
const bool:boolean = true;
const na:null = null;
const undef:undefined = undefined;
console.log(num);
console.log(str);
console.log(bool);
console.log(na);
console.log(undef);
출력 결과는 다음과 같다

literal type
literal type은 타입 대신 값을 사용하여 확정시켜주는 역할을 한다
let a:1 = 1;
만약 a를 다른 값으로 변경하려하면 오류가 난다
Array type
배열은 크게 두 가지의 방법으로 나타낼 수 있다
우선 [1,2,3]이라는 배열을 지정하는 법을 살펴보자
const arr:number[] = [1,2,3];
const _arr:Array<number> = [1,2,3];
우선 배열에 사용되는 type을 기재하고 배열임을 알리는 []를 사용하는 방법과
배열임을 알리고 그 내부의 Element들의 type을 지정하는 방법이 존재한다
만약 배열에 여러 가지 type이 사용된다면 다음과 같이 설정한다
const arr:(number|string|boolean)[] = [1,'a',true];
const _arr:Array<number|string|boolean> = [1,'a',true];
Tuple type
배열의 연장선이다
이는 배열을 좀 더 한정적으로 정의할때 사용한다
const arr:[number,number,string] = [1,2,'a'];
const _arr:[number,number,string] = [1,'a',2]; // error
배열을 정의할 때 길이와 각각의 원소들의 타입을 지정하여 사용하는 것이다
이는 2차원 이상의 배열에 유용하다
const arr:[number,number][] = [
[1,2],
[3,4],
];
만약 내부에 원소로 [1,2,3] 혹은 ['a',1]와 같은 배열이 들어오면 타입과 일치하지 않으므로 오류를 발생시킨다
object type
객체를 선언하는 방법은 간단하다
객체의 key를 선언하고 그에 해당하는 value의 type을 지정해주면 된다
const obj:{
name:string,
age:number,
}= {
name:'young-man',
age:25,
};
만약 key를 선언은 하지만 때에 따라 사용하고 싶지 않을때는 optional로 선언할 수 있다
const obj: {
name:string,
age:number,
address?:string,
} = {
name:'young-man',
age:25,
};
이때 address를 적지 않았지만 문제 없이 작동한다
이번엔 edit이 불가능하도록 만드는 방법을 살펴보자
const obj: {
name:string,
age:number,
readonly id:number,
} = {
name:'young-man',
age:25,
id:1,
}
이때 obj의 id에 접근하여 값을 변경하려고 하면 오류를 발생시킨다
객체는 tuple type과 유사하게 key의 종류가 정해져있다
따라서 점표기법과 대괄호 표기법으로 접근하여 새로운 property를 창조하는 것은 불가능하다
(타입에 지정되어있으면 문제 없다)
만약 이를 해소하고 싶다면 index signature를 사용하면 된다
const obj: {
name:string,
age:number,
[key:string]:number|string,
} = {
name:'young-man',
age:25,
}
obj.weather = '맑음';
alias type
객체의 경우를 살펴보면 type이 상당히 길어져서 코드의 가독성을 저하시킨다
이를 방지하기 위해 type을 분리하여 변수처럼 할당하는 것이 가능하다
사용법은 interface와 type을 사용해서 선언할 수 있다
interface Obj {
name:string,
age:number,
}
type _Obj = {
name:string,
age:number,
}
const obj:Obj = { // const obj:_Obj로도 사용 가능
name:'young-man',
age:25,
}
이렇게 하면 이전에 객체에서 생성한 것과 같은 동작을 한다
이렇게 interface와 type을 지정할땐 반드시 시작을 대문자로 한다
interface와 type의 차이는
interface는 동일한 변수를 여러 번 사용해도 된다(단, 이 경우 계속해서 병합된다)
type의 경우는 동일한 변수를 사용할 수 없다
interface a{};
interface a{}; // O
type b = {};
type b = {}; //X
alias는 여러 개를 합치거나 교집합을 만들 수도 있다
(엄밀히 따지자면 교집합은 애매한 느낌이다)
type A = {
a:number,
b:string,
}
type B = {
b:string,
c:boolean,
}
type New = A&B;
type _New = A|B;
type은 위와 같이 병합하기도 한다
New의 경우 A와B에 해당하는 모든 type을 사용해야하지만 _New의 경우 A혹은 B만 만족해도 사용이 가능하다
interface A {
a:number,
b:string,
}
interface B extends A {
c:boolean,
}
interface는 다음과 같이 확장을 사용한다
따라서 B에서 A의 모든 type 속성을 가져와 추가하는 것이다
이렇게 사용된 alias는 코드가 작동하는 같은 공간에 존재하는 것이 아닌
외부에서 적용하고 import해오는 방식을 채택한다
enum type
enum type은 쉽게 말해 대문자 상수를 정의한다고 생각하면 편하다
바로 코드로 살펴보자
enum Weekend {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
}
이렇게 사용하게 되면 SUNDAY부터 SATURDAY까지 각각 0~6이 할당된다
시작하는 값을 바꾸는 것도 가능하다
enum Weekend {
SUNDAY = 20,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
}
이러면 각각 20~26이 할당된다
또한 각각의 상수에 원하는 값을 할당하는 것도 가능하다
enum Weekend {
SUNDAY = 'sun',
MONDAY = 'mon',
TUESDAY = 'tue',
WEDNESDAY = 'wed',
THURSDAY = 'thu',
FRIDAY = 'fri',
SATURDAY = 'sat',
}
any type
무적의 type으로 볼 수 있다
모든 type을 수용하고 모든 type처럼 사용할 수 있다
하지만 이는 매우 불안전하다
예를들어 데이터로 받는 값은 number type인데 .toUpperCase()가 실행하도록 코드 설계를 할 수 있다는 의미이다
let a:any = 1;
a = {name:'young-man'};
a = 'hello';
a = [1,2,3,4];
a.toUpperCase();
위의 경우 모든 구역에서 오류를 반환하지 않는다
unknown type
unknown type은 any type과 상당히 유사하다
하지만 any type보다는 안전하다
이는 모든 타입을 받아올 수 있지만
그 어떠한 타입이라고도 생각하지 않는다
따라서 원하는 method를 사용할 수 없다
const aaa:unknown = 1.234;
aaa.toFixed(); // error
분명 number type으로 지정했지만 number type에 사용할 수 있는 method인 toFixed는 사용할 수 없는 모습이다
any와 unknown 모두 안전하게 사용하기 위해선 narrowing이 필요하다
narrowing
쉽게 말해 type좁히기이다
조건문을 이용해 type을 체크하고 그에 맞는 method를 실행하는 방법이다
let a:any = 'hello';
let b:unknown = 123;
if(typeof a === 'number') {
a.toFixed();
} else if(typeof a === 'string') {
a.toUpperCase();
} else if('key' in a) {
console.log(a.key);
}
if(typeof b === 'number') {
b.toFixed();
} else if(typeof b === 'string') {
b.toUpperCase();
} else if('key' in b) {
console.log(b.key);
}
등등을 사용하여 타입 검사를 할 수 있다
이때 가장 유용하게 사용되는 녀석은 instanceof이다
물론 any와 unknown에서만 narrowing이 사용되는 것은 아니다
함수에서도 사용되는 경우가 있다
함수는 매개변수를 여러 타입으로 받도록 설계하고 이 과정에서 의도치않게 type이 광범위 해지므로 이를 제한하는 narrowing을 사용한다
function processInput(input: object | string | number | any[] | Error): void {
if (typeof input === "string") {
console.log(input.toUpperCase());
}
if (typeof input === "number") {
console.log(input ** 2);
}
if (Array.isArray(input)) {
console.log(input.length);
}
if (input instanceof Error) {
console.log(input.message);
}
}
void type
함수가 실행은 하지만 반환 값이 없을 때 반환 type을 void로 기재한다
function outputHI():void {
console.log('hello');
}
never type
이도 역시 반환값이 없을 때 사용한다
하지만 특수한 경우에 사용한다
Error를 출력하거나 함수 내부에서 루프문을 돌 때 주로 사용한다
그렇다면 generator function에도 사용이 가능한거 아닌가? 라는 생각을 할 수 있지만
안타깝게도 generator function은 generator 객체를 반환한다
function infiniteLoop():never {
while(true) {
console.log('never');
}
}
이와 같은 타입은 볼 일이 많지는 않다
generic type
generic type은 TS의 꽃이다
이는 type을 지정할때 특정해서 선언하는 것이 아닌
변수로 선언한다
function sum<T>(value:T):T{
return value;
}
이는 sum function에 인수를 전달하는 값에 따라 type이 정해진다
sum(10); // <T> number type
sum('hello'); // <T> string
이처럼 보면 any타입과 다를바 없어 보이지만 <T>를 그대로 사용하는 경우는 거의 없다
조건을 걸어 그에 해당하는 type만 실행하도록 유도할 수 있다
function getLength<T extends {length:number}>(value:T):number {
return data.length;
}
위와 같이 사용하게 되면 매개변수로는 length의 method를 가진 변수만 전달받겠다는 의미로 변한다
또한 두 개 이상의 generic type을 지정하는 것도 가능하다
function swap<T,U>(a:T,b:U):(T|U)[] {
return [b,a];
}
마지막으로 generic type에 사용하는 관례적인 것들을 살펴보고 마무리하자
T:Type
U:Unknown or Unique
K:Key
V:Value
E:Element
R:Return
위와 같은 키워드를 주로 generic type에 부여한다
참고로 generic type의 이름은 맘대로 지정해도 문제는 없다