Day36 - TIL 타입스크립트

정태호·2023년 7월 21일
0

TIL

목록 보기
14/23
post-thumbnail

타입스크립트

정적 타입의 컴파일 언어

  • 자바스크립트(동적 타입) : 런타임에서 동작할 때 타입 오류 확인!
  • 타입스크립트(정적 타입) : 코드 작성 단계에서 타입 오류 확인!

지금 하고 있는 자바스크립트에서는 잘못된 오류가 있다고 하면 실제로 동작을 할 때 브라우저의 console 에서 에러를 확인할 수 있다. 다시 생각해보면 실제로 동작을 시키기 전에는 코드가 잘못된 것인지 확인할 수가 없다..

📌 타입스크립트를 사용하면 코드를 작성하는 단계에서 잘못된 코드를 확인할 수 있다!!! 하지만 브라우저나 Node.js 환경에서 동작할 수 있는 코드가 아니기 때문에 자바스크립트로 변환 후 사용한다.

타입스크립트는 자바스크립트와 완벽 호환하며 대부분의 플러그인, 라이브러리, 프레임워크 또한 타입스크립트를 지원한다.

타입스크립트 시작하기

  1. npm 프로젝트로 만들기 위해 npm init -y 입력을 통해 package.json 파일 생성
  2. 타입스크립트 생성 npm i -D typescript (개발의존성 패키지)
  3. package.json 확인 후 루트에 tsconfig.json 파일 생성

tsconfig.json

{
  "compilerOptions": {
    "strict": true //엄격 모드로 하겠다
  },
  "include": [
    // src라는 폴더에 모든 하위경로에 .ts 확장자인 모든파일
    "src/**/*.ts" //타입스크립트 코드를 제공할 경로
  ]
}

package.json 파일 scripts 부분에

"scripts": {
		"tsc": "tsc src/main.ts"
},

이 부분을 추가한 후 터미널에 npm run tsc를 입력하면 src/main.ts가 자바스크립트 파일로 변환된다.
tsconfig.json 파일에 자바스크립트로 변환될 때 자바스크립트의 몇 버전으로 적용이 될 껀지 target으로 지정해줄 수 있다.

{
	"compilerOptions": {
		"strict": true,
    	"target": "ES2015"
      	"lib": ["ESNext","DOM","DOM.Iterable"]
	},
	"include": ["./src/**/*.ts"]
}

lib으로는 타입스크립트에서 사용하려고 하는 라이브러리를 설정해줄 수 있다. ex) ESNext : 최신 버전, DOM, DOM.Iterable : 반복 가능한 객체를 사용할 때

Enum(이넘) 타입(값)

  • 타입 + 값(데이터)의 집합으로 역방향 매핑(reverse mapping) 가능.
// 배열
const week = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
console.log(week[0]) // Sun
console.log(week[6]) //Sat
console.log(week.findIndex(item => item === 'Sun')) //0
console.log(week.findIndex(item => item === 'Sat')) //6

//Enum 배열이나 객체스럽게 접근 가능
enum Week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}
console.log(Week[0]) //Sun
console.log(Week[6]) //Sat
console.log(Week.Sun) //0
console.log(Week.Sat) //6

//자바스크립트로 변환된다면 이렇게 될 것이다.
//역방향 매핑
const EnumWeek = {
  0: 'Sun', 1: 'Mon', 2: 'Tue', 3: 'Wed', 4: 'Thu', 5: 'Fri', 6: 'Sat',
  Sun : 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6
}
console.log(EnumWeek[0]) //'Sun' 
console.log(EnumWeek[6]) //'Sat'
console.log(EnumWeek.Sun) //0
console.log(EnumWeek.Sat) //6

숫자를 집어넣으면?

enum Colors {Red, Green = 4, Blue}

console.log(Colors.Red) //0
console.log(Colors[0]) //Red

// Red는 앞에 있고 따로 건들지 않았기 때문에 0을 가짐
console.log(Colors.Red) //0
console.log(Colors.Green) //4
console.log(Colors.Blue) //5

나열하는 개념이기 때문에 뒤에 있는 Blue는 인덱스가 5가 된다. 예를 들면 4부터 다시 시작한다고 생각하자!

문자를 집어넣으면?

  • 문자를 집어넣은 부분에 역방향 매핑이 불가능해진다!!!
enum Colors {Red = 'r', Green = 4, Blue}

console.log(Colors.Red) //'r'
console.log(Colors[4]) // Green
console.log(Colors.r) // 역방향 매핑 불가

