[우아한타입리액트] 5장. 타입 활용하기(2)

Lina Hongbi Ko·2024년 12월 5일
0
post-thumbnail

5장. 타입 활용하기 (2)

📍 PickOne 유틸리티 함수

  • 타입스크립트에는 서로 다른 2개 이상의 객체를 유니온 타입으로 받을 때, 타입 검사가 제대로 진행되지 않는 이슈가 있음

  • 이런 문제를 해결하기 위해 PickOne이라는 이름의 유틸리티 함수를 이용

    type Card = {
        card: string;
    };
    
    type Account = {
        account: string;
    };
    
    function withdraw(type: Card | Account) {
        ...
    }
    
    widthdraw({ card: "hyundai", account: "hana" });
  • 위의 코드를 보면 Card, Account 중 하나의 객체만 받고 싶어서 Card | Account 로 타입을 작성하면 의도한 대로 타입 검사가 이루어지지 않음

  • withdraw 함수의 인자로 { card: 'hyundai' } 또는 { account: 'hana' } 중 하나만 받고 싶지만 실제로는 card, account 속성을 모두 받아도 타입 에러가 발생하지 않음

  • 왜 타입 에러가 발생하지 않을까?

  • 집합 관점으로 볼 때, 유니온은 합집합이 되기 때문!
    => 따라서, card, account 속성이 하나씩만 할당된 상태도 허용하지만 card, account 속성이 모두 포함되어도 합집합의 범주에 들어가기 때문에 타입에러 발생하지 않음

🔆 식별할 수 있는 유니온으로 객체 타입을 유니온으로 받기

  • 식별할 수 있는 유니온은 각 타입에 type이라는 공통된 속성을 추가해 구분짓는 방법

     type Card = {
          type: "card"; // 식별할 수 있는 유니온
          card: string;
      };
    
      type Account = {
          type: "account"; // 식별할 수 있는 유니온
          account: string;
      };
    
      function withdraw(type: Card | Account) {
          ...
      }
    
      widthdraw({ type: "card", card: "hyundai" });
      widthdraw({ type: "account", card: "hana" });
  • 위의 예시를 보면 Card, Account 타입을 구분할 수 있도록 type 이라는 속성이 추가된 것을 볼 수 있음

  • 식별할 수 있는 유니온을 활용해 공통된 속성인 type을 기준으로 객체를 구분할 수 있어서 withdraw 함수를 사용하는 곳에서 정확한 타입을 추론할 수 있게 됨

  • but, 식별할 수 있는 유니온을 사용하면 일일이 type을 다 넣어줘야하는 문제점 발생 & 실수로 수정하지 않은 부분 생기면 또 다른 문제 발생
    => 이러한 상황을 방지하기 위해 PickOne 이라는 유티릴티 타입을 구현하여 적용

🔆 PickOne 커스텀 유틸리티 타입 구현하기

  • 타입스크립트에서 제공하는 유틸리티 타입을 활용하기 위해 커스텀 유틸리티 타입을 만들어야 함

  • 구현하고자 하는 타입은 account 또는 card 속성 하나만 존재하는 객체를 받는 타입
    처음에 작성한 것처럼 { account: string } | { card: string}으로 타입을 구현했을 때는 account와 card 속성을 모두 가진 객체도 허용되는 문제가 있었음

  • account일 때는 card를 받지 못하고, card일 때는 account를 받지 못하게 하려면 하나의 속성이 들어왔을 때 다른 타입을 옵셔널한 undefined 값으로 지정하는 방법을 생각해 볼 수 있음

    { account: string; card?: undefined } | { account?: undefined; card: string}

    옵셔널 + undefined로 타입을 지정하면 사용자가 의도적으로 undefined 값을 넣지 않는 이상, 원치 않는 속성에 값을 넣었을 때 타입 에러가 발생함

  • 이 타입을 정확히 이해하기 위해 account, card, payMoney 속성 중 하나만을 필수로 받는 PayMethod를 구현하면

    type PayMethod =
      | { account: string; card?: undefined; payMoney?: undefined }
      | { account: undefined; card?: string; payMoney?: undefined }
      | { account: undefined; card?: undefined; payMoney?: string }

    선택하려는 하나의 속성을 제외한 나머지 값을 옵셔널 타입 + undefined로 설정하면 원하고자 하는 속성만 받도록 구현할 수 있다. 이를 커스텀 유틸리티 타입으로 구현해보면

    type PickOne<T> = {
    	[P in keyof T]: Record<P, T[P]> & Partial<Record<Exclude<keyof T, P>, undefined>>;
    }[keyof T];

