[프론트엔드 데브코스 TIL] 2023.11.09 DAY 38 TS 1일차

SoJuSo·2023년 11월 9일
0

2023.09 ~ 2024.01 TIL

목록 보기
37/105

📚금일 학습 내용

타입스크립트 1일차, 개발환경 구성 및 타입 종류와 추론, 단언 등등..

너무 피곤한 관계로 실습하며 vscode에 정리한 내용 첨부..

🏫데브코스

📌Never타입 예시?

강의 듣다가 잘 이해가 안가서 즉석에서 찾아봤는데 아직 잘 모르겠다.

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를 ... 열심히 해야겠다.

  • 내일 하루종일 해서 그래도 아래 기본 content는 최대한 비슷하게 맞춰서 제출하기
  • 이후 scss 내부 코드 리팩토링

📖소회

그래도 1차 팀 종료 전에 멘토님과 팀원들과 만나서 서로 오프라인에서 이야기를 나눌 수 있는 기회를 가졌다. 하지만 팀원들도 그렇고 나도 그렇고 다들 컨디션이 안 좋다보니 뭔가 더 신나게 이야기하지 못한 부분이 아쉽긴하다.

내일도 달려봅시다! 화이팅!! 🔥🔥

profile
Junior Frontend Engineer

0개의 댓글