تایپاسکریپت - استنتاج نوع برای نوع عمومی با استفاده از دستور switch در تایپاسکریپت
من در تلاش هستم تا یک نوع عمومی ایجاد کنم که تنظیمات مختلفی داشته باشد. این تنظیمات تنها تفاوت بین پیادهسازیها هستند.
قطعه کد زیر باید آنچه را که من تلاش میکنم به دست آورم، نشان دهد:
interface Generic<Config extends ConfigurationType> {
configuration_type: Config
configuration: TypeToConfigMap[Config]
}
class ConfigA { z = 1 } // empty classes make bad examples
class ConfigB { y = 2 }
class ConfigC { x = 3 }
const enum ConfigurationType {
a = 'a',
b = 'b',
c = 'c',
}
interface TypeToConfigMap {
[ConfigurationType.a]: ConfigA,
[ConfigurationType.b]: ConfigB,
[ConfigurationType.c]: ConfigC,
}
قسمت اصلی مورد علاقه، تابع مثال زیر خواهد بود
function test(generic: Generic<ConfigurationType>[]) {
generic.forEach((generic) => {
switch (generic.configuration_type) {
case (ConfigurationType.a):
generic.configuration.z;
break;
case (ConfigurationType.b):
generic.configuration.y;
break;
case (ConfigurationType.c):
generic.configuration.x;
break;
}
})
}
با این حال، نوع جنریک و یا تنظیمات داخل موارد سوئیچ قابل استنتاج نیستند. من میخواهم آن را به این روش پیادهسازی کنم چرا که کد را تمیز نگه میدارد، بر خلاف بسیاری از روشهای دیگر. آیا راهی وجود دارد که کد بالا را بهگونهای پیادهسازی کرد که نوع جنریک و پیکربندی اعضا قابل استنتاج باشد؟
پاسخ شماره ۱
مشکل این است که تعریف شما از Generic<Config> بهطور خودکار بر روی اجتماعها در Config توزیع نمیشود. بنابراین در حالی که Generic<ConfigurationType.a> همان چیزی است که انتظار دارید، Generic<ConfigurationType> اینگونه نیست:
type G = Generic<ConfigurationType>;
/* type G = {
configuration_type: ConfigurationType;
configuration: ConfigA | ConfigB | ConfigC;
} */
میتوانید ببینید که هیچ همبستگیای بین ویژگی configuration_type و ویژگی configuration وجود ندارد. اولی میتواند ConfigurationType.a باشد در حالی که دومی ConfigB است.
آنچه شما میخواهید این است که Generic<X | Y | Z> روی آن اتحاد توزیع شود و معادل Generic<X> | Generic<Y> | Generic<Z> باشد. ابتدا، بیایید Generic شما را نامگذاری کنیم:
interface _Generic<Config extends ConfigurationType> {
configuration_type: Config
configuration: TypeToConfigMap[Config]
}
حالا میخواهیم Generic<Config> را به عنوان نسخهای توزیعی از _Generic<Config> تعریف کنیم. چند روش برای انجام این کار وجود دارد. یکی این است که Generic را یک نوع شرطی توزیعی کنیم:
type Generic<Config extends ConfigurationType> =
Config extends unknown ? _Generic<Config> : never
این ممکن است به نظر بیاثر بیاید، زیرا هر نوعی در تایپاسکریپت از نوع بالای unknown گسترش مییابد. اما وقتی یک نوع شرطی یک پارامتر نوع عمومی مانند Config را بررسی میکند، نوع حاصل بر روی اجتماعها در آن پارامتر نوع توزیع میشود. بنابراین از یک لحاظ، این واقعاً یک بیاثر است، به جز اینکه بر روی اجتماعها توزیع میشود. حال اگر آن را بررسی کنیم، نوع مورد انتظار را خواهیم دید:
type G = Generic<ConfigurationType>;
/* type G =
_Generic<ConfigurationType.a> |
_Generic<ConfigurationType.b> |
_Generic<ConfigurationType.c> */
روش دیگری برای نوشتن این است که Generic<Config> را به عنوان یک نوع شیء توزیعی که در microsoft/TypeScript#47109 ابداع شده، در نظر بگیریم:
type Generic<Config extends ConfigurationType> =
{ [C in Config]: _Generic<C> }[Config]
این یک نوع نگاشتشده بر روی عناصر Config است که بلافاصله با Config اندیسگذاری میکنیم. این منجر به همان اتحاد میشود:
type G = Generic<ConfigurationType>;
/* type G =
_Generic<ConfigurationType.a> |
_Generic<ConfigurationType.b> |
_Generic<ConfigurationType.c> */
هر دو روش به هدف شما خدمت خواهند کرد:
function test(generic: Generic<ConfigurationType>[]) {
generic.forEach((generic) => {
switch (generic.configuration_type) {
case (ConfigurationType.a):
generic.configuration.z; // okay
break;
case (ConfigurationType.b):
generic.configuration.y; // okay
break;
case (ConfigurationType.c):
generic.configuration.x; // okay
break;
}
})
}
من معمولاً ترجیح میدهم از نوعهای شیء توزیعی استفاده کنم هر جا ممکن باشد (شما تنها زمانی میتوانید از آنها استفاده کنید که اتحادیههایی که آنها را توزیع میکنید مانند کلید باشند؛ یعنی اگر به string | number | symbol قابل اختصاص باشند)، زیرا تایپاسکریپت آنها را برای جنریکها بهتر از نوعهای شرطی جنریک «میفهمد». اما در مثال شما، Config را جنریک نگه نداشتهاید (تابع شما تنها با نوع خاص Generic<ConfigurationType> کار میکند) بنابراین آنها در این مثال دقیقاً یکسان رفتار خواهند کرد.
لینک زمین بازی به کد
