TypeScript

이후띵·2022년 4월 27일
0

프론트_지식백과

목록 보기
8/8

타입스크립트는 왜 만들어 졌을까?

  • 타입의 안정성
    - 버그, 런타임에러, 생산성

자바스크립트는 타입의 안정성이 결여되어있다.

  • 매우 유연한 언어이다.

예시)
-> [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라는 함수가 없다는 것을
알려주는 것이 바람직할 것이다.


타입 스크립트는 앞에서 제시한 정신나간 상황등을 예방, 개선한다.

TypeScirpt..?

타입스크립트는 강타입(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

TypeScript의 타입

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 선언, 재활용

type Player = { // Player 라는 타입을 생성하여, 재사용
  name: string,
  age?: number
}
const jerry: Player = {
  name:"jerry"
}
const lynn: Player = {
  name:"lynn",
  age:12
}

함수 return에 타입 지정

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;

Using readonly - 불변성(immutability)

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 타입

any 타입은 타입스크립트의 모든 타입을 무력화 시킨다.

const a: any[] = [1,2,3,4]
const b: any = true
console.log(a+b); // OK, 출력: "1,2,3,4true" 

unknown 타입

변수의 타입을 미리 알지 못 할 때 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 타입

void는 아무것도 return 하지 않는다.

function hello(){
  console.log('x');
}

never

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 하지 않고 고의로 오류 발생
}

Call signatures

매개변수로 어떤 것이 오는지, 반환형이 무엇인지.

type Add = (a:number, b:number) => number; // 함수의 콜 시그니쳐
const add:Add = (a,b) => a + b; // OK

프로그램을 디자인하면서 타입을 먼저 생각하고 코드를 구현해야한다.

Overloading

여러개의 콜 시그니쳐가 있는 함수.

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

Polymorphism(다형성)

다형성이란, 여러 타입을 받아들임으로써 여러 형태를 가지는 것을 의미한다.

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을 사용하면,
타입스크립트는 콜 시그니쳐를 유추하여 변경한다.
즉,

generic VS any ?

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);
profile
이후띵's 개발일지

0개의 댓글