جنریکهای تایپاسکریپت، نوع مشخصه شی را بدون فراخوانی تابع استنتاج کنید


سؤالی نسبتاً ساده اینجا. من دارم سعی میکنم یک نوع عمومی مانند این ایجاد کنم:

export type RenderObject<TMeta = any> = {
  meta?: TMeta;
  render: (meta: TMeta) => JSX.Element;
}

و من میخواهم نوع TMeta بهطور خودکار استنباط شود بدون اینکه نیازی باشد آن را به پارامتر جنریک RenderObject هنگام تبدیل یک شیء به این نوع بدهیم، یا اینکه یک تابع کمکی یا سازنده کلاس اضافی برای استنباط نوع TMeta داشته باشیم. پس مثل این:

const thing: RenderObject = {
  meta: { foo: 'bar' },
  render: (meta) => <i>{meta.foo}</i>,
}

با این حال، این فقط TMeta را به عنوان any در تابع render در نظر میگیرد، به جای اینکه نوع { foo: string } را از انتساب استنتاج کند.

آیا کاری که سعی میکنم انجام دهم ممکن است؟ من باید از استفاده از تابع یا کلاس اضافی اجتناب کنم زیرا این باید بسیار پرکارآمد باقی بماند و از اضافه کردن هرگونه فراخوانی اضافی به پشته جلوگیری شود. عذرخواهی میکنم اگر این سوال قبلاً در جای دیگری پاسخ داده شده باشد، به نظر نمیرسد که بتوانم همین سوال دقیق را در جایی پیدا کنم، اما ممکن است جستجوی من با بسیاری از سوالات مشابه اما متفاوت تداخل داشته باشد.

پیشاپیش سپاسگزارم!

پاسخ شماره ۱

نه، همانطور که گفته شد، این امکانپذیر نیست.


مسائل موجود در microsoft/TypeScript#32794 و microsoft/TypeScript#30120 درخواست یک روش برای حاشیهنویسی یک متغیر اعلامشده با نوع عمومی را دارند، که در آن کامپایلر باید پارامتر نوع عمومی را استنتاج کند به جای اینکه نیاز به مشخصکردن دستی باشد. مسئله دوم به عنوان یک تکراری از microsoft/TypeScript#26242 بسته شد، که پیشنهادی برای پشتیبانی از «استنتاج جزئی پارامتر نوع» بود، که به مسئله عمومیتر اجازه میدهد توسعهدهندگان از کامپایلر بخواهند پارامترهای نوع را در موقعیتهایی که در حال حاضر تنها راهحل مشخصکردن دستی است، استنتاج کند. همانطور که متوجه شدهاید، پیشفرضهای پارامترهای عمومی این موضوع را حل نمیکنند: در حالی که پیشفرضها (مانند <T=any>) به شما اجازه میدهند پارامترهای نوع را نادیده بگیرید، کامپایلر هنگام انجام این کار هیچ چیزی را استنتاج نمیکند؛ صرفاً پیشفرض را جایگزین پارامتر حذفشده میکند.

اگر microsoft/TypeScript#26242 یا microsoft/TypeScript#32794 هرگاه پیادهسازی شود، ممکن است بتوان چیزی شبیه به این نوشت:

// ⚠ NOT VALID TYPESCRIPT, DO NOT USE ⚠
const thing: RenderObject<infer> = {
    meta: { foo: 'bar' },
    render: (meta) => <i>{meta.foo}</i>,
};

اما فعلاً نمیتوانید. مسئله GitHub باز است، اما مدتی است که بدون هیچ تغییر آشکار اخیر وجود داشته است. نمیدانم این موضوع به یک صورت یا صورت دیگر اهمیت خواهد داشت یا نه، اما اگر احساس میکنید که این باید پشتیبانی شود، ممکن است بخواهید به آن مسئله مراجعه کنید و به آن 👍 بدهید، یا مورد استفاده خود را توضیح دهید اگر فکر میکنید نسبت به موارد موجود قبلی به طور خاص جالب است.

بدون چنین حمایتی، هر کاری که برای به دست آوردن این نوع رفتار انجام دهید، صرفاً یک راهحل موقت خواهد بود.


راهحل پیشنهادی که معمولاً از آن حمایت میکنم استفاده از یک تابع کمکی است، زیرا فراخوانی یک تابع عمومی یکی از جاهایی است که کامپایلر واقعاً پارامترهای نوع را برای شما استنتاج میکند:

const asRenderObject = <T,>(x: RenderObject<T>) => x;

const thing = asRenderObject({
    meta: { foo: 'bar' },
    render: (meta) => <i>{meta.foo}</i>,
})
/* const thing: RenderObject<{
    foo: string;
}> */

این نسبتاً زیباست، یا حداقل میتوان بحث کرد که به اندازه RenderObject<infer> زیباست. شما نوع مورد نظر خود را بدون مشخص کردن اضافی به دست میآورید. اشکال کار این است که در زمان اجرا، یک فراخوان تابع اضافی دارید. تنها خود شما میتوانید واقعاً بدانید که اضافه کردن یک فراخوان آنجا آیا تأثیر قابل توجهی بر عملکرد زمان اجرای کد شما خواهد داشت یا نه. من کمی نسبت به این مسئله شک دارم، زیرا این امر نشان میدهد که شما در مدت زمان بسیار کوتاه، هزاران نمونه از این اشیاء را ایجاد میکنید... و اگر چنین است، احتمالاً میخواهید به بازسازی کد نگاه کنید تا قبل از نگرانی در مورد استنباط نوع TypeScript، عملکرد زمان اجرا را بهبود بخشید. از اینجا به بعد، من فرض میکنم که یک فراخوان تابع اضافی غیرقابل قبول است.


در این نقطه، تنها راهحل دیگری که میتوانم به آن فکر کنم این است که نوعها را به صورت دستی مشخص کنید؛ یا با توضیح دادن اعلان متغیر:

interface Thang { foo: string }
const thing: RenderObject<Thang> = {
    meta: { foo: 'bar' },
    render: (meta) => <i>{meta.foo}</i>,
};

یا با اجازه دادن به کامپایلر برای استنتاج نوع متغیر، اما حاشیهنویسی پارامتر callback render (چون جایی نیست که کامپایلر بتواند آن را به صورت متنی نوعبندی کند):

interface Thang { foo: string }
const thing3 = {
    meta: { foo: 'bar' },
    render: (meta: Thang) => <i>{meta.foo}</i>,
};

اما اکنون هیچ استنتاج پارامتر نوعی انجام نمیشود... این «راهحل موقت» برای اجتناب کامل از مشکل است.


پس، پس این هم هست. تا زمانی که microsoft/TypeScript#26242 یا چیزی شبیه به آن پیادهسازی نشود، هیچ راهی برای انجام کاری که شما میخواهید وجود ندارد. متأسفم!

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