تایپ‌اسکریپت یک روش پرطرفدار برای اضافه کردن تعریف type به کدهای جاوااسکریپت است. به‌صورت پیش‌فرض تایپ‌اسکریپت از JSX پشتیبانی می‌کند و با اضافه کردن @types/react و @types/react-dom به پروژه، پشتیبانی کامل از React Web را خواهید داشت.

نصب

تمامی فریم‌ورک‌های سطح تولید ری‌اکت از TypeScript پشتیبانی می‌کنند. برای نصب، راهنمای اختصاصی هر فریم‌ورک را دنبال کنید:

افزودن TypeScript به یک پروژه React موجود

برای نصب آخرین نسخه از تعاریف type ری‌اکت:

Terminal
npm install —save-dev @types/react @types/react-dom

گزینه‌های کامپایلر زیر باید در tsconfig.json شما تنظیم شوند:

  1. dom باید در lib گنجانده شود (نکته: اگر گزینه‌ای برای lib مشخص نشده باشد، dom به‌صورت پیش‌فرض گنجانده می‌شود).
  2. jsx باید به یکی از گزینه‌های معتبر تنظیم شود. preserve برای اکثر برنامه‌ها کافی است. اگر در حال انتشار یک کتابخانه هستید، برای انتخاب مقدار مناسب به مستندات jsx مراجعه کنید.

TypeScript با کامپوننت‌های ری‌اکت

نکته

هر فایلی که شامل JSX است باید از پسوند .tsx استفاده کند. این یک پسوند مخصوص TypeScript است که به TypeScript می‌گوید این فایل شامل JSX است.

نوشتن TypeScript با ری‌اکت بسیار شبیه به نوشتن جاوااسکریپت با ری‌اکت است. تفاوت اصلی هنگام کار با یک کامپوننت این است که می‌توانید برای props کامپوننت خود type تعریف کنید. این typeها می‌توانند برای بررسی صحت و ارائه مستندات درخط در ویرایشگرها استفاده شوند.

با استفاده از کامپوننت MyButton از راهنمای شروع سریع، می‌توانیم یک type برای توصیف title دکمه اضافه کنیم:

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a button" />
    </div>
  );
}

نکته

این sandboxها می‌توانند کد تایپ‌اسکریپت را اجرا کنند، اما type-checker را اجرا نمی‌کنند. این بدان معناست که می‌توانید sandboxهای تایپ‌اسکریپت را برای یادگیری تغییر دهید، اما هیچ خطا یا هشدار type دریافت نخواهید کرد. برای بررسی type، می‌توانید از TypeScript Playground یا یک sandbox آنلاین کامل‌تر استفاده کنید.

این نحوه‌ی نوشتن درون‌خطی (inline syntax) ساده‌ترین روش برای ارائه type‌ها برای یک کامپوننت است، اگرچه وقتی شروع به داشتن چند فیلد برای توصیف می‌کنید، می‌تواند دشوار شود. به جای آن، می‌توانید از interface یا type برای توصیف props کامپوننت استفاده کنید:

interface MyButtonProps {
  /** The text to display inside the button */
  title: string;
  /** Whether the button can be interacted with */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a disabled button" disabled={true}/>
    </div>
  );
}

type ای که برای props کامپوننت تعریف می‌کنید می‌تواند بسته به نیازتان ساده یا پیچیده باشد، اما باید حتماً یک نوع شیء باشد که با type یا interface مشخص شده است. می‌توانید درباره نحوه توصیف اشیاء در TypeScript در Object Types بیاموزید، اما ممکن است بخواهید از Union Types برای توصیف propهایی استفاده کنید که می‌توانند یکی از چند type مختلف باشند، و همچنین راهنمای Creating Types from Types را برای موارد کاربرد پیشرفته‌تر مطالعه کنید.

مثال‌های هوک

تعریف typeها از @types/react شامل typeهایی برای هوک‌های داخلی است، بنابراین می‌توانید بدون نیاز به تنظیمات اضافی از آن‌ها در کامپوننت‌های خود استفاده کنید. آن‌ها به گونه‌ای ساخته شده‌اند که کد نوشته شده در کامپوننت شما را در نظر می‌گیرند، بنابراین در بسیاری از مواقع inferred types را دریافت خواهید کرد و در حالت ایده‌آل نیازی به مدیریت جزئیات ارائه typeها ندارید.

