[Day 38] TypeScript 기본 문법 (1)

송히·2023년 11월 9일
post-thumbnail

Today I Learn📖

  • 개발 환경 구성 (강의)
  • 타입 종류 (강의)
  • 타입 추론 (강의)
  • 타입 단언, 할당 단언 (강의)
  • 타입 가드 (강의)
  • 타입 별칭 (강의)

개발 환경 구성

  • 타입스크립트: 정적 타입의 컴파일 언어
    => 동적 타입 언어인 자바스크립트에 정적 타입 문법을 추가해, 좀 더 엄격하게 개발할 수 있게 만들어진 프로그래밍 언어
    -> 동작할 때는 타입스크립트를 자바스크립트로 변환 후, 브라우저나 Nodejs 환경에서 동작함

    • 자바스크립트 (동적 타입) - 런타임(브라우저, Node.js)에서 동작할 때 타입 오류 확인
    • 타입스크립트 (정적 타입) - 코드 작성 단계에서 타입 오류 확인

vsc의 타입스크립트 환경 세팅 방법

  1. npm init -y (package.json 파일 생성, 프로젝트 시작)
  2. npm i -D typescript (개발 의존성 파일로 설치)
  3. tsconfig.json 파일 생성 (타입스크립트 옵션 지정)
{
  "compilerOptions": {
	"strict": true // 엄격한 옵션 추가 -> 타입 설정 꼭 필요
  },
  "include": [
	"src/**/*.ts" // src 하위에 있는 모든 .ts 파일에 적용
  ]
}
  1. src 폴더 생성 후 코드 작성 => main.ts
  2. 타입스크립트를 자바스크립트로 변환 -> package.json 파일 수정
// 원래 내용
"scripts": { 
  "test": "echo \"Error: no test specified\" &&- exit 1"
},
⠀ 
// 바꿔야하는 내용
"scripts": {
  "tsc": "tsc src/main.ts"
  // "이름": "tsc(타입스크리브의 명령어) src/main.ts(main 파일부터 변환 시작하겠다는 뜻)"
  // -> npm run "이름" 으로 실행하면 됨
  // 이름을 start로 지으면 npm start로만 사용 가능 (run 생략 가능)
},

=> 이후 npm run tsc 하면 같은 main.ts와 같은 내용의 main.js 만들어짐


Parcel 번들러를 통한 TS 세팅 방법

  1. npm init -y, npm i -D typescript parcel // parcel이라고만 치면 2버전, -bundler까지 치면 1버전 설치됨
  2. package.json 수정
"scripts": {
  "dev": "parcel ./src/index.html", // 개발 서버 오픈 명령어, 진입점 지정 가능 (./src/index.html처럼)
  "build": "parcel build ./src/index.html" // 빌드 명령어,  진입점 지정 가능 (./src/index.html처럼)
},
  1. tsconfig.json 생성 & src -> index.html, main.ts 생성
  2. index.html 작성
<! DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="module" defer src="./main.ts"></script> // 타입스크립트니까 type 작성 필수, defer 옵션으로 html이 다 해석된 후 script 동작하게 만들기, src로 연결할 파일 지정
</head>
<body>
</body>
</html>
  1. npm run dev로 서버 실행

eslint, prettier 설정 방법 (Parcel 사용시)

  • vsc의 확장 프로그램 중 ESLint, Prettier - Code formatter 설치 필요
  1. npm i -D eslint prettier eslint-plugin-prettier eslint-config-prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin 설치
    • estint-plugin-prettier: eslint와 prettier 연결
    • eslint-config-prettier: eslint와 prettier 충돌 방지
    • @typescript-eslint/parser: eslint가 TS 문법 이해하게 만드는 파서
    • @typescript-eslint/eslint-plugin: eslint와 TS 연결
  2. vercel 서버에 호스팅 할 경우 package.json 수정 필요
