타입스크립트 1일차, 개발환경 구성 및 타입 종류와 추론, 단언 등등..
너무 피곤한 관계로 실습하며 vscode에 정리한 내용 첨부..
강의 듣다가 잘 이해가 안가서 즉석에서 찾아봤는데 아직 잘 모르겠다.
functionunknownColor(x: never): never {
throw new Error("unknown color");
}
type Color = 'red' | 'green' | 'blue'
functiongetColorName(c: Color): string {
switch(c) {
case 'red':
return 'is red';
case 'green':
return 'is green';
default:
returnunknownColor(c); // Argument of type 'string' is not assignable to parameter of type 'never'
}
}
function hello(msg: string) {
console.log(`Hello ${msg}?!`);
}
hello('world')
// hello(123)
// hello(true)
// 문자, 숫자, 불린 타입
let str: string = "Hello world"
let num: number = 123
let boo: boolean = true
str = 'Good morning~'
str = 123
num = 'Good morning~'
boo = false
boo = 789
// 객체 타입
// obj. or obj[] 으로 데이터를 추가할 수 있는데 이러한 방식은 ts에서는 에러가 발생한다.
const obj: {a: number} = {a: 0} // 빈 객체 형식으로 만들어짐
obj.a = 123
// obj.b = 456 //위에 b가 정의되지 않았기 때문에
// 뒤에서 더 유연한 방식을 배운다.
// 배열 타입
// 튜플타입(?)인 상태이다.
// number타입의 배열이다! 라고 정의해 주어야함
// 즉 배열에서 큰 타입을 정의했을 때 내부의 타입도 해당 타입과 내부 타입이 모두 동일하지
// 않으면 에러가 난다.
// const arr: string[] = []
// arr[0] = '123' // [123] 형태로 되어야 함
// arr[1] = '456' // [123, "456"] 이라서 에러가 난다.
// arr.push(123)
// const arr: string[] = []
// 위 코드나 아래 코드나 같은 의미이다. 위 코드가 더 간단해서 위 코드를 주로 사용.
const arr: Array<string> = [];
arr.push('123')
// 함수 타입
// 화살표함수 구조로 매개 변수와 리턴값의 타입을 정의한다.
// 정의하는 구조랑 실제 함수의 구조가 같다..!
// 매개 변수의 이름은 다르게 지정해도 상관은 없다.
const hello: (a:string, b: number) => string = function(msg, xyz) {
// 요렇게 함수 형식으로 할 수 있다.
return msg
}
const hello2 = function(msg: string, xyz: number): string {
// 요렇게 함수 내에서 타입을 명시 할 수 있다.
return msg
}
function hello3(msg:string, xyz:number): string {
return msg
}
// Enum(이넘) 타입
// 타입 + 값(데이터)의 집합으로 역방향 매핑이 가능하다.
const week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
console.log(week[0]) // 'Sun'
console.log(week[6]) // 'Sat'
console.log(week.findIndex(d => d === 'Sun')) // 'Sun'
console.log(week.findIndex(d => d === 'Sat')) // 'Sat'
// 배열 데이터와 같다고 이해해도 된다.
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
// 실제로 enum데이터는 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
}
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 = 7}
console.log(Colors.Red) // 0
console.log(Colors[0]) // 'Red'
// 이렇게 임의로 index값을 바꿔줄 수 있다.
// 특정 인덱스를 바꿔주면 이후 index값이 해당 인덱스를 기준으로 바뀌는 것이 특징이다!
console.log(Colors.Green) // 4
// console.log(Colors.Blue) // 5
console.log(Colors[7]) // 'Blue'
enum Colors {Red = 'r', Green = 4, Blue = 7}
console.log(Colors.Red) // 'r'
console.log(Colors[4]) // 'Green'
console.log(Colors.r) // 이렇게 Red를 조회하지는 못한다.
// 즉 숫자가 아닌 index일 경우에는 값으로 index를 조회할 수는 있지만 반대는 불가능하다.
// Void타입
// 값을 반환하지 않는 함수의 반환 타입
// undefined??
// 뭐지 왜 에러 안뜨지
// 찾아보니까 undefined도 되는데 void를 쓰는 것이 권장됌
// 명시적으로 return undefined << 이렇게 작성하는 건 괜찮지만, 그렇지 않은 경우에는 void를 써라?
const hello: (msg: string) => void = msg => {
console.log(`Hello ${msg}`)
// return `Hello ${msg}`
return undefined
}
hello('World!')
// 튜플 타입
// 고정된 길이의 배열 타입
// 일반적으로 우리가 아는 아래와 같은 배열은 길이의 제한이 없고 달라져도 상관 없다.
// const tup: number[] = [4,5]
// tup[2] = 6 // [4,5,6]
// tup[3] = 7 // [4,5,6,7]
// 대괄호를 열어서 배열처럼 지정하지만 각 item의 타입을 정의해준다.
// 즉 아래 코드는 숫자가 2개만 들어가야한다!
// const tup: [number, number] = [4,5]
// tup[2] = 6 // 에러 발생
// tup[3] = 7 // 에러 발생
// 내가 원하는 데이터 구조를 잘 명시해서 사용할 수 있다.
// [id, name, isValid]
// const userA: [number, string, boolean] = [1, 'Heropy', true]
// const userB: [number, string, boolean] = [2, 'Neo', false]
// const userC: [number, string, boolean] = [3, 'Evan', true, 'evan@gamil.com']
// 튜플을 사용할 때 주의해야할 점!
const tup: [number, number] = [4,5]
tup[2] = 6
tup.push(6) // << 이러한 방식은 에러를 만들지 않는다!! 의도하지 않은 동작
tup.splice(2, 0, 6) // splice 방식 역시 문제가 발생한다.
const user: [number, string, boolean] = [1, 'Heropy', true]
user.push('evan@gamil.com')
// Never타입
// 어떤 것도 할당할 수 없는 타입,
// 혹은 정상적으로 종료되지 않는 함수의 반환 타입
// 아래처럼 어떠한 것도 넣을 수 없는..? 잘 이해가 안된다..
// const nev: [] = []
// nev.push(6)
// 그럼 예시를 보자
// passing anything else (or nothing) causes a type error
// 네버를 매개변수 타입에 넣으면 무조건 에러 발생
const myError: (m:never) => never = (msg) => {
throw `에러! - ${msg}`
}
try {
myError('Never 타입..')
} catch (error) {
console.error(error)
}
// Any 타입
// 어떤 것도 할당할 수 있는 타입
// any << 말 그대로 `모든 것`
// any는 결국 JS를 쓰는 것과 다르지 않다.
let anything:any = 'Hello';
anything = 123
anything = {a: 'A'}
anything = [1,2,3]
const a: string = anything
const b: number = anything
const c: boolean = anything
// Unknown 타입
// 어떤 것도 할당할 수 있지만, 정확히는 무엇인지 알 수 없는 타입
// 다른 타입에는 할당할 수 없음.
let anything:unknown = 'Hello'
anything = 123
anything = {a:'A'}
anything = [1,2,3]
// Any와 차이점
// unknown은 어떤 것도 할당할 수는 있지만 다른 타입에는 할당할 수 없음
if(typeof anything ==='string'){
const a: string = anything
}
const b: number = anything
const c: boolean = anything
// 타입 가드??
// unknown을 권장한다.
// 요게 중요하고 핵심인거 같은데 이해가 어렵네
// Any vs Unknown
let any:any = 'hello'
console.log(any.toUpperCase()) // Ok!
any = 123
// 작성할때는 문제 없었는데 런타임에서 문제 생긴다?? << 매우 심각하다.
console.log(any.toUpperCase()) // Ok! - 런타임 에러 발생!
let unk: unknown = 'hello'
if(typeof unk === 'string'){
console.log(unk.toUpperCase()) // 이때는 에러가 나지 않는다!
}
// console.log(unk.toUpperCase()) // Error!
// 실질적으로 사용하는 상황에서는 if로 묶어서 조건이 맞을때만 사용 되도록
unk = 123
if(typeof unk ==='number'){
console.log(unk.toUpperCase()) // Error!
// number' 형식에 'toUpperCase' 속성이 없습니다.
// 여기서는 에러가 발생한다! 왜냐하면 숫자데이터에서는 toUpperCase를 사용할 수 없기에
}
// 따라서 Any 의 경우에는 위와 같이 런타임 단계에서의 문제를 야기할 수 있는 큰 문제가 있으므로
// Unknown이라는 타입을 사용해서 더 엄격한 상황을 만들고, 조건을 추가해서 코드가 실행되도록 작성해야한다
// 단점은 코드의 양이 늘어나지만 코드의 안정성이 훨씬 향상된다.
// Union(유니언) 타입
// 2개 `이상`의 타입이 허용되는 타입
// 버티컬바를 사용해서 다른 형식을 추가할 수 있다.
// 유니언 타입이라고 이해하자.
// 뭐지? Array를 넣으니 경고문구가 다 사라졌다.
// 아 근데 Array에 빨간줄이 뜬다
let uni: string | number | number[]
uni = 'Hello'
uni = 123
uni = false
uni = null
uni = [1,2,3]
// intersection(인터섹션) 타입
// 2개 `이상`의 타입이 `병합`된 타입
// 위와 다르다, 이번에는 병합되어있다!
// `타입` 키워드, 우리가 만든 타입에 이름을 지정하는 키워드이다!
// 이렇게 선언하면 변수처럼 재사용이 가능하다.
// 자세한 건 나중에
// 객체 데이터의 타입을 UserA 형태로 만들었다~ 로 이해
type UserA = {
name: string, // 넌 이름
age:number // 넌 숫자
}
type UserB = {
// age: number,
isValid: boolean // 넌 부울대수
}
const userA: UserA = {
// { name: string; age: number; isValid: boolean; }' 형식은 'UserA' 형식에 할당할 수 없습니다.
// 개체 리터럴은 알려진 속성만 지정할 수 있으며 'UserA' 형식에 'isValid'이(가) 없습니다.
name:'A', age:12, isValid:true
}
const userB: UserB = {
// 여기서 age는 왜 에러표기가 안될까?
// 어차피 앞에서 안되니까 굳이?
// 그런 것 같다. name을 지우면 age에서 에러가 발생한다.
name:'B', age:55, isValid:true
}
// & << 그리고 라는 뜻을 가지고 있다
// 겹치는 속성이 있으면 어떨까
// 새로운 속성을 할당하면 당연히 안된다. 합쳐도 없는 속성이기 때문에
// 이런 intersection 개념이 없다면 아래와 같은 타입을 만들기 위해 새로운 타입을 만들어야 했지만
// 그런 것은 너무 낭비이므로 이렇게 병합해서 사용하는 방식이 생겼다.
const userC: UserA & UserB = {
name:'C', age:40, isValid:true, email: 'these'
}
// 타입 단언(Assertion)
// `단언`이란, 주저하지 아니하고 딱 잘라 말함.
// as, ! (Non-null 단언 연산자)
// 1)
// 만약 html에 아래 요소가 없다면 null을 반환할 수 있다.
// 'btn'은(는) 'null'일 수 있습니다.ts(18047)
// const btn: HTMLButtonElement | null
// null에서 classList를 사용하는 것은 불가능하다
// btn 변수의 타입을 주저하지 않고 딱 잘라서 HTMLButtonElement << 이거다! 라고 단언했기에 더 이상 에러를 표시하지 않는다.
// 즉 단언은 `개발자`가 `타입스크립트`에게 딱잘라서 얘기하는 것
// 근데 이렇게 할때마다 단언하냐? NO, 선언할 때 단언할 수 있다.
// 해석 : 야 나 HTML에서 버튼 찾을건데 그건 무조건 HTMLButtonElement임 이라고 개발자가 타입스크립트에게 단언
// const btn = document.querySelector('button') as HTMLButtonElement;
// 근데 이거를 더 간단하게 할 수 있다? ! << (Non-null 단언 연산자)
// 훨씬 간단하지만 null이 아닌 것만 명시하기 때문에 정확히 어떤 타입인지는 명시하지는 않는다.
// const btn = document.querySelector('button')!;
const btn = document.querySelector('button');
// 이렇게 btn 뒤쪽에 붙이는 것도 가능하지만 위에서처럼 한번에 하는게 편하겠지?
btn!.classList.add('btn');
btn!.id = 'abc'
// 2)
// val: 유니언 타입
function toTwoDecimals(val:number | string, isNum:boolean){
if(isNum){
// toFixed는 숫자에서
// 하지만 에러가 발생한다. 왜냐하면 문자열에서 toFixed는 사용하지 못하니까
// 우리가 boolean 변수로 구분을 해줘도 우리 TS가 약간 부족해서 이해하지 못한다.
// 그래서 우리가 여기서 형식 단언을 해줘야한다!
(val as number).toFixed(2)
}else{
// slice는 문자열에서 사용
(val as string).slice(0, 2)
}
}
// isNum으로 이게 숫자인지, 문자열인지 구분
// 단언은 `개발자` 가 `타입스크립트`에게 알려주는 것
// toTwoDecimals(3.141592, true)
// 하지만 아래와 같이 사람이 실수 할 수 있는데 이때는 타입 가드라는 것을 적용하자. 다음 단원에서 설명
toTwoDecimals(3.141592, false)
toTwoDecimals('Hello World', false)
// 3)
// 포맷은 json이지만 타입은 string이다.
const json = '{ "name": "Heropy", "age": 85}';
// const user = JSON.parse(json)
// 이메일 속성 없잖아? 그런데 왜 에러가 발생하지 않는걸까?
// 왜냐하면 TS가 문자열 -> JSON으로 파싱하는 것 까지 추적은 못하기 때문이다.
// 직접 알려줘야한다! HOW? 아래 json에서 as 키워드로 알려주자.
const user = JSON.parse(json) as {name:string, age:number};
// 위처럼 명시해주니 이제서야 에러가 뜬다.
// '{ name: string; age: number; }' 형식에 'email' 속성이 없습니다.ts(2339)
// console.log(user.email)
// 4)
// 뒤를 보면 할당이 안되어있다!
// let num; << 이 상태랑 같음 undefined 상태다.
// 하지만 TS에선 number으로 선언했지만 undefined가 들어가니까 에러가 발생한다.
// 선언할때 초기화 하거나, 사용하기 전에 초기화 해주면 에러는 발생하지 않는다.
// let num: number = 0
// let num:number;
// num = 0;
// 하지만 초기화 하고 사용할때가 있는데 그떄는 ! 키워드를 추가해주자.
// 그러면 실제 값이 있지는 않지만 TS가 초기화되었구나 라고 판단한다.
let num!:number;
console.log(num)
CSS를 ... 열심히 해야겠다.
그래도 1차 팀 종료 전에 멘토님과 팀원들과 만나서 서로 오프라인에서 이야기를 나눌 수 있는 기회를 가졌다. 하지만 팀원들도 그렇고 나도 그렇고 다들 컨디션이 안 좋다보니 뭔가 더 신나게 이야기하지 못한 부분이 아쉽긴하다.
내일도 달려봅시다! 화이팅!! 🔥🔥