typescript - چگونه میتوانم یک keyof را برای یک ویژگی از نوع مشخص الزامی کنم؟
من در تلاش هستم که یک تابع عمومی بنویسم که بتواند یک ویژگی بولی را در هر شیء بر اساس نام کلید تغییر دهد. من یادداشتهای انتشار TypeScript-2.8 را خواندم و فکر کردم که نوعهای شرطی قرار است این نوع مشکل را حل کنند. با این حال، نمیتوانم بفهمم چگونه تابعم را بنویسم.
تابع من شیئی را که باید تغییر کند و نام کلیدی که باید تغییر داده شود را میپذیرد. برای اطمینان از اینکه تنها کلیدهای مربوط به ویژگیهای بولی ارسال شوند، از عبارت نوع شرطی T[K] extends boolean ? K : never استفاده کردم. همانطور که فهمیدم، این باید باعث ایجاد خطا شود اگر سعی کنم کلیدی برای ویژگی غیر بولی ارسال کنم زیرا T[K] با extends boolean سازگار نخواهد بود. اما اگر سعی کنم کلیدی برای یک بولی ارسال کنم، باید آن K را بپذیرد.
با این حال، به نظر میرسد که حتی با این شرط، تایپاسکریپت درون تابع متوجه نمیشود که `T[K] extends boolean` باید درست باشد. بنابراین نمیتوانم مقداری که از شیء خواندهام را دوباره به شیء اختصاص دهم. این منجر به اولین خطای نشان داده شده در زیر میشود. خطای دوم این است که به نظر میرسد استنتاج نوع برای تابع من کار نمیکند. در تماسهای زیر، تنها دومین مورد تاکنون از بررسیهای تایپاسکریپت عبور میکند.
function invertProperty<T, K extends keyof T> (o:T, propertyName:(T[K] extends boolean ? K : never)) {
o[propertyName] = !o[propertyName]; // Type 'false' is not assignable to type 'T[T[K] extends boolean ? K : never]'. [2322]
}
const myObject:IObject = {
a: 1,
b: true,
c: 'hi',
};
invertProperty(myObject, 'b'); // Argument of type '"b"' is not assignable to parameter of type 'never'. [2345]
invertProperty<IObject, 'b'>(myObject, 'b'); // Works, but requires me to type too much.
invertProperty(myObject, 'a'); // Argument of type '"a"' is not assignable to parameter of type 'never'. [2345]
invertProperty<IObject, 'a'>(myObject, 'a'); // Argument of type '"a"' is not assignable to parameter of type 'never'. [2345]
interface IObject {
a:number,
b:boolean,
c:string,
}
من فکر میکنم که اگر در محدودیت نوع من از K extends keyof T بتوانم به نحوی همچنین بیان کنم and T[K] extends boolean، این کار درست انجام میشد. به نظر من این یک مشکل است که من سعی میکنم از never در نوع آرگومان استفاده کنم به جای اینکه بتوانم پارامتر نوع را محدود کنم. اما نمیتوانم هیچ راهی برای بیان آن پیدا کنم.
ایدهای دارید که چگونه میتوان این کار را با ایمنی کامل نوع انجام داد؟
پاسخ شماره ۱
اول، شما میتوانید تمام کلیدهای ویژگیهای بولی را با استفاده از این ساختار استخراج کنید (که کلیدهای مقادیر غیر بولی را به never تبدیل میکند و اتحادیهای از تمام کلیدها/never میگیرد، با استفاده از این واقعیت که T | never برابر T است):
type BooleanKeys<T> = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T];
سپس، برای خوشحال کردن TypeScript در مورد اختصاص دادن مقادیر بولی به خواص، شما نوع میانیای معرفی میکنید که اعلام شده فقط شامل خواص بولی باشد (متأسفانه TypeScript نمیتواند این بخش را خودش تشخیص دهد)
type OnlyBoolean<T> = { [k in BooleanKeys<T>]: boolean };
و شما اعلام میکنید که پارامتر نوع عمومی invertProperty با OnlyBoolean سازگار است (که واقعاً هست، ممکن است شامل خصوصیات غیر بولی اضافی باشد اما اشکالی ندارد)
توجه ممکن است بسته به نسخه کامپایلر به نسخههای مختلفی از کد نیاز داشته باشید، کد اصلی در این پاسخ با TypeScript 3.2 دیگر کار نمیکند:
// for TypeScript 3.1 or earlier
function invertProperty<T extends OnlyBoolean<T>>(o: T, propertyName: BooleanKeys<T>) {
o[propertyName] = !o[propertyName];
}
// for TypeScript 3.2 or later
function invertProperty<T>(o: OnlyBoolean<T>, propertyName: keyof OnlyBoolean<T>) {
o[propertyName] = !o[propertyName];
}
interface IObject {
a: number;
b: boolean;
c: string;
}
const myObject:IObject = {
a: 1,
b: true,
c: 'hi',
};
invertProperty(myObject, 'b'); // ok
invertProperty(myObject, 'a'); // error