void 타입

  • 값을 반환(return)하지 않는 함수의 반환 타입
const hello: (msg: string) => undefined = (msg) => {
  console.log(`Hello ${msg}`);
};

const hello: (msg: string) => void = (msg) => {
  console.log(`Hello ${msg}`);
};

hello('World!');

위처럼 함수는 undefined를 반환할 것이지만 반환하는 타입이 없으므로 void로 선언해 주어야 한다.

튜플(Turple) 타입

  • 고정된 길이(length)의 배열 타입
  • 대괄호의 바깥쪽에 타입이 선언되어 있으면 배열, 안쪽에 선언되어 있으면 튜플 타입이다.
// 배열
const tup: number[] = [4,5]
tup[2] = 6 //[4,5,6]
tup[3] = 7 //[4,5,6,7]

// 튜플 타입
const tup: [number,number] = [4,5]
tup[2] = 6 //[4,5,6] 길이가 달라지기 때문에 에러 발생
tup[3] = 7 //[4,5,6,7] 에러 발생

꼭 주의할 점

const tup: [number, number] = [4,5]
tup[2] = 6; //error
tup.push(6) // [4,5,6]
tup.splice(2,0,6) //[4,5,6]

// [id, username, isValid]
const userA: [number, string, boolean] = [1, 'abc', true]
user[3] = 'abc@naver.com'; //error
userA.push('abc@naver.com')

위 코드에서 pushsplice코드를 사용해서 튜플 타입에 값을 넣었을 때는 길이가 변했음에도 오류가 발생하지 않는다... 이러한 점에 꼭 주의해서 사용하도록 하자!!

Never 타입

  • 어떤 것도 할당할 수 없는 타입
  • 정상적으로 종료되지 않는 함수의 반환 타입(무한 루프..?)

어떤 것도 할당 x

  • 아이템에 타입이 전혀 없기 때문에 never라고 작성한 것과 동일하게 동작한다.
const nev: [] = []; // 아래 코드와 동일하게 동작
const nev: never[] = [];

nev.push(6) //할당 불가 Error

정상적인 종료 x

  • throw는 정상적으로 종료되지 않는 함수이다. 반환하는 타입도 없다. -> never 타입이 됨
const myError: (error: string) => never = (error) => {
  throw `에러임! - ${error}`;
};

try {
  myError('Never 타입입니다.'); // '에러임! - Never 타입입니다.'
} catch (e) {
  console.log(e);
}

any 타입

  • 어떤 것도 할당할 수 있는 타입
  • any 타입의 변수를 아무 타입의 변수에 할당하는 것도 가능해진다.
  • 엄격한 타입스크립트에서 좋은 부분이 아니기 때문에 사용에 주의하자!
let anything: any = 'Hello';

anything = 123;
anything = { A: 'a' };
anything = ['a', 'b', 'c'];

// 어떤 타입이든 상관없기 때문에..
const a: string = anything;
const b: number = anything;
const c: boolean = anything;

unknown 타입

  • 어떤 것도 할당할 수 있지만, 정확히 무엇인지 알 수 없는 타입
  • 다른 타입에는 할당할 수 없음
  • any 타입과 비슷하지만 더 엄격하기 때문에 unknown 타입의 사용을 권장한다.
// 어떤 것도 할당이 가능하기 때문에 unknown 타입의 변수에는 무엇이든 할당될 수 있음
let anything: unknown = 'Hello';

anything = 123;
anything = { A: 'a' };
anything = ['a', 'b', 'c'];

//타입가드
if(typeof anything === 'string'){
  const d: string = anything; // 이건 가능 any보단 unknown을 권장
}

// 다른 타입에는 할당 불가
const a: string = anything; //Error
const b: number = anything; //Error
const c: boolean = anything; //Error

Any vs Unknown

let any: any = 'Hello';
console.log(any.toUpperCase()); // 가능
any = 123;
console.log(any.toUpperCase()); // 가능 -> toUpperCase()는 문자열에서만 가능하므로 런타임에서는 에러 발생

오류가 있는 코드가 코드 상에서 오류가 없이 런타임으로 넘어가버린다.

let unknown: unknown = 'hi';
if (typeof unknown === 'string') {
  console.log(unknown.toUpperCase()); // 이건 가능
}
console.log(unknown.toUpperCase()); //Error!

unknown = 123;
// 숫자데이터는 toUpperCase라는 메소드를 사용하지 못한다.
//코드 상에서 에러 확인 가능
if (typeof unknown === 'number') {
  console.log(unknown.toUpperCase());
}

