استفاده از تایپاسکریپت
تایپاسکریپت یک روش پرطرفدار برای اضافه کردن تعریف type به کدهای جاوااسکریپت است. بهصورت پیشفرض تایپاسکریپت از JSX پشتیبانی میکند و با اضافه کردن @types/react و @types/react-dom به پروژه، پشتیبانی کامل از ریاکت وب را خواهید داشت.
یاد خواهید گرفت
نصب
تمامی فریمورکهای سطح تولید (production-grade) ریاکت از تایپاسکریپت پشتیبانی میکنند. برای نصب، راهنمای اختصاصی هر فریمورک را دنبال کنید:
افزودن تایپاسکریپت به یک پروژه ریاکت موجود
برای نصب آخرین نسخه از تعاریف type مربوط به React:
گزینههای زیر باید در فایل tsconfig.json تنظیم شوند:
domباید درlibگنجانده شود (نکته: اگر گزینهlibمشخص نشده باشد،domبهصورت پیشفرض شامل میشود).jsxباید به یکی از گزینههای معتبر تنظیم شود.preserveبرای اکثر برنامهها کافی است. اگر در حال انتشار یک کتابخانه هستید، برای انتخاب مقدار مناسب به مستنداتjsxمراجعه کنید.
تایپاسکریپت با کامپوننتهای ریاکت
نوشتن تایپاسکریپت با ریاکت بسیار شبیه به نوشتن جاوااسکریپت با ریاکت است. تفاوت اصلی هنگام کار با یک کامپوننت این است که میتوانید برای props کامپوننت خود 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 مشخص شده است. میتوانید درباره نحوه توصیف اشیاء در تایپاسکریپت در Object Types بیاموزید، اما ممکن است بخواهید از Union Types برای توصیف propهایی استفاده کنید که میتوانند یکی از چند نوع مختلف باشند، و همچنین راهنمای Creating Types from Types را برای کاربردهای پیشرفتهتر مطالعه کنید.
نمونههای هوک
تعریف typeها از @types/react شامل typeهایی برای هوکهای داخلی (built-in Hooks) است، بنابراین میتوانید بدون نیاز به تنظیمات اضافی از آنها در کامپوننتهای خود استفاده کنید. آنها به گونهای ساخته شدهاند که کد نوشته شده در کامپوننت شما را در نظر میگیرند، بنابراین در بسیاری از مواقع inferred types را دریافت خواهید کرد و در حالت ایدهآل نیازی به مدیریت جزئیات ارائه typeها ندارید.
با این حال، میتوانیم به چند نمونه از نحوه ارائه typeها برای هوکها نگاهی بیندازیم.
useState
هوک useState از مقدار ارسال شده به عنوان state اولیه برای تعیین نوع مقدار استفاده مجدد میکند. برای مثال:
// استنباط نوع به عنوان "boolean"
const [enabled, setEnabled] = useState(false);این کار نوع boolean را به enabled اختصاص میدهد، و setEnabled تابعی خواهد بود که یا یک آرگومان boolean یا تابعی که یک boolean برمیگرداند را میپذیرد. اگر میخواهید به صورت صریح نوعی را برای state ارائه دهید، میتوانید با ارائه یک آرگومان نوع به فراخوانی useState این کار را انجام دهید:
// تنظیم صریح نوع به "boolean"
const [enabled, setEnabled] = useState<boolean>(false);این در این مورد خیلی مفید نیست، اما یک مورد رایج که ممکن است بخواهید نوعی را ارائه دهید زمانی است که یک نوع اتحاد دارید. برای مثال، status در اینجا میتواند یکی از چند رشته مختلف باشد:
type Status = "idle" | "loading" | "success" | "error";
const [status, setStatus] = useState<Status>("idle");یا، همانطور که در اصول ساختاردهی state توصیه شده است، میتوانید stateهای مرتبط را به عنوان یک شیء گروهبندی کنید و امکانات مختلف را از طریق انواع شیء توصیف کنید:
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 اولیه را میگیرد. نوعهای تابع reducer از state اولیه استنباط میشوند. شما میتوانید به صورت اختیاری یک آرگومان نوع به فراخوانی useReducer ارائه دهید تا نوعی را برای state فراهم کنید، اما اغلب بهتر است نوع را روی 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> ); }
ما از تایپاسکریپت در چند جای کلیدی استفاده میکنیم:
interface Stateشکل state ریدیوسر را توصیف میکند.type CounterActionاکشنهای مختلفی که میتوانند به ریدیوسر ارسال شوند را توصیف میکند.const initialState: Stateنوعی را برای state اولیه فراهم میکند، و همچنین نوعی که بهصورت پیشفرض توسطuseReducerاستفاده میشود.stateReducer(state: State, action: CounterAction): Stateنوعها را برای آرگومانهای تابع ریدیوسر و مقدار برگشتی تنظیم میکند.
یک جایگزین صریحتر برای تنظیم نوع روی initialState، ارائه یک آرگومان نوع به 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 میتواند به عنوان یک مقدار پیشفرض منطقی به نظر برسد. با این حال، برای اینکه سیستم نوع بتواند کد شما را درک کند، باید به صورت صریح ContextShape | null را روی createContext تنظیم کنید.
این باعث میشود که نیاز داشته باشید | null را در نوع برای مصرفکنندگان 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 یک مقدار ذخیره شده را از فراخوانی تابع ایجاد/بازیابی میکنند، و تابع را فقط زمانی که وابستگیهای ارسال شده به عنوان پارامتر دوم تغییر کنند، مجدداً اجرا میکنند. نتیجه فراخوانی هوک از مقدار برگشتی تابع در پارامتر اول استنباط میشود. میتوانید با ارائه یک آرگومان نوع به هوک، صریحتر باشید.
// نوع visibleTodos از مقدار برگشتی filterTodos استنباط میشود
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);useCallback
useCallback یک مرجع پایدار به یک تابع را تا زمانی که وابستگیهای ارسال شده به پارامتر دوم یکسان باشند، فراهم میکند. مانند useMemo، type تابع از مقدار برگشتی تابع در پارامتر اول استنباط میشود، و میتوانید با ارائه یک آرگومان نوع به هوک، صریحتر باشید.
const handleClick = useCallback(() => {
// ...
}, [todos]);هنگام کار در حالت سختگیرانه تایپاسکریپت، useCallback نیاز به افزودن نوعها برای پارامترهای callback شما دارد. این به این دلیل است که type callback از مقدار برگشتی تابع استنباط میشود، و بدون پارامترها نوع نمیتواند به طور کامل درک شود.
بسته به ترجیحات سبک کد شما، میتوانید از توابع *EventHandler از نوعهای ریاکت برای ارائه نوع برای 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>
</>
);
}انواع پرکاربرد
مجموعه گستردهای از انواع وجود دارد که از بسته @types/react میآیند، وقتی با نحوه تعامل ریاکت و تایپاسکریپت راحت هستید، ارزش خواندن دارند. میتوانید آنها را در پوشه ریاکت در DefinitelyTyped پیدا کنید. ما در اینجا به چند نوع رایجتر میپردازیم.
رویدادهای 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> </> ); }
انواع زیادی از رویدادها در نوعهای ریاکت ارائه شده است - لیست کامل را میتوانید اینجا پیدا کنید که بر اساس رویدادهای پرکاربرد از DOM است.
هنگام تعیین نوعی (type) که به دنبال آن هستید، میتوانید ابتدا به اطلاعات hover برای event handler که استفاده میکنید نگاه کنید، که type رویداد را نشان خواهد داد.
اگر نیاز به استفاده از رویدادی دارید که در این لیست نیست، میتوانید از type React.SyntheticEvent استفاده کنید، که type پایه برای همه رویدادها است.
فرزندان
دو مسیر رایج برای توصیف فرزندان یک کامپوننت وجود دارد. اولین مورد استفاده از type React.ReactNode است، که اتحادی از تمام انواع ممکن است که میتوانند به عنوان فرزند در 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 در این TypeScript playground ببینید.
ویژگیهای استایل
هنگام استفاده از استایلهای درخط در ریاکت، میتوانید از React.CSSProperties برای توصیف شیء ارسال شده به prop استایل استفاده کنید. این type اتحادی از تمام ویژگیهای CSS ممکن است، و روش خوبی برای اطمینان از ارسال ویژگیهای CSS معتبر به prop استایل و دریافت تکمیل خودکار در ویرایشگر شما است.
interface MyComponentProps {
style: React.CSSProperties;
}یادگیری بیشتر
این راهنما مبانی استفاده از تایپاسکریپت با ریاکت را پوشش داده است، اما موارد بیشتری برای یادگیری وجود دارد. صفحات API جداگانه در مستندات ممکن است حاوی مستندات عمیقتری در مورد نحوه استفاده از آنها با تایپاسکریپت باشند.
ما منابع زیر را پیشنهاد میکنیم:
-
راهنمای تایپاسکریپت مستندات رسمی برای تایپاسکریپت است و اکثر ویژگیهای کلیدی زبان را پوشش میدهد.
-
یادداشتهای انتشار تایپاسکریپت ویژگیهای جدید را به صورت عمیق پوشش میدهند.
-
راهنمای سریع تایپاسکریپت ریاکت یک راهنمای سریع نگهداری شده توسط جامعه برای استفاده از تایپاسکریپت با ریاکت است، که بسیاری از موارد خاص مفید را پوشش میدهد و گستردگی بیشتری نسبت به این سند ارائه میدهد.
-
دیسکورد جامعه تایپاسکریپت مکان عالی برای پرسیدن سوالات و دریافت کمک با مشکلات تایپاسکریپت و ریاکت است.