استفاده از TypeScript
تایپاسکریپت یک روش پرطرفدار برای اضافه کردن تعریف type به کدهای جاوااسکریپت است. بهصورت پیشفرض تایپاسکریپت از JSX پشتیبانی میکند و با اضافه کردن @types/react
و @types/react-dom
به پروژه، پشتیبانی کامل از React Web را خواهید داشت.
یاد خواهید گرفت
نصب
تمامی فریمورکهای سطح تولید ریاکت از TypeScript پشتیبانی میکنند. برای نصب، راهنمای اختصاصی هر فریمورک را دنبال کنید:
افزودن TypeScript به یک پروژه React موجود
برای نصب آخرین نسخه از تعاریف type ریاکت:
گزینههای کامپایلر زیر باید در tsconfig.json
شما تنظیم شوند:
dom
باید درlib
گنجانده شود (نکته: اگر گزینهای برایlib
مشخص نشده باشد،dom
بهصورت پیشفرض گنجانده میشود).jsx
باید به یکی از گزینههای معتبر تنظیم شود.preserve
برای اکثر برنامهها کافی است. اگر در حال انتشار یک کتابخانه هستید، برای انتخاب مقدار مناسب به مستنداتjsx
مراجعه کنید.
TypeScript با کامپوننتهای ریاکت
نوشتن 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> ); }
این نحوهی نوشتن درونخطی (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
یک مقدار ذخیرهشده را از فراخوانی تابع ایجاد/بازیابی میکنند و تابع را فقط زمانی که وابستگیهای ارسالشده بهعنوان پارامتر دوم تغییر کنند، مجدداً اجرا میکنند. نتیجه فراخوانی هوک از مقدار برگشتی تابع در پارامتر اول استنباط میشود. میتوانید با ارائه یک آرگومان type به هوک، صریحتر باشید.
// نوع visibleTodos از مقدار برگشتی filterTodos استنباط میشود
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
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 و ریاکت است.