🔆 PickOne 살펴보기

  • 앞의 유틸리티 타입을 자세히 보자
  • 먼저 PickOne 타입을 2가지 타입으로 분리해서 생각할 수 있는데,
  1. 이때 T에는 객체가 들어 온다고 가정하고

    "One<T>"
    
    type One<T> = { [P in keyof T]: Record<P, T[P]> }[keyof T];

    1) [P in keyof T]에서 T는 객체로 가정하므로 P는 T 객체의 키값을 말함
    2) Record<P, T[P]>는 P타입을 키로 가지고, value는 P를 키로 둔 T 객체의 값의 레코드 타입
    3) 따라서, { [P in keyof T]: Record<P, T[P]> }에서는 key는 T 객체의 키 모음이고, value는 해당 키의 원본 객체 T를 말함
    4) 3번의 타입에서 다시 [keyof T]의 키값으로 접근하기 때문에 최종 결과는 전달받은 T와 같음

    type Card = { card: string };
    const one: One<Card> = {card: "hyundai"};
  2. 역시 T에는 객체가 들어옴

    "ExcludeOne<T>"
    
    type ExcludeOne<T> = { [P in keyof T]: Partial<Record<Exclude<keyof T, P>, undefined>>
    }[keyof T];

    1) [P in keyof T]에서 T는 객체로 가정하기 때문에 P는 T 객체의 키값을 말함
    2) Exclude<keyof T, P>는 T 객체가 가진 키값에서 P 타입과 일치하는 키값을 제외함.
    이 타입을 A라고 하면 (치환)
    3) Record<A, undefined>는 키로 A 타입을, 값으로 undefined 타입을 갖는 레코드 타입. 즉, 전달받은 객체 타입을 모두 { [key] : undefined } 형태로 만듦.
    이 타입을 B라고 하면 (또다시 치환)
    4) Partial<B>는 B타입을 옵셔널로 만듦. 따라서 { [key]?: undefined }와 같음
    5) 최종적으로 [P in keyof T]로 매핑된 타입에서 동일한 객체의 키값인 [keyof T]로 접근하기 때문에 4번 타입이 반환됨

  • 결론으로, 타입은 속성 하나와 나머지는 옵션 + undefined인 타입이기 때문에 앞의 속성을 활용해 PickOne 타입을 표현할 수 있음

    "PickOne<T>"
    
    type PickOne<T> = One<T> & ExcludeOne<T>

(1) One & ExcludeOne는 [P in keyof T]를 공통으로 갖기 떄문에 아래처럼 교차됨

[P in keyof T]: Record<P, T[P]> & Partial<Record<Exclude<keyof T, P>, undefined>>

(2) 이 타입을 해석하면 전달된 T 타입의 1개의 키는 값을 가지고 있으며, 나머지 키는 옵셔널한 undefined 값을 가진 객체를 의미

type Card = { card: string };
type Account = { account: string };

const pickOne1: PickOne<Card & Account> = { card: "hyundai" }; // O
const pickOne2: PickOne<Card & Account> = { account: "hana" }; // O
const pickOne3: PickOne<Card & Account> = { card: "hyundai", account: undefined }; // O
const pickOne4: PickOne<Card & Account> = { card: undefined, account: "hana" }; // O
const pickOne5: PickOne<Card & Account> = { card: "hyundai", account: "hana" }; // X

🔆 PickOne 타입 적용하기

  • PickOne을 활용해 앞의 코드를 수정해보면, withdraw({ card: "hyundai", account: "hana" })를 활용할 때 타입 에러가 발생하는 것 확인 할 수 있음

    type Card = {
        card: string
    };
    
    type Account = {
        account: string  
    };
    
    type CardOrAccount = PickOne<Card & Account>;
    function withdraw (type: CardOrAccount) {
        ...  
    }
    
    withdraw( {card: "hyundai", account: "hana" }); // 에러 발생함
  • 유틸리티 타입만으로 원하는 타입을 추출하기 어려울 때, 커스텀 유틸리티 타입을 구현함 (하지만 쉬운일은 아님) -> 커스텀 유틸리티 타입을 구현할 때는 정확히 어떤 타입을 구현해야하는지 파악하고, 필요한 타입을 작은 단위로 쪼개어 생각해 단계적으로 구현하는게 좋음

