Typescript를 적용해보자! - boostcampwm-2021/WEB08-AgileStorming GitHub Wiki

작성자: 김유석

타입스크립트는 JS를 사용하는 프론트, 백엔드 환경에서 필수적인 언어이다. 자바스크립트 기본 문법에 타입스크립트 문법을 추가해 자바스크립트와 100% 호환되고, 정적 타입의 컴파일 언어로서 런타임에서 오류를 발견할 수 있는 자바스크립트와 달리 코드 작성 단계에서 타입을 체크해 오류를 확인할 수 있고, 미리 타입을 결정하기 때문에 실행 속도가 매우 빠르다고 한다. 또한, 코드 자동완성과 작업과 동시에 디버깅이 가능해 생산성을 높일 수 있다. 그렇기 때문에 현업에서의 대형 프로젝트를 진행 함에 있어 타입스크립트는 반드시 필요할 것이다.

사실, 우리 같은 작은 프로젝트에서도 크게 생산성이 향상되는 것은 기대하기 힘들고, 러닝커브 또한 거쳐야 하기 때문에 오히려 프로젝트 진행 속도는 느려질 것이다. 그럼에도 직접 타입스크립트를 써보며 장점을 부딪히며 느껴보고 경험해보는 것도 좋은 선택이라고 생각하기에 이번 프로젝트에 적용해보고자 한다.

