타입스크립트는 강 타입 프로그래밍 언어(strongly typed programming language)이다.
프로그래밍 언어라고 하면 컴파일러
를 떠올릴 수 있는데, 언어를 작성하고 나면 코드를 컴파일해서 다른 종류의 언어로 변환하는 것을 말한다.
타입스크립트는 자바스크립트로 변환된다.
또한 타입스크립트는 자바스크립트를 기반으로 한 superset 언어이다. 자바스크립트를 기반으로 하되 보다 더 확장된 프로그래밍 언어라는 뜻이다.
자바스크립트의 기본적인 문법, 코드 작성법, if문, for반복문, 객체 사용법 등을 그대로 사용한다.
다만 타입스크립트는 자바스크립트 문법에 몇 가지 기능을 추가한 것이다.
타입스크립트는 리액트와 달리 JS의 라이브러리가 아니기 때문에 JS의 기존 기능을 기반으로 새로운 기능을 만들거나 기능을 확장하지는 않는다.
대신 JS의 주요 문법보다 확장된 문법을 가진다.
가장 중요한 것은 정적 타입(statically Typed)의 특징을 갖는다는 점이다. 타입스크립트의 이름도 여기에서 유래했다.
정적 타입 기능이 추가된 이유는 JS가 원래 동적 타입(dynamically typed)언어이기 때문이다.
JS에는 자료형(Type)
이 있다.
add()
함수의 매개변수로 자료형이 Number
인 2
와 5
를 전달해보자.
그러면 콘솔에 찍히는 결과 값은 7
이 나온다.
function add(a, b) {
return a + b;
}
// 매개변수로 전달한 두 값은 Number 타입
const result = add(2, 5);
console.log(result);
// ✅ 결과는 7
JS는 타입스크립트 없이도 자체적으로 타입(자료형)을 알고 있다.
하지만 JS는 동적 타입 언어이기 때문에 함수 선언 시점
에서는 자료형을 특정 짓지 않는다. 단지 두 개의 매개 변수를 받는 다는 것만 알고 있다.
즉, 타입이 고정되어 있지 않고, 이 함수에서 사용할 타입을 미리 정해놓지 않은 상태에서 전달된 매개 변수를 받아 코드를 실행한다.
이런 경우 아래 처럼 String
문자형을 매개변수로 보내면 어떻게 될까?
function add(a, b) {
return a + b;
}
// 매개변수로 전달한 두 값은 String 타입
const result = add("2", "5");
console.log(result);
// ✅ 결과는 25
문자형을 매개변수로 보냈기 때문에 "2"
와 "5"
를 나란히 둔 "25"
가 출력된다.
혹시나!!! 자료형을 잘못 넘길 경우, 내가 원하는 결과가 출력되지 않을 수 있다.
따라서 코드를 보완하고 오류를 방지하기 위해 정적 타입
이 필요한 것이다.
위 경우는 아주 작은 예시이므로 오류라고는 할 수 없지만, 만약 대규모 프로젝트를 진행한다면 소스 파일도 많고 많은 사람들이 동일한 코드 베이스에서 작업하기 때문에 함수나 객체를 의도치 않은 방식으로 사용하는 일이 발생할 수 있다.
이 함수는 이렇게 사용하면 안됩니다! 라고 알려주는게 있으면 좋은데, 이럴 때 타입스크립트가 도움이 된다.
타입 표기(Type Annotation)
를 추가할 수 있다.// 타입표기에 number 설정
function add(a: number, b: number) {
return a + b;
}
// 🔥 매개변수로 전달할 수 있는 값은 항상 Number
const result = add(2, 5);
console.log(result);
// ✅ 결과는 7
타입표기로 타입을 미리 설정해 두면, 코드를 실행하기 전에 의도치 않은 방식의 함수 사용을 미리 잡아낼 수 있다.
오류를 피할 수 있기 때문에 더 나은 코드를 작성하는데 도움이 된다.
npm init
npm i typescript
로 타입스크립트를 설치한다.npm i -g typescript
로 설치하면 된다.설치를 완료하면 타입스크립트 컴파일러
를 사용할 수 있다.
npx tsc
그런데 오류가 발생한다.
명령어를 이렇게 실행하려면 타입스크립트 구성 파일을 프로젝트 폴더에 추가하여 타입스크립트에게 컴파일할 파일을 알려줘야 하는데 현재 구성 파일이 없기 때문에 오류가 발생한다.
구성파일이 아직 없기 때문에 일단은 컴파일할 파일을 직접 지정하여 명령어를 실행해보자.
npm tsc with-typescipt.ts
그러면 TS파일이 컴파일 되어 같은 이름을 가진 JS 파일이 생성된다.
ts 파일에 오류가 있어도 컴파일된 Js 파일은 형성된다. 오류가 있으면 알려주고 js 파일도 제공해준다.
ts파일에서 오류를 수정해 주면 된다.
컴파일 완료된 js 파일을 html에서 연결해주자.
그러면 숫자형으로 7
이 콘솔에 출력된다.
이제 브라우저에서는 이전과 같은 오류는 발생하지 않을 거다.
소문자
로 써야한다.더 복잡한 타입: arrays, objects
Function types, parameters
// number
let age: number;
age = 11;
//string
let userName: string;
userName = "BTS";
//boolean(논리형)
let isInstructor: boolean;
isInstructor = true;
//null, undefined은 보통 이렇게 표기하지 않음
//1. 배열
let hobbies: string[]; //문자열 배열
hobbies = ["tennis", "work out", "make music", "dancing"];
//객체는 기본적으로 any로 설정된다.
//let members: any;
//이렇게 표기하면 타입스크립트에게 이 변수에 저장할 값의 타입에 대해 알려줄게 없다고 말하는 것으로 어떤 값이든 저장할 수 있다.
//하지만 이 타입은 예비적으로 상요되는 타입이므로 사용하지 않는 것이 좋다.
//굳이 타입스크립트까지 사용하면서 아무거나 다 사용가능하다고 할 필요는 없으니까 말이다.
//2. 객체: 객체 구조 정의
let person: {
name: string;
age: number;
};
person = {
name: "RM",
age: 28,
};
// 3. 객체를 여러 개 가진 배열
// {객체구조}[]
let members: { name: string; age: number; position: string[] }[];
members = [
{ name: "RM", age: 28, position: ["Rapper", "Leader"] },
{ name: "JIN", age: 30, position: ["Vocalist", "Big Bro"] },
{ name: "SUGA", age: 30, position: ["Rapper", "Producer"] },
{ name: "j-hope", age: 29, position: ["Rapper", "Dancer"] },
{ name: "Jimin", age: 27, position: ["Vocalist", "Dancer"] },
{ name: "V", age: 27, position: ["Vocalist", "Producer"] },
{ name: "JK", age: 26, position: ["Vocalist", "Dancer"] },
];
여기까지는
1. 변수를 선언하면서 명시적으로 타입을 지정
2. 그런 후 값 설정
따라서 코드 길이가 길어지고 있다.
코드 작성시 변수를 만들면서 바로 시작 값을 할당해도 된다. 그렇게 해도 타입스크립트의 핵심기능인 타입 추론 기능을 통해 타입을 추론할 수 있다.
// Type Inference(타입 추론)
let song = "Dynamite"; //변수 생성시 바로 string 값 할당
song = 2020; //number 할당
이렇게 타입 추론 기능을 활용하여 코드를 작성하는 것이 권장되는 방식이다.
let c: number[] = [];
c.push(1);
// [1]
보통 변수는 한가지 자료형만 가진다. 그래서 숫자만 저장하거나, 문자열만 저장하는 등, 보통 그렇게 사용한다.
하지만 변수에 다양한 타입을 여러 개 저장하는 경우도 있다.
이렇게 하나의 변수에 문자열과 숫자를 함께 저장하거나, 서로 다른 형태의 객체를 동일한 변수에 저장해야 하는 경우, 하나 이상의 타입을 지정할 수 있게 해주는 유니온 타입
이라는 기능이 있다.
명시적
으로 타입을 지정한다.|
)를 넣고 뒤에 다른 타입을 추가하면 된다.//Union type
let song: string | number = "Dynamite";
song = 2020;
//그러면 song에 숫자를 넣어도 오류가 안 뜬다.
let memberName: string | string[];
memberName = "Namjoon";
memberName = ["Kim", "Namjoon"];
//이렇게 이름만 적어도 되고, 성과 이름을 분리하여 배열로 작성해도 된다.
타입스크립트 작성하다 보면 동일한 타입을 반복해서 정의하는 일이 많아진다.
// 객체
let person: {
name: string;
age: number;
};
person = {
name: "RM",
age: 28,
}
// 배열 안에 든 객체
let people: {
name: string;
age: number;
}[];
people = [
{
name: "RM",
age: 28,
},
{
name: "JIN",
age: 30,
},
]
people은 배열 안에 객체가 들어가 있다는 차이가 있지만 people의 배열 안에 든 객체는 person객체와 똑같다.
이럴 때 타입 별칭(Type Alias)
을 만들면 코드 중복을 피할 수 있다.
직접 기본(base)타입을 만들어 거기에 복잡한 타입을 정의해 두고, 중복해서 타입을 정의하는 대신 그 타입 별칭을 사용하면 된다.
type
키워드로 새로운 타입 별칭을 정의 할 수 있다.:
) 대신 이퀄사인(=
) 으로 정의한다. 이는 타입스크립트의 기능이기 때문에 컴파일하면 js파일에서는 안 보인다.// 타입 별칭 정의
type Person = { name: string; age: number };
let person: Person; // 객체 타입에 직접 만든 별칭 타입 할당
person = {
name: "RM",
age: 28,
};
//객체를 여러개 가진 members 배열
let members: Person[]; // 객체 타입에 직접 만든 별칭 타입 할당
members = [
{ name: "RM", age: 28 },
{ name: "JIN", age: 30 },
{ name: "SUGA", age: 30 },
];
//타입 별칭 만들기
type Player = {
name: string,
photo?: string // 선택적 변수(optional parameter) 지정
// photo: string | undefined
}
//타입으로 타입 별칭 사용
const rm: Player = {
name: "RM",
photo: "https://~~.com"
}
//타입으로 타입 별칭 사용
const jin: Player = {
name: "JIN",
}
타입을 가진 함수를 말할 때, 함수와 타입이라고 하는 이유는 함수를 사용할 때 타입을 지정하는 위치가 따로 있기 때문이다.
function add(a: number, b: number) {
return a + b;
}
그런데 이 함수에서는 타입이 추론되는 부분도 있다.
함수에서 값을 반환하는데, 이를 통해 타입스크립트는 반환 값의 타입을 알게 된다.
즉, 함수의 반환 값
이 가지는 타입을 통해 함수 타입을 추론한다.
add()함수에 마우스 커서를 가져다 대면 타입값을 확인할 수 있는데, 매개 변수 목록 뒤의 콜론과 타입은 반환 값의 타입에서 추론된 것이다.
타입스크립트는 이 함수에서 number가 반환되는 것을 알고 있다. 매개변수에 숫자타입을 대입하면 결과는 항상 숫자타입이기 때문에 그렇게 추론한다.
직접 반환값에 대해 명시적으로 표기할 수도 있다.
function addFn(a: number, b: number): number {
return a + b;
}
여기서 알아둬야 할 것은, 함수에서 타입을 사용할 때는 매개변수
의 타입뿐만 아니라, 반환 값
의 타입도 생각해야 한다는 것이다.
함수에는 입력만 있는 것이 아니라 출력도 있다!
player object 만들고 그것을 return 해보자.
//타입 별칭
type Player = {
name: string,
age?: number
}
//함수
//화살표 함수로는 이렇게 적으면 된다.
//const playerMaker = (name:string) => ({name})
function playerMaker(name: string){
return {
name
};
}
//함수의 리턴값은 { name: string } 으로 추론된다.
const bts = playerMaker("bts");
//따라서
bts.age = 11; // 오류 뜸! 왜냐면 객체에 age가 없으니까
하지만 type Player
를 playerMaker()
함수 리턴 값의 타입으로 지정해 주면 age를 추가하는 것이 가능하다.
//타입 별칭
type Player = {
name: string,
age?: number
}
//함수의 리턴값에 대해 명시적 표기
//화살표 함수로는 이렇게 적으면 된다.
//const playerMaker = (name:string): Player => ({name})
function playerMaker(name: string): Player {
return {
name
};
}
//함수의 리턴값은 { name: string, age: number | undefined } 으로 추론된다.
const bts = playerMaker("bts"); // 이제 const bts: Player 타입으로 추론된다.
bts.age = 11; // 오류 안뜸!
//아무것도 반환하지 않는 함수 printOutput
function printOutput(value: any) {
console.log(value);
}
함수에서 반환 값이 없을 경우에 가지는 특별한 반환 타입은 void
이다.
void
는 null
과 undefined
과 비슷하지만 항상 함수와 결합해서 사용한다는 특징이 있다.
따라서 void
는 함수에 반환 값이 없다는 것을 뜻한다.
만약 이 함수의 반환 값을 받아 작업하려면 undefined
타입으로 값을 받아야 한다.
void는 함수에만 있는 특수한 타입으로 함수의 반환 타입에 사용된다.
기존 배열은 유지하면서 배열에 새로운 값을 추가하여, 새로운 배열을 반환하는 유틸리티 함수를 만들어보자.
이때, 매개변수의 타입은 any로 설정하여 숫자,문자 뭐든 받을 수 있게 한다고 해보자.
function insertAtBeginning(array: any, value: any) {
// 전개 연산자로 새 배열 만들기
const newArray = [value, ...array];
//새로운 배열 반환
return newArray;
}
const demoArray = [1, 2, 3];
const updatedArray = insertAtBeginning(demoArray, -1);
// 결과 값 [-1, 1, 2, 3];
//배열에 있는 첫번째 요소에 스플릿 메서드를 호출한다고 해보자.
//(참고) split() 메서드는 문자형(string)에 사용되는 메서드이다.
updatedArray[0].split("");
//이는 문자열에 사용하는 함수이지만 오류가 발생하지 않는다!! 물론 실행하면 오류가 발생하겠지만..
타입스크립트는 updatedArray의 첫 번째 요소의 값이 숫자라는 것을 알수 없으므로 오류인지 알아차릴 수 없기 때문에 number
형에 split()
메서드를 사용하려고 해도 타입스크립트는 오류를 알아차리지 못한다.
애초에 타입스크립트는 updatedArray
배열에 숫자만 들어있다는 것을 인식하지 못한다. 왜냐하면 insertAtBeginning()
함수 정의 시 매개변수의 타입을 any
로 지정했기 때문이다.
number
로 할 수도 없다. 다른 타입인 문자열(string)
배열에 대해서도 이 insertAtBeginning()
유틸리티 함수를 사용할수도 있기 때문이다.any
타입이 필요하긴 한데 any
를 사용하면 함수를 호출한 후 타입스크립트로부터 어떤 지원도 받을 수 없게 된다.이럴 때 타입스크립트의 generic
기능을 사용할 수 있다.
이 함수 안에서만 사용할 수 있는 타입인 제너릭 타입을 정의할 수 있다.
T
를 따서 식별자로 사용한다.//✅ 제네릭 타입 정의 및 매개변수에 제네릭 타입 사용하기
function insertAtBeginning<T>(array: T[], value: T) {
// 전개 연산자로 새 배열 만들기
const newArray = [value, ...array];
// 새로운 배열 반환
return newArray;
}
const demoArray = [1, 2, 3];
const updatedArray = insertAtBeginning(demoArray, -1);
// updatedArray = [-1, 1, 2, 3];
updatedArray[0].split(""); //🔥 오류 뜸!!
이제 타입스크립트는 오류를 캐치할 수 있다.
demoArray가 숫자 배열이란 것도 추론한다.
updatedArray가 숫자 배열이란 것도 추론한다.
제네릭 타입을 사용하여 타입스크립트에게 any
타입의 값이 아니라는 것을 알려주고, 대신 array
배열과 value
값이 같은 타입(T
)을 가져야 한다는 것을 알려주었기 때문에 타입스크립트가 정상적으로 오류를 캐치할 수 있다.
문자열로 테스트를 해보자.
function insertAtBeginning<T>(array: T[], value: T) {
const newArray = [value, ...array];
return newArray;
}
const stringArray = insertAtBeginning(["a", "b", "c"], "d");
// updatedArray = ["d", "a", "b", "c"];
stringArray[0].split(""); //오류 안뜸
제네릭 타입을 사용하여 매개변수에 명시해줬기 때문에 stringArray
배열은 문자열 타입으로 추론된다. 따라서 stringArray
배열에 split()
메서드를 사용하면 정상적으로 작동한다.
이 처럼 제네릭 타입을 사용하면 함수의 타입 안정성
과 유연성
을 가질 수 있다.
생성 시점
에 타입이 명시된다. 따라서 하나의 타입 뿐만 아니라 다양한 타입을 사용할 수 있다.readonly
속성은 요소들을 읽기 전용
으로 만들 수 있다.
type Player = {
readonly name: string, //🔥 Player의 name 요소를 읽기 전용으로 설정
age?: number
}
//함수의 리턴값에 대해 명시적 표기
const playerMaker = (name:string): Player => ({name})
const bts = playerMaker("bts");
bts.age = 11;
bts.name = "stb" //🔥 읽기 전용이라서 이름 못 바꾼다.
readonly
를 사용하면 이렇게 name을 함부러 바꿀 수 없다.
const ids: readonly string[]= ["test1", "test2"]
ids.push("test3") //ids 배열이 readonly이기 때문에 배열을 바꾸는 것은 불가능!
ids.filter((id) => id !== "test1") // ["test2"]
//ids 배열을 변경하지 않고 새로운 배열을 반환하는 filter나 map 같은 메서드는 사용 가능!
배열을 바꾸는 메서드는 사용할 수 없지만, 새로운 배열을 반환하는 메서드는 사용할 수 있다.
즉, 타입스크립트의 보호장치로 immutability(불변성)을 가질 수 있어서 좋다.
Tuple
은 배열
을 생성할 수 있게 하는데 최소한의 길이
를 가져야 하고, 특정 위치에 특정 타입
이 있어야 한다.
name, age, 챔피언인지 아닌지에 대한 요소를 가진 배열을 만든다고 해보자.
["bts", 11, true]
//배열의 첫 번째 요소는 string,
//두 번째 요소는 number
//세 번째 요소는 boolean
그러면 이렇게 타입을 설정하면 된다.
const player: [string, number, boolean] = ["bts", 11, true];
palyer[0] = 1 // 오류뜸! 첫 번째 인덱스의 타입은 string이기 때문
그러면 타입스크립트에세 이 배열이 최소 3개의 요소를 가지고, 순서대로 적어야 함을 알려준다.
Tuple
타입을 이용하면 항상 정해진 갯수의 요소를 가져야 하는 array를 지정할 수 있다.
readonly를 같이 쓰면 아무것도 바꿀수 없게 된다.
const player: readonly [string, number, boolean] = ["bts", 11, true];
palyer[0] = "hi" // 오류뜸! readonly 이기 때문에 배열을 바꿀 수 없음
let a: undefined = undefined
let b: null = null
가끔 ?
, 선택적 타입(optional type)을 사용하는데, 선택적 타입은 undefined이 될 수도 있다.
타입스크립트에서 벗어나고 싶다면 any를 쓰면 된다..^^!
any는 타입스크립트의 모든 보호장치를 비활성화 시킨다.
웬만하면 any는 쓰지말자.
어떤 타입인지 모르는 변수는 타입스크립트에게 어떻게 말해줘야 할까?
만약 API로 부터 응답을 받는데, 응답의 타입을 모른다면 unknwon
타입을 사용 할 수 있다.
let a: unknown;
이렇게 하면 타입스크립트로부터 일종의 보호를 받을 수 있다.
let a: unknown;
let b = a + 1; //a가 unknwon 타입이라서 오류남
따라서 변수의 타입을 먼저 확인하자.
let a: unknown;
let b = a + 1; //a가 unknwon 타입이라서 오류남
//a의 타입이 number라면 실행해라
if(typeof a === "number"){
let b = a + 1 //이 스코프(범위) 안에서는 a가 number이므로 타입 오류가 발생하지 않음
}
a.toUpperCase() //당연히 오류 발생, a의 타입은 unknwon
if(typeof a === "string"){
let b = a.toUpperCase() //이 스코프 안에서는 a가 string이므로 타입 오류 발생하지 않음
}
이렇게 변수의 타입을 미리 알 수 없다면, unknown 타입을 명시하여 사용하면 된다.
그러고 나서 변수의 타입을 조건문을 통해 체크하여 해당하는 타입에 맞게 실행될 수 있게 하면 된다.
별로 많이 사용되지는 않지만 알아두자.
never
는 함수가 절대 return 하지 않을 때 발생한다.
function hello(): never{
return "X";
}
hello() // 리턴 값이 있으므로 작동하지 않음
function hello(): never{
throw new Error("에러 발생!");
//리턴하지 않고 오류 발생시키는 함수
}
hello() // 리턴 값이 없으므로 작동
function hello(name: string | number){
if(typeof name === "string"){
console.log(name); //이 스코프 안에서는 name: string
} else if (typeof name === "number"){
name + 1; //이 스코프 안에서는 name: number
} else {
name //이 스코프 안에서는 name: never
//만약 타입이 올바로 들어온다면, 위의 두 조건 중 하나가 실행된다.
//그러므로 이 코드는 절대 실행되지 않아야 한다는 뜻
}
}
타입스크립트는 다른 언어로 컴파일되는 강 타입 프로그래밍 언어이다.
타입스크립트 코드를 자바스크립트 코드로 변환된다.
타입스크립트에는 타입 추론 기능이 있다.
타입스크립트가 제공하는 타입 추론 기능은 타입스크립트 코드가 자바스크립트 코드로 변환되기 전에 작동한다.
즉, 타입스크립트 코드에 에러가 있으면 그 코드는 자바스크립트로 컴파일 되지 않는다. 컴파일 되기 전, 미리 오류를 알려주어 보호하는 기능을 한다.