با این حال، می‌توانیم به چند نمونه از نحوه ارائه typeها برای هوک‌ها نگاهی بیندازیم.

useState

هوک useState از مقدار ارسال‌شده به‌عنوان state اولیه برای تعیین اینکه نوع مقدار چه باید باشد، استفاده مجدد می‌کند. برای مثال:

// استنباط نوع به عنوان "boolean"
const [enabled, setEnabled] = useState(false);

این کار type boolean را به enabled اختصاص می‌دهد، و setEnabled تابعی خواهد بود که یا یک آرگومان boolean یا تابعی که یک boolean برمی‌گرداند را می‌پذیرد. اگر می‌خواهید به صورت صریح typeی را برای state ارائه دهید، می‌توانید با ارائه یک آرگومان type به فراخوانی useState این کار را انجام دهید:

// تنظیم صریح نوع به "boolean"
const [enabled, setEnabled] = useState<boolean>(false);

این در این مورد خیلی مفید نیست، اما یک مورد رایج که ممکن است بخواهید یک type ارائه دهید زمانی است که یک union type دارید. برای مثال، status در اینجا می‌تواند یکی از چند رشته مختلف باشد:

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

یا، همان‌طور که در اصول ساختاردهی state توصیه شده است، می‌توانید stateهای مرتبط را به‌صورت یک شیء گروه‌بندی کنید و امکانات مختلف را از طریق object typeها توصیف کنید:

type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });

useReducer

هوک useReducer یک هوک پیچیده‌تر است که یک تابع reducer و یک state اولیه را می‌گیرد. typeهای تابع reducer از state اولیه استنباط می‌شوند. شما می‌توانید به صورت اختیاری یک آرگومان type به فراخوانی useReducer ارائه دهید تا typeی را برای state فراهم کنید، اما اغلب بهتر است type را روی state اولیه تنظیم کنید:

import {useReducer} from 'react';

interface State {
   count: number
};

type CounterAction =
  | { type: "reset" }
  | { type: "setCount"; value: State["count"] }

const initialState: State = { count: 0 };

function stateReducer(state: State, action: CounterAction): State {
  switch (action.type) {
    case "reset":
      return initialState;
    case "setCount":
      return { ...state, count: action.value };
    default:
      throw new Error("Unknown action");
  }
}

export default function App() {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });
  const reset = () => dispatch({ type: "reset" });

  return (
    <div>
      <h1>Welcome to my counter</h1>

      <p>Count: {state.count}</p>
      <button onClick={addFive}>Add 5</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

ما در چند جای کلیدی از TypeScript استفاده می‌کنیم:

  • interface State شکل state ریدوسر را توصیف می‌کند.
  • type CounterAction اکشن‌های مختلفی که می‌توانند به ردیوسر ارسال شوند را توصیف می‌کند.
  • const initialState: State یک type برای state اولیه فراهم می‌کند، و همچنین typeی که به‌صورت پیش‌فرض توسط useReducer استفاده می‌شود.
  • stateReducer(state: State, action: CounterAction): State typeها را برای آرگومان‌های تابع ریدیوسر و مقدار برگشتی تنظیم می‌کند.

یک جایگزین صریح‌تر برای تنظیم type روی initialState، ارائه یک آرگومان type به useReducer است:

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

هوک useContext تکنیکی برای انتقال داده در درخت کامپوننت بدون نیاز به انتقال props از طریق کامپوننت‌ها است. این هوک با ایجاد یک کامپوننت provider و اغلب با ایجاد یک هوک برای مصرف مقدار در یک کامپوننت فرزند استفاده می‌شود.

type مقدار ارائه‌شده توسط context از مقداری که به فراخوانی createContext ارسال می‌شود، استنباط می‌شود:

import { createContext, useContext, useState } from 'react';

type Theme = "light" | "dark" | "system";
const ThemeContext = createContext<Theme>("system");

const useGetTheme = () => useContext(ThemeContext);

export default function MyApp() {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext value={theme}>
      <MyComponent />
    </ThemeContext>
  )
}