console.log(unknown.toUpperCase()); //Error! 코드 상에서 확인 가능

unknown 사용 시 타입을 정확하게 알 수 있는 경우에서만 활용이 가능해진다.

Union 타입

  • 2개 이상의 타입이 허용되는 타입
let uni: string | number | number[];

uni = 'Hello';
uni = 123;
uni = false; // Error
uni = null; // Error
uni = [1, 2, 3];

Intersection(인터섹션) 타입

  • 2개 이상의 타입이 병합된 타입
  • union 타입과 반대되는 개념으로 2개 이상의 타입이 허용되는게 아니라 병합해서 하나의 타입으로 만들어 논 것을 의미한다.
type UserA = {
  name: string;
  age: number;
};

type UserB = {
  isValid: boolean;
};

const user1: UserA = {
  name: 'A',
  age: 30,
  isValid: true, //Error! UserA 타입에는 isValid 속성은 없다
};

const user2: UserB = {
  name: 'B', //Error! UserB 타입에는 name속성은 없다
  age: 11, //Error! UserB 타입에는 age 속성은 없다
  isValid: true,
};

const user3: UserA & UserB = {  // type1 & type2 -> 병합한 새로운 type이 됨
  name: 'B',
  age: 11,
  isValid: true,
};

타입 추론(Inference)

  • 추론이란, 어떠한 판단을 근거로 삼아 다른 판단을 이끌어 냄.
  1. 초기화된 변수
  2. 기본값이 지정된 매개변수
  3. 반환이 있는 함수
//(1) 초기화된 변수의 타입추론
let a = 'Hello';
a = 123; //Error
a = true; //Error

function join(a: string, b: string = ''): string {
   return a + b;
}

//(2)기본값을 통해서 b라는 매개변수가 string 타입이라는걸 추론할 수 있음
//(3)들어오는 매개변수가 모두 string 타입이기 때문에 반환타입 또한 추론 가능하다.
function join(a: string, b = ''){
  return a + b;
}

join('Hello', 'World');
join('GoodGood');

타입 단언(Assertion)

  • 단언이란 주저하지 않고 딱 잘라 말함
  • as, !(Non-null 단언 연산자)
  • 느낌표가 붙은 앞의 코드가 null이나 undefined가 아니라고 단언해주는다는 개념
const btn = document.querySelector('button') 
btn.classList.add('btn'); //Error null일수도 있다.
btn.id = 'add' //Error -> 위의 코드처럼 다시 as로 단언해주지 않으면 똑같은 코드를 써줘야만 제대로 동작한다.

const btn = document.querySelector('button') as HTMLButtonElement;
btn.classList.add('btn'); //가능
btn.id = 'add' //가능

//해결방법 단언!
// as 사용 -> btn이라는 변수의 타입을 HTMLButtonElement 타입으로 딱 잘라 타입스크립트에게 전달

//!(Non-null 단언 연산자)
//느낌표가 붙은 앞의 코드가 null이나 undefined가 아니라고 단언해주는다는 개념
// 정확하게 어떤 타입인지는 알 수가 없다.

const btn = document.querySelector('button')!
btn.classList.add('abc');
btn.id = 'abc';

function toTwoDecimals(val: number | string, isNum: boolean){
  if(isNum){
    (val as number).toFixed(2) //Error -> union 타입이기때문에 val이 string일 경우에는 toFixed를 사용하지 못한다.
  }else{
    (val as string).slice(0,2) //Error -> union 타입이기때문에 val이 number일 경우에는 slice를 사용하지 못한다.
  }
}

function toTwoDecimals(val: number | string, isNum: boolean){
  if(isNum){
    (val as number).toFixed(2) //가능
  }else{
    (val as string).slice(0,2) //가능
  }
}
toTwoDecimals(3.141414, true)
toTwoDecimals('GOOD', false)

const json = '{"name" : "jth", "age" : 25}'
const user = JSON.parse(json) as {name: string, age: number}
console.log(user.work) // work 속성이 없는데 에러가 나타나지 않음
// 타입스크립트가 문자 데이터 내용까지 이해해서 json으로 바꼈을 때까지 예측할 순 없다.
// 단언 해줘야만 제대로 에러가 나타남

let num: number
console.log(num) //Error => num에는 아무값이 초기화 되있지 않으므로 undefined 값이다. 그렇기 때문에 number 타입이 아니다.

let num!: number //할당 단언
console.log(num) //가능

