چطور میتوانم به تایپاسکریپت بفهمانم نوع مقدار یک شیء همگن چیست بدون اینکه کلیدها از اعلان ایستا از دست بروند؟


چطور میتوانم Typescript را وادار کنم تا نوع مقدار یک شیء را بفهمد بدون اینکه دسترسی به اطلاعات کلید از اعلان ثابت آن را از دست بدهم؟

در کد زیر من میخواهم از مزیت

  • TS با دانستن نوع مقدار، در این مورد یک تابع، پس نیازی نیست که هر بار امضای آن را مشخص کنم.
  • TypeScript با دانستن کلیدهای شیء از اعلامیهی ایستا، بنابراین میتوانم به صورت ایمن نوع آنها را از جاهای دیگر ارجاع دهم

این شیء همگن است و عملکردهای زیادی خواهد داشت، مشخص کردن امضا هر بار به نظر یک دردسر میآید.

مثال ۱ - TS کلیدهای شیء را نمیداند

    interface Foo {
        [key: string]: (n: number) => any
    }
    
    const obj1: Foo = {
        // TS already knows that `n` is a number 🙂
        bar(n) {return n}
    }
    
    type KeyOfObj1 = keyof typeof obj1
    const kob1: KeyOfObj1 = "xyz" // This does not fail static type checking 😢
    

مثال ۲ - تایپاسکریپت نمیداند امضا (نوع ورودی و خروجی) توابعی که مقادیر شیء هستند چیست

    const obj2 = {
        // Notice here I have to specify the type of `n` 😢
        bar(n: number) {return n}
    }
    
    type KeyOfObj2 = keyof typeof obj2
    const kob2: KeyOfObj2 = "xyz" // This fails static type checking 🙂

چطور میتوانم از هر دو دنیا بهترین بهره را ببرم؟

پینوشت: میدانم که میتوانم کلیدها را به یک enum ببرم، اما این کار کمی زشت و تکراری به نظر میرسد، هرچند شاید واقعاً تنها راه باشد 😕

پاسخ شماره ۱

این یک وضعیت نسبتاً رایج است: شما میخواهید یک شیء بنویسید، اعضای آن را به صورت متنی و طبق یک نوع ضعیفتر (Foo در این حالت) نوعدهی کنید، اما همچنان نوع قویتر را برای هدف دیگری حفظ کنید (در این حالت، نمیخواهید «فراموش کنید» کلیدها چه هستند).

کار معمول این است که یک تابع هویتی عمومی بنویسید، مانند زیر، که بررسی میکند نوع آرگومان T یک زیرنوع از Foo باشد، در حالی که نوع سختگیرانهتر T را بازمیگرداند:

function foo<T extends Foo>(obj: T): T {
    return obj;
}

سپس زمانی که شیء را ایجاد میکنید، کامپایلر بهصورت زمینهای آن را بهعنوان نوعی زیررشته از Foo نوعبندی میکند، اما همچنان آن زیررشته (با نامهای کلیدی خاص خود) را برای انتساب به obj حفظ میکند:

const obj = foo({
    foo(n) { return n + 1; },
    bar(n) { return n * 2; },
});

// 'foo' | 'bar'
type ObjKeys = keyof typeof obj

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

متأسفانه، حداقل در حال حاضر، برای اینکه این کار انجام شود، شما نیاز به تابع هویت عمومی دارید. در حال حاضر پیشنهادی برای افزودن نحوی جدید به زبان وجود دارد که این نوع مشکل را بتوان بدون اضافه کردن یک تابع اضافی "بیفایده" حل کرد.