타입스크립트는 왜 만들어 졌을까?
자바스크립트는 타입의 안정성이 결여되어있다.
예시)
-> [1,2,3,4] + false
-> '1,2,3,4false' //결과
자바스크립트는 위에 보이는 말도 안되는 예시도 허용한다.
예시)
funciion divide(a,b){
return a / b
}
divide(2,3) // 1. 0.666666666
divide("xxxxx") // 2. NaN
입력 값으로 a,b 2개가 필요하지만, 하나만 넣어줘도 정상적으로 실행된다.
Error를 발생시키는 것이 바람직하다.
자바스크립트는 단지 코드를 실행할 뿐이고,
a,b가 어떤 타입이 와야하는지 또는 divide 함수에 2개의 매개변수가
선택사항인지, 필요사항인지 따지지 않는다.
최악의 상황 - 코드를 실행해야만 에러를 알 수 있다.
예시)
const jerry = { name: "jerry"}
jerry.hello()
코드가 실행되기 전에 자체적으로 객체 내부의 hello라는 함수가 없다는 것을
알려주는 것이 바람직할 것이다.
타입 스크립트는 앞에서 제시한 정신나간 상황등을 예방, 개선한다.
타입스크립트는 강타입(strongly typed) 프로그래밍 언어다.
C#, Java, Go, Rust와 같은 언어에서 컴파일러는
0101로 바꿔주거나, 어셈블리 코드, 바이트 코드 등의
기계가 실행할 수 있는 다른 종류의 코드로 변환된다.
타입스크립트는 작성한 코드가 자바스크립트로 변환된다.
변환하는 이유는, 브라우저가 자바스크립트 언어를 이해하기 때문이다.
(참고로, Node JS는 타입스크립트, 자바스크립트 양 쪽 다 이해할 수 있다.)
타입스크립트를 컴파일하면 자바스크립트로 변환되는데,
만약 에러가 발생할 시 자바스크립트로 컴파일 되지 않는다.
타입스크립트 코드 테스트
https://www.typescriptlang.org/play
타입추론
let a = "hello" // type: String
a = "bye" // OK
// a = 1 // Not OK, Should be String
타입 명시적으로 표시
let b: boolean = true // OK
// let b: boolean = "x" // Not OK, Should be boolean
타입을 추론할 수 있고, 명시적으로 표시하는 방법이 있다.
일반적으로 TypeScript의 Type Checker에게 타입을 추론하는 것이 권장된다.
코드도 짧고, 가독성이 좋기 때문이다.
let c = [1,2,3] // js와 달리, 하나의 타입으로만 array를 이루어야 한다.
// 다른 타입으로 하려면, 각각 타입을 줘야댐
// const c: [string, number, boolean] = ["jerry", 1, true];
c.push(4); // OK
// c.push("1") // Not OK, Should be Number
let a: number = 1;
let b: string = "i1";
let c: boolean[] = [true]; // 동치
// let c = [true]; // 동치
const player: { // 명시적으로 타입을 줄 때
name: string, // string 형의 name은 항상 있어야한다.
age?: number // ?를 붙이게 되면, age요소는 없어도 된다.
} = {
name: "jerry"
}
if(player.age && player.age < 10){ // player.age < 10 만 쓰면 안댐,
// age는 ?가 붙어있으므로,
// do Something
}
type Player = { // Player 라는 타입을 생성하여, 재사용
name: string,
age?: number
}
const jerry: Player = {
name:"jerry"
}
const lynn: Player = {
name:"lynn",
age:12
}
type Age = number;
type Name = string;
type Player = { // Player 라는 타입을 생성하여, 재사용
name: Name,
age?: Age // optional type, number or undefined
}
function playerMaker(name: string) : Player{ // Player 타입을 리턴
return {
name
}
}
// 화살표 함수 사용법
// const playerMaker = (name:string) : Player -> ({name})
const jerry = playerMaker("jerry");
jerry.age = 20;
type Player = {
readonly name: Name, // readonly
age?: Age
}
const playerMaker = (name:string) : Player -> ({name})
const jerry = playerMaker("jerry");
jerry.age = 20;
// jerry.name = "daedfsdafads" // error, readonly!
타입에 readonly를 추가하면 수정이 불가능하다.
마찬가지로,
const numbers: readonly number[] = [1,2,3,4];
// numbers.push(1); // error, readonly!
readonly로 타입을 지정할 시, 역시 수정이 불가능해진다.
즉, 불변성(immutability) 을 가진다.
any 타입은 타입스크립트의 모든 타입을 무력화 시킨다.
const a: any[] = [1,2,3,4]
const b: any = true
console.log(a+b); // OK, 출력: "1,2,3,4true"
변수의 타입을 미리 알지 못 할 때 unknown을 사용한다.
let a:unknown;
if(typeof a === 'number'){
let b = a + 1; // OK, a는 number type이다.
}
if(typeof a === 'string'){
let b = a.toUpperCase(); // OK, a는 string type이다.
}
void는 아무것도 return 하지 않는다.
function hello(){
console.log('x');
}
function hello(name:string|number){
if(typeof name === "string"){
name // string 타입
}else if(typeof name === "number"){
name // number 타입
}else{
name // never 타입, 이 코드는 절대 실행되지 않아야 한다.
}
}
function hello():never{
// return "X" // 오류
throw new Error("xxx"); //return 하지 않고 고의로 오류 발생
}
매개변수로 어떤 것이 오는지, 반환형이 무엇인지.
type Add = (a:number, b:number) => number; // 함수의 콜 시그니쳐
const add:Add = (a,b) => a + b; // OK
프로그램을 디자인하면서 타입을 먼저 생각하고 코드를 구현해야한다.
여러개의 콜 시그니쳐가 있는 함수.
type Add = {
(a: number, b: number) :number
(a: number, b: number, c: number): number,
}
const add: Add = (a, b, c?:number) => {
if (c) return a + b + c;
return a + b;
}
add(1,2) // OK, 3
add(1,2,3) // OK, 6
다형성이란, 여러 타입을 받아들임으로써 여러 형태를 가지는 것을 의미한다.
type SuperPrint = {
(arr: number[]):void
(arr: boolean[]):void
}
const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i));
}
superPrint([1,2,3,4]);
superPrint([true, true, false, true]);
// superPrint(["1","2","3"]); // NOT OK
다양한 타입을 받기 위해서는, 사용할 타입을 모두 선언해줘야하는 불편함이 있다.
여기서, Generic 의 개념이 등장한다.
call signature를 작성할 때, 들어올 확실한 타입을 모를 때 generic을 사용한다.
Generic은 다음과 같이 사용한다.
type SuperPrint = {
<T>(arr: T[]):void; // 임의의 타입 T가 주어진다 -> <T>
}
Generic을 사용하면,
타입스크립트는 콜 시그니쳐를 유추하여 변경한다.
즉,
any와 비슷하다고 생각할 수 있지만 다르다.
any를 사용하면 모든 타입을 허용하기 때문에 함수내에서 입력된 arr이 어떤 타입인지 알 수 없다.
따라서, 함수에 입력된 arr을 다룰 때, 타입스크립트에서 발생시키는 에러를 피하기 위해
타입에따라 분기처리를 해줘야하는 번거로움이 발생할 수 있을 것으로 예상된다.
이하의 예시를 살펴보면,
type SuperPrint = {
<T>(arr: T[]):T;
}
const superPrint: SuperPrint = {
(arr) => arr[0];
}
const x = superPrint([1,2,3,4]); // 1
const y = superPrint([true, false, true]); // true
const z = superPrint([[1,2,3,4], 1, true, [1,2,3,4]]); // [1,2,3,4]
console.log(z); // [1,2,3,4]
return type도 제네릭을 활용하여 유연하게 사용할 수 있다.
함수형에서 제네릭은 다음과 같이 사용된다.
function superPrint_ <T>(a: T[]):T{
return a[0];
}
console.log(superPrint_(["1",2,3,4])); // "1"
타입 선언에서도 다음과 같이 제네릭을 사용할 수 있다.
type Holy<Cow> = {
name:string
extraInfo:Cow
}
const jerry: Holy<{favFood:string}> = {
name: "jerry",
extraInfo: {
favFood:"Kimchi"
}
}
const min: Holy<null> = {
name: "min",
extraInfo: null
}
// 타입 확장
type HolyExtra = {
favFood:string
}
type HolyJerry = Holy<HolyExtra>
const jerry2: HolyJerry = {
name: "jerry2",
extraInfo: {
favFood: "Kimbab"
}
}
HolyExtra 이하에서 처럼, 타입을 입맛에 맞게 확장하여 사용할 수 있다.
React 에서 useState 훅에서 사용할 때는 다음과 같이 작성할 수 있다.
const [number, setNumber] = useState<number>(0);