Typescript 설정

  1. CRA에서 설정

    npx create-react-app my-app[프로젝트 이름] --template typescript

    CRA가 기본적인 것들을 설치하고 설정해주기 때문에 설정을 수정하고 싶다면

    yarn eject

    를 시행하면 config, scripts 폴더가 생기면서 설정파일들이 보여지게 된다. (다시 돌이킬 수 없다.)

    CRA가 자동으로 설정해주지만 기본적인 tsconfig 설정에 대해 알아보자.

    // tsconfig.json
    
    {
      "compilerOptions": {
        "strict": true,
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "baseUrl": "src",
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "forceConsistentCasingInFileNames": true,
        "noFallthroughCasesInSwitch": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx"
      },
      "include": ["src", "public/API"]
    }

    compilerOptions 항목은 tsc 명령 형식에서 옵션을 나타내고, include 항목은 대상 파일 목록을 나타낸다.

    위 예시의 include 항목 내 "./src~"는 src 디렉터리는 물론 src의 하위 디렉터리에 있는 모든 파일을 컴파일 대상으로 포함한다는 의미이다.

    strict : 엄격모드 사용 여부. true로 설정하지 않으면 Typescript를 사용하는 의미가 퇴색된다.

    target : 트랜스파일 시 /build에 생성되는 자바스크립트의 버전을 설정 (es5, es6 등)

    lib : 컴파일 할 때 포함 될 라이브러리 목록, esnext는 가장 최신 버전

    baseUrl : 모듈 이름을 처리할 기준 디렉토리

    import abc from 'src/components/abc' 
    import abc from 'components/abc' // baseUrl을 src로 설정시 이와 같이 접근할 수 있다.

    allowJs: TypeScript 컴파일 작업 진행시 js파일도 컴파일 할것인지 여부

    skipLibCheck : 정의 파일의 타입 확인을 건너 뛸것인지

    esModuleInterop : 타입스크립트 코드는 CommonJS 방식으로 동작하지만 AMD 방식으로 웹 브라우저에서 동작하다는 가정으로 만들어진 라이브러리도 동작시키기 위해 true값을 설정해야한다.

    allowSyntheticDefaultImports : default export를 쓰지 않은 모듈도 default import가 가능하게 할건지 여부

    forceConsistentCasingInFileNames : 같은 파일에 대한 일관되지 않은 참조를 허용하지 않을지 여부

    noFallthroughCasesInSwitch : switch문에서 fallthrough 에 대한 에러 보고

    module : 프로그램에서 사용할 모듈 시스템을 결정한다. 즉 모듈 내보내기/불러오기 코드가 어떠한 방식의 코드로 컴파일 될지 결정한다.

    • CommonJS (target 프로퍼티가 ES3 혹은 ES5로 지정되었을 때의 기본값)
    • ES6/ES2015 (target 프로퍼티가 ES6 혹은 그 이상의 버전으로 지정되었을 때의 기본값)
    • 나머지 (ES2020, ESNext, AMD, UMD, System, None)

    moduleResolution : 모듈 해석 방법 설정. module 키의 값이 commonjs이면 node로 설정하지만, amd이면 classic으로 설정한다. classic으로 설정할 일은 거의 없을 것이라고 한다.

    resolveJsonModule : TypeScript 모듈 내에서 json 모듈을 가져올수 있는 옵션

    isolatedModules : 각 파일을 분리된 모듈로 트랜스파일

    noEmit : 결과 파일을 내보낼지 여부

    jsx : jsx 코드 생성 설정

    [그외 추가 옵션들]

    noImplicitAny : 타입스크립트 컴파일러는 기본적으로 타입을 명시하지 않은 코드일 경우 암시적으로 any 타입을 설정한 것으로 간주한다. true로 설정하면 오류 메시지를 알려준다.

    outDir : baseDir 설정값을 기준으로 했을 때 하위 디렉터리의 이름

    paths : 소스 파일의 import 문에서 from 부분을 해석할 때 찾아야 하는 디렉터리를 설정

    sourceMap : true이면 트랜스파일 디렉터리에 .js 파일 이외에 .js.map 파일이 만들어진다. 이 소스맵 파일은 변환된 자바스크립트 코드가 타입스크립트 코드의 어디에 해당하는지 알려준다. 주로 디버깅에 사용된다.

    downlevelIteration : 생성기(generator)라는 타입스크립트 구문이 정상적으로 동작하려면 반드시 true 값으로 설정해야한다.

  2. eslint 설정

    eslint linting 라이브러리 eslint, eslint가 typescript를 lint 할 수 있도록 허용해주는 parser인 @typescript-eslint/parser typescript에 구체화된 ESLINT rule들을 잔뜩 포함하는 플러그인인 @typescript-eslint/eslint-plugin

    npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

    또 리액트에서 타입스크립트를 사용하려면 eslint-plugin-react를 설치해야 한다.

    npm i -D eslint-plugin-react

    .eslintrc.js 을 프로젝트 디렉토리 최상단, 즉 root 디렉토리에 만들어준다.

    // .eslintrc.js
    
    module.exports = {
      parser: "@typescript-eslint/parser", // Specifies the ESLint parser
      extends: [
        "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
        "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin
      ],
      parserOptions: {
        ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
        sourceType: "module", // Allows for the use of imports
        ecmaFeatures: {
          jsx: true // Allows for the parsing of JSX
        }
      },
      rules: {
      },
      settings: {
        react: {
          version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
        }
      }
    };

    rules 부분에 Lint할 조건들을 적어주면 된다고 적혀있지만 prettier config를 통해서 바로 formatting으로 해줄 수 있다고 한다.

  3. prettier 설정

    핵심 prettier 라이브러리인 prettier와 prettier와 충돌을 일으키는 ESLint 규칙들을 비활성화 시키는 config인 eslint-config-prettier 그리고 ESLint 규칙에 따라 작동하게 해주는 플러그인인 eslint-plugin-prettier 설치

    npm i -D prettier eslint-config-prettier eslint-plugin-prettier

    .prettierrc.js파일을 프로젝트 디렉토리 최상단에 만든다.

    module.exports =  {
        parser:  '@typescript-eslint/parser', 
        extends:  [
            'plugin:@typescript-eslint/recommended', 
            'prettier/@typescript-eslint', 
            'plugin:prettier/recommended',  
        ],
        parserOptions:  {
        	ecmaVersion:  2018, 
        	sourceType:  'module', 
        	ecmaFeatures:  {
          	jsx:  true,  
        	},
        },
        rules:  {
        // 추후 .prettierrc.js 파일에서 설정해줄 예정
        },
        settings:  {
          react:  {
            version:  'detect', 
          },
        },
      };

    prettier 옵션들

    {
      "arrowParens": "avoid", // 화살표 함수 괄호 사용 방식
      "bracketSpacing": false, // 객체 리터럴에서 괄호에 공백 삽입 여부 
      "endOfLine": "auto", // EoF 방식, OS별로 처리 방식이 다름 
      "htmlWhitespaceSensitivity": "css", // HTML 공백 감도 설정
      "jsxBracketSameLine": false, // JSX의 마지막 `>`를 다음 줄로 내릴지 여부 
      "jsxSingleQuote": false, // JSX에 singe 쿼테이션 사용 여부
      "printWidth": 80, //  줄 바꿈 할 폭 길이
      "proseWrap": "preserve", // markdown 텍스트의 줄바꿈 방식 (v1.8.2)
      "quoteProps": "as-needed" // 객체 속성에 쿼테이션 적용 방식
      "semi": true, // 세미콜론 사용 여부
      "singleQuote": true, // single 쿼테이션 사용 여부
      "tabWidth": 2, // 탭 너비 
      "trailingComma": "all", // 여러 줄을 사용할 때, 후행 콤마 사용 방식
      "useTabs": false, // 탭 사용 여부
      "vueIndentScriptAndStyle": true, // Vue 파일의 script와 style 태그의 들여쓰기 여부 (v1.19.0)
      "parser": '', // 사용할 parser를 지정, 자동으로 지정됨
      "filepath": '', // parser를 유추할 수 있는 파일을 지정
      "rangeStart": 0, // 포맷팅을 부분 적용할 파일의 시작 라인 지정
      "rangeEnd": Infinity, // 포맷팅 부분 적용할 파일의 끝 라인 지정,
      "requirePragma": false, // 파일 상단에 미리 정의된 주석을 작성하고 Pragma로 포맷팅 사용 여부 지정 (v1.8.0)
      "insertPragma": false, // 미리 정의된 @format marker의 사용 여부 (v1.8.0)
      "overrides": [ 
        {
          "files": "*.json",
          "options": {
            "printWidth": 200
          }
        }
      ], // 특정 파일별로 옵션을 다르게 지정함, ESLint 방식 사용
    }
    스크린샷 2021-11-02 오전 9 56 39