function MyComponent() {
  const theme = useGetTheme();

  return (
    <div>
      <p>Current theme: {theme}</p>
    </div>
  )
}

این تکنیک زمانی کار می‌کند که یک مقدار پیش‌فرض منطقی داشته باشید - اما گاهی اوقات مواردی وجود دارد که ندارید، و در این موارد null می‌تواند به عنوان یک مقدار پیش‌فرض منطقی به نظر برسد. با این حال، برای اینکه type-system بتواند کد شما را درک کند، باید به صورت صریح ContextShape | null را روی createContext تنظیم کنید.

این باعث می‌شود که نیاز داشته باشید | null را در type برای مصرف‌کنندگان context حذف کنید. توصیه ما این است که هوک یک بررسی زمان اجرا برای وجود آن انجام دهد و در صورت عدم وجود، خطا پرتاب کند:

import { createContext, useContext, useState, useMemo } from 'react';

// This is a simpler example, but you can imagine a more complex object here
type ComplexObject = {
kind: string
};

// The context is created with `| null` in the type, to accurately reflect the default value.
const Context = createContext<ComplexObject | null>(null);

// The `| null` will be removed via the check in the Hook.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}

export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);

return (
<Context value={object}>
<MyComponent />
</Context>
)
}

function MyComponent() {
const object = useGetComplexObject();

return (
<div>
<p>Current object: {object.kind}</p>
</div>
)
}

useMemo

نکته

کامپایلر ری‌اکت به صورت خودکار مقادیر و توابع را ممویزه می‌کند، که نیاز به فراخوانی‌های دستی useMemo را کاهش می‌دهد. می‌توانید از کامپایلر برای مدیریت خودکار ممویزه‌سازی استفاده کنید.

هوک‌های useMemo یک مقدار ذخیره‌شده را از فراخوانی تابع ایجاد/بازیابی می‌کنند و تابع را فقط زمانی که وابستگی‌های ارسال‌شده به‌عنوان پارامتر دوم تغییر کنند، مجدداً اجرا می‌کنند. نتیجه فراخوانی هوک از مقدار برگشتی تابع در پارامتر اول استنباط می‌شود. می‌توانید با ارائه یک آرگومان type به هوک، صریح‌تر باشید.

// نوع visibleTodos از مقدار برگشتی filterTodos استنباط می‌شود
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback

نکته

کامپایلر ری‌اکت به صورت خودکار مقادیر و توابع را ممویزه می‌کند، که نیاز به فراخوانی‌های دستی useCallback را کاهش می‌دهد. می‌توانید از کامپایلر برای مدیریت خودکار ممویزه‌سازی استفاده کنید.

useCallback یک مرجع پایدار به یک تابع را تا زمانی که وابستگی‌های ارسال‌شده به پارامتر دوم یکسان باشند، فراهم می‌کند. مانند useMemo، type تابع از مقدار برگشتی تابع در پارامتر اول استنباط می‌شود، و می‌توانید با ارائه یک آرگومان type به هوک، صریح‌تر باشید.

const handleClick = useCallback(() => {
// ...
}, [todos]);

هنگام کار در حالت سختگیرانه TypeScript، useCallback نیاز به افزودن typeها برای پارامترهای callback شما دارد. این به این دلیل است که type callback از مقدار برگشتی تابع استنباط می‌شود، و بدون پارامترها type نمی‌تواند به طور کامل درک شود.

بسته به ترجیحات سبک کد شما، می‌توانید از توابع *EventHandler از typeهای ری‌اکت برای ارائه type برای event handler همزمان با تعریف callback استفاده کنید:

import { useState, useCallback } from 'react';

export default function Form() {
const [value, setValue] = useState("Change me");

const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])

return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}

typeهای پرکاربرد

