رشته - استخراج اطلاعات از نوع Typescript


من تازه وارد تایپاسکریپت هستم، پس احتمالاً از اصطلاحات درست استفاده نمیکنم... لطفاً شکیبا باشید

پیشزمینه

من تلاش کردهام که انواع اولیهای مانند string را با موفقیت متوسط گسترش دهم. حتی قادر هستم مقداری اطلاعات نوع سفارشی را روی رشته توسعهیافته خود ذخیره کنم، مانند این:

// yes, we can extends strings!
type ExtendStr<P extends string = string, MaxLen extends number = 0> = P & { parent: P, options: { max: MaxLen } }

// create a LongStr with max length of 256
type LongStr = ExtendStr<string, 256>

من قادر هستم اطلاعات اضافی که در اطلاعات نوع ذخیره کردهام را بازیابی کنم، مانند این:

// get the max length of a LongStr
type LongStrMax = LongStr['options']['max']
// yields 256... Hoorah! 

من حتی میتوانم LongStr را گسترش دهم و به درستی نوع گسترش/تنگشدن را دریافت کنم:

// make a ShortStr that extends LongStr but has max length 8
type ShortStr = ExtendStr<LongStr, 8>

// two example variables
let short: ShortStr = 'Hello' as ShortStr 
let long: LongStr = 'Omg this is a very long string!!' as LongStr 

// widening conversion is allowed as it should
long = short 

// narrowing conversion gives compiler error... sweet! 
short = long 

با این حال، به نظر میرسد این اطلاعات ذخیره شده را «مخفی» میکند...

حالا سؤال

من میخواهم مقدار ذخیرهشده 'max' را برای ShortStr بدست بیاورم. من آن را در اطلاعات نوع میبینم...

اما وقتی سعی میکنم آن را به دست بیاورم...

type ShortStrMax = ShortStr['options']['max']

..این هرگز تولید میکند.... راهی وجود دارد؟؟؟

زمین بازی

پاسخ شماره ۱

وقتی مینویسی

type ShortStr = ExtendStr<LongStr, 8>

و

long = short

به نظر میرسد شما میخواهید یک ShortStr زیرنوعی از LongStr باشد. یعنی هر رشتهای با ۸ یا کمتر کاراکتر همچنین یک رشته با ۲۵۶ یا کمتر کاراکتر است. این منطقی است. اما تعریف شما از ExtendStr<T, N> میگوید که یک ویژگی options.max از نوع N وجود خواهد داشت. بنابراین یک ShortStr نیاز دارد که ویژگی options.max داشته باشد که مقدار آن هم 8 و 256 باشد. هیچ مقداری از این نوع وجود ندارد، بنابراین نوع این ویژگی معادل نوع never است و چیزها شروع به رفتار عجیب میکنند.


از نظر مفهومی، منطقیتر است که تصور کنیم ویژگی length رشته چگونه است. مقدار ExtendStr<string, 256> باید دارای ویژگی length باشد که مقدار آن یک عدد صحیح غیر منفی کمتر یا مساوی 256 باشد. شما میتوانید این را بهصورت نوع اتحادی مانند 0 | 1 | 2 | ... | 254 | 255 | 256 نمایش دهید. و ExtendString<string, 8> باید دارای ویژگی length از نوع 0 | 1 | 2 | ... | 6 | 7 | 8 باشد. حالا کاملاً ممکن است که یک رشته ویژگی length از هر دو نوع داشته باشد، زیرا نوع دوم بهطور کامل زیردسته نوع اول است. بنابراین اگر بتوانیم به صورت برنامهنویسی نوع اتحادی صحیح اعداد را بر اساس N تولید کنیم، میتوانیم ExtendStr را بر اساس آن تعریف کنیم.

در اینجا یک روش برای انجام آن وجود دارد:

type LessThan<N extends number, A extends number[] = []> =
    N extends A['length'] ? A[number] : LessThan<N, [A['length'], ...A]>;

type LessThanOrEqual<N extends number> = N | LessThan<N>

