मैपिंग करते समय जेनेरिक गुणों के साथ समस्या


11

मेरे पास एक पुस्तकालय है जो निम्नलिखित के समान एक उपयोगिता प्रकार का निर्यात करता है:

type Action<Model extends object> = (data: State<Model>) => State<Model>;

यह उपयोगिता प्रकार आपको एक फ़ंक्शन घोषित करने की अनुमति देता है जो "कार्रवाई" के रूप में प्रदर्शन करेगा। यह एक सामान्य तर्क प्राप्त करता है Modelकि कार्रवाई के खिलाफ काम करेगा।

data"कार्रवाई" का तर्क फिर एक और उपयोगिता प्रकार है कि मैं निर्यात के साथ लिखा गया;

type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;

Stateउपयोगिता प्रकार मूल रूप से भेजे लेता Modelसामान्य और फिर एक नए प्रकार जहां सभी गुण है कि प्रकार के होते हैं बनाता है Actionहटा दिया गया है।

उदाहरण के लिए यहां ऊपर का एक बुनियादी उपयोगकर्ता भूमि कार्यान्वयन है;

interface MyModel {
  counter: number;
  increment: Action<Model>;
}

const myModel = {
  counter: 0,
  increment: (data) => {
    data.counter; // Exists and typed as `number`
    data.increment; // Does not exist, as stripped off by State utility 
    return data;
  }
}

ऊपर बहुत अच्छा काम कर रहा है। 👍

हालाँकि, एक ऐसा मामला है जिससे मैं जूझ रहा हूँ, खासकर जब एक जेनेरिक मॉडल की परिभाषा को परिभाषित किया गया है, साथ ही जेनेरिक मॉडल के उदाहरणों का निर्माण करने के लिए एक फैक्ट्री फ़ंक्शन के साथ।

उदाहरण के लिए;

interface MyModel<T> {
  value: T; // 👈 a generic property
  doSomething: Action<MyModel<T>>;
}

function modelFactory<T>(value: T): MyModel<T> {
  return {
    value,
    doSomething: data => {
      data.value; // Does not exist 😭
      data.doSomething; // Does not exist 👍
      return data;
    }
  };
}

ऊपर के उदाहरण में, मैं dataतर्क को टाइप करने की अपेक्षा करता हूं जहां doSomethingकार्रवाई को हटा दिया गया है, और सामान्य valueसंपत्ति अभी भी मौजूद है। हालांकि यह मामला नहीं है - valueसंपत्ति को हमारी Stateउपयोगिता द्वारा भी हटा दिया गया है ।

मेरा मानना ​​है कि इसका कारण यह है कि Tकिसी भी प्रकार के प्रतिबंधों के बिना यह सामान्य है / इस पर लागू होने वाली संकीर्णता, और इसलिए प्रकार प्रणाली यह तय करती है कि यह एक Actionप्रकार के साथ प्रतिच्छेद करती है और बाद में इसे dataतर्क प्रकार से हटा देती है ।

क्या इस प्रतिबंध के आसपास आने का कोई रास्ता है? मैंने कुछ शोध किया है और उम्मीद कर रहा था कि कुछ ऐसा तंत्र होगा जिसमें मैं यह बता सकता हूँ कि Tकोई भी एक को छोड़कर है Action। यानी एक नकारात्मक प्रकार का प्रतिबंध।

कल्पना कीजिए:

