Typescript가 중첩 된 제네릭 함수의 유형을 유추 할 수없는 이유는 무엇입니까?

Nov 24 2020

Typescript 는 아래 호출에 대한 state인수를 . 를 사용하여 유형을 명시 적으로 지정하면 작동 하지만 이것이 필요한 이유는 무엇입니까? 유형이이어야하는 것이 꽤 분명해 보입니다 .applyReducerunknownapplyReducer<State>State

(Typescript v4.1.2)

reducerFlow.ts :

type UnaryReducer<S> = (state: S) => S

export const applyReducer = <S>(reducer: UnaryReducer<S>) =>
  (state: S) => reducer(state)

foo.ts

interface State { a: number, b: number }
const initialState: State = { a: 0, b: 0 }

// Why is the type of state unknown?
// Typescript can't infer that it is State?
const foo = applyReducer(
    state => ({ ...state, b: state.b + 1 })
)(initialState)

TS 운동장

답변

JeffreyWesterkamp Nov 24 2020 at 03:56

질문에 답하려면 :

applyReducer로 유형을 명시 적으로 지정하면 작동하지만 이것이 필요한 이유는 무엇입니까?

문제는 이것이 카레 함수라는 사실에 (x) => (y) => z있습니다. 유형 매개 변수 S가 첫 번째 함수에 있기 때문에 해당 함수를 호출하자마자 '인스턴스화'(구체적인 유형 가져 오기)됩니다. fn아래 유형을 살펴보면 실제로 작동하는 것을 볼 수 있습니다 .

const fn = applyReducer((state) => ({ ...state, b: 2 }))
//                                    ^^^^^^^^ error (because you can't spread 'unknown')
//
// const fn: (state: unknown) => unknown

인수 (state) => ({ ...state, b: 2 })에는 무엇이 S되어야 하는지에 대한 정보가 없기 때문에 typescript는 기본적으로 unknown. 그래서 지금 S은 알려지지 않았 fn으며 나중에 무엇을 부르든 상관없이 알려지지 않을 것입니다.

이 문제를 해결하는 한 가지 방법은 언급했듯이 명시 적으로 형식 인수를 제공하는 것입니다.

const fna = applyReducer<State>((state) => ({ ...state, b: 2 }))

또 다른 방법은 typescript에 대한 유형을 유추 할 수있는 정보를 제공하는 것 S입니다 (예 state: 매개 변수 에 대한 유형 제약) .

const fnb = applyReducer((state: State) => ({ ...state, b: 2 }))
captain-yossarian Nov 24 2020 at 03:07

업데이트 내 대답은 다음과 같습니다.

예:


type UnaryReducer = <S>(state: S) => S

interface ApplyReducer {
  <T extends UnaryReducer>(reducer: T): <S,>(state: ReturnType<T> & S) => ReturnType<T> & S;
}

export const applyReducer: ApplyReducer = (reducer) =>
  (state) => reducer(state)


interface State { a: number, b: number }
const initialState: State = { a: 0, b: 0 }


const bar = applyReducer(
  state => ({ ...state, b: 2, })
)(initialState)
bar // {b: number; } & State

const bar2 = applyReducer(
  state => ({ ...state, b: '2', }) 
)(initialState) // Error: b is not a string

const bar3 = applyReducer(
  state => ({ ...state, b: 2, c:'2' }) 
)(initialState) // Error: Property 'c' is missing in type 'State'
 
const bar4 = applyReducer(
  state => ({ ...state }) 
)(initialState) // Ok

const bar5 = applyReducer(
  state => ({ a: 0, b: 0 }) // Error: you should always return object wich is extended by State
)(initialState)

const bar6 = applyReducer(
  state => ({...state, a: 0, b: 0 }) // Ok
)(initialState)

화살표 함수에 대한 일반 매개 변수를 직접 정의해야합니다.

type UnaryReducer = <S>(state: S) => S

initialState리듀서의 인수와 ReturnType을 어떻게 든 바인딩해야합니다.

interface ApplyReducer {
  <T extends UnaryReducer>(reducer: T): <S,>(state: ReturnType<T> & S) => ReturnType<T> & S;
}

state리듀서 (콜백)의 인자는 항상 반환형의 일부 여야 한다는 뜻입니다 .

즉, 다음을 시도 할 경우 :

state => ({ a:0, b: 2, }) 

작동하지 않을 텐데 할 이유가 없다고 생각합니다