// 이 부분 삭제 후
"main": "index.js",
⠀ 
// 이 부분 삽입
"type": "module",
  1. .eslintrc.json 생성 -> 취향따라 다르게 설정 가능
{
  "extends": [
	"eslint: recommended", // 추천하는 것 하겠다는 의미
	"plugin:@typescript-eslint/recommended", // 추천하는 것 하겠다는 의미
	"plugin:prettier/recommended" // 추천하는 것 하겠다는 의미
  ],
  "parser": "@typescript-eslint/parser" // 미리 설치한 parser 연결
}
  1. .prettierrc 생성 -> 취향따라 다르게 설정 가능
{
  "semi": false, // 세미콜론 False
  "singleQuote": true, // 작은 따옴표 사용
  "endOfLine": "lf" // 항상 맨 마지막 줄은 비워두겠다는 의미
}
  1. .vscode 폴더 생성 후 settings.json 파일 생성
    => eslint와 prettier가 각각의 프로젝트 단위마다 적용되게 vsc와 연결하는 것 (global하게 할 수도 있음)
{
  "[typescript]": { // 적용할 언어
	"editor.formatOnSave": true, // 저장할 때마다 포맷팅 적용
	"editor.defaultFormatter": "esbenp.prettier-vscode" // 기본 포맷터로 프리티어 사용
  }
}

Vite를 이용한 TS 환경 세팅 방법

  1. npm create vite@latest . -> 비트의 최신 버전으로 프로젝트 생성
    • 사용할 프레임 워크, 언어 선택
    • npm i, npm run dev로 npm 설치 후 개발 서버 오픈 가능
    • 이미 index.html, main.ts 등을 이용한 기본 프로젝트 제공해주기 때문에 main.ts 내용 지우고 새로 작성하며 바로 프로젝트 시작 가능
  2. eslint, prettier 설정하기

타입 종류

  • 타입 지정 방법: let 변수명: 타입 으로 작성
    ex)let str: string = "Hello"

객체 타입

=> 좀 더 유연한 방법도 존재

// 에러 발생
const obj1: {} = {} // : {}이니까 빈 객체 형식으로 선언한 것 -> 여기까진 OK
obj1.a = 123 // 따라서 a를 추가할 수 없음

// 에러 발생
const obj2: { a: number } = {} // : { a: number}에는 a가 있는데, 할당된 객체에는 a가 없으니까(빈배열이니까) -> 여기서부터 에러
obj2.a = 123

// 가능
const obj3: { a: number } = { a: 0 } // 할당된 객체에 a의 초기값도 설정
obj3.a = 123
obj3.b = 456 // 타입 선언에 b는 없기 때문에 에러 발생

배열 타입

: 변수명 뒤에 : 타입[] 형식으로 작성

const arr1: string[] = [] // 초기값 설정 없이 []로 할당해도 됨
// 타입 없이 `: [] = []` 만 적으면 튜플됨
arr1[0] = "Hello"
arr1.push("World")
arr1.push(123) // 숫자 타입이니까 에러 발생

const arr2 = Array<string> // arr1과 같은 것, 표현법만 다름

함수 타입

  1. 함수명 뒤에 화살표 함수로 : (매개변수: 타입) => 반환 타입 적기
const hello: (a: string, b: number) => string = (msg, xyz) => { // 명시하는 매개변수와 실제 매개변수의 이름은 달라도 됨
  return msg
}

// 화살표 함수 특성이 TS에서도 그대로 유효함
const hello: (a: string, b: number) => string = (msg, xyz) => msg 

// 화살표 함수 대신 function 키워드도 사용 가능
const hello: (a: string, b: number) => string = function (msg, xyz) { 
  return msg
}
  1. 매개변수에 바로 타입 적고 : 반환타입까지 적기
const hello = (msg: string, xyz: string): string => {
  return msg
}

function hello(msg: string, xyz: string): string {
  return msg
}

Enum(이넘) 타입

: 타입 + 값(데이터)의 집합, 숫자일 경우 역방향 매핑 가능(값으로 인덱스 찾기 가능)
=> enum 변수명 { 데이터들 }로 사용

enum Week { Sun, Mon, Tue, Wed, Thu, Fri, Sat }
console.log (Week[0]) // 'Sun'
console.log (Week.Sun) // 0

// enum Week를 JS의 객체로 풀어쓰면 다음과 같음 (TS -> JS 변환되면서 다음처럼 바뀜)
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
} // 역방향도 같이 생김
  • 이넘은 인덱스 조작 가능 => 다음 값 인덱스는 바뀐 인덱스부터 +1 됨
enum Colors { Red, Green = 4, Blue }
console.log (Colors[0]) // 'Red'
console.log (Colors.Red) // 0
console.log (Colors.Green) // 4 -> 원래는 1여야 하지만 조작했음
console.log (Colors.Blue) // 5 -> 원래는 2여야 하지만 앞이 조작되어 4 + 1 = 5됨