function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {

लेकिन टाइपस्क्रिप्ट के लिए वह सुविधा मौजूद नहीं है।

क्या किसी को इस तरह से पता है कि मुझे यह काम करने के लिए मिल सकता है जैसा कि मुझे उम्मीद है?


यहाँ डिबगिंग की सहायता के लिए एक पूर्ण कोड स्निपेट है:

// Returns the keys of an object that match the given type(s)
type KeysOfType<A extends object, B> = {
  [K in keyof A]-?: A[K] extends B ? K : never
}[keyof A];

// Filters out an object, removing any key/values that are of Action<any> type
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;

// My utility function.
type Action<Model extends object> = (data: State<Model>) => State<Model>;

interface MyModel<T> {
  value: T; // 👈 a generic property
  doSomething: Action<MyModel<T>>;
}

function modelFactory<T>(value: T): MyModel<T> {
  return {
    value,
    doSomething: data => {
      data.value; // Does not exist 😭
      data.doSomething; // Does not exist 👍
      return data;
    }
  };
}

आप इस कोड उदाहरण के साथ यहां खेल सकते हैं: https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14

जवाबों:


7

यह एक दिलचस्प समस्या है। सामान्य प्रकार में सामान्य प्रकार के मापदंडों के संबंध में टाइपस्क्रिप्ट आमतौर पर बहुत कुछ नहीं कर सकता है। यह सिर्फ किसी मूल्यांकन का बचाव करता है extendsअगर यह पाता है कि मूल्यांकन में एक प्रकार का पैरामीटर शामिल है।

एक अपवाद लागू होता है अगर हम एक विशेष प्रकार के प्रकार के संबंध का उपयोग करने के लिए टाइपस्क्रिप्ट प्राप्त कर सकते हैं, अर्थात्, एक समानता संबंध (एक विस्तारित संबंध नहीं)। एक समानता संबंध संकलक के लिए समझने में सरल है, इसलिए सशर्त प्रकार के मूल्यांकन को स्थगित करने की आवश्यकता नहीं है। जेनेरिक अवरोध संकलक के उन कुछ स्थानों में से एक हैं जहाँ प्रकार की समानता का उपयोग किया जाता है। आइए एक उदाहरण देखें:

function m<T, K>() {
  type Bad = T extends T ? "YES" : "NO" // unresolvable in ts, still T extends T ? "YES" : "NO"

  // Generic type constrains are compared using type equality, so this can be resolved inside the function 
  type Good = (<U extends T>() => U) extends (<U extends T>() => U) ? "YES" : "NO" // "YES"

  // If the types are not equal it is still un-resolvable, as K may still be the same as T
  type Meh = (<U extends T>()=> U) extends (<U extends K>()=> U) ? "YES": "NO" 
}

खेल का मैदान लिंक

हम विशिष्ट प्रकार की पहचान करने के लिए इस व्यवहार का लाभ उठा सकते हैं। अब, यह एक सटीक प्रकार का मैच होगा, न कि एक विस्तारित मिलान, और सटीक प्रकार के मैच हमेशा उपयुक्त नहीं होते हैं। हालाँकि, चूंकि Actionकेवल एक फंक्शन सिग्नेचर है, सटीक प्रकार के मैच पर्याप्त रूप से काम कर सकते हैं।

देखते हैं कि क्या हम उन प्रकारों को निकाल सकते हैं जो एक साधारण फ़ंक्शन हस्ताक्षर से मेल खाते हैं जैसे (v: T) => void:

interface Model<T> {
  value: T,
  other: string
  action: (v: T) => void
}

type Identical<T, TTest, TTrue, TFalse> =
  ((<U extends T>(o: U) => void) extends (<U extends TTest>(o: U) => void) ? TTrue : TFalse);

function m<T>() {
  type M = Model<T>
  type KeysOfIdenticalType = {
    [K in keyof M]: Identical<M[K], (v: T) => void, never, K>
  }
  // Resolved to
  // type KeysOfIdenticalType = {
  //     value: Identical<T, (v: T) => void, never, "value">;
  //     other: "other";
  //     action: never;
  // }

}

खेल का मैदान लिंक

उपरोक्त प्रकार KeysOfIdenticalTypeवह है जो हमें फ़िल्टर करने के लिए आवश्यक है। के लिए other, संपत्ति का नाम संरक्षित है। के लिए action, संपत्ति का नाम मिटा दिया जाता है। चारों ओर सिर्फ एक pesky मुद्दा है value। चूंकि valueप्रकार का है T, यह तुच्छ रूप से resolvable नहीं है T, और (v: T) => voidसमान नहीं हैं (और वास्तव में वे नहीं हो सकते हैं)।

हम अभी भी यह निर्धारित कर सकते हैं कि valueयह समान है T: प्रकार की संपत्तियों के लिए T, इस चेक को (v: T) => voidसाथ में काट दें never। के साथ किसी भी चौराहे neverतुच्छ रूप से resolvable है never। फिर हम Tएक अन्य पहचान जाँच का उपयोग करके प्रकार के गुण जोड़ सकते हैं:

interface Model<T> {
  value: T,
  other: string
  action: (v: T) => void
}

type Identical<T, TTest, TTrue, TFalse> =
  ((<U extends T>(o: U) => void) extends (<U extends TTest>(o: U) => void) ? TTrue : TFalse);

function m<T>() {
  type M = Model<T>
  type KeysOfIdenticalType = {
    [K in keyof M]:
      (Identical<M[K], (v: T) => void, never, K> & Identical<M[K], T, never, K>) // Identical<M[K], T, never, K> will be never is the type is T and this whole line will evaluate to never
      | Identical<M[K], T, K, never> // add back any properties of type T
  }
  // Resolved to
  // type KeysOfIdenticalType = {
  //     value: "value";
  //     other: "other";
  //     action: never;
  // }

}

खेल का मैदान लिंक

अंतिम समाधान कुछ इस तरह दिखता है:

// Filters out an object, removing any key/values that are of Action<any> type
type State<Model extends object, G = unknown> = Pick<Model, {
    [P in keyof Model]:
      (Identical<Model[P], Action<Model, G>, never, P> & Identical<Model[P], G, never, P>)
    | Identical<Model[P], G, P, never>
  }[keyof Model]>;

// My utility function.
type Action<Model extends object, G = unknown> = (data: State<Model, G>) => State<Model, G>;


type Identical<T, TTest, TTrue, TFalse> =
  ((<U extends T>(o: U) => void) extends (<U extends TTest>(o: U) => void) ? TTrue : TFalse);

interface MyModel<T> {
  value: T; // 👈 a generic property
  str: string;
  doSomething: Action<MyModel<T>, T>;
  method() : void
}


function modelFactory<T>(value: T): MyModel<T> {
  return {
    value,
    str: "",
    method() {

    },
    doSomething: data => {
      data.value; // ok
      data.str //ok
      data.method() // ok 
      data.doSomething; // Does not exist 👍
      return data;
    }
  };
}

/// Still works for simple types
interface MyModelSimple {
  value: string; 
  str: string;
  doSomething: Action<MyModelSimple>;
}


function modelFactory2(value: string): MyModelSimple {
  return {
    value,
    str: "",
    doSomething: data => {
      data.value; // Ok
      data.str
      data.doSomething; // Does not exist 👍
      return data;
    }
  };
}

खेल का मैदान लिंक

नोट: यहाँ सीमा यह है कि यह केवल एक प्रकार के पैरामीटर के साथ काम करता है (हालाँकि इसे संभवतः अधिक के लिए अनुकूलित किया जा सकता है)। इसके अलावा, एपीआई किसी भी उपभोक्ता के लिए थोड़ा भ्रमित करने वाला है, इसलिए यह सबसे अच्छा समाधान नहीं हो सकता है। ऐसे मुद्दे हो सकते हैं जिनकी मैंने अभी तक पहचान नहीं की है। अगर आपको कोई मिल जाए, तो मुझे बताएं let


2
मुझे लगता है कि गैंडालफ द व्हाइट ने खुद को प्रकट किया। 🤯 टीबीएच I इसे संकलक सीमा के रूप में लिखने के लिए तैयार था। इसलिए इसे आजमाने की जिद्द की। धन्यवाद! 🙇
ctrlplusb

@ctrlplusb OL LOL, उस टिप्पणी ने मेरा दिन बना दिया ern
टिटियन

मैं इस जवाब पर इनाम को लागू करना चाहता था, लेकिन मुझे नींद में चलने वाले बच्चे के दिमाग की कमी है और गर्भपात हो रहा है। मैं क्षमाप्रार्थी हूं! यह एक काल्पनिक रूप से व्यावहारिक जवाब है। प्रकृति में काफी जटिल है। 😅 इसका उत्तर देने के लिए समय निकालने के लिए बहुत-बहुत धन्यवाद।
ctrlplusb

@ctrlplusb :( ओह वेल .. जीतो कुछ तो कुछ हारे :)
Titian Cernicova-Dragomir

2

यह बहुत अच्छा होगा अगर मैं व्यक्त कर सकता हूं कि टी टाइप एक्शन का नहीं है। एक विलोम का विलोम

बिल्कुल जैसा आपने कहा, समस्या यह है कि हमारे पास अभी तक नकारात्मक बाधा नहीं है। मुझे यह भी उम्मीद है कि वे जल्द ही इस तरह की सुविधा प्रदान कर सकते हैं। प्रतीक्षा करते समय, मैं इस तरह से एक प्रस्ताव प्रस्तुत करता हूं:

type KeysOfNonType<A extends object, B> = {
  [K in keyof A]-?: A[K] extends B ? never : K
}[keyof A];

// CHANGE: use `Pick` instead of `Omit` here.
type State<Model extends object> = Pick<Model, KeysOfNonType<Model, Action<any>>>;

type Action<Model extends object> = (data: State<Model>) => State<Model>;

interface MyModel<T> {
  value: T;
  doSomething: Action<MyModel<T>>;
}

function modelFactory<T>(value: T): MyModel<T> {
  return {
    value,
    doSomething: data => {
      data.value; // Now it does exist 😉
      data.doSomething; // Does not exist 👍
      return data;
    }
  } as MyModel<any>; // <-- Magic!
                     // since `T` has yet to be known
                     // it literally can be anything
}

आदर्श नहीं है, लेकिन एक अर्द्ध
वर्कआउट के

1

countऔर valueहमेशा कंपाइलर को दुखी करेगा। इसे ठीक करने के लिए आप कुछ इस तरह की कोशिश कर सकते हैं:

{
  value,
  count: 1,
  transform: (data: Partial<Thing<T>>) => {
   ...
  }
}

चूंकि Partialउपयोगिता प्रकार का उपयोग किया जा रहा है, आप ठीक होंगे मामले में transformविधि मौजूद नहीं है।

Stackblitz


1
"गणना और मूल्य हमेशा संकलक को नाखुश करेंगे" - मैं यहां क्यों कुछ अंतर्दृष्टि की सराहना करूंगा। xx
ctrlplusb

1

आम तौर पर मैंने दो बार पढ़ा और पूरी तरह से समझ नहीं पाया कि आप क्या हासिल करना चाहते हैं। मेरी समझ से आप transformउस प्रकार से छोड़ना चाहते हैं जो वास्तव में दिया गया है transform। यह मानते हुए कि यह सरल है, हमें ओमिट का उपयोग करने की आवश्यकता है :

interface Thing<T> {
  value: T; 
  count: number;
  transform: (data: Omit<Thing<T>, 'transform'>) => void; // here the argument type is Thing without transform
}

// 👇 the factory function accepting the generic
function makeThing<T>(value: T): Thing<T> {
  return {
    value,
    count: 1,
      transform: data => {
        data.count; // exist
        data.value; // exist
    },
  };
}

सुनिश्चित नहीं है कि यदि आप अतिरिक्त उपयोगिता प्रकारों में दी गई जटिलता के कारण ऐसा चाहते हैं, तो आप क्या चाहते हैं। आशा है ये मदद करेगा।


धन्यवाद, हाँ मैं चाहता हूँ। लेकिन यह एक उपयोगिता प्रकार है जिसे मैं तीसरे पक्ष के उपभोग के लिए निर्यात कर रहा हूं। मैं उनकी वस्तुओं के आकार / गुणों को नहीं जानता। मुझे सिर्फ इतना पता है कि मुझे सभी फंक्शंस संपत्तियों को अलग करने की जरूरत है और ट्रांसफ़ॉर्म फ़ंक्शनल डेटा तर्क के विरुद्ध परिणाम का उपयोग करना है।
ctrlplusb

मैंने अपने मुद्दे के विवरण को इस उम्मीद में अद्यतन किया है कि यह इसे और अधिक स्पष्ट करता है।
ctrlplusb

2
मुख्य मुद्दा यह है कि टी एक्शन टाइप भी हो सकता है क्योंकि इसे बाहर करने के लिए परिभाषित नहीं किया गया है। आशा है कि कुछ समाधान मिलेगा। लेकिन मैं उस जगह पर हूं जहां गिनती ठीक है लेकिन टी अभी भी बची हुई है क्योंकि यह एक्शन के साथ है
मैकिएज सिकोरा

यह बहुत अच्छा होगा अगर मैं व्यक्त कर सकता हूं कि टी टाइप एक्शन का नहीं है। एक विलोम का विलोम।
ctrlplusb

सापेक्ष चर्चा: stackoverflow.com/questions/39328700/…
ctrlplusb
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.