وبلاگ
تایپ اسکریپت با React: توسعه کامپوننتهای Type-safe و پایدار
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
تایپ اسکریپت با React: توسعه کامپوننتهای Type-safe و پایدار
در دنیای پویای توسعه وب فرانتاند، React به عنوان یکی از محبوبترین کتابخانههای ساخت رابط کاربری، جایگاه ویژهای پیدا کرده است. از سوی دیگر، TypeScript به عنوان یک اَبَر مجموعه (Superset) بر پایه جاوااسکریپت، با افزودن سیستم نوعبندی استاتیک، تحولی عظیم در کیفیت و مقیاسپذیری پروژههای بزرگ ایجاد کرده است. ترکیب این دو فناوری قدرتمند، یعنی React و TypeScript، به توسعهدهندگان این امکان را میدهد که کامپوننتهایی با اطمینانپذیری بالا، قابلیت نگهداری بیشتر و خطاهای زمان اجرا (Runtime Errors) کمتر توسعه دهند.
این مقاله جامع برای توسعهدهندگانی طراحی شده است که قصد دارند دانش خود را در زمینه ترکیب React و TypeScript عمیقتر کنند و پروژههای خود را به سطح بالاتری از پایداری و Type-safety برسانند. ما از مفاهیم پایهای شروع کرده و تا الگوهای پیشرفتهتر پیش خواهیم رفت، با هدف ارائه یک راهنمای کامل و عملی برای ساخت کامپوننتهای قدرتمند و Type-safe.
چرا Type-safety با React ضروری است؟
جاوااسکریپت به دلیل ماهیت پویا و بدون نوعبندی خود، در پروژههای بزرگ و پیچیده میتواند منجر به خطاهای غیرمنتظره و زمانبر شود. این خطاها اغلب در زمان اجرا و پس از استقرار برنامه آشکار میشوند که کشف و رفع آنها پرهزینه است. TypeScript با معرفی سیستم نوعبندی استاتیک، این چالشها را به شکل چشمگیری کاهش میدهد. اما چرا این ویژگی برای React که خود بر پایه جاوااسکریپت است، اینقدر حیاتی است؟
۱. کاهش خطاهای زمان اجرا (Runtime Errors)
مهمترین مزیت استفاده از TypeScript، تشخیص و رفع خطاها در زمان کامپایل (Compile Time) است، قبل از اینکه کد شما به مرورگر برسد. در React، این به معنای اطمینان از اینکه Propsهای ارسال شده به کامپوننتها، State مدیریت شده، و مقادیر برگشتی از Hooks، همگی مطابق با انتظارات تعریف شده هستند. به عنوان مثال، اگر یک کامپوننت انتظار یک عدد را برای Prop خاصی داشته باشد و به اشتباه یک رشته به آن ارسال شود، TypeScript فوراً این خطا را گزارش میکند. این امر به طور قابل توجهی خطاهای رایج مانند `undefined is not a function` یا `Cannot read property ‘x’ of undefined` را کاهش میدهد.
۲. بهبود تجربه توسعهدهنده (Developer Experience – DX)
IDEها (مانند VS Code) با پشتیبانی کامل از TypeScript، قابلیتهای پیشرفتهای نظیر تکمیل خودکار کد (Autocompletion)، بررسی خطا در لحظه (Live Error Checking)، و ابزارهای Refactoring ایمن را ارائه میدهند. وقتی Props یک کامپوننت را تعریف میکنید، IDE به شما کمک میکند تا با تایپ صحیح آنها را استفاده کنید، و حتی هنگام Refactoring یک نام Prop، تمام ارجاعات به آن به طور خودکار و ایمن بهروزرسانی میشوند. این ویژگیها بهرهوری تیم را بالا برده و زمان صرف شده برای یافتن خطاهای ساده را به حداقل میرسانند.
۳. نگهداری و مقیاسپذیری بهتر کد
در پروژههای بزرگ با دهها یا صدها کامپوننت، درک اینکه هر کامپوننت چه Propهایی را میپذیرد و چه انتظاراتی از دادهها دارد، بدون نوعبندی صریح میتواند دشوار باشد. TypeScript به عنوان یک مستندات زنده برای کد عمل میکند. هر توسعهدهندهای که به یک کامپوننت نگاه میکند، فوراً متوجه میشود که چگونه باید از آن استفاده کند و چه دادههایی را باید به آن ارسال کند. این شفافیت، فرآیند نگهداری را آسانتر کرده و توسعه ویژگیهای جدید را در یک codebase بزرگ و پیچیده، مقیاسپذیرتر میسازد.
۴. همکاری تیمی آسانتر
در تیمهای توسعه، TypeScript به عنوان یک زبان مشترک عمل میکند که انتظارات دادهای را برای همه روشن میکند. این موضوع از سوءتفاهمها و خطاهای ناشی از عدم تطابق دادهها جلوگیری میکند. توسعهدهندگان میتوانند با اطمینان بیشتری روی بخشهای مختلف پروژه کار کنند، زیرا سیستم نوعبندی TypeScript به عنوان یک دروازه امنیتی عمل میکند که از تزریق دادههای نادرست جلوگیری مینماید.
۵. مستندسازی از طریق نوعبندی
تعریف نوعها برای Propها، State، و توابع در React، به خودی خود نوعی مستندسازی است. این مستندسازی همیشه بهروز و دقیق است، زیرا در صورت عدم تطابق با کد، TypeScript خطا میدهد. این امر نیاز به مستندات جداگانه و دستی را کاهش داده و تضمین میکند که مستندات کد همیشه با واقعیت منطبق هستند.
با درک این مزایا، روشن میشود که چرا Type-safety، به ویژه در اکوسیستم React، نه تنها یک گزینه، بلکه یک ضرورت برای توسعه نرمافزارهای پایدار و با کیفیت بالا است.
تنظیم پروژه React-TypeScript شما
برای شروع توسعه کامپوننتهای Type-safe با React و TypeScript، ابتدا باید یک محیط توسعه مناسب راهاندازی کنید. دو روش اصلی برای انجام این کار وجود دارد: استفاده از Create React App (CRA) با قالب TypeScript، یا افزودن TypeScript به یک پروژه React موجود.
۱. راهاندازی پروژه جدید با Create React App (CRA)
سادهترین راه برای شروع یک پروژه جدید React با TypeScript، استفاده از Create React App است. CRA یک محیط توسعه آماده با تنظیمات بهینه برای React فراهم میکند و با استفاده از پرچم `–template typescript`، TypeScript را به طور خودکار پیکربندی میکند.
npx create-react-app my-type-safe-app --template typescript
cd my-type-safe-app
npm start
یا با استفاده از Yarn:
yarn create react-app my-type-safe-app --template typescript
cd my-type-safe-app
yarn start
این دستورات یک پروژه React جدید ایجاد میکنند که شامل فایلهای پیکربندی TypeScript (`tsconfig.json`) و وابستگیهای لازم است. شما میتوانید بلافاصله شروع به نوشتن کامپوننتهای React با TypeScript کنید.
۲. افزودن TypeScript به پروژه React موجود
اگر پروژه React موجودی دارید که مایل به اضافه کردن TypeScript به آن هستید، مراحل کمی متفاوت است. شما نیاز به نصب پکیجهای TypeScript و نوعبندی (Type Definitions) React دارید.
npm install --save-dev typescript @types/react @types/react-dom @types/node
یا با Yarn:
yarn add --dev typescript @types/react @types/react-dom @types/node
پس از نصب، باید یک فایل `tsconfig.json` در ریشه پروژه خود ایجاد کنید. این فایل شامل تنظیمات کامپایلر TypeScript است. میتوانید با اجرای دستور زیر آن را به صورت خودکار ایجاد کنید:
npx tsc --init
سپس محتوای آن را برای یک پروژه React بهینهسازی کنید. یک پیکربندی پایه برای `tsconfig.json` میتواند به شکل زیر باشد:
{
"compilerOptions": {
"target": "es2016",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
توضیحات مختصر برای برخی از مهمترین گزینهها:
target
: نسخه ECMAScript هدف برای کدهای کامپایل شده (مثلاًes2016
).lib
: کتابخانههایی که در دسترس هستند (مثلاًdom
برای APIهای مرورگر).jsx
: نحوه تبدیل JSX (برای React ازreact-jsx
یاreact
استفاده کنید).strict
: فعال کردن تمام بررسیهای نوعبندی سختگیرانه (بسیار توصیه میشود).esModuleInterop
: فعال کردن سازگاری با ماژولهای CommonJS و ES Modules.skipLibCheck
: رد کردن بررسی نوعبندی برای فایلهای تعریف نوع کتابخانهها (برای سرعت).
در نهایت، باید پسوند فایلهای React خود را از `.js` یا `.jsx` به `.ts` یا `.tsx` تغییر دهید تا TypeScript شروع به بررسی آنها کند. برای مثال، `src/index.js` به `src/index.tsx` و `src/App.js` به `src/App.tsx` تبدیل میشوند.
با این تنظیمات، شما آمادهاید تا از قدرت TypeScript در پروژه React خود بهرهمند شوید.
تعریف Props و State کامپوننتها با TypeScript
یکی از مهمترین جنبههای Type-safety در React، تعریف دقیق Props و State کامپوننتها است. TypeScript به ما این امکان را میدهد که ساختار دادههایی که به کامپوننتها ارسال میشوند یا توسط آنها مدیریت میشوند را به وضوح مشخص کنیم.
۱. تعریف Props برای کامپوننتهای تابعی (Functional Components)
اکثر کامپوننتهای React امروزه به صورت تابعی نوشته میشوند. برای تعریف Props آنها، میتوانید از Interface یا Type Alias استفاده کنید. Type Alias معمولاً برای تعریف انواع سادهتر یا ترکیبی استفاده میشود، در حالی که Interface برای تعریف شکل شیء (Object Shape) یا کلاسها کاربرد دارد و میتواند Extend شود. در عمل، هر دو برای Props کار میکنند و انتخاب بین آنها اغلب سلیقهای است.
// استفاده از Interface
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean; // Prop اختیاری
}
const Button: React.FC<ButtonProps> = ({ label, onClick, disabled }) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
// استفاده از Type Alias
type CardProps = {
title: string;
content: string;
footer?: React.ReactNode; // مثال برای Children یا JSX
};
const Card = ({ title, content, footer }: CardProps) => {
return (
<div>
<h3>{title}</h3>
<p>{content}</p>
{footer && <div>{footer}</div>}
</div>
);
};
نکات مهم:
React.FC<Props>
: این نوع، به React.FunctionComponent نیز معروف است و برای تعریف کامپوننتهای تابعی به همراه Props آنها استفاده میشود. این نوع به طور خودکار Propchildren
را شامل میشود. با این حال، استفاده از آن کاملاً ضروری نیست و برخی توسعهدهندگان ترجیح میدهند Props را به صورت مستقیم به تابع پاس دهند (مانند مثالCard
).?
(Optional Props): با افزودن?
پس از نام Prop، آن را اختیاری میکنید. اگر Prop ارسال نشود، مقدار آنundefined
خواهد بود.children
Prop: اگر کامپوننت شما محتوایی را به عنوان Children میپذیرد، میتوانید آن را به صورت صریح تعریف کنید.React.ReactNode
یک نوع عمومی است که شامل هر چیزی است که React میتواند render کند (JSX، رشته، عدد، Fragment، پورتال، بولین، null یا undefined).
interface ContainerProps {
children: React.ReactNode;
className?: string;
}
const Container: React.FC<ContainerProps> = ({ children, className }) => {
return <div className={className}>{children}</div>;
};
۲. تعریف State برای کامپوننتهای کلاسی (Class Components)
اگرچه کامپوننتهای تابعی با Hooks بیشتر مورد استفاده قرار میگیرند، اما آشنایی با تعریف Props و State برای کامپوننتهای کلاسی نیز مفید است. کامپوننتهای کلاسی از جنریکها (Generics) برای تعریف نوع Props و State خود استفاده میکنند.
interface CounterProps {
initialCount?: number;
}
interface CounterState {
count: number;
}
class Counter extends React.Component<CounterProps, CounterState> {
constructor(props: CounterProps) {
super(props);
this.state = {
count: props.initialCount || 0,
};
}
increment = () => {
this.setState((prevState) => ({
count: prevState.count + 1,
}));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
در اینجا، CounterProps
نوع Props و CounterState
نوع State را تعریف میکند. React.Component<CounterProps, CounterState>
این نوعها را به کامپوننت کلاسی اعمال میکند.
۳. مدیریت Default Props
در React، گاهی اوقات میخواهید مقادیر پیشفرض برای Props تعریف کنید. TypeScript به شما این امکان را میدهد که با ترکیب نوعها، این کار را به خوبی انجام دهید.
interface GreetingProps {
name: string;
message?: string;
}
const Greeting: React.FC<GreetingProps> = ({ name, message = "Hello" }) => {
return <div>{message}, {name}!</div>;
};
// استفاده:
// <Greeting name="Alice" /> // خروجی: Hello, Alice!
// <Greeting name="Bob" message="Hi" /> // خروجی: Hi, Bob!
در این مثال، message
به عنوان یک Prop اختیاری تعریف شده است، اما با استفاده از مقدار پیشفرض در دیسساختارینگ (Destructuring)، تضمین میشود که همیشه یک مقدار رشتهای داشته باشد. TypeScript به خوبی این موضوع را درک میکند و نوع message
را به صورت string
(نه string | undefined
) در بدنه کامپوننت infer میکند.
با تعریف دقیق Props و State، شما اطمینان حاصل میکنید که دادهها در سراسر برنامه شما سازگار و قابل پیشبینی هستند، که این امر به شدت به پایداری و نگهداری کد کمک میکند.
کار با Hooks در TypeScript: useState, useEffect, useRef و useContext
Hooks انقلابی در توسعه کامپوننتهای تابعی React ایجاد کردهاند. TypeScript به طور عالی با Hooks ادغام میشود و به شما امکان میدهد State، Side Effects، Refها و Context را با امنیت نوعبندی مدیریت کنید.
۱. useState
useState
برای مدیریت State در کامپوننتهای تابعی استفاده میشود. TypeScript معمولاً میتواند نوع State را از مقدار اولیه infer کند، اما میتوانید نوع را به صورت صریح نیز تعریف کنید.
// inferring type from initial value (string)
const [name, setName] = useState(""); // name: string
// inferring type from initial value (number or null)
const [userId, setUserId] = useState<number | null>(null); // userId: number | null
// Explicitly defining type for an object
interface User {
id: number;
name: string;
email: string;
}
const [user, setUser] = useState<User | null>(null);
// برای بهروزرسانی state نیز نوعبندی اعمال میشود:
setUser({ id: 1, name: "Alice", email: "alice@example.com" }); // OK
// setUser({ id: 1, name: "Alice" }); // Error: Property 'email' is missing
هنگام استفاده از مقادیر اولیه پیچیده یا زمانی که State میتواند چندین نوع داشته باشد (مانند number | null
)، تعریف صریح نوع با استفاده از جنریکها (useState<Type>
) توصیه میشود.
۲. useEffect
useEffect
برای مدیریت Side Effects استفاده میشود و معمولاً نیازی به تعریف نوع صریح ندارد، زیرا TypeScript میتواند نوع پارامترها و مقادیر برگشتی آن را به درستی infer کند.
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
return () => {
// Cleanup function
};
}, [count]); // Dependency array
نکته مهم در useEffect
، اطمینان از صحت آرایه وابستگیها (Dependency Array) است. TypeScript به طور مستقیم در این مورد کمک نمیکند، اما ابزارهای linting مانند ESLint میتوانند مشکلات وابستگی را شناسایی کنند.
۳. useRef
useRef
برای دسترسی به المانهای DOM یا نگهداری هر مقدار قابل تغییر در طول عمر کامپوننت استفاده میشود.
// For a DOM element
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
// For a generic mutable value
const timerId = useRef<number | null>(null);
useEffect(() => {
timerId.current = window.setInterval(() => {
// do something
}, 1000);
return () => {
if (timerId.current) {
clearInterval(timerId.current);
}
};
}, []);
همیشه مقدار اولیه useRef
را به null
تنظیم کنید و نوع آن را به صورت HTMLInputElement | null
یا number | null
تعریف کنید، زیرا current
در ابتدا null
است تا زمانی که المان DOM (یا مقدار) به آن اختصاص داده شود.
۴. useContext
useContext
برای مصرف مقادیر از React Context استفاده میشود. تعریف نوع برای Context بسیار مهم است تا اطمینان حاصل شود که دادههای مورد انتظار را دریافت میکنید.
interface ThemeContextType {
theme: "light" | "dark";
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Theme Provider
const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<"light" | "dark">("light");
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
};
const contextValue = useMemo(() => ({ theme, toggleTheme }), [theme]);
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
};
// Consuming the context
const ThemeButton: React.FC = () => {
const themeContext = useContext(ThemeContext);
if (!themeContext) {
throw new Error("ThemeButton must be used within a ThemeProvider");
}
const { theme, toggleTheme } = themeContext;
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
};
در مثال ThemeContext
، مقدار اولیه Context را undefined
قرار دادهایم و نوع آن را ThemeContextType | undefined
تعریف کردهایم. این کار از مشکلاتی که هنگام دسترسی به Context قبل از مقداردهی اولیه رخ میدهد، جلوگیری میکند. در کامپوننت مصرفکننده (ThemeButton
)، ما یک بررسی برای !themeContext
اضافه کردهایم تا اطمینان حاصل کنیم که کامپوننت در داخل یک ThemeProvider
قرار دارد و خطاهای زمان اجرا را کاهش دهیم.
۵. useReducer
useReducer
برای مدیریت State پیچیدهتر که شامل منطق بهروزرسانی پیچیده است، استفاده میشود. تعریف نوع برای State و Action بسیار مهم است.
interface Todo {
id: number;
text: string;
completed: boolean;
}
type TodoAction =
| { type: "ADD_TODO"; text: string }
| { type: "TOGGLE_TODO"; id: number }
| { type: "REMOVE_TODO"; id: number };
function todoReducer(state: Todo[], action: TodoAction): Todo[] {
switch (action.type) {
case "ADD_TODO":
return [
...state,
{ id: Date.now(), text: action.text, completed: false },
];
case "TOGGLE_TODO":
return state.map((todo) =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case "REMOVE_TODO":
return state.filter((todo) => todo.id !== action.id);
default:
return state;
}
}
const TodoList: React.FC = () => {
const [todos, dispatch] = useReducer(todoReducer, []);
const addTodo = (text: string) => {
dispatch({ type: "ADD_TODO", text });
};
return (
<div>
<input
type="text"
onKeyDown={(e) => {
if (e.key === "Enter") {
addTodo(e.currentTarget.value);
e.currentTarget.value = "";
}
}}
placeholder="Add a new todo"
/>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
onClick={() => dispatch({ type: "TOGGLE_TODO", id: todo.id })}
>
{todo.text}
</span>
<button onClick={() => dispatch({ type: "REMOVE_TODO", id: todo.id })}>
Remove
</button>
</li>
))}
</ul>
</div>
);
};
با تعریف TodoAction
به عنوان یک Union Type از تمام اکشنهای ممکن، TypeScript میتواند بررسی کند که آیا اکشنهای ارسال شده به dispatch
معتبر هستند یا خیر. این کار به جلوگیری از خطاهای ناشی از اکشنهای اشتباه کمک میکند.
الگوهای پیشرفته TypeScript در React
TypeScript فراتر از Type-safety پایهای برای Props و State عمل میکند و الگوهای پیشرفتهای را ارائه میدهد که به شما کمک میکنند کامپوننتهای انعطافپذیرتر و قابل استفاده مجدد ایجاد کنید.
۱. کامپوننتهای Polymorphic و Generic
گاهی اوقات میخواهید یک کامپوننت داشته باشید که بتواند به عنوان المانهای مختلف HTML رندر شود، اما همچنان Props مربوط به آن المان را بپذیرد. اینجاست که کامپوننتهای Polymorphic وارد میشوند.
// Generic (Polymorphic) Button Component
import React from 'react';
// 1. Define a type for the component's default props
interface ButtonProps<T extends React.ElementType> {
as?: T; // Allows the component to be rendered as a different HTML element
children: React.ReactNode;
className?: string;
// Add other common props you expect
}
// 2. Define props for the specific element that `as` represents
// This uses React.ComponentPropsWithoutRef to infer props for the element,
// and then Omit to remove props that are already defined in ButtonProps
type PolymorphicComponentProps<T extends React.ElementType, P = {}> =
React.PropsWithChildren<P & ButtonProps<T>> &
Omit<React.ComponentPropsWithoutRef<T>, keyof ButtonProps<T>>;
// 3. Create the component function
// React.forwardRef allows forwarding refs to the underlying DOM element
const Button = React.forwardRef(
<T extends React.ElementType = 'button'>(
{ as, children, className, ...rest }: PolymorphicComponentProps<T>,
ref?: React.ComponentPropsWithRef<T>['ref']
) => {
const Component = as || 'button';
return (
<Component ref={ref} className={className} {...rest}>
{children}
</Component>
);
}
);
// This ensures type safety when using the component
// Example usage:
const App: React.FC = () => {
return (
<div>
<Button onClick={() => alert("Clicked!")}>
Regular Button
</Button>
<Button as="a" href="https://example.com" target="_blank">
Link Button
</Button>
<Button as="div" style={{ padding: '10px', border: '1px solid black' }}>
Div Button
</Button>
</div>
);
};
این الگو کمی پیچیده است، اما بسیار قدرتمند. as?: T
به کامپوننت اجازه میدهد به عنوان یک المان HTML دیگر رندر شود. PolymorphicComponentProps
از Generics برای ترکیب Props پیشفرض کامپوننت با Propsهای خاص المان HTML استفاده میکند و از Omit
برای جلوگیری از تداخل نام Props استفاده میکند.
۲. Utility Types در TypeScript
TypeScript مجموعهای از Utility Types داخلی را فراهم میکند که به شما در ساخت انواع پیچیده کمک میکنند. اینها برای استفاده با React نیز مفید هستند:
Partial<T>
: تمام ویژگیهایT
را اختیاری میکند.Pick<T, K>
: زیرمجموعهای از ویژگیهایT
را بر اساس کلیدهایK
انتخاب میکند.Omit<T, K>
: زیرمجموعهای از ویژگیهایT
را با حذف کلیدهایK
انتخاب میکند.Exclude<T, U>
: انواعU
را ازT
حذف میکند.NonNullable<T>
: انواعnull
وundefined
را ازT
حذف میکند.ReturnType<T>
: نوع مقدار برگشتی یک تابع را استخراج میکند.Parameters<T>
: نوع پارامترهای یک تابع را به صورت یک تاپل استخراج میکند.
// Example with Pick and Omit
interface UserProfile {
id: number;
name: string;
email: string;
avatarUrl: string;
}
type UserPreviewProps = Pick<UserProfile, "name" | "avatarUrl">;
const UserPreview: React.FC<UserPreviewProps> = ({ name, avatarUrl }) => {
return (
<div>
<img src={avatarUrl} alt={name} />
<p>{name}</p>
</div>
);
};
type UserFormProps = Omit<UserProfile, "id" | "avatarUrl">;
const UserForm: React.FC<UserFormProps> = ({ name, email }) => {
// ... form logic
return (
<form>
<input type="text" value={name} />
<input type="email" value={email} />
<button type="submit">Save</button>
</form>
);
};
این Utility Types به شما کمک میکنند تا انواع دادهای را از انواع موجود مشتق کنید و از تکرار کد جلوگیری کنید.
۳. Higher-Order Components (HOCs) با TypeScript
اگرچه Hooks به طور فزایندهای جایگزین HOCs میشوند، اما HOCs هنوز در برخی سناریوها کاربرد دارند. تایپ کردن HOCها میتواند چالشبرانگیز باشد.
// A simple HOC for logging props
function withLogger<P extends object>(Component: React.ComponentType<P>) {
type ComponentProps = React.ComponentProps<typeof Component>;
const WrapperComponent: React.FC<ComponentProps> = (props) => {
useEffect(() => {
console.log("Props:", props);
}, [props]);
return <Component {...props} />;
};
WrapperComponent.displayName = `withLogger(${Component.displayName || Component.name})`;
return WrapperComponent;
}
interface MyComponentProps {
message: string;
count: number;
}
const MyComponent: React.FC<MyComponentProps> = ({ message, count }) => {
return <div>{message}: {count}</div>;
};
const LoggedMyComponent = withLogger(MyComponent);
// Usage: <LoggedMyComponent message="Hello" count={5} />
در این مثال، withLogger
یک تابع جنریک است که نوع Props کامپوننت ورودی را حفظ میکند. P extends object
به TypeScript میگوید که P
باید یک شیء باشد (که تمام Propsها از آن مشتق میشوند). React.ComponentProps<typeof Component>
نوع Props کامپوننت wrapped شده را استخراج میکند.
۴. Render Props با TypeScript
الگوی Render Props نیز راهی برای به اشتراک گذاشتن کد بین کامپوننتها است. تایپ کردن آن در TypeScript نسبتاً ساده است.
interface DataFetcherProps<T> {
url: string;
render: (data: T | null, isLoading: boolean, error: Error | null) => React.ReactNode;
}
function DataFetcher<T>({ url, render }: DataFetcherProps<T>) {
const [data, setData] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result: T = await response.json();
setData(result);
} catch (err: any) {
setError(err);
} finally {
setIsLoading(false);
}
};
fetchData();
}, [url]);
return <>{render(data, isLoading, error)}</>;
}
interface User {
id: number;
name: string;
}
const UserList: React.FC = () => {
return (
<DataFetcher<User[]>
url="https://jsonplaceholder.typicode.com/users"
render={(users, loading, error) => {
if (loading) return <p>Loading users...</p>;
if (error) return <p style={{ color: "red" }}>Error: {error.message}</p>;
if (!users) return <p>No users found.</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}}
/>
);
};
با استفاده از Generics برای DataFetcher
، میتوانیم مشخص کنیم که چه نوع دادهای (T
) از API انتظار میرود، و تابع render
نیز از این نوع استفاده میکند تا Type-safety را در زمان استفاده از دادهها تضمین کند.
بهترین روشها برای توسعه Type-Safe در React
برای حداکثر بهرهوری از ترکیب React و TypeScript، پیروی از مجموعهای از بهترین روشها میتواند تفاوت چشمگیری در کیفیت و مقیاسپذیری کد ایجاد کند.
۱. فعال کردن Strict Mode در tsconfig.json
یکی از مهمترین گامها، فعال کردن "strict": true
در فایل tsconfig.json
است. این گزینه تمام بررسیهای Type-safety سختگیرانه را فعال میکند، از جمله:
noImplicitAny
: جلوگیری از استفاده ازany
در مواردی که TypeScript نمیتواند نوع را infer کند.strictNullChecks
: اجبار به بررسی صریح مقادیرnull
وundefined
.strictFunctionTypes
: بررسی دقیقتر امضای توابع.strictPropertyInitialization
: اطمینان از مقداردهی اولیه Propertyهای کلاس.
اگرچه در ابتدا ممکن است با خطاهای بیشتری مواجه شوید، اما این کار به شما کمک میکند کدی بسیار با کیفیتتر و بدون خطای زمان اجرا بنویسید.
۲. از Any اجتناب کنید (حتی الامکان)
استفاده از any
تقریباً تمام مزایای TypeScript را از بین میبرد. در مواقعی که مجبور به استفاده از آن هستید (مثلاً هنگام تعامل با کتابخانههای بدون تعریف نوع یا دادههای API غیرقابل پیشبینی)، سعی کنید محدوده any
را به حداقل برسانید. در بیشتر موارد، راههایی برای تعریف دقیقتر نوعها وجود دارد، حتی اگر به معنای کمی تلاش بیشتر باشد.
۳. از Inference (استنتاج نوع) به درستی استفاده کنید
TypeScript بسیار باهوش است و میتواند بسیاری از نوعها را به طور خودکار Infer کند. همیشه نیازی به Type کردن صریح هر متغیر یا پارامتر نیست. از قابلیتهای Inference TypeScript بهره ببرید تا کد شما خواناتر و کمتر verbose شود. با این حال، در مرزهای سیستم (مثلاً Props کامپوننت، ورودی/خروجی API)، صریح بودن بسیار مهم است.
۴. سازماندهی نوعها
با بزرگ شدن پروژه، تعداد Interfaceها و Type Aliasها نیز افزایش مییابد. بهتر است این نوعها را در فایلهای جداگانه (مثلاً `types.ts` یا `interfaces.ts`) یا در کنار کامپوننتهایی که از آنها استفاده میکنند، سازماندهی کنید. برای کامپوننتهای بزرگتر یا نوعهای مشترک، ایجاد یک دایرکتوری `types/` در ریشه پروژه میتواند مفید باشد.
۵. تایپ کردن Event Handlers
وقتی با Eventهای DOM در React کار میکنید، مطمئن شوید که Event Listenerها را به درستی تایپ کردهاید. React انواع Event خاص خود را دارد که میتوانید از آنها استفاده کنید.
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.value);
};
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log("Button clicked!");
};
return (
<div>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>Click me</button>
</div>
);
انواع رایج عبارتند از: React.ChangeEvent
، React.MouseEvent
، React.FormEvent
، React.KeyboardEvent
. شما میتوانید المان هدف را نیز با Generics (مثلاً HTMLInputElement
) مشخص کنید.
۶. استفاده از لینتر (Linter) با پشتیبانی از TypeScript
ابزارهایی مانند ESLint با پلاگینهای TypeScript (مانند `@typescript-eslint/eslint-plugin`) میتوانند به شما در اعمال بهترین روشها و شناسایی مشکلات قبل از کامپایل کمک کنند. این ابزارها میتوانند خطاهای احتمالی را شناسایی کرده و سبک کدگذاری را یکنواخت کنند.
۷. نوشتن تستهای Type-Safe
تستهایی که برای کامپوننتهای React خود مینویسید، میتوانند از Type-safety بهرهمند شوند. استفاده از کتابخانههای تست مانند React Testing Library یا Jest با TypeScript، اطمینان میدهد که Propsهای ارسال شده به کامپوننتها در تستها نیز صحیح هستند.
با رعایت این بهترین روشها، میتوانید به طور موثرتری از TypeScript در پروژههای React خود استفاده کنید و کدی بنویسید که هم قدرتمند و هم پایدار باشد.
عیبیابی خطاهای رایج TypeScript در React
هنگام کار با TypeScript و React، احتمالاً با برخی از خطاهای رایج TypeScript روبرو خواهید شد. درک این خطاها و نحوه رفع آنها میتواند زمان توسعه شما را به شدت بهبود بخشد.
۱. Property ‘X’ does not exist on type ‘Y’
این خطا یکی از رایجترین خطاها است و نشان میدهد که شما سعی دارید به یک ویژگی (Property) دسترسی پیدا کنید که در نوع تعریف شده وجود ندارد.
interface User {
name: string;
age: number;
}
const user: User = { name: "Alice", age: 30 };
// console.log(user.email); // Error: Property 'email' does not exist on type 'User'.
راه حل:
- اگر ویژگی واقعاً وجود ندارد: آن را به Interface یا Type Alias اضافه کنید.
- اگر ویژگی اختیاری است: از عملگر
?
استفاده کنید (مثلاًemail?: string;
). - اگر میدانید که ویژگی در زمان اجرا وجود خواهد داشت (مثلاً از یک پاسخ API): میتوانید از Optional Chaining (
?.
) یا Non-null Assertion Operator (!
) استفاده کنید، اما با احتیاط.
۲. Type ‘A’ is not assignable to type ‘B’
این خطا نشان میدهد که شما سعی دارید مقداری از یک نوع را به متغیری که نوع دیگری دارد، اختصاص دهید. این خطا معمولاً هنگام ارسال Props با نوع اشتباه رخ میدهد.
interface ButtonProps {
label: string;
onClick: () => void;
}
const MyButton: React.FC<ButtonProps> = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
// <MyButton label={123} onClick={() => {}} /> // Error: Type 'number' is not assignable to type 'string'.
راه حل:
- مطمئن شوید که نوع دادهای که اختصاص میدهید با نوع تعریف شده مطابقت دارد.
- اگر داده از یک منبع خارجی (مثل API) میآید، مطمئن شوید که آن را به نوع صحیح نگاشت کردهاید.
- از Union Types (
TypeA | TypeB
) استفاده کنید اگر متغیر میتواند چندین نوع مختلف را نگه دارد.
۳. Object is possibly ‘null’ or ‘undefined’
این خطا به دلیل فعال بودن strictNullChecks
رخ میدهد و به شما هشدار میدهد که متغیری که به آن دسترسی پیدا میکنید، ممکن است null
یا undefined
باشد. این یک ویژگی امنیتی TypeScript است.
const myRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// console.log(myRef.current.clientWidth); // Error: Object is possibly 'null'.
if (myRef.current) {
console.log(myRef.current.clientWidth); // OK
}
}, []);
راه حل:
- قبل از دسترسی، بررسی
null
/undefined
انجام دهید (مانند مثال بالا باif (myRef.current)
). - از Optional Chaining (
myRef.current?.clientWidth
) استفاده کنید اگر میخواهید در صورتnull
/undefined
بودن، عملیات متوقف شود وundefined
برگردانده شود. - در مواردی که مطمئن هستید مقدار
null
/undefined
نیست، میتوانید از Non-null Assertion Operator (!
) استفاده کنید (مثلاًmyRef.current!.clientWidth
)، اما این کار را با احتیاط و فقط زمانی که کاملاً مطمئن هستید، انجام دهید.
۴. Argument of type ‘X’ is not assignable to parameter of type ‘Y’ (for Event Handlers)
این خطا اغلب زمانی رخ میدهد که شما یک Event Handler را به درستی تایپ نکردهاید.
// Incorrect:
const handleChange = (e: React.FormEvent) => {
// console.log(e.target.value); // Error: Property 'value' does not exist on type 'EventTarget'.
};
// Correct:
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value); // OK
};
// <input type="text" onChange={handleChange} />
راه حل:
- همیشه از انواع Event صحیح React استفاده کنید (
React.ChangeEvent
،React.MouseEvent
، و غیره). - پارامتر Generic مربوط به نوع المان را در صورت لزوم (مثلاً
HTMLInputElement
) مشخص کنید تا به ویژگیهای خاص آن المان (مانندvalue
یاchecked
) دسترسی داشته باشید.
۵. Type ‘X’ has no compatible call signatures (for function props)
این خطا زمانی رخ میدهد که یک تابع را به عنوان Prop ارسال میکنید، اما امضای آن (پارامترها و نوع برگشتی) با آنچه در Prop تعریف شده است، مطابقت ندارد.
interface ChildProps {
onSave: (data: { name: string; age: number }) => void;
}
const ChildComponent: React.FC<ChildProps> = ({ onSave }) => {
const handleClick = () => {
onSave({ name: "John", age: 30 });
};
return <button onClick={handleClick}>Save</button>;
};
const ParentComponent: React.FC = () => {
const handleSave = (data: { name: string }) => {
console.log(data.name);
};
// <ChildComponent onSave={handleSave} /> // Error: Argument of type '{ name: string; }' is not assignable to parameter of type '{ name: string; age: number; }'. Property 'age' is missing.
return <ChildComponent onSave={(data) => console.log(data)} />;
};
راه حل:
- مطمئن شوید که تابع ارسال شده به Prop، دقیقاً همان پارامترها و نوع برگشتی را که در تعریف Prop مشخص شده است، میپذیرد.
- اگر تابع شما به همه پارامترها نیاز ندارد، میتوانید از آنها چشمپوشی کنید یا آنها را اختیاری کنید، اما نوع باید همچنان مطابقت داشته باشد.
با شناسایی و درک این خطاهای رایج، میتوانید فرآیند دیباگینگ خود را سرعت بخشید و به طور موثرتری از TypeScript در توسعه React استفاده کنید.
نتیجهگیری: آینده توسعه Type-Safe با React و TypeScript
همانطور که در این مقاله جامع بررسی کردیم، ترکیب TypeScript با React نه تنها یک مزیت، بلکه یک ضرورت برای توسعه کامپوننتهای مدرن، پایدار و قابل نگهداری در اکوسیستم فرانتاند است. از کاهش خطاهای زمان اجرا و بهبود چشمگیر تجربه توسعهدهنده گرفته تا افزایش مقیاسپذیری و همکاری تیمی، مزایای Type-safety غیرقابل انکار هستند.
ما گام به گام نحوه راهاندازی یک پروژه React-TypeScript را بررسی کردیم، به عمق تعریف Props و State برای کامپوننتهای تابعی و کلاسی پرداختیم، و نحوه استفاده امن و Type-safe از Hooks محبوب React (مانند useState
، useEffect
، useRef
و useContext
) را آموختیم. همچنین با الگوهای پیشرفته TypeScript مانند کامپوننتهای Polymorphic، Utility Types و نحوه تایپ کردن HOCs و Render Props آشنا شدیم که به ما امکان میدهند کامپوننتهایی انعطافپذیرتر و قابل استفاده مجدد بسازیم.
پیروی از بهترین روشها، مانند فعال کردن Strict Mode، اجتناب از any
، استفاده هوشمندانه از Inference، و سازماندهی مناسب نوعها، به شما کمک میکند تا از پتانسیل کامل TypeScript بهرهمند شوید. در نهایت، درک و عیبیابی خطاهای رایج TypeScript، به شما این قدرت را میدهد که با اطمینان بیشتری کدنویسی کنید و چالشها را به سرعت حل کنید.
با پیشرفت فناوریها، تقاضا برای نرمافزارهای با کیفیت بالا و بدون خطا رو به افزایش است. TypeScript ابزاری قدرتمند است که به توسعهدهندگان React کمک میکند تا این انتظارات را برآورده کنند. سرمایهگذاری در یادگیری و پیادهسازی TypeScript در پروژههای React شما، نه تنها به نفع پروژه فعلی شما خواهد بود، بلکه مهارتهای شما را به عنوان یک توسعهدهنده به طور قابل توجهی ارتقا خواهد داد و شما را برای چالشهای آینده توسعه وب آماده میکند.
پس، با اطمینان قدم در مسیر توسعه Type-safe بگذارید و از ساخت کامپوننتهایی قدرتمندتر و پایدارتر با React و TypeScript لذت ببرید.
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان