
Today I Learn📖
- 개발 환경 구성 (강의)
- 타입 종류 (강의)
- 타입 추론 (강의)
- 타입 단언, 할당 단언 (강의)
- 타입 가드 (강의)
- 타입 별칭 (강의)
타입스크립트: 정적 타입의 컴파일 언어
=> 동적 타입 언어인 자바스크립트에 정적 타입 문법을 추가해, 좀 더 엄격하게 개발할 수 있게 만들어진 프로그래밍 언어
-> 동작할 때는 타입스크립트를 자바스크립트로 변환 후, 브라우저나 Nodejs 환경에서 동작함
vsc의 타입스크립트 환경 세팅 방법
npm init -y(package.json 파일 생성, 프로젝트 시작)npm i -D typescript(개발 의존성 파일로 설치)- tsconfig.json 파일 생성 (타입스크립트 옵션 지정)
{ "compilerOptions": { "strict": true // 엄격한 옵션 추가 -> 타입 설정 꼭 필요 }, "include": [ "src/**/*.ts" // src 하위에 있는 모든 .ts 파일에 적용 ] }
- src 폴더 생성 후 코드 작성 => main.ts
- 타입스크립트를 자바스크립트로 변환 -> 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 세팅 방법
npm init -y,npm i -D typescript parcel// parcel이라고만 치면 2버전, -bundler까지 치면 1버전 설치됨- package.json 수정
"scripts": { "dev": "parcel ./src/index.html", // 개발 서버 오픈 명령어, 진입점 지정 가능 (./src/index.html처럼) "build": "parcel build ./src/index.html" // 빌드 명령어, 진입점 지정 가능 (./src/index.html처럼) },
- tsconfig.json 생성 & src -> index.html, main.ts 생성
- 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>
npm run dev로 서버 실행
eslint, prettier 설정 방법 (Parcel 사용시)
- vsc의 확장 프로그램 중
ESLint,Prettier - Code formatter설치 필요
⠀
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 연결
- vercel 서버에 호스팅 할 경우 package.json 수정 필요
// 이 부분 삭제 후 "main": "index.js", ⠀ // 이 부분 삽입 "type": "module",
.eslintrc.json생성 -> 취향따라 다르게 설정 가능{ "extends": [ "eslint: recommended", // 추천하는 것 하겠다는 의미 "plugin:@typescript-eslint/recommended", // 추천하는 것 하겠다는 의미 "plugin:prettier/recommended" // 추천하는 것 하겠다는 의미 ], "parser": "@typescript-eslint/parser" // 미리 설치한 parser 연결 }
.prettierrc생성 -> 취향따라 다르게 설정 가능{ "semi": false, // 세미콜론 False "singleQuote": true, // 작은 따옴표 사용 "endOfLine": "lf" // 항상 맨 마지막 줄은 비워두겠다는 의미 }
.vscode폴더 생성 후settings.json파일 생성
=> eslint와 prettier가 각각의 프로젝트 단위마다 적용되게 vsc와 연결하는 것 (global하게 할 수도 있음){ "[typescript]": { // 적용할 언어 "editor.formatOnSave": true, // 저장할 때마다 포맷팅 적용 "editor.defaultFormatter": "esbenp.prettier-vscode" // 기본 포맷터로 프리티어 사용 } }
Vite를 이용한 TS 환경 세팅 방법
npm create vite@latest .-> 비트의 최신 버전으로 프로젝트 생성
- 사용할 프레임 워크, 언어 선택
npm i,npm run dev로 npm 설치 후 개발 서버 오픈 가능- 이미 index.html, main.ts 등을 이용한 기본 프로젝트 제공해주기 때문에 main.ts 내용 지우고 새로 작성하며 바로 프로젝트 시작 가능
- eslint, prettier 설정하기
let 변수명: 타입 으로 작성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과 같은 것, 표현법만 다름
: (매개변수: 타입) => 반환 타입 적기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
}
: 반환타입까지 적기const hello = (msg: string, xyz: string): string => {
return msg
}
function hello(msg: string, xyz: string): string {
return msg
}
: 타입 + 값(데이터)의 집합, 숫자일 경우 역방향 매핑 가능(값으로 인덱스 찾기 가능)
=> 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
} // 역방향도 같이 생김
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 -> 조작됨
: 값을 반환하지 않는 함수의 반한 타입
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) // 이것도 됨. 아무것도 지우지 않고 쓰면 값 추가됨.
const nev: never[] = []
nev.push(6) // 에러 발생 -> never 타입이니까
const nev2: [] = [] // nev와 같음. 앞이나 안에 타입 지정 안 하면 자동 never 타입 됨
const myError: (m: string) => never = (msg) => {
throw `에러! - ${msg}` // 에러 강제 발생이므로 반환 타입 never
}
try {
myError('Never 타입..')
} catch (err) {
console.error(err) // "에러! - Never 타입.." 출력
}
: 어떤 것도 할당할 수 있는 타입 => TS의 의미가 흐려지므로 사용 지양
let anything: any = 'Hello'
anything = 123 // any 타입이니까 뭐든 가능
anything = { a: 'A' } // any 타입이니까 뭐든 가능
const a: string = anything // anything 변수가 any 타입이니까(현재 객체 타입이어도) 에러 발생 X, 엄격한 검사 불가
: 정확히 무엇인지 알 수 없는 타입, 어떤 것도 할당할 수 있지만 타입이 다를 경우에는 할당 할 수 없음.
=> 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은 객체라서 타입이 다르니까
}
: 2개 이상의 타입이 허용되는 타입, let 변수명: 타입1 | 타입2 | ...처럼 |로 사용
let uni: string | number | number[]
uni = 'Hello'
uni = 123
uni = false // 에러 발생
uni = null // 에러 발생
uni = [1, 2, 3]
: 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
}
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)
}
})
: 직접 커스텀하는 타입, type 타입명 = 타입으로 사용
-> 재사용 할 때 유용함
type MyTypeName = string | number
type MyArray = MyTypeName[]
type UserA = {
name: string
age: number
}
type UserB = {
isValid: boolean
}
type UserX = UserA & UserB // 인터섹션 타입
타입스크립트와 자바스크립트의 차이점이 무엇인지, 왜 요즘 다 타입스크립트를 사용하는지 알 수 있었다.
굳이 실행시켜보지 않아도 에러를 발견할 수 있으니, 내가 익숙해지기만 한다면 개발 시간과 에러 잡는 시간이 훨씬 단축될 것 같다 !👍🏻
심지어 대충 예상하던 습관도 고쳐지고, 실수도 줄어들 것 같다 ㅎㅎ
JS에는 없는 타입들이 있었지만 어렵지 않아서 사용하다보면 금방 익숙해질 듯~!!