نوع LessThan<N> یک نوع شرطی دنبالهای بازگشتی است که یک نوع عددی ثابت مانند 10 را به یک اجتماع از اعداد صحیح غیرمنفی کمتر از آن تبدیل میکند، با ساختن یک تاپل از این مقادیر و متوقف شدن زمانی که طول تاپل به N برسد. بنابراین 5 به [0, 1, 2, 3, 4] تبدیل میشود که به 0 | 1 | 2 | 3 | 4 تبدیل میگردد.

و LessThanOrEqual<N> فقط اجتماع N با LessThan<N> است، بنابراین LessThanOrEqual<5> برابر است با 0 | 1 | 2 | 3 | 4 | 5.

و حالا ExtendStr:

type ExtendStr<P extends string = string, MaxLen extends number = 0> =
    P &  { length: LessThanOrEqual<MaxLen> } 

توجه داشته باشید که به جای ایجاد ویژگیهای خیالی parent و options.max، من فقط از نوع رشتهای خود و ویژگی موجود length استفاده میکنم. اگر بخواهید میتوانید به روش خود ادامه دهید، اما من در این مثال چندان استفادهای برای آن نمیبینم. این بستگی به شما دارد.

یک نکته دیگر... شما میخواهید بتوانید حداکثر طول را از نوع استخراج کنید. یعنی، با توجه به ExtendStr<string, N>، میخواهید N را بازیابی کنید. در حال حاضر اگر ویژگی length را بررسی کنید، یک اتحاد بزرگ خواهید داشت، و شما فقط میخواهید بیشترین عضو آن اتحاد را بگیرید. خب، میتوانید این کار را به این صورت انجام دهید:

type Max<N extends number> = Exclude<N, LessThan<N>>

این کار میکند زیرا LessThan<3 | 5> خواهد شد 0 | 1 | 2 | 3 | 4 و Exclude<3 | 5, 0 | 1 | 2 | 3 | 4> برابر است با 5.


پس، بیا امتحانش کنیم:

type LongStr = ExtendStr<string, 256>

type LongStrMax = Max<LongStr['length']>
/* type LongStrMax = 256 */

type ShortStr = ExtendStr<LongStr, 8>

let short: ShortStr = 'Hello' as ShortStr
let long: LongStr = 'Omg this is a very long string!!' as LongStr

long = short // okay
short = long // error

type ShortStrMax = Max<ShortStr['length']>;
//type ShortStrMax = 8

خوب به نظر میرسد!


راهحل بالا برای من به اندازه کافی خوب عمل میکند، اما نوعهای شرطی بازگشتی میتوانند پیچیده باشند و گاهی باعث مشکلات عملکرد کامپایلر شوند. اگر چنین چیزی رخ دهد، ممکن است بخواهید به نسخه فعلی خود بازگردید و سپس مسئله را به روش دیگری حل کنید.

لینک زمین بازی به کد

پاسخ شماره ۲

گسترش نوع رشته برای داشتن طول حداکثر/حداقل کاملاً ساده نیست. شما میتوانید این سؤال را درباره نحوه انجام آن ببینید: اعلام نوع رشته با طول حداقل/حداکثر در تایپاسکریپت

مشکلی که با نوعهای شما به وجود آمده از اینجا شروع میشود

type ShortStr = ExtendStr<LongStr, 8>

شما میتوانید نوع را بررسی کنید و ببینید ShortStr به چه چیزی گسترش مییابد

type ShortStr = string & {
    parent: string;
    options: {
        max: 256;
    };
} & {
    parent: LongStr;
    options: {
        max: 8;
    };
}

میتوانید ببینید که ویژگیهای options با یکدیگر سازگار نیستند، زیرا ویژگی max توسط کامپایلر به گونهای در نظر گرفته میشود که هرگز نمیتواند شرط max === 8 و max === 256 را برآورده کند.

بنابراین، options به never کاهش مییابد، اما شما هنوز هم میتوانید به parent دسترسی داشته باشید، زیرا نوعها سازگار هستند.

type ShortStrParent = ShortStr['parent']
// expands to
type ShortStrParent = string & {
    parent: string;
    options: {
        max: 256;
    };
}