مقالات

نحوه نوشتن تابع هویت محدود (CIF) در TypeScript


ج
نحوه نوشتن کامپوننت React در TypeScript
من یک نمونه React کامپوننت نوشتم. این جایی است که ما کار را متوقف کردیم:

const operations = {
	'+': (left: number, right: number): number => left + right,
	'-': (left: number, right: number): number => left - right,
	'*': (left: number, right: number): number => left * right,
	'/': (left: number, right: number): number => left / right,
}

type CalculatorProps = {
	left: number
	operator: keyof typeof operations
	right: number
}

function Calculator({ left, operator, right }: CalculatorProps) {
	const result = operations[operator](left, right)
	return (
		div>
			code>
				{left} {operator} {right} = output>{result}output>
			code>
		div>
	)
}

const examples = (
	
		Calculator left={1} operator="+" right={2} />
		Calculator left={1} operator="-" right={2} />
		Calculator left={1} operator="*" right={2} />
		Calculator left={1} operator="/" right={2} />
	>
)

من راضی نیستم operations هر چند عملکرد من می دانم که همه
تابع در آن شی دقیقاً همان نوع را خواهد داشت (بر حسب ضرورت به دلیل
به مورد استفاده):

type OperationFn = (left: number, right: number) => number

را operations شی در واقع فقط یک رکورد از رشته هایی از عملیات است که به a نگاشت شده است
تابعی که با دو عدد کار می کند. بنابراین اگر یک نوع حاشیه نویسی به خود اضافه کنیم
operations متغیر، پس نیازی نیست که هر تابع را جداگانه معرفی کنیم.
بیایید این را امتحان کنیم:

type OperationFn = (left: number, right: number) => number
const operations: Recordstring, OperationFn> = {
	'+': (left, right) => left + right,
	'-': (left, right) => left - right,
	'*': (left, right) => left * right,
	'/': (left, right) => left / right,
}

type CalculatorProps = {
	left: number
	operator: keyof typeof operations
	right: number
}

خوب است، بنابراین لازم نیست هر تابع را به صورت جداگانه معرفی کنیم، اما اوه نه… اکنون
در typeof operations است Record و در keyof از آن
خواهد بود string که به معنی ماست CalculatorProps['operator'] نوع اراده
باشد string. اوه 😩

در اینجا کاری است که ما می توانیم برای رفع این مشکل انجام دهیم:

type OperationFn = (left: number, right: number) => number
type Operator = '+' | '-' | '/' | '*'
const operations: RecordOperator, OperationFn> = {
	'+': (left, right) => left + right,
	'-': (left, right) => left - right,
	'*': (left, right) => left * right,
	'/': (left, right) => left / right,
}

type CalculatorProps = {
	left: number
	operator: keyof typeof operations
	right: number
}

اما اکنون به اضافه شدن برگردیم ** در دو جا اگر تصمیم بگیریم اضافه کنیم
اپراتور درجه بندی اما در این مورد، TypeScript به ما یک پاسخ می دهد
خطای کامپایلر اگر آن را به یکی اضافه کنیم و دیگری را اضافه نکنیم، پس یک پله بالاتر است.

این همان جایی است که وقتی اولین بار این مؤلفه را نوشتم آن را ترک کردم، اما از آن زمان
@AlekseyL13 پیشنهاد کرد
که من یک تابع هویتی درست تایپ شده را امتحان می کنم.

اول، بیایید در نظر داشته باشیم که ما 2 هدف داریم:

  1. مطمئن شوید که نوع هر ویژگی یکسان است (در این مثال ساده،
    این فقط یک عدد است، اما در مثال واقعی ما یک نوع تابع است)
  2. مطمئن شوید که keyof typeof زیرا شی ما منجر به اتحاد نهایی می شود
    کلیدها

معرفی TypeScript نسخه 4.9.0 satisfies که … اوه … ما را راضی می کند
از موارد در اینجا استفاده کنید لطفا با خیال راحت به آن بپرید
اگر از TypeScript نسخه 4.9.0 یا استفاده می کنید پایان دهید
بزرگتر

با TypeScript، داشتن هر دو یک چالش است. به طور پیش فرض دریافت می کنیم
گل دوم مشکل این است که وقتی سعی می کنید به هدف اول برسید
نوع حاشیه نویسی به عنوان const operations: Record = ...،
شما در نهایت گسترش پیدا می کنید key بنابراین keyof typeof منجر به string. اوه چطور
آزار دهنده

اینجاست که ویژگی هویت محدود وارد می شود.
“محدود” وضعیتی را توصیف می کند که در آن شما تابعی دارید که a را می پذیرد
یک نسخه باریکتر از یک ورودی نسبت به گذشته. در اینجا یک مثال ساده است:

type NamedObject = { name: string }
function getUserNameUser extends NamedObject>(user: User) {
	return user.name
}

