ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TS] 타입 제한자
    codeStates front-end/Typescript 2023. 3. 21. 17:01
    반응형

     

     

    목차

       

       

       

       

      📌  타입 제한자

       

      지금 까지 기본 자바스크립트 구성요소와 함께 작동방식을 봤더라면. 이제부터는 타입 시스템에서

      한 걸음 나아가 다른 타입을 기반으로 하는 타입뿐만 아니라 더 정확한 타입 작성에 중점을 둔 기능을 본다.

       

       

      📍top 타입

       

      top 타입은 시스템에서 가능한 모든 값을 나타내는 타입

       

      any 다시 보기

       

      any 타입은 모든 타입의 위치에 제공 될수 있는 점에서 top타입처럼 작동할 수 있다.

      But!!!! any 👉🏻👉🏻 해당 값에 대한 할당 가능성 또는 멤버에 대해 타입 검사를 수행하지 않도록 명시적으로 지시

      타입 검사를 건너 뛰어 용용하지만, 타입스크립트의 유용성이 줄어든다.

       

      any 타입을 사용한 예시

       

      function greet(name: any) {
        console.log(`Hello, my name is ${name.toUpperCase()}!`);
      }
      
      greet({ name: 'Kim' });

       

       

       

      🔗  unknown

      unknown은 진정한 top 타입이다.

      any 타입과 유사하나, 주요 차이점은 unknown 타입의 값을 훨씬 더 제한적으로 취급한다는 점

      1. unknown 타입의 값 속성에 접근하려면 오류가 발생한다.
      2. unknown 타입은 top 타입이 아닌 타입에는 할당할 수 없다.

       

      // unknown 타입의 값 속성에 접근하려면 오류가 발생
      function greet(name: unknown) {
        console.log(`Hello, my name is ${name.toUpperCase()}!`);
        // Error: 'name' is of type 'unknown'.
      }

       

      💁💁 unknown 타입의 값에 접근하려면 어떻게 해야 할까? instanceof나 typeof 또는 타입 어서션

       

       

      function greet(name: unknown) {
        if (typeof name === 'string') {
          console.log(`Hello, my name is ${name.toUpperCase()}!`);
        } else {
          console.log("I'm Nobody");
        }
      }
      
      greet('Kim');
      greet({});

       

       

       

      🔗  타입 서술어

       

      instanceof 나 typeof 제한된 검사로 이 방법을 직접 사용할 때는 괜찮지만,

      타입을 좁히는 로직을 함수로 감싸면 타입을 좁힐 수 없게 된다.

       

      function isNumberOrString(value: unknown) {
      //number 또는 string인지 나타내는 boolean 값을 나타냄
      // 그러나 타입을 좁히기 위함인지는 알 수 없다.
        return ['number', 'string'].includes(typeof value);
      }
      
      function logValue(value: number | string | null | undefined) {
        if (isNumberOrString(value)) value.toString();
        // Error: 'value' is possibly 'null' or 'undefined'.
        else console.log('value does not exist', value);
      }

       

      이처럼 특정 타입인지 여부를 나타내기 위해 boolean 값을 반환하는 함수를 위한 특별한 구문을 타입 서술어 또는

      사용자 정의 타입 가드라고 하며, 타입 서술어는 일반적으로 매개변수로 전달된 인수가 매개변수의 타입보다 더 구체적인 타입인지 여부를 나타내는 데 사용

       

      // 타입 서술어의 반환 타입 선언
      function typePredicate(input: WideType): input is NarrowType;

       

      타입 서술어를 사용해 수정

       

      // 타입 서술어 선언
      function isNumberOrString(value: unknown): value is number | string {
        return ['number', 'string'].includes(typeof value);
      }
      
      function logValue(value: number | string | null | undefined) {
        // value: string | number
        if (isNumberOrString(value)) value.toString();
        // value: null | undefined
        else console.log('value does not exist', value);
      }

       

       

      타입 서술어는 단순히 boolean 값을 반환하는 것이 아니라 인수가 더 구체적인 타입임을 나타내는 것이라고 생각할 수 있다.

       

       

       

      🔗  타입 연산자

       

      키워드나 기존 타입의 이름만 사용해 모든 타입을 나타낼 순 없다.

      기존 타입의 속성 일부를 변환해서 두 타입을 결합하는 새로운 타입을 생성해야 한다.

       

      Keyof

       

      객체의 속성 이름을 문자열 리터럴 타입으로 추출하는 연산자

       

      interface Person {
        name: string;
        age: number;
        location: string;
      }
      
      // personKeys 타입은 "name" | "age" | "location"
      type PersonKeys = keyof Person;
      // 선택적으로 만들어진 타입을 나타냄
      type PersonPartial = Partial<Person>;
      // PersonKeys 타입을 키로하고, string 타입을 값으로 갖는 객체를 나타냄
      type PersonRecord = Record<PersonKeys, string>;

       

      객체의 속성 이름을 추출하는 데 유용하지만, 제ㅔ릭 타입에선 주의해야 함!

      객체가 비어 있는 경우 👉🏻👉🏻 never 타입이 된다.

      비어있는 객체에는 keyof 대신 PropertyKey   더 적절하다.

       

       

      typeof

       

      변수나 값의 타입을 추론하여 문자열 리터럴 타입으로 반환하는 연산자

       

      const obj = {
        name: 'Kim',
        age: 20,
        location: 'seoul',
      };
      
      type Info = typeof obj;
      /* 
      type Info = {
          name: string;
          age: number;
          location: string;
      }
      */
      
      const obj2: Info = {
        name: 'Roh',
        age: 30,
        location: 'incheon',
      };

       

       

      Keyof typeof

       

      객체의 속성 이름을 추출하여 문자열 리터럴 타입으로 반환하는 연산자

      typeof 연산자를 통해 객체의 타입을 추론, keyof 연산자를 통해 해당 객체의 속성 이름을 추출

       

      const person = {
        name: 'Roh',
        age: 30,
        location: 'Incheon',
      };
      
      function printPersonProperty(key: keyof typeof person) {
        console.log(person[key]);
      }
      
      printPersonProperty('name'); // Ok
      
      printPersonProperty('gender');
      // Error: Argument of type '"gender"' is not assignable
      // to parameter of type '"name" | "age" | "location"'.

       

       

      🔗  타입 어시션

       

      값의 타입에 대한 타입 시스템의 이해를 재정의하기 위한 구문으로 타입 어시션을 제공

      다른 타입을 의미하는 값의 타입 다음에 as 키워드를 배치한다.

       

      const rawData = '["javascript", "typescript"]';
      
      // 타입 any
      JSON.parse(rawData);
      
      // 타입: string[]
      JSON.parse(rawData) as string[];
      
      // 타입: [string, string]
      JSON.parse(rawData) as [string, string];
      
      // 타입: ['javascript', 'typescript']
      JSON.parse(rawData) as ['javascript', 'typescript'];

       

      타입 어시션 주의사항

       

      👉🏻👉🏻 타입 어시션은 가능한 사용하지 않는 것이 좋다 ! 그러나 종종 필요할 때 있음 !

       

      어서션 vs 선언

       

      👉🏻👉🏻 변수에 타입 애너테이션과 초기값이 모두 있을 때, 타입 검사기는 변수의 타입 애너테이션에 대한 변수의 초기값에 대해 할당 가능성 검사를 수행한다. 하지만 타입 어서션은 타입스크립트에 타입 검사 중 일부를 건너뛰도록 명시적으로 지시한다.

       

      interface User {
        name: string;
        age: number;
      }
      
      const declaredUser: User = {
        // Error: Property 'age' is missing in type
        // '{ name: string; }' but required in type 'User'.
        name: 'Kim',
      };
      
      const assertedUser = {
        name: 'Roh',
      } as User; // 허용되지만 런타임 시 오류 발생.

       

       

      🔗  non-null 어시션

       

      변수나 속성 끝에 ! 를 붙여서 사용하는 연산자

      해당 변수나 속성이 null 또는 undefined가 아니라고 컴파일러에게 알려준다.

       

      let maybeDate = Math.random() > 0.5 ? undefined : new Date();
      
      // 타입이 Date라고 간주됨
      maybeDate as Date;
      
      // 타입이 Date라고 간주됨
      maybeDate!;
      
      //non-null 어서션 값이 존재하지 않으면 undefined를 반환하는 Map객체의 get()메서드를 사용할 때 유용하다.
      
      const map = new Map([
        ['one', 1],
        ['two', 2],
      ]);
      
      const maybeValue = map.get('one');
      console.log(maybeValue.toString());
      // 'maybeValue' is possibly 'undefined'.
      
      const numValue = map.get('one')!;
      console.log(numValue.toString()); // Ok

       

       

       

      🔗  어시션 할당 가능성

       

      완전히 서로 관련이 없는 두 타입 사이에 타입 어시션이 있는 경우에는 TS가 타입 오류를 감지하고 알려준다.

       

      const value = 'String' as number;
      // Error: Conversion of type 'string' to type 'number' may be a mistake
      // because neither type sufficiently overlaps with the other.
      // If this was intentional, convert the expression to 'unknown' first.

      하나의 타입에서 값을 완전히 관련 없는 타입으로 전환해야 하는 경우 이중 타입 어서션(double type assertion)을 사용한다. 먼저 값을 any나 unkown 같은 top 타입으로 전환한 다음 관련 없는 타입으로 전환한다.

      const value = 'String' as unknown as number; // 허용되지만 이렇게 사용하면 안 됨

       

      as unknown as… 이중 타입 어서션은 위험하고 거의 항상 코드의 타입이 잘못되었다는 징후를 나타낸다. 이중 타입 어서션의 문제점은 다음과 같다.

       

      • 가독성 문제: 이중 타입 어서션을 사용하면 코드가 복잡해지고 가독성이 떨어진다.
      • 타입 안정성 문제: 이중 타입 어서션을 사용하면 타입스크립트가 변수나 값의 타입을 추론하지 못할 수 있다. 이는 코드에서 에러가 발생할 가능성을 높인다.
      • 디버깅 문제: 이중 타입 어서션을 사용하면 에러가 발생했을 때 어디에서 발생했는지 추적하기 힘들어 디버깅이 어려워진다.

       

      🔗  const 어시션

       

      const 어서션은 배열, 원시 타입, 값, 별칭 등 모든 값을 상수로 취급해야 함을 나타내는 데 사용한다. 특히 as const는 수신하는 모든 타입에 다음 세 가지 규칙을 적용한다.

       

      • 배열은 가변 배열이 아니라 읽기 전용 튜플로 취급된다.
      • 리터럴은 일반적인 원시 타입과 동등하지 않고 리터럴로 취급된다.
      • 객체의 속성은 읽기 전용으로 간주 된다.

       

      const arr = [0, ''];
      // arr: (string | number)[]
      
      const arrAsConst = [0, ''] as const;
      // arrAsConst: readonly [0, ""]

       

      리터럴에서 원시 타입으로

       

      타입 시스템이 리터럴 값을 일반적인 원시 타입으로 확장하기보다 특정 리터럴로 이해하는 것이 유용할 수 있다.

       

      //코드에서 getNameConst의 반환 타입은 string 대신 Roh라는 더 구체적인 값이다.
      // const getName: () => string
      const getName = () => 'Roh';
      
      // const getNameConst: () => "Roh"
      const getNameConst = () => 'Roh' as const;

       

      //객체의 특정 필드를 리터럴 값으로 설정하는 것은 매우 유용하다. 
      //인기 있는 라이브러리 중에는 값을 구분하는 필드가 특정한 리터럴 값이 되도록 요구하는 경우가 많다. 
      //이렇게 함으로써 코드의 타입을 보다 구체적으로 추론할 수 있다.
      
      
      interface Joke {
        quote: string;
        style: 'story' | 'one-liner';
      }
      
      function tellJoke(joke: Joke) {
        if (joke.style === 'one-liner') {
          console.log(joke.quote);
        } else {
          console.log(joke.quote.split('\n'));
        }
      }
      
      const narrowJoke = {
        quote: 'If you stay alice for no other reason do it for spite.',
        style: 'one-liner' as const,
      };
      
      tellJoke(narrowJoke); // Ok

       

       

      🔗 읽기 전용 객체

       

      as const를 사용해 값 리터럴을 어서션하면 유추된 타입이 가능한 한 구체적으로 전환된다.

      모든 멤버 속성은 readonly가 되고, 리터럴은 일반적인 원시 타입 대신 고유한 리터럴 타입으로 간주되며.

      배열은 읽기 전용 튜플이 된다.

      따라서 값 리터럴에 const 어서션을 적용하면 해당 값 리터럴이 변경되지 않으며, 모든 멤버에 같은 const 어서션 로직이

      재귀적으로 적용 된다.

       

       

      🤷‍♂️뭔 소리야

       

      예제를 보자!

       

      function describePreference(preference: 'maybe' | 'no' | 'yes') {
        switch (preference) {
          case 'maybe':
            return 'I suppose... ';
          case 'no':
            return 'No thanks.';
          case 'yes':
            return 'Yes please!';
        }
      }
      //preferencesMutable 값은 as const 없이 선언되었으므로 이름은 원시 타입인 string, 수정 허용
      const preferencesMutable = {
        movie: 'maybe',
        standup: 'yes',
      };
      /* 타입
      const preferencesMutable: {
          movie: string;
          standup: string;
      }
       */
      
      describePreference(preferencesMutable.movie);
      // Error: Argument of type 'string' is not assignable
      // to parameter of type '"maybe" | "no" | "yes"'.
      
      preferencesMutable.movie = 'no'; // Ok
      
      //preferencesReadonly는 as const로 선언되 었으므로 해당 멤버 값은 리터럴, 수정 허용 X
      const preferencesReadonly = {
        movie: 'maybe',
        standup: 'yes',
      } as const;
      /* 타입
      const preferencesReadonly: {
          readonly movie: "maybe";
          readonly standup: "yes";
      }
       */
      
      describePreference(preferencesReadonly.movie); // Ok
      preferencesReadonly.movie = 'no';
      // Error: Cannot assign to 'movie' because it is a read-only property.

       

       

       

       

       

      러닝 타입스크립트와 https://chamdom.blog/ts-type-guard/ 보고 블로그를 작성하였다.

      반응형

      'codeStates front-end > Typescript' 카테고리의 다른 글

      [learn-typescript] live server 이용해서 user 정보 가져오기  (0) 2023.06.28
      [TS] 제네릭  (0) 2023.03.23
      [TS] 클래스  (0) 2023.03.20
      [TS] 인터페이스  (0) 2023.03.12
      [TS] 배열과 튜플  (0) 2023.03.12

      댓글

    Designed by Tistory.