즉 단언이란 개발자타입스크립트에게 이 부분은 이런 타입이다 딱 잘라 말하는 개념
타입 단언은 코드상에서 오류가 없더라도 결국 런타임에서 오류가 날 수 있기 때문에 타입 가드를 사용하는 것이 좋다!!

타입 가드(Guard)

  • 타입 추론이 가능한 특정 범위(Scope) 안에서 타입을 보장
  • typeof, instanceof, in
const btn = document.querySelector('button');
btn.classList.add('abc'); //Error
btn.id = 'abc'; //Error 

const btn = document.querySelector('button');
// null 값이 들어오면 조건 수행이 되지 않기 때문에 훨씬 안전함
if(btn){ //btn이 있을때만 동작!
	btn.classList.add('abc'); //Error
	btn.id = 'abc'; //Error
}

const btn = document.querySelector('button');
if(btn instanceof HTMLButtonElement){ //HTMLButtonElement의 인스턴스다~
  btn.classList.add('abc'); //Error
  btn.id = 'abc'; //Error
} 
function toTwoDecimals(val: number | string, isNum: boolean) {
  if (isNum) {
    val.toFixed(2); // Error
  } else {
    val.slice(0, 2); // Error
  }
}

toTwoDecimals(3.14, true);
toTwoDecimals('Good', false);

function toTwoDecimals(val: number | string) {
  if (typeof val === 'number') { // 타입이 number 일때만~
    val.toFixed(2);
  } else {
    val.slice(0, 2);
  }
}

toTwoDecimals(3.14);
toTwoDecimals('Good');

타입가드 함수 사용

function isNum(val: unknown) :val is number {
  return typeof val === 'number'
}

function toTwoDecimals(val: number | string) {
  if (isNum(val)) { // 타입이 number 일때만~
    val.toFixed(2);
  } else {
    val.slice(0, 2);
  }
}

toTwoDecimals(3.14);
toTwoDecimals('Good');

타입가드 함수

  • user is UserA 처럼 타입 가드가 목적인 함수 반환하는 타입부분에 is라는 키워드를 쓸 수 있다.
  • 반환하는 타입은 user라는 매개변수가 UserA 타입인지 확인하는 것
type UserA = { name: string; age: number };
type UserB = { id: string; email: string };

// 타입 가드 함수 -> UserA 타입인지 확인
function isUserA(user: unknown): user is UserA {
  if (user && user.constructor === Object) { // user데이터가 존재하고 && 객체 데이터 일때만
    const u = user as UserA; // user는 UserA 타입이라고 단언
    //user 매개변수를 UserA 타입처럼 u에 할당
    return typeof u.name === 'string' && typeof u.age === 'number';
  }
  return false;
}

fetch('https://example.com')
	.then(res => res.json())
	.then((user: UserA | UserB) => {
		console.log(user.name[0]) // 문자데이터라면 되야 하지만 오류 발생
		console.log(user.age - 10) // 숫자데이터라면 되야 하지만 오류 발생
})

fetch('https://example.com')
  .then(res => res.json())
  .then((user: UserA | UserB) => {
    if(isUserA(user)){ // 타입가드 함수를 사용함으로써 제대로 동작
      console.log(user.name[0])
      console.log(user.age - 10)
    }
})

타입가드 함수를 사용할 때 반환하는 타입에 is 키워드를 사용한 반환타입을 제대로 넣어주지 않으면 단순히 다른 함수를 호출하는 개념이므로 그 함수가 실행될 때 까지 타입스크립트는 어떤 타입인지 제대로 알지 못해 오류가 발생한다. 꼭 제대로 넣어줘서 호출 시에 타입가드 함수인지를 알 수 있도록 만들어주자!!!

타입 별칭(type alias)

  • 새로운 타입 조합 생성
  • 타입에 이름을 붙여줄 수 있다!!

type Alias

type MyTypeName = string | number;
type MyArray = MyTypeName[];

type UserA = {
  name: string;
  age: number;
};

type UserB = {
  isValid: boolean;
};

type UserX = UserA & UserB //type Intersection(인터섹션)

const A : MyTypeName = 'a'
const B : MyArray = [1,'A','B',2,3]
const userA : UserA = {
  name: 'jth',
  age: 25
}

const userB : UserB = {
  isValid: false
}

const userX : UserX = {
  name: 'jth',
  age: 25,
  isValid: true
}
profile
주니어 프론트엔드 개발자가 되고 싶습니다!

0개의 댓글