const obj = { name: 'Hannah', age: 3 }
getUserName(obj)

بنابراین، شیئی که به آن منتقل می شود getUserName باید با همه انواع مطابقت داشته باشد
NamedObject. را getUserName ورودی را محدود می کند تا حداقل با آن مطابقت داشته باشد
نوع

و یک “تابع هویت” تابعی است که مقداری را می گیرد و آن را برمی گرداند
ارزش من گاهی اوقات از این نوع توابع به عنوان مقدار پیش فرض استفاده می کنم
تماس های برگشتی:

const identity = Type extends unknown>(item: Type) => item

type ModifyConfigFn = (config: ConfigType) => ConfigType
function buildProject(modifyConfig: ModifyConfigFn = identity) {
	const config: ConfigType = {
		/* some config */
	}
	const modifiedConfig = modifyConfig(config)
	// more stuff...
}

بنابراین با این تعاریف خارج از مسیر، یک “تابع هویت محدود” a
تابعی که آنچه داده شده را برمی گرداند و همچنین به TypeScript کمک می کند تا آن را محدود کند
نوع این دقیقاً همان کاری است که ما می خواهیم انجام دهیم.

ما می توانیم آن را a CIF (تلفظ می شود “see eye eff”). مطمئنا، بیایید با آن ادامه دهیم.

بگذارید ابتدا یک مثال ساده را به شما نشان دهم، سپس توضیح می دهم که چه اتفاقی در حال رخ دادن است
ما می توانیم آن را به طور مفیدتری در مثال پیچیده تر خود اعمال کنیم:

type Value = number
const createNumbers = ObjectType extends Recordstring, Value>>(
	obj: ObjectType,
) => obj

const numbers = createNumbers({ one: 1, two: 2, three: 3, four: 4 })

// @ts-expect-error we don't have 'five' yet
numbers['five']

بنابراین createNumbers تابع هویت محدود است. برمی گردد
obj این یک امر داده شده است، امیدوارم واضح باشد. اما چگونه سهم ما را تحمیل می کند و
محدود کردن نوع؟

بذار اینطوری توضیح بدم اگر شروع کنیم با:

const numbers = { one: 1, two: 2, three: 3, four: 4 }
// typeof numbers:
// {
//   one: number;
//   two: number;
//   three: number;
//   four: number;
// }

اما در آینده، ممکن است شخصی به این کد برسد و آن را به شکل زیر تغییر دهد:

const numbers = { one: 1, two: 2, three: 3, four: 4, five: '5' }
// typeof numbers:
// {
//   one: number;
//   two: number;
//   three: number;
//   four: number;
//   five: string; // 😱
// }

آه! نه، ما نمی توانیم آن را داشته باشیم! (و مهمتر از آن، در ما Calculator
برای مثال، هدف در اینجا معرفی خودکار توابع است).

بنابراین بیایید انواع مقادیر خود را با یک حاشیه نویسی نوع اعمال کنیم:

// @ts-expect-error HA! We gotcha! No strings in this object!
const numbers: Recordstring, number> = {
	one: 1,
	two: 2,
	three: 3,
	four: 4,
	five: '5',
}

اما اکنون با وارد کردن صریح مقادیر خود، به TypeScript گفته ایم که ما key
می تواند یک رشته باشد متأسفانه، هیچ راهی برای گفتن به TypeScript وجود ندارد، “این چیز
دارای کلیدهایی است که دارد، اما مقادیر از این نوع خاص هستند.” IMO، این یک است
تابع TypeScript وجود ندارد. مال ما createNumbers تابع هویت محدود
(اوه… “CIF”) یک راه حل است.

بنابراین این راه حل چیست:

توابع هویت محدود به ما این امکان را می دهند نه به صراحت مال ما را حاشیه نویسی کنید
متغیر در حالی که هنوز اجرای ارزش ها

بنابراین ما شی را ایجاد می کنیم، بهترین نوع را که TypeScript می تواند به ما ارائه دهد دریافت می کنیم (که
شامل کلیدهای باریک و مقادیر گسترده است) سپس آن را به تابعی منتقل می کنیم که
کلیدهای گسترده و مقادیر باریک را می پذیرد. TypeScript این را ترکیب می کند تا به ما یک پاسخ بدهد
Record با یک key و value که هر دو باریک هستند!

خوب، بیایید CIF را در وضعیت اولیه خود اعمال کنیم:

type OperationFn = (left: number, right: number) => number
const createOperations = OperationsType extends Recordstring, OperationFn>>(
	operations: OperationsType,
) => operations

const operations = createOperations({
	'+': (left, right) => left + right,
	'-': (left, right) => left - right,
	'*': (left, right) => left * right,
	'/': (left, right) => left / right,
})

type CalculatorProps = {
	left: number
	operator: keyof typeof operations
	right: number
}

// @ts-expect-error we haven't added support
// for the exponentiation operator yet
operations['**'](1, 2)