مجموعه گسترده‌ای از typeها وجود دارد که از بسته @types/react می‌آیند، وقتی با نحوه تعامل ری‌اکت و تایپ‌اسکریپت راحت هستید، ارزش خواندن دارند. می‌توانید آن‌ها را در پوشه ری‌اکت در DefinitelyTyped پیدا کنید. ما در اینجا به چند type رایج‌تر می‌پردازیم.

رویدادهای DOM

هنگام کار با رویدادهای DOM در ری‌اکت، type رویداد اغلب می‌تواند از event handler استنباط شود. با این حال، وقتی می‌خواهید تابعی را برای ارسال به یک event handler استخراج کنید، باید به صورت صریح type رویداد را تنظیم کنید.

import { useState } from 'react';

export default function Form() {
  const [value, setValue] = useState("Change me");

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setValue(event.currentTarget.value);
  }

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Value: {value}</p>
    </>
  );
}

انواع زیادی از رویدادها در typeهای ری‌اکت ارائه شده است - لیست کامل را می‌توانید اینجا پیدا کنید که بر اساس رویدادهای پرکاربرد از DOM است.

هنگام تعیین type که به دنبال آن هستید، می‌توانید ابتدا به اطلاعات hover برای event handler که استفاده می‌کنید نگاه کنید، که type رویداد را نشان خواهد داد.

اگر نیاز به استفاده از رویدادی دارید که در این لیست نیست، می‌توانید از type React.SyntheticEvent استفاده کنید که type پایه برای همه رویدادها است.

فرزندان

دو مسیر رایج برای توصیف فرزندان یک کامپوننت وجود دارد. اولین مورد استفاده از type React.ReactNode است، که اتحادی از تمام typeهای ممکن است که می‌توانند به عنوان فرزند در JSX ارسال شوند:

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

این یک تعریف بسیار گسترده از فرزندان است. دومین مورد استفاده از type React.ReactElement است، که فقط شامل المنت‌های JSX است و نه مقادیر اولیه جاوااسکریپت مانند رشته‌ها یا اعداد:

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

توجه داشته باشید که نمی‌توانید از تایپ‌اسکریپت برای توصیف اینکه فرزندان type خاصی از المنت‌های JSX هستند استفاده کنید، بنابراین نمی‌توانید از سیستم type برای توصیف کامپوننتی که فقط فرزندان <li> را می‌پذیرد استفاده کنید.

می‌توانید نمونه‌ای از هر دو React.ReactNode و React.ReactElement را با type-checker در این TypeScript playground ببینید.

ویژگی‌های style

هنگام استفاده از استایل‌های درخط در ری‌اکت، می‌توانید از React.CSSProperties برای توصیف شیء ارسال‌شده به prop @@INLN_1@@ استفاده کنید. این type اتحادی از تمام ویژگی‌های ممکن CSS است و روشی خوب برای اطمینان از ارسال ویژگی‌های معتبر CSS به prop @@INLN_2@@ و دریافت تکمیل خودکار در ویرایشگر شما است.

interface MyComponentProps {
style: React.CSSProperties;
}

یادگیری بیشتر

این راهنما مبانی استفاده از TypeScript با ری‌اکت را پوشش داده است، اما موارد بیشتری برای یادگیری وجود دارد. صفحات جداگانه API در مستندات ممکن است حاوی مستندات عمیق‌تری در مورد نحوه استفاده از آن‌ها با TypeScript باشند.

ما منابع زیر را توصیه می‌کنیم:

  • راهنمای TypeScript مستندات رسمی برای TypeScript است و اکثر قابلیت‌های کلیدی زبان را پوشش می‌دهد.

  • یادداشت‌های انتشار TypeScript قابلیت‌های جدید را به‌طور عمیق پوشش می‌دهند.

  • راهنمای سریع TypeScript ری‌اکت یک راهنمای سریع نگهداری‌شده توسط جامعه برای استفاده از TypeScript با ری‌اکت است که بسیاری از موارد خاص مفید را پوشش می‌دهد و گستردگی بیشتری نسبت به این سند ارائه می‌دهد.

  • دیسکورد جامعه TypeScript مکان عالی برای پرسیدن سوالات و دریافت کمک با مشکلات TypeScript و ری‌اکت است.