کلیدهای متقاطع در تایپاسکریپت
من میخواهم یک تابع کمکی نوع-ایمن برای مقایسه ویژگیهای دو شیء داده شده در تایپاسکریپت ۴.۰ بنویسم. تلاش اولیه من به شکل زیر است:
export function propsAreEqual<O extends object, T extends O, S extends O>(first: T, second: S, props: (keyof O)[]) {
return props.every(prop => first[prop] === second[prop])
}
با این حال، با این روش یک خطای کامپایل TS2367 دریافت میکنم که بیان میکند: این شرط همیشه 'false' برمیگرداند چرا که نوعهای 'T[keyof O]' و 'S[keyof O]' هیچ همپوشانیای ندارند.
این خطا برای من ضد شهودی به نظر میرسد. اگر هر دو T و S از شیئی از نوع O توسعه یافته باشند، آیا نباید هر دو بهطور الزامی شامل تمام کلیدهای نوع O باشند؟ خیلی خوشحال میشوم اگر کسی بتواند روشن کند که من اینجا چه چیزی را از دست دادهام و آیا رویکرد منطقیتری برای آنچه که میخواهم به آن دست یابم وجود دارد یا خیر؟
پاسخ شماره ۱
خطایی که دریافت میکنید نادرست نیست، توسعه دادن O به سادگی به معنای توسعه دادن object است، و این به نوبه خود بدین معناست که میتوانید داشته باشید T={ a: number } و O={ a: string } (به عنوان مثال). کلید یکی است، اما هیچ همپوشانی بین T[keyof O] و O[keyof O] وجود ندارد. ممکن است همپوشانی وجود داشته باشد، اما هنگام کار با TS، انتظار دارد که بتواند صحت تابع را برای هر نمونهسازی معتبر از پارامتر نوع ثابت کند، و همانطور که در اینجا میبینیم، میتواند مواردی وجود داشته باشد که این تابع معتبر نیست.
شما میتوانید چندین رویکرد برای تعریف این تابع داشته باشید؛ میتوانید از یک پارامتر نوع برای شیء اول استفاده کنید و شیء دوم را به عنوان یک Pick یا خصوصیات ارسالی تعریف کنید.
function propsAreEqual<T extends object, K extends keyof T>(first: T, second: Pick<T, K>, props: K[]) {
return props.every(prop => first[prop] === second[prop])
}
لینک زمین بازی
این نسخه اطمینان میدهد که T[K] برای هر دو پارامتر نوع یکسانی داشته باشد. عیب این نسخه این است که اگر سعی کنید یک شیء literal را به عنوان پارامتر دوم منتقل کنید، بررسیهای ویژگی اضافی فعال خواهند شد. همچنین عیب دیگر این است که Intellisense همه ویژگیهای T را در پارامتر سوم پیشنهاد میدهد و اگر ویژگی مشترک نباشد، در پارامتر دوم با خطا مواجه خواهید شد.
شخصاً من ترجیح میدهم گزینهای که بررسی کامل نوع در تابع را فدا میکند به ازای رفع دو مشکل فوق انتخاب کنم. این نسخه خواهد بود:
function propsAreEqual<T extends object, S extends Pick<T, K>, K extends keyof T & keyof S>(first: T, second: S, props: K[]) {
return props.every(prop => first[prop] === (second as Pick<T, K>)[prop])
}
لینک زمین بازی
پاسخ شماره ۲
وقتی شما یک پارامتر نوع را استنباط میکنید، تایپاسکریپت میخواهد آن را فقط از یکی از آرگومانها استنباط کند؛ بنابراین امضای propsAreEqual<T>(a: T, b: T, props: (keyof T)[]) کار نمیکند زیرا تایپاسکریپت T را از نوع آرگومان اول استنباط میکند و سپس وقتی میبیند که آرگومان دوم قابل انتساب به آن نوع نیست، سعی نمیکند آن را شل کند.
راه حل: از یک نوع عمومی استفاده کنید که از props استنتاج شود به جای اینکه از a یا b استنتاج شود.
function propsAreEqual<K extends PropertyKey>(a: Record<K, unknown>, b: Record<K, unknown>, props: K[]) {
return props.every(prop => a[prop] === b[prop]);
}
لینک زمین بازی