// 인덱스를 숫자가 아닌 값으로 조작
enum Colors { Red = 'r' , Green = 4, Blue = 7 }
console.log (Colors.Red) // 0
console.log (Colors.r) // 에러 발생 -> 숫자가 아닌 값은 인덱스로 조회 불가
console.log (Colors[4]) // 'Green'
console.log (Colors.Blue) // 7 -> 조작됨

Void 타입

: 값을 반환하지 않는 함수의 반한 타입

const hello: (msg: string) => void = msg => { // return 값이 없으니까 void 반환
  console.log (`Hello ${msg}`)
}

hello ('World!')

// return undefined 일 때 
const hello: (msg: string) => void = msg => {
  console.log (`Hello ${msg}`)
  return undefined // undefined를 명시적으로 반환하면 void / undefined 둘 다 사용 가능
}

hello ('World!')

튜플

: 고정된 길이의 배열 타입
=> push, splice 사용하면 코드 & 런타임에서 에러를 못 잡기 때문에 주의 필요

const tup: [number, string] = [4, "Hi"] // 길이 2, 타입 고정(각 요소들의 타입은 달라도 됨)
tup[2] = 6 // 에러 발생 -> 길이 넘었으니까
tup.push(6) // 이건 됨. 그래서 원치 않게 길이가 수정 될 수 있으니 주의 필요 !!
tup.splice(2, 0, 6) // 이것도 됨. 아무것도 지우지 않고 쓰면 값 추가됨.

Never 타입

  1. 어떤 것도 할당할 수 없는 타입
const nev: never[] = []
nev.push(6) // 에러 발생 -> never 타입이니까

const nev2: [] = [] // nev와 같음. 앞이나 안에 타입 지정 안 하면 자동 never 타입 됨
  1. 정상적으로 종료되지 않는 함수의 명시적 반환 타입.
const myError: (m: string) => never = (msg) => {
  throw `에러! - ${msg}` // 에러 강제 발생이므로 반환 타입 never
}
try {
  myError('Never 타입..')
} catch (err) {
  console.error(err) // "에러! - Never 타입.." 출력
}

Any 타입

: 어떤 것도 할당할 수 있는 타입 => TS의 의미가 흐려지므로 사용 지양

let anything: any = 'Hello'
anything = 123 // any 타입이니까 뭐든 가능
anything = { a: 'A' }  // any 타입이니까 뭐든 가능

const a: string = anything  // anything 변수가 any 타입이니까(현재 객체 타입이어도) 에러 발생 X, 엄격한 검사 불가

Unknown 타입

: 정확히 무엇인지 알 수 없는 타입, 어떤 것도 할당할 수 있지만 타입이 다를 경우에는 할당 할 수 없음.
=> any 타입 대신 사용

let anything: unknown = 'Hello'
anything = { a: 'A' } // 값 바꾸는 건 뭐든 가능

const a: number = anything // 에러 발생, 지금 anything은 객체라서 타입이 다르니 할당 불가
if (typeof anything === 'string') { // 조건을 걸면 사용 가능
  const b: obj = anything
  const c: string = anything // 에러 발생. 지금 anything은 객체라서 타입이 다르니까
}

Union(유니언) 타입

: 2개 이상의 타입이 허용되는 타입, let 변수명: 타입1 | 타입2 | ...처럼 |로 사용

let uni: string | number | number[]
  uni = 'Hello'
  uni = 123
  uni = false // 에러 발생
  uni = null // 에러 발생
  uni = [1, 2, 3]

Intersection(인터섹션) 타입

: 2개 이상의 타입이 병합된 타입

type UserA = {
  name: string
  age: number
}
type UserB = {
  isValid: boolean
}

const userB: UserB = {
  name: 'B', // 에러 발생 -> UserB에 없는 타입이니까
  age: 85, // 에러 발생 -> UserB에 없는 타입이니까
  isValid: false
}

const userC: UserA & UserB = { // 인터섹션 -> 필수 타입 중 하나라도 빠지면 에러 발생, 추가되도 에러 발생
  name: 'C',
  age: 40,
  isValid: false
}

타입 추론 (Inference)