عجب! بنابراین با این راه حل نیازی به تایپ صریح کل عملیات نداریمدقیقاً به همان روش عمل می کند و ما هنوز هم می توانیم نوع اتحادیه را از همه موجودات دریافت کنیم
عملیات

ممکن است متوجه شده باشید که در بخش قبل دو CIF داشتیم:

type Value = number
const createNumbers = ObjectType extends Recordstring, Value>>(
	obj: ObjectType,
) => obj

type OperationFn = (left: number, right: number) => number
const createOperations = OperationsType extends Recordstring, OperationFn>>(
	operations: OperationsType,
) => operations

آیا عالی نیست اگر بتوانیم آنها را با هم ترکیب کنیم؟ من قطعا. اما تو نخواهی رفت
برای دوست داشتن آن … این چیزی است که من ابتدا امتحان کردم:

const constrain = Given, Inferred extends Given>(item: Inferred) => item

// @ts-expect-error Expected 2 type arguments, but got 1.(2558)
const numbers = constrainRecordstring, number>>({ one: 1 /* etc. */ })

یه روز غمگین متأسفانه، امروزه این به سادگی با TypeScript امکان پذیر نیست. اما
در اینجا یک راه حل وجود دارد:

const constrain =
	Given extends unknown>() =>
	Inferred extends Given>(item: Inferred) =>
		item

const numbers = constrainRecordstring, number>>()({ one: 1 /* etc. */ })

… بله، به شما گفتم که دوست ندارید. اینجوری یه کم بهتره:

const createNumbers = constrainRecordstring, number>>()
const numbers = createNumbers({ one: 1 /* etc. */ })

اما مانند، خ. زننده

خوشبختانه، من اغلب CIF را به هر حال انجام نمی دهم، و اینطور نیست
تایپ کردن سخت است، بنابراین نیازی به انتزاع برای آنها ندارم. فکر کردم می شود
هنوز هم برای به اشتراک گذاشتن با شما جالب است 😄

اس satisfies کلمه کلیدی در TypeScript، شما می توانید اجتناب کنید همه این سوالات
بسیار آسان:

type OperationFn = (left: number, right: number) => number

const operations = {
	'+': (left, right) => left + right,
	'-': (left, right) => left - right,
	'*': (left, right) => left * right,
	'/': (left, right) => left / right,
} satisfies Recordstring, OperationFn>

این اساساً همان کار را انجام می دهد و هیچ یک از اشکالاتی ندارد. هورا برای
پیشرفت!

در اینجا نسخه نهایی جزء ماشین حساب ما با همه چیز وارد شده است
CIF ما:

type OperationFn = (left: number, right: number) => number
const createOperations = OperationsType extends Recordstring, OperationFn>>(
	opts: OperationsType,
) => opts

const operations = createOperations({
	'+': (left, right) => left + right,
	'-': (left, right) => left - right,
	'*': (left, right) => left * right,
	'/': (left, right) => left / right,
})

type CalculatorProps = {
	left: number
	operator: keyof typeof operations
	right: number
}
function Calculator({ left, operator, right }: CalculatorProps) {
	const result = operations[operator](left, right)
	return (
		div>
			code>
				{left} {operator} {right} = output>{result}output>
			code>
		div>
	)
}

const examples = (
	
		Calculator left={1} operator="+" right={2} />
		Calculator left={1} operator="-" right={2} />
		Calculator left={1} operator="*" right={2} />
		Calculator left={1} operator="/" right={2} />
	>
)

بله، کل این پست وبلاگ فقط برای توضیح آن 3 خط کد نوشته شده است
شما پس بله، آنجاست.

برخی از افراد ممکن است این پست را بخوانند و به تمسخر بگیرند و جملاتی مانند «چرا
اگر نیاز به انجام کارهای عجیب و غریب داشته باشید، آیا می خواهید از TypeScript استفاده کنید
این؟”

اول، من می گویم که فقط به این دلیل که ابزاری مانند TypeScript به راه حل هایی برای آن نیاز دارد
بعضی چیزها مثل این به این معنی نیست که ارزشش را ندارند. قیمت در اینجا حداقل است
و سود قابل توجه است. من اینجا نیستم که شما را متقاعد کنم که از TypeScript استفاده کنید.
من مطمئن هستم که نمی توانم شما را به خوبی باگ های زمان اجرا متقاعد کنم 😜
دوم، این قطعاً چیزی است که می توان با TypeScript در آن را بهبود بخشید
آینده در واقع
این می تواند گام خوبی برای بهبود اوضاع باشد.
در نهایت، همانطور که گفتم، این کاری نیست که ما همیشه انجام دهیم. اکثر آنها
زمان من با TypeScript لذت بخش است.

مراقب باشید!


Source link

نوشته های مشابه

دیدگاهتان را بنویسید

دکمه بازگشت به بالا