টাইপ ম্যাপিংয়ের সময় জেনেরিক বৈশিষ্ট্যগুলি প্রদান করুন


11

আমার কাছে একটি লাইব্রেরি রয়েছে যা নিম্নলিখিতগুলির মতো একটি ইউটিলিটি ধরণের রফতানি করে:

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

এই ইউটিলিটি ধরণ আপনাকে এমন কোনও ক্রিয়া ঘোষণা করতে দেয় যা "ক্রিয়া" হিসাবে সম্পাদন করবে perform এটি 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সম্পত্তি এখনও বিদ্যমান exists এটি তবে এটি নয় - 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?foutsize=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, সম্পত্তি নাম মুছে ফেলা হয়। চারপাশে কেবলমাত্র একটি উদ্বেগজনক সমস্যা রয়েছে value। যেহেতু valueপ্রকারের T, তুচ্ছভাবে সমাধানযোগ্য নয় Tএবং (v: T) => voidএটি অভিন্ন নয় (এবং বাস্তবে তারা নাও থাকতে পারে)।

আমরা এখনও নির্ধারণ করতে পারেন যে valueঅভিন্ন করার T: ধরনের বৈশিষ্ট্যের জন্য T, জন্য এই চেক ছেদ (v: T) => voidসঙ্গে never। এর সাথে neverযে কোনও ছেদটি সংক্ষিপ্ত আকারে সমাধানযোগ্য 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;
    }
  };
}

খেলার মাঠের লিঙ্ক

দ্রষ্টব্য: এখানে সীমাবদ্ধতাটি হ'ল এটি কেবলমাত্র এক ধরণের প্যারামিটারের সাথে কাজ করে (যদিও এটি সম্ভবত আরও কিছুতে মানিয়ে নেওয়া যেতে পারে)। এছাড়াও, এপিআই কোনও গ্রাহকের জন্য কিছুটা বিভ্রান্তিকর, তাই এটি সেরা সমাধান নাও হতে পারে। এমন কিছু সমস্যা থাকতে পারে যা আমি এখনও চিহ্নিত করতে পারি নি। যদি আপনার কোনও সন্ধান হয় তবে আমাকে জানান 😊


2
আমার মনে হয় গ্যান্ডাল্ফ হোয়াইট নিজেকে প্রকাশ করেছে। B টিবিএইচ আমি সংকলক সীমাবদ্ধতা হিসাবে এটি লিখতে প্রস্তুত ছিল। এটি চেষ্টা করার জন্য স্টোকড। ধন্যবাদ! 🙇
ctrlplusb

@ctrlplusb 😂 এলএল, এই মন্তব্যটি আমার দিনকে পরিণত করেছে 😊
তিতিয়ান সার্নিকোভা-ড্রাগোমির

আমি এই উত্তরে অনুগ্রহটি প্রয়োগ করতে চাইছিলাম তবে আমার ঘুমের বাচ্চার মস্তিষ্কের প্রচণ্ড ঘাটতি চলছে এবং ভুলভাবে ক্লিক করা হয়েছে। আমার ক্ষমা! এটি একটি চমত্কার অন্তর্দৃষ্টিপূর্ণ উত্তর। প্রকৃতির ক্ষেত্রে বেশ জটিল। It এর উত্তর দেওয়ার জন্য সময় দেওয়ার জন্য আপনাকে অনেক ধন্যবাদ।
ctrlplusb

@ctrlplusb :( ওহ ভাল .. কিছু হারান জয় কিছু :)
তিতিয়ান সের্নিকোভা-ড্রাগোমির

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
    },
  };
}

অতিরিক্ত ইউটিলিটি ধরণের ক্ষেত্রে যে জটিলতা দিয়েছেন তা আপনি নিশ্চিত হয়েছিলেন কিনা তা নিশ্চিত নয় sure আশা করি এটা সাহায্য করবে.


ধন্যবাদ, হ্যাঁ তবে এটি একটি ইউটিলিটি ধরণের যা আমি তৃতীয় পক্ষের ব্যবহারের জন্য রপ্তানি করছি। আমি তাদের বস্তুর আকৃতি / বৈশিষ্ট্যগুলি জানি না। আমি কেবল জানি আমার সমস্ত ফাংশন বৈশিষ্ট্যগুলি কেড়ে ফেলা এবং রূপান্তর ফানক ডেটা যুক্তির বিপরীতে ফলাফলটি ব্যবহার করা দরকার।
ctrlplusb

আমি আমার ইস্যুর বিবরণটি আরও স্পষ্ট করে দেওয়ার আশায় আপডেট করেছি।
ctrlplusb

2
মূল সমস্যাটি হ'ল টি হ'ল অ্যাকশন টাইপও হতে পারে কারণ এটি বাদ দেওয়ার জন্য এটি সংজ্ঞায়িত হয়নি। আশা করি এর কোন সমাধান পাওয়া যাবে। তবে আমি সেই জায়গায় আছি যেখানে গণনা ঠিক আছে তবে টি এখনও বাদ পড়েছে কারণ এটি অ্যাকশনের সাথে ছেদ করেছে
ম্যাকিয়েজ সিকোড়া

এটি দুর্দান্ত হবে যদি আমি প্রকাশ করতে পারি যে টি টাইপ অ্যাকশন নয়। বিস্তারের বিপরীতে বাছাই করুন।
ctrlplusb

আপেক্ষিক আলোচনা: stackoverflow.com/questions/39328700/...
ctrlplusb
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.