Typescript 문법

타입 주석(type annotation)과 타입 추론(type inference)

타입 주석 : 변수 뒤에 콜론(:)과 타입 이름을 기입해 변수에 타입을 지정한다.

🤨 let키워드로 변수 선언시 지정한 타입 외에 다른 값으로 변경할 수 없다!

타입 추론 : 변수의 타입 부분이 생략되면 대입 연산자(=)의 오른쪽 값을 분석해 왼쪽 변수의 타입을 결정한다.

🤨 변수에 타입 표기를 하지 않아도 코드 흐름을 파악하는 데 어려움이 없을 것이기에 변수 선언시 타입을 명시적으로 표기하지 않도록 해보자!

let n: number = 1
let m = 2

타입스크립트 기본 제공 타입

  1. The primitives: string, boolean, number

    const bool: boolean = false
    const num: number = 1
    const str: string = 'hi'
  2. Arrays

    두가지 형태로 표시 가능.

    1. type[]
    2. Array<type>
    const numArr: num[] = [1, 2, 3]
    const strArr: string[] = ['hello', 'world']
    const boolArr: Array<boolean> = [false, true]

    🙃 [number]와 같은 표현으로 나타내진 타입은 Array가 아닌 Tuple이라고 불리는 타입이다.

  3. any

    특정 값으로 인해 타입체킹 에러가 발생하는 것을 원하지 않을 때 사용한다. 즉, any 타입에는 어떤 값이든 써도 된다. Typescript를 쓰는 이유를 퇴색시킴!

  4. Object Types

    객체의 프로퍼티들과 각 프로퍼티의 타입들을 나열하면 된다.

    각 프로퍼티를 구분할 때, , 또는 ;를 사용할 수 있고, 마지막에 위치한 구분자의 표기는 선택 사항이다.

    옵셔널 프로퍼티 활용 가능

    function printName(obj: { first: string; last?: string }) {}
  5. Null and Undefined

    초기화되지 않은 값을 가리키는 두 가지 원시 타입

    각 타입의 동작 방식은 strictNullChecks 옵션 설정 여부에 따라 달라진다.

    strictNullChecks가 설정되지 않았다면, 어떤 값이 null 또는 undefined 일 수 있더라도 해당 값에 평소와 같이 접근할 수 있고, nullundefined 는 모든 타입의 변수에 대입될 수 있다.

    Null 검사의 부재는 버그의 주요 원인이 되기도 하므로 별다른 이유가 없으면 코드 전반에 걸쳐 strictNullChecks 옵션을 설정하는 것을 권장한다.

    Null 아님 단언 연산자 (접미사!)

    명시적인 검사를 하지 않고도 타입에서 nullundefined 를 제거할 수 있는 특별한 구문을 제공한다.

    표현식 뒤에 ! 를 작성하면 해당 값이 null 또는 undefined 가 아니라고 타입 단언하는 것이다.

    function liveDangerously(x?: number | undefined) {
      // 오류 없음
      console.log(x!.toFixed());
    }

    다른 타입 단언과 마찬가지로 코드의 런타임 동작을 변화시키지 않으므로 반드시 해당 값이 null 또는 undefiend가 아닌 경우에만 사용해야 한다.

  6. Enums

    이름이 있는 상수들의 집합을 정의할 수 있다.

    Enum을 사용하면 의도를 문서화 하거나 구분되는 사례 집합을 더 쉽게 만들 수 있다.

    enum Direction {
      Up = 1,
      Down,
      Left,
      Right,
    }

    위 코드에서 Up 이 1로 초기화된 숫자 열거형을 선언했다. 그 지점부터 뒤따르는 멤버들은 자동으로 증가된 값을 갖는다.

    원한다면, 전부 초기화 하지 않을 수 있고, 자동으로 0부터 값이 지정된다.

    초기화할 수 있는 값으로는 컴파일 시 알아낼 수 있는 표현식을 포함한다.

    Enum의 이름을 사용해 타입을 선언해 사용하면 된다.

    enum UserResponse {
      No = 0,
      Yes = 1,
    }
     
    function respond(recipient: string, message: UserResponse): void {
      // ...
    }
     
    respond("Princess Caroline", UserResponse.Yes);

    **문자열 열거형(String enums)**은 유사한 개념이지만 런타임에서 열거형의 동작이 약간 다르다.

    각 멤버들을 문자열 리터럴 또는 다른 문자열 열거형 멤버로 상수 초기화 해야 한다.

    enum Direction {
      Up = "UP",
      Down = "DOWN",
      Left = "LEFT",
      Right = "RIGHT",
    }

    숫자 열거형처럼 자동 증가하는 기능은 없지만 '직렬화'를 잘한다는 이점이 있다.

    숫자만으로는 이것이 어떤 의미인지 유의미한 정보를 제공해주지 않을 수 있기 때문에 문자열 열거형을 통해 유의미하고 읽기 좋은 값을 이용하여 실행할 수 있다.

    열거형 타입 자체가 효율적으로 각각의 열거형 멤버의 유니언으로 사용할 수도 있고, 열거형 멤버를 리터럴 타입처럼 사용할 수도 있다.

    런타임 시에는 열거형은 실제 존재하는 객체이다. 따라서 함수에 인자로 사용하든지 할 수 있다.

    런타임 시 실제 객체라고 할지라도, 컴파일 시 keyof 키워드는 일반적인 객체와는 다르게 동작한다. 대신, keyof

    typeof` 를 사용하면 열거형의 모든 키를 문자열로 나타내는 타입을 가져온다.

    enum LogLevel {
      ERROR,
      WARN,
      INFO,
      DEBUG,
    }
    type LogLevelStrings = keyof typeof LogLevel;
    /**
     * 이것은 아래와 동일합니다. :
     * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
     */

    다음과 같이 역매핑을 사용할 수 있다.

    enum Enum {
      A,
    }
     
    let a = Enum.A;
    let nameOfA = Enum[a]; // "A"
  7. Unknown

    any와 같이 최상위 타입인 unknown은 알 수 없는 타입을 의미한다. 어떤 타입의 값도 할당할 수 있지만, unknown을 다른 타입에 할당할 수 없다.

    따라서 any보다는 안전하게 사용할 수 있다.

    타입을 단언하면 할당할 수 있다.

  8. Void

    void는 일반적으로 값을 반환하지 않는 함수에서 사용햔다.

    값을 반환하지 않는 함수는 실제로는 undefined를 반환한다.

  9. Never

    Never은 절대 발생하지 않을 값을 나타내며, 어떠한 타입도 적용할 수 없다.

    즉, 예외를 반환했거나, 프로그램의 실행이 종료됐다는 것을 의미한다.

    function error(message: string): never {
      throw new Error(message);
    }

    보통 다음과 같이 빈 배열을 타입으로 잘못 선언한 경우 Never을 볼 수 있다.

    const never: [] = [];
    never.push(3); //Argument of type '3' is not assignable to parameter of type 'never'
  10. Symbol

  11. Tuple

    길이가 정해져 있고, 순차적으로 type이 지정되어 있는 배열을 나타낸다.

    ex) const tuple: [string, number, string] = ['']

함수에서의 타입 표기

  1. 매개변수 타입 표기

    매개변수 이름 뒤에 표기한다.

  2. 반환 타입 표기

    반환 타입은 매개변수 목록 뒤에 표기한다.

    🙃 변수의 타입 표기와 마찬가지로, 반환 타입은 표기하지 않아도 되는 것이 일반적이라고 한다.

function greet(name: string): void {
  console.log("Hello, " + name.toUpperCase() + "!!");
}

Union Types (합집합)

유니언 타입은 서로 다른 두개 이상의 타입들을 사용하여 만드는 것으로, 사용된 타입 중 하나를 타입으로 가질 수 있다.

조합에 사용된 타입을 유니언 타입의 멤버라고 부른다.

function printId(id: number | string) {}

유니언 타입을 사용할 시 모든 멤버에 대하여 유효한 작업에 대해서만 허용된다. string | number라는 유니언 타입에서 string 타입에만 유효한 메서드는 사용할 수 없고, 이를 사용하려면 분기처리가 필요하다.

타입 별칭(type aliases)

객체 타입이나 유니언 타입을 사용할 때 직접 다 표기하지 않고 이름을 부여해 표기할 수 있다.

type Point = {
  x: number;
  y: number;
};
 
// 앞서 사용한 예제와 동일한 코드입니다
function printCoord(pt: Point) {}

인터페이스

객체 타입을 만드는 또 다른 방법이다.

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {}

마치 타입이 없는 임의의 익명 객체를 사용하는 것처럼 동작한다.

타입 별칭과의 차이점 - 타입은 새 프로퍼티를 추가하도록 개방될 수 없는 반면, 인터페이스의 경우 항상 확장될 수 있다.

// 인터페이스 확장
interface Animal {
  name: string
}
interface Bear extends Animal {
  honey: boolean
}

// 교집합을 이용한 타입 확장
type Animal = {
  name: string
}
type Bear = Animal & { 
  honey: Boolean 
}

타입 단언(type assertion)

때로는 Typescript보다 우리가 어떤 값의 타입을 더 잘 아는 경우도 있다.

이런 경우, 타입 단언을 사용해 구체적으로 명시할 수 있다.

타입 단언은 보다 구체적인 또는 덜 구체적인 버전의 타입으로 변환하는 타입 단언만이 허용된다.

즉, 불가능한 강제 변환을 방지한다.

두 가지 형태로 사용할 수 있다.

<타입>객체 (, jsx 구문 분석에 어려움이 있을  있으므로 .tsx 파일에서는 허용하지 않음)
객체 as 타입
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

🙃 타입 단언은 컴파일 시 제거되므로, 타입 단언과 관련된 검사는 런타임 중에 이루어지지 않는다!

리터럴 타입

const 키워드로 변수를 선언하면 값을 변경할 수 없기 때문에 아래와 같이 타입을 지정할 수 있다.

const constantString: "Hello World" = "Hello World"

리터럴 타입은 그 자체만으로는 그닥 유용하지 않지만 유니언과 함께 사용하면 유용하게 표현할 수 있다.

function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}

function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

리터럴 추론

타입스크립트에서는 객체의 프로퍼티는 이후에 값이 변화할 수 있다고 가정한다.

따라서 객체의 프로퍼티를 리터럴 타입으로 추론하지 않는다.

const handleRequest(url: string, method: "GET" | "POST") {}

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);

// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

위와 같이 함수의 매개변수를 리터럴 타입으로 지정했을 때, 객체의 프로퍼티를 인자로 넘기면 GET 타입으로 인식하지 않고 string타입으로 인식해 에러가 발생한다.

이러한 경우를 해결하는 데 다음과 같은 두 가지 방법을 사용할 수 있다.

  1. 둘 중에 한 위치에 타입 단언을 추가

    // 수정 1:
    const req = { url: "https://example.com", method: "GET" as "GET" };
    // 수정 2
    handleRequest(req.url, req.method as "GET");

    수정 1은 ”req.method가 항상 리터럴 타입 "GET"이기를 의도하며, 이에 따라 해당 필드에 "GUESS"와 같은 값이 대입되는 경우를 미연에 방지하겠다”는 것을 의미

    수정 2는 “무슨 이유인지, req.method"GET"을 값으로 가진다는 사실을 알고 있다”는 것을 의미

  2. as const를 사용하여 객체 전체를 리터럴 타입으로 변환

    const req = { url: "https://example.com", method: "GET" } as const;
    handleRequest(req.url, req.method);

    as const 접미사는 일반적인 const와 유사하게 작동하는데, 해당 객체의 모든 프로퍼티에 string 또는 number와 같은 보다 일반적인 타입이 아닌 리터럴 타입의 값이 대입되도록 보장한다.

제네릭 타입

재사용 가능한 컴포넌트를 생성할 때, 즉, 다양한 타입에서 작동하는 컴포넌트를 작성할 때 사용할 수 있다.

function identity<Type>(arg: Type): Type {
  return arg;
}

타입에 적용되는 타입 변수를 활용한다.

위의 경우, identity 함수에 Type라는 타입 변수를 추가했다.

Type는 유저가 준 인수의 타입을 캡처하고 (예 - number), 이 정보를 나중에 사용할 수 있게 한다. 여기에서는 Type를 반환 타입으로 다시 사용한다.

위의 제네릭 identity 함수는 두 가지 방법 중 하나로 호출할 수 있다.

첫 번째 방법은 함수에 타입 인수를 포함한 모든 인수를 전달하는 방법이다.

let output = identity<string>("myString"); // 출력 타입은 'string'입니다.

두 번째 방법은 아마 가장 일반적인 방법이다. 여기서는 타입 인수 추론 을 사용한다.

let output = identity("myString"); // 출력 타입은 'string'입니다.
function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length); // 오류: Type에는 .length 가 없습니다.
// Property 'length' does not exist on type 'Type'.
  return arg;
}

위와 같은 변수 타입은 any나 모든 타입을 의미하기 때문에 .length 멤버가 없는 타입이 포함될 수 있다.

실제로 이 함수가 Type이 아닌 Type의 배열에서 동작하도록 의도했다고 가정하면 아래와 같이 표현할 수 있다.

function loggingIdentity<Type>(arg: Type[]): Type[] {
  console.log(arg.length); // 배열은 .length를 가지고 있습니다. 따라서 오류는 없습니다.
  return arg;
}

제네릭 인터페이스로 표현 가능

interface GenericIdentityFn {
  <Type>(arg: Type): Type;
}
 
function identity<Type>(arg: Type): Type {
  return arg;
}
 
let myIdentity: GenericIdentityFn = identity;

제네릭 제약조건(generic constraints)

any와 모든 타입에 동작하는 대신에, .length 프로퍼티가 있는 any와 모든 타입에서 동작하도록 제한하고 싶으면?

extends 키워드로 표현한 인터페이스를 이용해 명시한다.

interface Lengthwise {
  length: number;
}
 
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length); // Now we know it has a .length property, so no more error
  return arg;
}

readonly Properties

extends Keywords

keyof Type Operator, typeof Type Operator

Utility Types

참고 블로그 및 사이트

tsconfig.json 설정1

tsconfig.json 설정2

eslint, prettier 1

eslint, prettier 2

타입스크립트 공식 문서

⚠️ **GitHub.com Fallback** ⚠️