📍 NonNullable 타입 검사 함수를 사용하여 간편하게 타입 가드하기

  • null을 가질 수 있는 값의 null 처리는 자주 사용되는 타입 가드 패턴의 하나임
  • 일반적으로 if문을 사용해서 null 처리 타입 가드를 적용하지만, is 키워드와 NonNullable 타입으로 타입 검사를 위한 유틸 함수를 만들어서 사용할 수도 있음

🔆 NonNullable 타입이란

  • 유틸리티 타입으로 제네릭으로 받는 T가 null 또는 undefined일 때 never 또는 T를 반환하는 타입
  • NonNullable을 사용하면 null 이나 undefined가 아닌 경우를 제외할 수 있음
    type NonNullable<T> = T extends null | undefined ? never : T;

🔆 null, undefined를 검사해주는 NonNullable 함수

  • NonNullable 유틸리티 타입을 사용하여 null 또는 undefined를 검사해주는 타입 가드 함수를 만들어 쓸 수 있음
    function NonNullable<T>(value: T): value is NonNullable<T> {
        return value !== null && value !== undefined;  
    }
  • NonNullable 함수는 매개변수인 value가 null 또는 undefined라면 false를 반환
  • is키워드가 쓰였기 때문에 NonNullable 함수를 사용하는 쪽에서 true가 반환되면 넘겨준 인자는 null이나 undefined가 아닌 타입으로 타입 가드(타입 좁히기)가 됨

🔆 Promise.all을 사용할 때 NonNullable 적용하기

  • Promise.all을 사용할 때 NonNullable를 적용한 예시를 보면
    1. 각 상품 광고를 노출하는 API 함수 레이어

    class AdCampaignAPI {
        static async operating(shopNo: number): Promise<AdCampaign[]> {
            try {
                return await fetch(`/ad/shopNumber=${shopNo}`);
            } catch {
                return null;
            }
        }  
    }
    • 상품 광고 API는 상품 번호인 shopNumber 매개변수에 따라 각기 다른 응답 값을 반환하는 광고 조회 API이다.
    • 여러 상품의 광고를 조회할 때 하나의 API에서 에러가 발생한다고 해서 전체 광고가 보이지 않으면 안됨
      => 따라서, try-catch문을 사용해 에러가 발생할 때는 null을 반환
  1. AdCampaignAPI를 사용해서 여러 상품의 광고를 받아오는 로직. Promise.all을 사용해 각 shop의 광고를 받음

    const shopList = [
        { shopNo: 100, category: "chicken" },
        { shopNo: 101, category: "pizza" },
        { shopNo: 102, category: "noodle" },
    ];
    
    const shopAdCampaignList = await Promise.all(shopList.map((shop) 
    	=> AdCampaignAPI.operating(shop.shopNo))
    ); 
  • 이때 AdCampaignAPI.operating 함수에서 null을 반환할 수 있기 때문에 shopAdCampaignList 타입은 Array<AdCampaign[] | null>로 추론됨

  • shopAdCampaignList 변수를 NonNullable 함수로 필터링하지 않으면 shopAdCampaignList를 순회할 때(map이나 foreach 순회 메서드를 사용할때)마다 고차 함수 내 콜백 함수에서 if문을 사용한 타입 가드를 반복하게 됨

  • NonNullable 함수를 사용하지 않고 단순하게 필터링한다면 [shopAdCampaignList.filter((shop) => !!shop)]가 원하는 Array<AdCampaign[]> 타입으로 추론되는 것이 아니라 null이 될 수 있는 상태인 Array<AdCampaign[] | null>로 추론됨

    const shopList = [
          { shopNo: 100, category: "chicken" },
          { shopNo: 101, category: "pizza" },
          { shopNo: 102, category: "noodle" },
      ];
    
    const shopAdCampaignList = await Promise.all(shopList.map((shop) 
      => AdCampaignAPI.operating(shop.shopNo))
    );
    
    const shopAds = shopAdCampaignList.filter(NonNullable);

    NonNullable을 사용해서 shopAdCampaignList를 필터링하면 shopAds는 원하는 타입인 Array<AdCampaign[]>로 추론할 수 있게 됨