TS가 다음 3가지 상황에서는 명시하지 않아도 타입을 추론함
=> 초기화된 변수 / 기본값이 지정된 매개변수 / 반환이 있는 함수
-> 꼭 필요한 부분에만 타입을 적을 수 있게 해줌

let a = 'Hello' // a는 string 값으로 초기화된 변수니까 `: string` 생략 가능

function join (a: string, b = '') {
  // b는 빈문자열로 기본값이 지정된 매개변수니까 `: string` 생략 가능
  // join 함수는 string이라는 반환이 있는 함수니까 반환값 생략 가능
  return a + b
}
join('Hello', 'World')
join ('Good')

타입 단언, 할당 단언

: 개발자가 TS에게 변수의 타입을 말해주는 것
-> 변수를 초기화 하지 않고도 사용할 수 있게 해줌
=> 문장 뒤에 as 타입 또는 !을 붙임 (!는 타입을 확정하지 않고 null 또는 undefined이 아님만 보장)

// as 사용
const btn = document.querySelector('button') as 

// ! 사용
const btn = document.querySelector('button')!

// 변수 선언할 때 말고, 사용할 때 단언해도 됨
const btn = document.querySelector('button')
(btn as HTMLButtonElement).classList.add('btn') 
btn!.id = 'btn'

// 잘못된 코드
const json = '{ "name": "Heropy", "age": 85 }'
const user = JSON.parse(json)
console.log(user.email) // user에는 email이 없는데도 잡아내지 못하니까

// 수정된 코드
const json = '{ "name": "Heropy", "age": 85 }'
const user = JSON.parse(json) as { name: string, age: number } // 개발자가 어떤 속성이 있는지 as로 설명해줘야함
console.log(user.email) // 에러 발생

타입 가드

: 타입 추론이 가능한 특정 범위를 지정해주는 것 (그 범위 안에서 타입 보장)
=> typeof 타입, instanceof 타입, in

// if문으로 null이 아니라는 가드 만들어주기
const btn = document.querySelector('button')
if (btn instanceof HTMLButtonElement) { // instanceof HTMLButtonElement이 없어도 null이 아닌 건 보장돼서 에러 안 남
  btn.classList.add ('btn')
  btn.id = 'abc'
}

// typeof 사용
function toTwoDecimals(val: number | string) {
  if (typeof val === 'number') {
    val.toFixed(2)
  } else {
    val.slice(0, 2)
  }
}
toTwoDecimals(3.141592)
toTwoDecimals('Hello world!')

// 함수형 타입 가드
type UserA = { name: string, age: number }
type UserB = { id: string, email: string }

function isUserA(user: unknown): user is UserA { // 타입 가드용 함수이기 때문에 실제 bool값이 아닌, 어느 타입을 단언하는지 반환값으로 적어줘야함
  if (user && user.constructor === Object) {
	const u = user as UserA // user가 UserA 타입이라는 것을 단언
	return typeof u.name === 'string' && typeof u.age === 'number'
  }
  return false
}

fetch('https//exam.site')
  .then (res => res.json())
  .then ((user: UserA | UserB) => {
	if (isUserA(user)) { // 타입 가드 함수 사용 -> UserA 값 보장
	  console.log (user.name [0])
	  console.log (user.age - 10)
    }
})

타입 별칭 (Alias)

: 직접 커스텀하는 타입, type 타입명 = 타입으로 사용
-> 재사용 할 때 유용함

type MyTypeName = string | number
type MyArray = MyTypeName[]
type UserA = {
  name: string
  age: number
}
type UserB = {
  isValid: boolean
}
type UserX = UserA & UserB // 인터섹션 타입


😊오늘의 느낀점😊

타입스크립트와 자바스크립트의 차이점이 무엇인지, 왜 요즘 다 타입스크립트를 사용하는지 알 수 있었다.
굳이 실행시켜보지 않아도 에러를 발견할 수 있으니, 내가 익숙해지기만 한다면 개발 시간과 에러 잡는 시간이 훨씬 단축될 것 같다 !👍🏻
심지어 대충 예상하던 습관도 고쳐지고, 실수도 줄어들 것 같다 ㅎㅎ
JS에는 없는 타입들이 있었지만 어렵지 않아서 사용하다보면 금방 익숙해질 듯~!!

profile
데브코스 프론트엔드 5기

0개의 댓글