'인터페이스' 유형의 인수는 '인터페이스' 유형의 매개변수에 할당할 수 없습니다.

13064 단어 typescript
Twitter에서 나를 팔로우하세요. | 구독하기 Newsletter | timdeschryver.dev에 원래 게시되었습니다.


내가 working on a refactor NgRx 프로젝트에서 NgRx 감속기on - variadic tuple types 🥳 내에서 N개의 액션 핸들러createReducer를 허용하기 위해 🥳 동안 TypeScript 문제가 발생했습니다. 이미 부딪쳤습니다.

문제는 "'인터페이스' 유형의 인수를 '인터페이스' 유형의 매개변수에 할당할 수 없습니다"라는 메시지를 표시하는 컴파일 시간 오류입니다. 내가 했던 것처럼 전체 오류 메시지를 읽지 않으면 문제, 발생 이유 및 이 오류를 해결하는 방법을 이해하는 데 시간이 걸릴 수 있습니다.

내 이해에 따르면 다음과 같은 래퍼 메서드가 있을 때 오류가 나타납니다.
  • 은 일반
  • 을 사용합니다.
  • 및 동일한 일반
  • 과 함께 작동하는 콜백 인수가 있습니다.
  • 콜백 인수가 동일한 제네릭을 포함하는 유형을 반환함
  • 및 제네릭 유형이 100% 동일하지 않음

  • 아래 스니펫은 가장 간단한 방법으로 문제를 보여줍니다.

    // The `Callback<T>` interface is used in the `wrapper` method as well as the `callback` method.
    interface Callback<T extends object> {
      (arg: T): any
    }
    
    function wrapper<T extends object>(callback: Callback<T>): any {
      return callback({} as T)
    }
    
    function callback<T extends object>(cb: () => T): Callback<T> {
      return cb
    }
    


    이러한 함수는 사용할 때 다음과 같은 결과를 제공합니다.

    // --- INVALID --- //
    
    const givesAnError = wrapper<{ prop?: string }>(callback(() => ({ prop: '' })))
    // |> Argument of type 'Callback<{ prop: string; }>' is not assignable to parameter of type 'Callback<{ prop?: string | undefined; }>'.
    // |>  Types of property 'prop' are incompatible.
    // |>    Type 'string | undefined' is not assignable to type 'string'.
    
    // --- VALID --- //
    
    // Types are 100% identical
    const works = wrapper<{ prop: string }>(callback(() => ({ prop: '' })))
    
    // Provide the same generic to the callback method
    const worksCallbackGeneric = wrapper<{ prop?: string }>(a
      callback<{ prop?: string }>(() => ({ prop: '' })),
    )
    
    // A type assertion on the return value of the callback
    const worksTypeAssertion = wrapper<{ prop?: string }>(
      callback(() => ({ prop: '' } as { prop?: string })),
    )
    


    이제 이상한 부분은 callback 메서드가 동일한 유형의 입력 매개변수를 수락하면 컴파일된다는 것입니다.
    그러나 인수가 사용되는 경우에만.

    function callbackWithInput<T extends object>(cb: Callback<T>): Callback<T> {
      return cb
    }
    
    // --- VALID --- //
    
    // Note the argument `_arg` isn't used but helps to make this compile
    const works = wrapper<{ prop?: string }>(
      callbackWithInput((_arg) => ({ prop: '' })),
    )
    
    // --- INVALID --- //
    
    // Same but without an argument gives the same compile error as before
    const stillDoesntWork = wrapper<{ prop?: string }>(
      callbackWithInput(() => ({ prop: '' })),
    )
    // |> Argument of type 'Callback<{ prop: string; }>' is not assignable to parameter of type 'Callback<{ prop?: string | undefined; }>'.
    // |>  Types of parameters 'input' and 'input' are incompatible.
    // |>    Type '{ prop?: string | undefined; }' is not assignable to type '{ prop: string; }'
    


    내가 볼 수 있었던 것은 TypeScript가 더 이상 제네릭의 인터페이스를 올바르게 추론할 수 없다는 것입니다.
    솔직히 말해서, 나는 그 이유를 모르겠고 서명이 같은 유형을 가지고 있기 때문에 이것이 컴파일될 것으로 예상합니다.

    위의 사용 예에서 볼 수 있듯이 이 작업을 수행할 수 있지만 서명이나 콜백 메서드가 호출되는 방식을 변경해야 합니다. 소비자의 관점에서 이것은 나쁘고 NgRx를 사용하는 사람들에게는 엄청난 변화가 되었을 것입니다.

    수정 사항으로 NgRx의 Iintroduced a new generic에서 TypeScript의 간섭을 돕습니다. 처음에는 이것이 수정된 것처럼 보였지만 on 메서드의 서명이 변경되었기 때문에 숨겨진 주요 변경 사항이 도입되었습니다.

    Noteworthy to mention that if you're adding a generic to only use it once, you're probably doing something wrong. This rule "Type Parameters Should Appear Twice" is explained in The Golden Rule of Generics, written by .



    운 좋게도 소비자에게 영향을 미치지 않는 제공a better solution.

    이 문제를 올바르게 해결하는 솔루션은 TypeScript가 유형을 유추하도록 돕는 것입니다. 이를 위해 제네릭의 엄격함을 조정할 수 있습니다. 제네릭을 직접 사용하는 대신 keyof 를 사용하여 매핑된 유형을 만들 수 있습니다.

    interface Callback<T extends object> {
      (
        input: {
          [P in keyof T]?: T[P]
        },
      ): S
    }
    


    여기서 또 다른 이상한 부분은 NgRx 유형에서 Alex가 제네릭 속성을 잠재적으로 undefined로 입력할 필요가 없다는 것입니다(here에서 볼 수 있듯이).

    따라서 이 블로그 게시물은 우리에게 약간의 불분명함을 남기지만 이 문제에 대한 해결책을 제공합니다.
    이 재생산을 가지고 놀려면 TypeScript Playground link을 참조하십시오.


    Twitter에서 나를 팔로우하세요. | 구독하기 Newsletter | timdeschryver.dev에 원래 게시되었습니다.

    좋은 웹페이지 즐겨찾기