💡 불변 객체 타입으로 활용하기

  • 플젝을 진행하면 상숫값을 관리할 때 객체를 사용함

  • 예를 들어, 프로젝트의 전체적인 스타일을 관리하는 theme 객체, 자주 사용하는 애니메이션을 모아둔 객체, 상숫값을 담은 객체 등 다양한 곳에 활용됨

  • 컴포넌트나 함수에서 이런 객체를 사용할 때 열린 타입으로 설정할 수 있는데, 함수 인자로 키를 받아서 value를 반환하는 함수를 보면

    const colors = {
      red: "#f45452",
      green: "#0c952a",
      blue: "#1a7cff",
    };
    
    const getColorHex = (key: string) => colors[key];
  • 키 타입을 해당 객체에 존재하는 키값으로 설정하는 것이 아니라 string으로 설정하면 getColorHex 함수의 반환 값은 any가 된다. colors에 어떤 값이 추가될지 모르기 때문.

  • 여기서 as const 키워드로 객체를 불변 객체로 선언하고, keyof 연산자를 사용해 getColorHex 함수 인자로 실제 colors 객체에 존재하는 키값만 받도록 설정할 수 있음

    keyof, as const로 객체 타입을 구체적으로 설정하면 타입에 맞지 않는 값을 전달할 경우 타입 에러가 반환 -> 컴파일 단계에서 발생할 수 있는 실수 방지 & 자동 완성 기능을 통해 어떤 값이 있는 쉽게 파악 가능

    => 이런 방법으로 객체 타입을 더 정확하고 안전하게 설정할 수 있음!

📍 Atom 컴포넌트에서 theme style 객체 활용하기

  • Atom 단위의 작은 컴포넌트(Button, Header, Input 등)는 폰트 크기, 폰트 색상, 배경 색상 등 다양한 환경에서 유연하게 사용될 수 있도록 구현되어야 하는데 이러한 설정값은 props로 넘겨주도록 설계함

  • props로 직접 색상 값을 넘겨줄 수도 있지만 그렇게 하면 사용자가 모든 색상 값을 인지해야하고, 변경 사항이 생길 때 직접 값을 넣은 모든 곳을 찾아 수정해야함

  • 이런 문제를 해결하기 위해 대부분의 프로젝트에서는 해당 프로젝트의 스타일 값을 관리해주는 theme 객체를 두고 관리함
    -> Atom 컴포넌트에서 theme 객체의 색상, 폰트 사이즈 등의 키 값을 props로 받은 뒤, theme 객체에서 값을 받아오도록 설계
    컴포넌트에서 props의 colors, fontSize 등 값을 정의할 때는 아래처럼 string으로 설정 할 수도 있음

    interface Props {
        fontSize?: string;
        backgroundColor?: string;
        color?: string;
        onClick: (event: React.MouseEvent<HTMLButtonElement>) => void | Promise<void>;
    }
    
    const Button: FC<Props> = ({ fontSize, backgroundColor, color, children }) => {
        return (
            <ButtonWrap fontSize={fontSize} backgroundColor={backgroundColor} color={color}>
              {children}
            </ButtonWrap>
        )  
    }
    
    const ButtonWrap = style.button<Omit<Props, "onClick">>`
        color: ${({ color }) => theme.color[color ?? "default"]};
        background-color: ${({ backgroundColor }) => theme.bgColor[backgroundColor ?? "default"]};
        font-size: ${({ fontSize }) => theme.fontSize[fontSize ?? "default"]};
    `;
  • 코드에서 fontsize, backgroundcolor 같은 props 타입이 string이면 Button 컴포넌트의 props로 color, backgroundColor를 넘겨줄 때 키값이 자동 완성되지 않고, 잘못된 키값을 넣어도 에러가 발생하지 않게 됨 -> 이러한 문제는 theme 객체로 타입을 구체화해서 해결할 수 있음
    -> 구체화 하기 위해서는 keyof, typeof 연산자가 타입스크립트에서 어떻게 사용되는지 알아야함

