export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
برای رفع این مشکل، میتوان رخداد کلیک ماوس بر روی یک دکمه را از نوع بسیار عمومی React.MouseEvent تعریف کرد:
import React from "react"; type Props = { onClick: (e: React.MouseEvent) => void; }; export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} />
زمانیکه نوع یک رویداد به صورت کلی e: React.MouseEvent تعریف میشود، اگر به تصویر فوق دقت کنیم، تایپاسکریپت آنرا قابل اعمال به هر نوع Element ای میداند. اما اگر بخواهیم دامنهی دید آنرا صرفا به المان استاندارد button تعریف شده محدود کنیم، اشارهگر ماوس را در فایل src\components\Button.tsx بر روی رویداد onClick المان button تعریف شده قرار میدهیم:
همانطور که مشخص است، نوع این المان را به صورت HTMLButtonElement ذکر کردهاست. بنابراین میتوان تعریف کلی قبلی را به صورت زیر محدود کرد:
import React from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; }; export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
تعیین نوع رخداد onChange
در ادامه فرض کنید میخواهیم اطلاعات رویداد onChange را نیز صادر کنیم. روش عمومی تشخیص نوع آن، قرار دادن اشارهگر ماوس بر روی رویداد مدنظر و سپس استفاده از همان نوعی است که نمایش میدهد؛ مانند تصویر زیر:
import React from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: React.FormEvent<HTMLInputElement>) => void; }; export const Button = ({ onClick, onChange }: Props) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>Click Me</button> </> ); };
سؤال: اطلاعات نمایش داده شدهی نوعهای متناظر با رویدادهای مختلف در تصاویر فوق، از کجا تامین میشوند؟
اگر به فایل package.json پروژهی تایپاسکریپتی ایجاد شده مراجعه کنید، میتوانید حداقل دو مدخل جدید زیر را نیز در آن مشاهده کنید:
{ "dependencies": { "@types/react": "^16.9.35", "@types/react-dom": "^16.9.8", },
سؤال: آیا بستههای @types دار موجود در فایل package.json، حجم فایلهای نهایی برنامه را افزایش میدهند؟ آیا برنامههای React مبتنی بر TypeScript، حجم بیشتری را نسبت به نمونههای ES6 آن دارند؟
اکثر این تعاریف @types به صورت اینترفیسهای تایپاسکریپتی تعریف شدهاند که صرفا در زمان کامپایل برنامه، کمک حال کامپایلر آن خواهند بود و پس از کامپایل، تبخیر شده و در کدهای نهایی تولیدی، حضور نخواهند داشت. به عبارتی استفادهی از تایپاسکریپت، حجم نهایی پروژهها را افزایش نمیدهد؛ چون اینترفیسهای تایپاسکریپت، معادلی را در جاوااسکریپت استاندارد ندارند و فقط به عنوان راهنمای کامپایلر تایپاسکریپت عمل میکنند.
یک نکته: تعریف رویدادهای مدنظر را میتوان در importها نیز قرار داد:
import React, { FormEvent } from "react"; type Props = { onChange?: (e: FormEvent<HTMLInputElement>) => void; };
تعریف نوع Children Props
زمانیکه داخل تعریف المان یک کامپوننت، کامپوننت دیگری ذکر میشود، به عنوان فرزند او در React درنظر گرفته میشود. برای نمونه تعریف کامپوننت Button را در فایل src\App.tsx به صورت زیر تغییر میدهیم تا فرزندی را به صورت «Hello world» بپذیرد:
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} > Hello world! </Button>
با توجه به اینکه این فرزند، از نوع رشتهای است، فقط کافی است خاصیت children را با همین نوع، به Props اضافه کرده و از آن استفاده کنیم:
import React, { FormEvent } from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: FormEvent<HTMLInputElement>) => void; children: string; }; export const Button = ({ onClick, onChange, children }: Props) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>{children}</button> </> ); };
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} > <img src={logo} className="App-logo" alt="logo" /> </Button>
عنوان میکند که با این تغییر، نوع children به Element تغییر یافتهاست؛ اما در تعریف Props آن، به صورت رشتهای معرفی شدهاست. در این حالت اگر به تعریف Props مراجعه کنیم و نوع string را به Element تغییر دهیم، باز هم این خطا برطرف نمیشود. حتی اگر children: HTMLImageElement را نیز اضافه کنیم، باز هم این خطا تغییری نمیکند.
روش صحیح حل این مشکل را در ادامه مشاهده میکنید:
import React, { FormEvent } from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: FormEvent<HTMLInputElement>) => void; // children: HTMLImageElement; }; export const Button: React.FC<Props> = ({ onClick, onChange, children }) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>{children}</button> </> ); };
- سپس ذکر نوع Props را هم از Object Destructuring صورت گرفته، حذف میکنیم.
- در آخر، نوع کامپوننت جاری را به صورت <React.FC<Props معرفی میکنیم. در اینجا FC یعنی Functional Component و با تعریف آن، میتوان نوع props را به صورت آرگومان جنریک آن مشخص کرد. پس از آن دیگر نیازی به ذکر خاصیت children، در Props نیست؛ چون این خاصیت جزئی از React.FC میباشد و به صورت خودکار شناسایی میشود:
بنابراین در حالت کلی اگر از خاصیت children استفاده نمیکنید، از همان syntax قبلی که به صورت export const Button = ({ onClick, onChange }: Props) است، استفاده کنید؛ در غیراینصورت استفادهی از نوع <React.FC<T، ضروری خواهد بود.