टाइपस्क्रिप्ट एक नेस्टेड जेनेरिक फ़ंक्शन के प्रकार का अनुमान क्यों नहीं लगा सकता है?

Nov 24 2020

टाइपस्क्रिप्ट नीचे दिए गए कॉल के stateतर्क को टाइप applyReducerकरता है unknown। यह काम करता है अगर मैं स्पष्ट रूप से प्रकार निर्दिष्ट करता हूं applyReducer<State>, लेकिन यह आवश्यक क्यों है? यह बहुत स्पष्ट लगता है कि प्रकार होना चाहिए State

(टाइपस्क्रिप्ट 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)

टीएस खेल का मैदान

जवाब

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बनना चाहिए, टाइपस्क्रिप्ट डिफॉल्ट करता है unknown। तो Sअब अज्ञात है, और fnइसके बाद आप जिसे भी बुलाते हैं , वह अज्ञात रहेगा।

इसे ठीक करने का एक तरीका है - जैसा कि आपने उल्लेख किया है - स्पष्ट रूप से प्रकार तर्क प्रदान करें:

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

और दूसरा तरीका टाइपस्क्रिप्ट को कुछ जानकारी देना है, जहां से यह टाइप कर सकता है 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तर्क और रिड्यूसर के रिटर्न टाइप को बांधना चाहिए

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

इसका अर्थ है कि stateReducer (कॉलबैक) का तर्क हमेशा रिटर्न प्रकार का एक हिस्सा होना चाहिए।

इसका मतलब है, कि यदि आप निम्न करने की कोशिश करेंगे:

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

यह काम नहीं करने वाला है, लेकिन मुझे लगता है कि ऐसा करने के लिए कोई सेंस नहीं है