🔆 타입스크립트 keyof 연산자로 객체의 키값을 타입으로 추출하기

  • keyof 연산자는 객체 타입을 받아 해당 객체의 키값을 string 또는 number의 리터럴 유니온 타입을 반환함

  • 객체 타입으로 인덱스 시그니처가 사용되었다면 keyof는 인덱스 시그니처의 키 타입을 반환함

    interface ColorType {
        red: string;
        green: string;
        blue: string;
    }
    
    type ColorKeyType = keyof ColorType; // 'red' | 'green' | 'blue'

    ColorType 객체 타입의 keyof ColorType을 사용하면 객체의 키값인 red, green, blue가 유니온으로 나오게 된다.

🔆 타입스크립트 typeof 연산자로 값을 타입으로 다루기

  • keyof 연산자는 객체 타입을 받음 -> 따라서 객체의 키값을 타입으로 다루려면 값 객체를 타입으로 변환해야 하는데, 이때 타입스크립트의 typeof 연산자를 활용할 수 있음

  • 자바스크립트에서는 typeof가 타입을 추출하기 위한 연산자로 사용된다면, 타입스크립트에서는 typeof가 변수 혹은 속성의 타입을 추론하는 역할을함

  • 타입스크립트의 typeof 연산자는 단독으로 사용되기보다 주로 ReturnType과 같이 유틸리티 타입이나 keyof 연산자와 같이 타입을 받는 연산자와 함께 쓰임

    const colors = {
        red: "#f45452",
        green: "#0c952a",
        blue: "#1a7cff",
    };
    
    type ColorsType = typeof colors;
    /**
    {
        red: string;
        green: string;
        blue: string;
    }
    */

🔆 객체의 타입을 활용해 컴포넌트 구현하기

  • keyof, typeof 연산자를 사용해서 theme 객체 타입을 구체화하고, string으로 타입을 설정했던 Button 컴포넌트를 개선해보면

    import { FC } from "react";
    import styled from "styled-components";
    
    const colors = {
        black: "#000000";
        gray: "#222222";
        white: "#ffffff";
        mint: "#2ac1bc",
    };
    
    const theme = {
        colors: {
            default: colors.gray,
            ...colors
        },
        backgroundColor: {
            default: colors.white,
            gray: colors.gray,
            mint: colors.mint,
            black: colors.blakck,
        },
        fontSize: {
            default: "16px",
            small: "14px",
            large: "18px",
        }
    };
    
    type ColorType = typeof keyof theme.colors;
    type BackgroundColorType = typeof keyof theme.backgroundColor;
    type FontSizeType = typeof keyof theme.fontSize;
    
    interface Props {
        color?: ColorType;
        backgroundColor?: BackgroundColorType;
        fontSize?: FontSizeType;
        onClick: (event: React.MouseEvent<HTMLButtonElement>) => void | Promise<void>;
    }
    
    const Button: FC<Props> = ({ fontSize, backgroundColor, color, children }) => {
        return (
            <ButtonWrap fontSize={fontSize} backgroundColor={backgroundColor} color={color}>
              {children}
            </ButtonWrap>
        )  
    }
    
    const ButtonWrap = styled.button<Omit<Props, "onClick">>`
        color: ${({ color }) => theme.colors[color ?? "default"]};
        background-color: ${({ backgroundColor }) => theme.backgroundColor[backgroundColor ?? "default"]};
        font-size: ${({ fontSize }) => theme.fontSize[fontSize ?? "default"]};
    `;
    

    예를 들어, Button 컴포넌트를 사용하는 곳에서 backgroundColor의 값만 받을 수 있게 되었고, 다른 값을 넣었을 때는 타입 오류가 발생한다.

이처럼 theme 뿐만 아니라 여러 상숫값을 인자나 props로 받은 다음에 객체의 키 값을 추출한 타입을 활용하면 객체에 접근할때 타입스크립트의 도움을 받아 실수를 방지할 수 있다!

💡 Record 원시 타입 키 개선하기

  • 객체 선언시 키가 어떤 값인지 명확하지 않다면 Record의 키를 string이나 number 같은 원시 타입으로 명시하곤 함
    -> 이때 타입스크립트는 키가 유효하지 않더라도 타입상으로는 문제 없기 때문에 오류를 표시하지 않음
    but!! 이것은 예상치 못한 런타임 에러를 야기할 수 있음

    따라서, Record를 명시적으로 사용하는 방법을 사용하면 됨

📍 무한한 키를 집합으로 가지는 Record

type Category = string;

interface Food {
	name: string;
    // ...
}
  
