تایپ اسکریپت یک زبان برنامه نویسی محبوب است، زیرا کارهای متعددی در کد ما انجام میدهد از جمله بررسی قوی تایپ استاتیک، قابل فهم بودن و استنتاج تایپ و غیره. هنگامی که تایپ اسکریپت همراه با React استفاده میشود، تجربه توسعهدهنده را بهبود میدهد و قابلیت پیشبینی بیشتری را برای پروژه ارائه میکند.
در این مقاله قصد داریم تا با انجام یک پروژه to-do list، با نحوه استفاده از تایپ اسکریپت با React Context آشنا شویم. برای استفاده حداکثری از این مقاله، داشتن آشنایی اولیه با React و تایپ اسکریپت لازم است. برای این منظور ما آموزش TypeScript – دوره جامع و آموزش React – دوره فشرده فرانت کست را پیشنهاد میکنیم.
React Context API در نسخه React 16 به عنوان راهی برای به اشتراک گذاشتن دادهها در یک درخت کامپوننت بدون نیاز به ارسال props در هر سطح معرفی شد.
Context API برای دادههایی که سراسری در نظر گرفته میشوند، ایده آل است. اما برای یک state manager اختصاصی مثل Redux یا MobX، برای کارهایی مانند زبان فعلی کاربر، تم فعلی یا حتی دادههای یک فرم چند مرحلهای قبل از ارسال به یک API به اندازه کافی بزرگ یا پیچیده نیست.
برای آشنایی با React Context، یک برنامه to-do list میسازیم که از Context API برای مدیریت تسکهای موجود در لیست و همچنین برای موضوع theming استفاده میکند.
ما در این مقاله از Create React App برای داشتن یک پیکربندی مدرن و بدون مشکل استفاده میکنیم.
ترمینال را باز کرده و دستور زیر را در آن اجرا میکنیم:
npx create-react-app react-context-todo --template typescript
برای ساختن آسان یک پروژه تایپ اسکریپت با CRA، باید flag --template typescript
را به پروژه اضافه کنیم، در غیر این صورت برنامه ما فقط از جاوااسکریپت پشتیبانی خواهد کرد.
در مرحله بعد، ساختار پروژه را به صورت زیر آماده میکنیم:
src ├── @types │ └── todo.d.ts ├── App.tsx ├── components │ ├── AddTodo.tsx │ └── Todo.tsx ├── containers │ └── Todos.tsx ├── context │ └── todoContext.tsx ├── index.tsx ├── react-app-env.d.ts └── styles.css
در اینجا، دو فایل داریم که باید به آنها توجه داشته باشیم:
context/todoContext.tsx
، که context ایجاد شده را برای functionality برنامه to-do و provider آن export میکند.todo.d.ts
در فایل @types
شامل تعاریف تایپ برای بخشهایی از برنامه است که مربوط به اجرای to-do list میباشد.یکی از بهترین کارهایی که در پروژه خود میتوانیم انجام دهیم این است که فایلهای اختصاصی برای تعریف تایپ داشته باشیم. زیرا این کار ساختار پروژه ما را بهبود میبخشد. میتوانیم تایپهایی که تعریف کردهایم را بدون import کردن و به صورت رفرنس استفاده نماییم. یا این که در ابتدا آنها را export کرده و در فایل دیگری import کنیم و مورد استفاده قرار دهیم.
تایپهای تایپ اسکریپت این امکان را به ما میدهند تا بتوانیم تعیین کنیم که یک متغیر یا تابع، به عنوان مقداری که به کامپایلر کمک میکند تا قبل از زمان اجرا خطاها را تشخیص دهد، انتظار چه تایپی را داشته باشد:
// @types.todo.ts export interface ITodo { id: number; title: string; description: string; status: boolean; } export type TodoContextType = { todos: ITodo[]; saveTodo: (todo: ITodo) => void; updateTodo: (id: number) => void; };
همانطور که میبینیم، اینترفیس ITodo
شکل یک آبجکت to-do
را تعریف میکند. در مرحله بعد، تایپ TodoContextType
را داریم که آرایهای از to-dos را export میکند و متدهایی برای افزودن یا بهروزرسانی یک to-do انجام میدهد.
React Context ما را قادر میسازد تا بدون استفاده از props، بتوانیم state را در بین کامپوننتهای خود به اشتراک بگذاریم و آن را مدیریت کنیم. context دادهها را فقط به کامپوننتهایی که به آنها نیاز دارند ارائه میدهد:
// context/todoContext.tsx import * as React from 'react'; import { TodoContextType, ITodo } from '../@types/todo'; export const TodoContext = React.createContext<TodoContextType | null>(null); const TodoProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { const [todos, setTodos] = React.useState<ITodo[]>([ { id: 1, title: 'post 1', description: 'this is a description', status: false, }, { id: 2, title: 'post 2', description: 'this is a description', status: true, }, ]);
در کدی که داریم کار خود را با ایجاد یک context جدید و تنظیم تایپ آن برای مطابقت با TodoContextType
یا null
شروع میکنیم. هنگام ساخت context، مقدار پیشفرض را به طور موقت null
میکنیم. مقادیر مورد نظر را به provider تخصیص میدهیم.
در مرحله بعد، کامپوننت TodoProvider
را ایجاد میکنیم که context را برای مصرفکنندگان کامپوننت فراهم میکند. در اینجا، ما state را با برخی از دادهها مقداردهی اولیه میکنیم تا todos
که داریم کار کند:
// context/todoContext.tsx const saveTodo = (todo: ITodo) => { const newTodo: ITodo = { id: Math.random(), // not really unique - but fine for this example title: todo.title, description: todo.description, status: false, } setTodos([...todos, newTodo]) } const updateTodo = (id: number) => { todos.filter((todo: ITodo) => { if (todo.id === id) { todo.status = true setTodos([...todos]) } }) }
تابع saveTodo
یک to-do جدید بر اساس اینترفیس ITodo
ایجاد میکند و سپس آبجکت را به آرایه to-dos اضافه مینماید. تابع بعدی یعنیupdateTodo
، به دنبال ID مربوط به to-doهای ارسال شده به عنوان پارامتر در آرایه to-dos میگردد و سپس آن را بهروزرسانی میکند:
// context/todoContext.tsx return ( <TodoContext.Provider value={{ todos, saveTodo, updateTodo }}> {children} </TodoContext.Provider> ); }; export default TodoProvider;
در مرحله بعد، مقادیر را به context ارسال میکنیم تا آنها را برای کامپوننتها قابل استفاده کنیم:
// context/todoContext.tsx import React from 'react'; import { TodoContextType, ITodo } from '../@types/todo'; export const TodoContext = React.createContext<TodoContextType | null>(null); const TodoProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { const [todos, setTodos] = React.useState<ITodo[]>([ { id: 1, title: 'post 1', description: 'this is a description', status: false, }, { id: 2, title: 'post 2', description: 'this is a description', status: true, }, ]); const saveTodo = (todo: ITodo) => { const newTodo: ITodo = { id: Math.random(), // not really unique - but fine for this example title: todo.title, description: todo.description, status: false, }; setTodos([...todos, newTodo]); }; const updateTodo = (id: number) => { todos.filter((todo: ITodo) => { if (todo.id === id) { todo.status = true; setTodos([...todos]); } }); }; return <TodoContext.Provider value={{ todos, saveTodo, updateTodo }}>{children}</TodoContext.Provider>; }; export default TodoProvider;
اکنون میتوانیم context را مورد استفاده قرار دهیم. در بخشهای بعدی کامپوننتها را ایجاد خواهیم کرد.
هوک UseContext
یک ابزار حیاتی در توسعه React برای مدیریت کارآمد state و انتقال داده بین کامپوننتها است. این هوک به کامپوننتها کمک میکند تا بدون این که props را از کامپوننتهای واسطه عبور دهند، به مقادیر context دسترسی پیدا کنند.
هنگامی که هوک UseContext
با تایپ اسکریپت استفاده میشود، یک لایه اضافی از type safety به برنامه میافزاید و اطمینان حاصل میکند که تایپهای صحیح در سراسر برنامه مورد استفاده قرار میگیرند. این هوک بخشی از React Hooks API است و مقادیر را از React Context مصرف میکند و مقدار context فعلی را برای آن context برمیگرداند.
در بخش بعدی نحوه import کردن و استفاده از هوک UseContext
را در کامپوننت خود بررسی خواهیم کرد.
در ادامه یک کامپوننت فرم داریم که به ما این امکان را میدهد تا دادههای وارد شده توسط کاربر را با استفاده از هوک useState
دیریت کنیم. هنگامی که دادههای فرم را دریافت میکنیم، از تابع saveTodo
که از آبجکت context به دست آمده است، برای افزودن یک to-do جدید استفاده میکنیم:
// components/AddTodo.tsx import * as React from 'react'; import { TodoContext } from '../context/todoContext'; import { TodoContextType, ITodo } from '../@types/todo'; const AddTodo: React.FC = () => { const { saveTodo } = React.useContext(TodoContext) as TodoContextType; const [formData, setFormData] = React.useState<ITodo | {}>(); const handleForm = (e: React.FormEvent<HTMLInputElement>): void => { setFormData({ ...formData, [e.currentTarget.id]: e.currentTarget.value, }); }; const handleSaveTodo = (e: React.FormEvent, formData: ITodo | any) => { e.preventDefault(); saveTodo(formData); }; return ( <form className="Form" onSubmit={(e) => handleSaveTodo(e, formData)}> <div> <div> <label htmlFor="name">Title</label> <input onChange={handleForm} type="text" id="title" /> </div> <div> <label htmlFor="description">Description</label> <input onChange={handleForm} type="text" id="description" /> </div> </div> <button disabled={formData === undefined ? true : false}>Add Todo</button> </form> ); }; export default AddTodo;
باید به این نکته توجه داشته باشیم که برای جلوگیری از خطاهای تایپ اسکریپت، از typecasting در هوک useContext
استفاده میکنیم، زیرا context در ابتدا null
خواهد بود:
// components/Todo.tsx import * as React from 'react'; import { ITodo } from '../@types/todo'; type Props = { todo: ITodo; updateTodo: (id: number) => void; }; const Todo: React.FC<Props> = ({ todo, updateTodo }) => { const checkTodo: string = todo.status ? `line-through` : ''; return ( <div className="Card"> <div className="Card--text"> <h1 className={checkTodo}>{todo.title}</h1> <span className={checkTodo}>{todo.description}</span> </div> <button onClick={() => updateTodo(todo.id)} className={todo.status ? `hide-button` : 'Card--button'}> Complete </button> </div> ); }; export default Todo;
همانطور که در اینجا می بینیم، ما یک کامپوننت نمایشی داریم که یک to-do منفرد را نشان میدهد. این کامپوننت آبجکت todo
و تابع updateTodo
را برای بهروزرسانی آبجکت به عنوان پارامتر دریافت میکند که باید با تایپ Props
، که در بالا تعریف شده است مطابقت داشته باشند:
// containers/Todos.tsx import * as React from 'react'; import { TodoContextType, ITodo } from '../@types/todo'; import { TodoContext } from '../context/todoContext'; import Todo from '../components/Todo'; const Todos = () => { const { todos, updateTodo } = React.useContext(TodoContext) as TodoContextType; return ( <> {todos.map((todo: ITodo) => ( <Todo key={todo.id} updateTodo={updateTodo} todo={todo} /> ))} </> ); }; export default Todos;
این کامپوننت لیستی از تسکهایی که انجام شدهاند را هنگام لود صفحه به نمایش میگذارد و todos
و تابع updateTodo
را از to-do context دریافت میکند. در مرحله بعد، با ایجاد loop بر روی آرایه، آبجکتی را که میخواهیم نشان دهیم به کامپوننت Todo
منتقل میکنیم.
از این مرحله به بعد، میتوانیم to-do context را در فایل App.tsx
برای تکمیل ساخت برنامه ارائه کنیم. بنابراین، در قسمت بعدی از context provider استفاده میکنیم.
// App.tsx import * as React from 'react' import TodoProvider from './context/todoContext' import Todos from './containers/Todos' import AddTodo from './components/AddTodo' import './styles.css' export default function App() { return ( <TodoProvider> <main className='App'> <h1>My Todos</h1> <AddTodo /> <Todos /> </main> </TodoProvider> ) }
در کدی که داریم، کامپوننت TodoProvider
را import میکنیم و مصرفکنندههای to-do context را درون آن قرار میدهیم. اکنون میتوانیم به آرایه todos
و تابع افزودن یا بهروزرسانی یک to-do با استفاده از هوک useContext
در سایر کامپوننتها دسترسی داشته باشیم.
میتوانیم پروژه را در ترمینال باز کنیم و یکی از دستورات زیر را اجرا نماییم:
yarn start npm start
اگر همه چیز به درستی کار کند، میتوانیم خروجی را در آدرس http://localhost:3000
در مرورگر ببینیم.
هنگام مدیریت stateهای مشترک پیچیده در React Context، ترکیب هوک useReducer
با تایپ اسکریپت به مدیریت state در سطح بالاتر و اشتراکگذاری آن در بین کامپوننتها کمک میکند و استحکام کد و تجربیات توسعهدهنده را افزایش میدهد.
این مزایا شامل type safety، تکمیل خودکار کد و پشتیبانی از refactoring است. استفاده از هوک useReducer
همچنین به متمرکز کردن تغییرات state کمک میکند و یک انتقال state قابل پیشبینی را تضمین مینماید.
این بار برنامه خود را با استفاده از context reducer همراه با تایپ اسکریپت بازنویسی میکنیم. ابتدا تایپهای action برای reducer را تعریف میکنیم:
// @types/todo.d.ts export type TodoAction = | { type: 'ADD_TODO'; payload: ITodo } | { type: 'UPDATE_TODO'; payload: number };
سپس یک تابع reducer ایجاد میکنیم که تغییرات state را براساس actionهایی که ارسال شدهاند، مدیریت میکند. در این مثال، actionها شامل اضافه کردن یک todo
جدید یا بهروزرسانی state یک تسک موجود است:
// reducers/todoReducer.ts export const todoReducer = (state: ITodo[], action: TodoAction): ITodo[] => { switch (action.type) { case 'ADD_TODO': return [...state, action.payload]; case 'UPDATE_TODO': return state.map((todo) => todo.id === action.payload ? { ...todo, status: true } : todo ); default: return state; } };
اکنون در todoContext
که داریم، state اولیه و تابع reducer را به آن ارائه میکنیم:
// context/todoContext.tsx import * as React from 'react'; import { ITodo, TodoAction } from '../@types/todo'; import { todoReducer } from '../reducers/todoReducer'; export const TodoContext = React.createContext<{ todos: ITodo[]; dispatch: React.Dispatch<TodoAction>; } | null>(null); // TodoProvider component with the useReducer hook const TodoProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { const [todos, dispatch] = React.useReducer(todoReducer, [ { id: 1, title: 'post 1', description: 'this is a description', status: false, }, { id: 2, title: 'post 2', description: 'this is a description', status: true, }, ]); return ( <TodoContext.Provider value={{ todos, dispatch }}> {children} </TodoContext.Provider> ); }; export default TodoProvider;
در این مرحله، برنامه Reactای که داریم با استفاده از تایپ اسکریپت و به کمک context reducer پیکربندی شده است که type safety و یک رویکرد ساختاریافته برای مدیریت state سراسری را تضمین میکند.
کد موجود در فایل AddTodo.tsx
را با کد زیر بهروزرسانی میکنیم:
// components/AddTodo.tsx import * as React from 'react'; import { TodoContext } from '../context/todoContext'; import { ITodo } from '../@types/todo'; const AddTodo: React.FC = () => { const { dispatch } = React.useContext(TodoContext)!; const [formData, setFormData] = React.useState<ITodo | {}>(); const handleForm = (e: React.FormEvent<HTMLInputElement>): void => { setFormData({ ...formData, [e.currentTarget.id]: e.currentTarget.value, }); }; const handleSaveTodo = (e: React.FormEvent, formData: ITodo | any) => { e.preventDefault(); dispatch({ type: 'ADD_TODO', payload: formData }); }; return ( <form className="Form" onSubmit={(e) => handleSaveTodo(e, formData)}> <div> <div> <label htmlFor="name">Title</label> <input onChange={handleForm} type="text" id="title" /> </div> <div> <label htmlFor="description">Description</label> <input onChange={handleForm} type="text" id="description" /> </div> </div> <button disabled={formData === undefined ? true : false}>Add Todo</button> </form> ); }; export default AddTodo;
در این مثال، برای دریافت تابع dispatch
از TodoContext
، از هوک useContext
استفاده میکنیم. سپس کامپوننتی که داریم از تابع dispatch
برای ارائه قابلیت افزودن تسکهای جدید استفاده میکند.
کد موجود در فایل Todo.tsx
را با کد زیر بهروزرسانی میکنیم:
// components/Todo.tsx import * as React from 'react'; import { ITodo } from '../@types/todo'; type Props = { todo: ITodo; updateTodo: () => void; }; const Todo: React.FC<Props> = ({ todo, updateTodo }) => { const checkTodo: string = todo.status ? `line-through` : ''; return ( <div className="Card"> <div className="Card--text"> <h1 className={checkTodo}>{todo.title}</h1> <span className={checkTodo}>{todo.description}</span> </div> <button onClick={() => updateTodo()} className={todo.status ? `hide-button` : 'Card--button'}> Complete </button> </div> ); }; export default Todo;
در این قسمت کامپوننتی که داریم، آبجکت todo
را دریافت میکند و آن را به عنوان پارامترهایی که باید با تایپ Props
تعریف شده مطابقت داشته باشد بهروزرسانی میکند:
// containers/Todos.tsx import * as React from 'react'; import { ITodo } from '../@types/todo'; import { TodoContext } from '../context/todoContext'; import Todo from '../components/Todo'; const Todos = () => { const { todos, dispatch } = React.useContext(TodoContext)!; return ( <> {todos.map((todo: ITodo) => ( <Todo key={todo.id} updateTodo={() => dispatch({ type: 'UPDATE_TODO', payload: todo.id })} todo={todo} /> ))} </> ); }; export default Todos;
لیستی از todos
را نمایش میدهیم و با استفاده از تابع dispatch
که از TodoContext
دریافت میکنیم، todo را بهروزرسانی مینماییم.
باید مطمئن شویم که محتویات موجود در فایل App.tsx
را داخل TodoProvider
قرار دادهایم تا بتوانیم به آرایه todos
و تابع افزودن یا بهروزرسانی یک todo با استفاده از هوک useContext
در سایر کامپوننتها دسترسی داشته باشیم:
// App.tsx import * as React from 'react' import TodoProvider from './context/todoContext' import Todos from './containers/Todos' import AddTodo from './components/AddTodo' import './styles.css' export default function App() { return ( <TodoProvider> <main className='App'> <h1>My Todos</h1> <AddTodo /> <Todos /> </main> </TodoProvider> ) }
در این بخش، قصد داریم تا نگاهی به یکی دیگر از کاربردهای Context API یعنی مبحث theming بیاندازیم:
// @types/theme.d.ts export type Theme = 'light' | 'dark'; export type ThemeContextType = { theme: Theme; changeTheme: (theme: Theme) => void; }; };
ما تایپهای لازم برای پیادهسازی theming را ایجاد میکنیم: Theme
حالتهای ممکن را برای تم مشخص میکند وThemeContextType
ویژگیهایی را تعیین میکند که در context تم در هنگام استفاده از آن در دسترس خواهند بود.
در مرحله بعد، یک فایل themeContext.tsx
ایجاد میکنیم که context تم خام و provider آن را export میکند:
// context/themeContext.tsx import * as React from 'react'; import { Theme, ThemeContextType } from '../@types/theme'; export const ThemeContext = React.createContext<ThemeContextType | null>(null); const ThemeProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { const [themeMode, setThemeMode] = React.useState<Theme>('light'); return ( <ThemeContext.Provider value={{ theme: themeMode, changeTheme: setThemeMode }}> {children} </ThemeContext.Provider> ); }; export default ThemeProvider;
پس از ایجاد context، یک کامپوننت ThemeWrapper
میسازیم که هم آن را استفاده میکند و هم تم را در برنامه تغییر میدهد:
// components/ThemeWrapper.tsx import React from 'react'; import { ThemeContextType, Theme } from '../@types/theme'; import { ThemeContext } from '../context/themeContext'; const ThemeWrapper: React.FC<{children: React.ReactNode}> = ({ children }) => { const { theme, changeTheme } = React.useContext(ThemeContext) as ThemeContextType; const handleThemeChange = (event: React.ChangeEvent<HTMLSelectElement>) => { changeTheme(event.target.value as Theme); }; return ( <div data-theme={theme}> <select name="toggleTheme" onChange={handleThemeChange}> <option value="light">Light</option> <option value="dark">Dark</option> </select> {children} </div> ); }; export default ThemeWrapper;
با آماده شدن ThemeWrapper
، اکنون میتوانیم آن را در App.tsx
که داریم import کنیم و مورد آزمایش قرار دهیم:
// App.tsx import * as React from 'react'; import TodoProvider from './context/todoContext'; import ThemeProvider from './context/themeContext'; import Todos from './containers/Todos'; import AddTodo from './components/AddTodo'; import ThemeWrapper from './components/ThemeWrapper'; import './styles.css'; export default function App() { return ( <ThemeProvider> <TodoProvider> <ThemeWrapper> <main className="App"> <h1>My Todos</h1> <AddTodo /> <Todos /> </main> </ThemeWrapper> </TodoProvider> </ThemeProvider> ); }
هنگامی که ویژگی data-theme
توسط change handler المنت انتخابی اصلاح شد، فایل CSSای که از پیش نوشته شده است بقیه موارد را بر عهده میگیرد. میتوانیم سورس کد کامل را در این لینک مشاهده نماییم.
با وجود مزایای فراوانی که React Context دارد، اما توسعهدهندگان اغلب در هنگام استفاده از آن همراه با تایپ اسکریپت با چالشهایی مواجه میشوند.
تایپ اسکریپت میتواند برای استنباط صحیح تایپها، به خصوص با ساختارهای پیچیده یا contextهای تودرتو مشکل داشته باشد. برای بهبود type safety، باید تایپها را به صراحت برای مقادیر context تعریف کنیم یا این که از کلمه کلیدی as
برای type assertion استفاده نماییم:
// Using the context with type assertion const todoContext = useContext(TodoContext) as TodoContextType; // type assertion export const TodoContext = React.createContext<TodoContextType>();
در شرایطی که مقدار پیشفرض context به متغیرهای دیگر بستگی دارد یا این که در طول ایجاد context نمیتوانیم آن را تعیین کنیم، لازم است مقادیر undefined
یا null
را به عنوان مقدار پیشفرض به context اختصاص دهیم؛ در غیر این صورت تایپ اسکریپت خطا تایپ ایجاد میکند.
این مشکل را میتوانیم با تعریف تایپ context بهعنوان مقدار undefined
یا null
و یا استفاده از optional chaining یا non-null assertionها برای داشتن دسترسی safe، حل کنیم:
// defining the context type as nullable export const TodoContext = React.createContext<TodoContextType | null>(null); // non-null assertions const { todos, dispatch } = React.useContext(TodoContext)!; // using optional chaining const safeValue = todos?.property;
راه حل دیگر برای رفع این مشکل استفاده از تابع کمکی میباشد:
// context/todoContext.tsx export const useTodoContext = () => { const context = useContext(TodoContext); if (!context) { throw new Error('useTodoContext must be used inside the TodoProvider'); } return context; };
این تابع از هوک useContext
برای بدست آوردن مقدار فعلی TodoContext
استفاده میکند. اگر این مقدار null
یا undefined
باشد، خطایی را نشان میدهد که نیاز به استفاده از تابع useTodoContext
در TodoProvider
را بیان میکند. اگر این مقدار در دسترس باشد، با اطمینان از اینکه کامپوننت مصرفکننده، مقدار context را دریافت میکند و تایپ اسکریپت میتواند تایپهای صحیح را استنباط نماید آن را return میکند:
const TodoList: React.FC = () => { // Use the useTodoContext helper to obtain the context value safely const { todos, dispatch } = useTodoContext(); // ... rest of the component code };
اکنون، todos
و dispatch
دارای تایپهای صحیح هستند و مشکلات null
یا undefined
مدیریت میشوند.
اطمینان از type safety در توابع reducer، به ویژه هنگام کار با ساختارهای state پیچیده میتواند دشوار باشد.
استفاده از unionهای discriminated برای مدیریت شرایط action مختلف و ایجاد تعاریف روشن و دقیق برای تایپهای action، موضوع type safety را در reducer تضمین میکند و به تایپ اسکریپت در تفسیر مناسب تایپها کمک میکند:
type TodoAction = | { type: 'ADD_TODO'; payload: ITodo } | { type: 'UPDATE_TODO'; payload: number };
اگر از context استفاده بیش از اندازه داشته باشیم این موضوع میتواند تأثیر منفی بر روی رندرهای مجدد و عملکرد داشته باشد:
import React, { useContext } from 'react'; // Counter 1: Renders a counter using context const Counter1 = () => { const counter = useContext(CounterContext); console.log('Counter1 rendered'); return ( <div> <p>Counter1 Counter: {counter}</p> </div> ); }; // Counter 2: Renders another counter using context const Counter2 = () => { const counter = useContext(CounterContext); console.log('Counter2 rendered'); return ( <div> <p>Counter2 Counter: {counter}</p> </div> ); }; const App = () => { const [counter, setCounter] = useState(0); console.log('App rendered'); return ( <CounterContext.Provider value={counter}> <div> <button onClick={() => setCounter((prevCounter) => prevCounter + 1)}> Increment Counter </button> <Counter1 /> <Counter2 /> </div> </CounterContext.Provider> ); }; export default App;
در مثالی که داریم دو کامپوننت Counter1
و Counter2
از یک context به نام CounterContext
برای دسترسی به یک مقدار شمارنده استفاده میکنند. کامپوننت App
هر دو کامپوننت را رندر میکند، اما وقتی شمارنده افزایش مییابد، هر دو کامپوننت دوباره رندر میشوند. این موضوع یک مشکل رایج است که هنگام استفاده بیش از حد از context ایجاد میشود و احتمالاً در مبحث عملکرد برنامه هم مشکلاتی را ایجاد میکند.
برای برطرف کردن این مشکل، باید از context
برای دادههای سراسری یا deeply shared استفاده کنیم، راهحلهای مدیریت state جایگزین مانند Redux را در نظر بگیریم، یا بهروزرسانیهای context را با استفاده از تکنیکهای memoization بهینه کنیم:
onst Counter1 = React.memo(() => { const counter = useContext(CounterContext); console.log('Counter1 rendered'); return ( <div> <p>Counter1 Counter: {counter}</p> </div> ); }); const Counter2 = React.memo(() => { const counter = useContext(CounterContext); console.log('Counter2 rendered'); return ( <div> <p>Counter2 Counter: {counter}</p> </div> ); });
React.memo
با جلوگیری از رندرهای مجدد غیرضروری در زمانی که props بدون تغییر باقی میمانند، کامپوننتها را بهینه میکند و در نتیجه مشکلات عملکرد را که به دلیل بهروزرسانیهای بیش از حد context بود، کاهش میدهد.
تایپ اسکریپت یک زبان برنامه نویسی عالی است که میتواند کد ما را بهبود ببخشد. در این مقاله سعی کردیم نحوه استفاده از تایپ اسکریپت با React Context و روش پیادهسازی React Context Reducer با تایپ اسکریپت را بررسی کنیم. همچنین به برخی از مشکلات رایجی که ممکن است توسعهدهندگان هنگام استفاده از React Context با تایپ اسکریپت با آن مواجه شوند، پرداختیم.