const foodByCategory: Record<Category, Food[]> = {
	한식: [{ name: "제육덮밥"}, { name: "뚝배기 불고기" }],
    일식: [{ name: "초밥" }, { name: "텐동" }],
};
  
  • 음식 분류(한식, 일식)를 키로 사용하는 음식 배열이 담긴 객체를 만들었음

  • 여기에서 Category의 타입은 string이고, Category를 Record의 키로 사용하는 foodByCategory 객체는 무한한 키 집합을 가지게 됨

  • 이때 foodByCategory 객체에는 없는 키값을 사용하더라도 타입스크립트는 오류를 표시하지 않음!

    foodByCategory["양식"]; // Food[]로 추론
    foodByCategory["양식"].map((food) => console.log(food.name)); // 오류 발생하지 않음
  • 그러나 foodByCategory["양식"]은 런타임에서 undefined가 되어 오류를 반환함

  • 이때 자바스크립트의 옵셔널 체이닝 등을 사용해 런타임 에러를 방지할 수 있음
    *옵셔널 체이닝(optional chaining) : 객체의 속성을 찾을 때 중간에 null 또는 undefined가 있어도 오류 없이 안전하게 접근하는 방법-> ?.문법으로 표현되며 옵셔널 체이닝을 사용할 때 중간에 null 또는 undefined인 속성이 있는지 검사한다. 속성이 존재하면 해당 값을 반환하고, 존재하지 않으면 undefined를 반환한다.

    foodByCategory["양식"]?.map((food) => console.log(food.name));
  • 그러나 어떤 값이 undefined인지 매번 판단해야 하는 번거로움이 생김 & 실수로 undefined일 수 있는 값을 인지하지 못하고 코드를 작성하면 예상치 못한 런타임 에러 발생함
    반면, 타입스크립트의 기능을 활용해 개발 중에 유효하지 않은 키가 사용되었는지 또는 undefined일 수 있는 값이 있는지 등을 사전에 파악할 수 있음

📍 유닛 타입으로 변경하기

  • 키가 유한한 집합이라면 유닛 타입(다른 타입으로 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입)을 사용

    type Category = "한식" | "일식";
    
    interface Food {
        name: string;
        // ...
    }
    
    const foodByCategory: Record<Category, Food[]> = {
        한식: [{ name: "제육덮밥"}, { name: "뚝배기 불고기" }],
        일식: [{ name: "초밥" }, { name: "텐동" }],
    }
    
    // Property '양식' does not exist on type 'Record<Category, Food[]>'.
    foodByCategory["양식"];
  • Category로 한식 또는 일식만 올 수 있기 때문에 양식을 키로 사용하면 에러 발생

  • 유닛 타입을 활용하면 개발 중에 유효하지 않은 키가 사용되었는지를 확인할 수 있음

  • 그러나 키가 무한해야 하는 상황에는 적합하지 않음

📍 Partial을 활용하여 정확한 타입 표현하기

  • 키가 무한한 상황에서는 Partial을 사용해 해당 값이 undefined일 수 있는 상태임을 표현할 수 있음

  • 객체 값이 undefined일 수 있는 경우에 Partial을 사용해서 PartialRecord 타입을 선언하고 객체를 선언할 때 이것을 활용할 수 있음

    type PartialRecord<K extends string, T> = Partial<Record<K, T>>;
    type Category = string;
    
    interface Food {
        name: string;
        // ...
    }
    
    const foodByCategory: PartialRecord<Category, Food[]> = {
        한식: [{ name: "제육덮밥"}, { name: "뚝배기 불고기" }],
        일식: [{ name: "초밥" }, { name: "텐동" }],
    };
    
    foodByCategory["양식"] // Food[] 또는 undefined 타입으로 추론
    foodByCategory["양식"].map((food) => console.log(food.name)); // Object is possibly 'undefined'
    foodByCategory["양식"]?.map((food) => console.log(food.name)); // OK
  • 타입스크립트는 foodByCategory[key]를 Fodd[] 또는 undefined로 추론하고, 개발자에게 이 값은 undefined일 수 있으니 해당 값에 대한 처리가 필요하다고 표시해줌 -> 개발자는 안내를 보고 옵셔널 체이닝을 사용하거나 조건문을 사용하는 등 사전에 조치할 수 있어서 예상치 못한 런타임 오류를 줄일 수 있음

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글