Redux یک کتابخانه مدیریت state برای اپلیکیشنهای جاوااسکریپت است. این کتابخانه به ما اجازه میدهد تا اپلیکیشنهایی بسازیم که به صورت قابل پیشبینی عمل میکنند و در محیطهای مختلف، از جمله سرور و محیطهای native، اجرا میشوند. Redux Toolkit روش پیشنهادی برای نوشتن منطق Redux است و برای سادهتر کردن کار با Redux ایجاد شده است.
بهطور سنتی، نوشتن منطق Redux نیاز به مقدار زیادی کد اولیه، پیکربندی و نصب وابستگیها داشت. این موضوع کار با Redux را دشوار میکرد. RTK برای حل این مشکلات ایجاد شد. RTK شامل ابزارهایی است که وظایف رایج Redux مانند پیکربندی store، ایجاد reducerها و بهروزرسانی state را به صورت immutable سادهتر میکند.
Redux Toolkit Query (RTK Query) یک افزونه اختیاری است که در پکیج Redux Toolkit گنجانده شده است. این افزونه برای سادهسازی دریافت و ذخیرهسازی دادهها در اپلیکیشنهای وب طراحی شده است. RTK Query بر پایه Redux Toolkit ساخته شده و از Redux برای طراحی معماری داخلی خود استفاده میکند.
در این مقاله، یاد میگیریم که چگو
نه RTK Query را در اپلیکیشنهای React خود با Redux Toolkit ادغام کنیم. برای این کار، یک اپلیکیشن ساده CRUD برای مدیری
ت فیلمها خواهیم ساخت.
هسته اصلی RTK Query تابع createApi
است. این تابع به ما اجازه میدهد که یک API slice تعریف کنیم. API slice شامل آدرس پایه سرور و مجموعهای از endpointها است که نحوه دریافت و تغییر دادهها را از سرور مشخص میکنند.
RTK Query به صورت خودکار یک هوک سفارشی برای هر یک از endpointهای تعریف شده تولید میکند. این هوکها را میتوانیم در کامپوننتهای React برای نمایش محتوای مورد نظر بر اساس وضعیت درخواست API استفاده کنیم.
کد زیر نحوه ایجاد یک API slice با استفاده از createApi
را نشان میدهد:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' export const apiSlice = createApi({ reducerPath: 'api', baseQuery: fetchBaseQuery({ baseUrl: 'https://server.co/api/v1/'}), endpoints: (builder) => ({ getData: builder.query({ query: () => '/data', }) }) }) export const { useGetDataQuery } = apiSlice;
fetchBaseQuery
یک wrapper سبک برای تابع fetch جاوااسکریپت است که درخواستهای API را سادهتر میکند. ویژگی reducerPath
دایرکتوری ذخیره API slice ما را مشخص میکند که معمولاً با نام api
تعریف میشود. ویژگی baseQuery
از تابع fetchBaseQuery
برای تعیین آدرس پایه سرور استفاده میکند. این آدرس را میتوانیم به عنوان ریشهای در نظر بگیریم که سایر endpointها به آن اضافه میشوند.
useGetDataQuery
یک هوک auto-generated است که میتوانیم آن را در کامپوننتها مورد استفاده قرار دهیم.
در این بخش، نحوه ادغام RTK Query با Redux Toolkit را با ساخت یک اپلیکیشن ساده مدیریت فیلمها یاد میگیریم. در این اپلیکیشن، کاربران میتوانند فیلمهای ذخیره شده در بکاند را مشاهده کنند (البته این یک بکاند شبیهسازی شده خواهد بود)، فیلمهای جدید اضافه کنند و همچنین فیلمها را بهروزرسانی یا حذف نمایند. به طور کلی، یک اپلیکیشن CRUD با استفاده از RTK Query خواهیم ساخت.
همچنین در این آموزش از تایپ اسکریپت استفاده خواهیم کرد. اگر بخواهیم از جاوااسکریپپت استفاده کنیم، میتوانیم تایپها و interface
ها را حذف کنیم و ..tsx
/.ts
را با ..jsx
/.js
جایگزین نماییم.
یک پروژه جدید React را با استفاده از دستور زیر ایجاد میکنیم:
npm create vite@latest
سپس، پکیجهای react-redux
و @reduxjs/toolkit
را با استفاده از دستور زیر نصب میکنیم:
# npm npm install @reduxjs/toolkit react-redux # yarn yarn add @reduxjs/toolkit react-redux
برای بخش بکاند، از json-server
استفاده میکنیم. json-server
یک ابزار سبک در Node.js است که با استفاده از فایلهای JSON، یک API شبیهسازی شده RESTful ایجاد میکند. این ابزار به توسعهدهندگان فرانتاند این امکان را میدهد که بدون نیاز به نوشتن کد سمت سرور، یک API آزمایشی ایجاد کنند.
برای اطلاعات بیشتر درباره json-server
، مطالعه مستندات آن میتواند مفید باشد.
با استفاده از دستور زیر، json-server
را نصب میکنیم:
npm install -g json-server
در دایرکتوری root برنامه، یک پوشه به نام data
ایجاد میکنیم. درون این پوشه، یک فایل به نام db.json
میسازیم. این فایل محلی خواهد بود که «بکاند» ما در آن ذخیره میشود.
در دایرکتوری src
، دو پوشه component
و state
ایجاد میکنیم.
داخل پوشه component
، دو پوشه CardComponent
و Modal
و یک فایل به نام Movies.tsx
ایجاد میکنیم.
داخل پوشه state
نیز، یک پوشه به نام movies
و یک فایل به نام store.ts
میسازیم.
پس از ایجاد این پوشهها و فایلها، ساختار برنامه ما باید به شکل زیر باشد:
MY-APP │-- data │ └── db.json │-- node_modules │-- public │-- src │ │-- assets │ │-- component │ │ │-- CardComponent │ │ │-- Modal │ │ └── Movies.tsx │ │-- state │ │ └── movies │ │ └── store.ts │ │-- App.css │ │-- App.tsx │ │-- index.css │ │-- main.tsx │ └── vite-env.d.ts │-- .gitignore
ابتدا باید JSON Server را راهاندازی کنیم:
فایل db.json
را باز کرده و کد زیر را درون آن قرار میدهیم:
{ "movies": [ { "title": "John Wick", "description": "Retired assassin John Wick is pulled back into the criminal underworld when gangsters kill his beloved dog, a gift from his late wife. With his unmatched combat skills and a thirst for vengeance, Wick single-handedly takes on an entire criminal syndicate.", "year": 2014, "thumbnail": "https://m.media-amazon.com/images/M/MV5BNTBmNWFjMWUtYWI5Ni00NGI2LWFjN2YtNDE2ODM1NTc5NGJlXkEyXkFqcGc@._V1_.jpg", "id": "2" }, { "id": "3", "title": "The Dark Knight", "year": 2008, "description": "Batman faces off against his archenemy, the Joker, a criminal mastermind who plunges Gotham City into chaos. As the Joker tests Batman’s limits, the hero must confront his own ethical dilemmas to save the city from destruction.", "thumbnail": "https://m.media-amazon.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_FMjpg_UX1000_.jpg" }, { "title": "Die Hard", "description": "NYPD officer John McClane finds himself in a deadly hostage situation when a group of terrorists takes control of a Los Angeles skyscraper during a Christmas party. Armed only with his wit and a handgun, McClane must outsmart the heavily armed intruders to save his wife and others.", "year": 1988, "thumbnail": "https://m.media-amazon.com/images/M/MV5BMGNlYmM1NmQtYWExMS00NmRjLTg5ZmEtMmYyYzJkMzljYWMxXkEyXkFqcGc@._V1_.jpg", "id": "4" }, { "title": "Mission: Impossible – Fallout", "description": "Ethan Hunt and his IMF team must track down stolen plutonium while being hunted by assassins and former allies. With incredible stunts and non-stop action sequences, Hunt races against time to prevent a global catastrophe.", "year": 2018, "thumbnail": "https://m.media-amazon.com/images/M/MV5BMTk3NDY5MTU0NV5BMl5BanBnXkFtZTgwNDI3MDE1NTM@._V1_.jpg", "id": "5" }, { "title": "Gladiator", "description": "Betrayed by the Emperor’s son and left for dead, former Roman General Maximus rises as a gladiator to seek vengeance and restore honor to his family. His journey from slavery to becoming a champion captures the hearts of Rome’s citizens.", "year": 2010, "thumbnail": "https://m.media-amazon.com/images/M/MV5BZmExODVmMjItNzFlZC00MDA0LWJkYjctMmQ0ZTNkYTcwYTMyXkEyXkFqcGc@._V1_.jpg", "id": "6" } ] }
سپس، JSON Server را با استفاده از دستور زیر اجرا میکنیم:
json-server --watch data\db.json --port 8080
این دستور، JSON Server را اجرا کرده و یک API Endpoint را روی پورت ۸۰۸۰ راهاندازی میکند.
در مرحلهی بعد، یک API Slice ایجاد میکنیم. این API Slice برای پیکربندی Redux store استفاده میشود.
به پوشهی movies
رفته و یک فایل به نام movieApiSlice.ts
ایجاد میکنیم. فایل movieApiSlice.ts
را باز کرده و کد زیر را در آن قرار میدهیم:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; export const moviesApiSlice = createApi({ reducerPath: "movies", baseQuery: fetchBaseQuery({ baseUrl: "http://localhost:8080", }), endpoints: (builder) => { return { getMovies: builder.query({ query: () => `/movies`, }), addMovie: builder.mutation({ query: (movie) => ({ url: "/movies", method: "POST", body: movie, }), }), updateMovie: builder.mutation({ query: (movie) => { const { id, ...body } = movie; return { url: `movies/${id}`, method: "PUT", body } }, }), deleteMovie: builder.mutation({ query: ({id}) => ({ url: `/movies/${id}`, method: "DELETE", body: id, }), }), }; }, }); export const { useGetMoviesQuery, useAddMovieMutation, useDeleteMovieMutation, useUpdateMovieMutation, } = moviesApiSlice;
در کد بالا، یک movieApiSlice
با استفاده از تابع createApi
از RTK Query ایجاد شده است که یک آبجکت را به عنوان پارامتر دریافت میکند.
reducerPath
مسیر مربوط به API Slice را مشخص میکند.baseQuery
از fetchBaseQuery
استفاده میکند. تابع fetchBaseQuery
یک آبجکت را به عنوان پارامتر دریافت کرده و شامل ویژگی baseURL
است که آدرس ریشهی API ما را مشخص میکند.http://localhost:8080
به عنوان آدرس سرور JSON استفاده کردهایم.endpoints
، نحوهی تعامل API ما را مشخص میکند. این ویژگی یک تابع است که پارامتر builder
را دریافت کرده و یک آبجکت شامل متدهایی مانند getMovies
، addMovie
، updateMovie
و deleteMovie
را return میکند، که برای تعامل با API مورد استفاده قرار میگیرد.endpoints
نامگذاری میشوند.این هوکهای سفارشی به ما این امکان را میدهند که از داخل کامپوننتهای فانکشنال با API تعامل داشته باشیم.
در این مرحله، باید Redux Store خود را تنظیم کنیم. به فایل store.ts
که در پوشهی state قرار دارد میرویم و کد زیر را در آن قرار میدهیم:
import { configureStore } from "@reduxjs/toolkit"; import { moviesApiSlice } from "./movies/moviesApiSlice"; export const store = configureStore({ reducer: { [moviesApiSlice.reducerPath]: moviesApiSlice.reducer, }, middleware: (getDefaultMiddleware) => { return getDefaultMiddleware().concat(moviesApiSlice.middleware); } })
در کد بالا، یک Redux store با استفاده از تابع configureStore
از Redux Toolkit راهاندازی کردهایم.
reducer
یک reducer را برای بهروزرسانی state در Redux Store مشخص میکند. moviesApiSlice.reducer
reducerای است که state مربوط به API ما را بهروزرسانی میکند.middleware
، یک Middleware برای مدیریت بهروزرسانیهای غیرهمزمان state ایجاد کردهایم. نیازی نیست نگران این بخش باشیم، این ویژگی برای انجام عملیات Caching و سایر قابلیتهای RTK Query ضروری است.قبل از ادامهی کار، باید Redux store را به برنامهی خود اضافه نماییم.
برای این کار، به فایل main.tsx
یا index.tsx
میرویم و کد آن را با کد زیر جایگزین میکنیم:
// main.tsx import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import App from "./App.tsx"; import { Provider } from "react-redux"; import { store } from "./state/store.ts"; createRoot(document.getElementById("root")!).render( <StrictMode> <Provider store={store}> <App /> </Provider> </StrictMode> );
در کد بالا:
Provider
از react-redux
و store
که قبلاً ایجاد کردیم، import شده است.Provider
اطراف کامپوننت App
قرار داده شده است.store
برای ارسال Redux Store به کل برنامه استفاده میشود.در این بخش، قصد داریم تا کامپوننت Movies.tsx
را پیادهسازی کنیم که تمام منطق برنامه در آن قرار دارد.
به فایل Movies.tsx
میرویم و کد زیر را در آن قرار میدهیم:
import "../movie.css"; import { ChangeEvent, FormEvent, useState } from "react"; import { useGetMoviesQuery, useAddMovieMutation, useDeleteMovieMutation, } from "../state/movies/moviesApiSlice"; import MovieCard from "./CardComponent/MovieCard"; export interface Movie { title: string; description: string; year: number; thumbnail: string; id: string; } export default function Movies() { // Form input states const [title, setTitle] = useState<string>(""); const [year, setYear] = useState<string>(""); const [thumbnail, setThumbnail] = useState<string>(""); const [description, setDescription] = useState<string>(""); const { data: movies = [], isLoading, isError } = useGetMoviesQuery({}); const [ addMovie ] = useAddMovieMutation(); const [ deleteMovie ] = useDeleteMovieMutation(); // Handle form submission to add a new movie const handleSubmit = (e: FormEvent<HTMLFormElement>): void => { e.preventDefault(); console.log("New movie submitted:", { title, thumbnail, description, year }); addMovie({ title, description, year: Number(year), thumbnail, id: String(movies.length + 1) }) // Reset form inputs after submission setTitle(""); setThumbnail(""); setDescription(""); setYear(""); }; if (isError) { return <div>Error</div>; } if (isLoading) { return <div>Loading...</div>; } return ( <div className="movie-container"> <h2>Movies to Watch</h2> {/* Form to add a new movie */} <div className="new-movie-form"> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="title">Title</label> <input type="text" name="title" id="title" placeholder="Enter movie title" value={title} onChange={(e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)} required /> </div> <div className="form-group"> <label htmlFor="imageAddress">Image Link:</label> <input type="text" name="imageAddress" id="imageAddress" placeholder="Enter image link address" value={thumbnail} onChange={(e: ChangeEvent<HTMLInputElement>) => setThumbnail(e.target.value)} required /> </div> <div className="form-group"> <label htmlFor="year">Year of release:</label> <input type="text" name="year" id="year" placeholder="Enter year of release" value={year} onChange={(e: ChangeEvent<HTMLInputElement>) => setYear(e.target.value)} /> </div> <div className="form-group"> <label htmlFor="description">Description</label> <textarea name="description" id="description" placeholder="Enter movie description" value={description} onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)} required ></textarea> </div> <button type="submit">Add Movie</button> </form> </div> {/* Render list of movies */} <div className="movie-list"> {movies.length === 0 ? ( <p>No movies added yet.</p> ) : ( movies.map((movie: Movie) => ( <div key={movie.id}> <MovieCard movie={movie} deleteMovie={deleteMovie} /> </div> )) )} </div> </div> ); }
در کد بالا، ما یک کامپوننت Movies
ایجاد میکنیم که از RTK Query برای مدیریت عملیات CRUD بهرهمند میشود.
ابتدا توابع useGetMoviesQuery
، useAddMovieMutation
و useDeleteMovie
را از moviesApiSlice
import میکنیم. این توابع برای دریافت، اضافه کردن و حذف فیلمها استفاده میشوند.
کامپوننت MovieCard
را نیز import میکنیم، که برای نمایش هر فیلم مورد استفاده قرار میگیرد. این کامپوننت را در مرحله بعد ایجاد خواهیم کرد.
اینترفیس Movie
برای تعریف ساختار دادههای فیلم استفاده شده است. اگر در پروژه خود از جاوااسکریپت استفاده میکنیم این قسمت را نادیده میگیریم.
متغیرهای state تعریف میکنیم که شامل title
، year
، thumbnail
و description
برای ذخیره مقادیر فرم هستند.
هوک useGetMoviesQuery
دادههای فیلم را هنگام بارگذاری کامپوننت fetch میکند و سه مقدار data
(با نام مستعار movies
)، isLoading
و isError
را return میکند.
دو هوک useAddMovieMutation
و useDeleteMovieMutation
، توابع addMovie
و deleteMovie
را return میکنند که به ترتیب برای افزودن و حذف فیلمها مورد استفاده قرار میگیرند.
تابع handleSubmit
ارسال فرم را کنترل میکند. هنگامی که فرم ارسال می شود، تابع addMovie
را برای افزودن فیلم جدید فراخوانی میکند. مقدار year
را به عدد تبدیل کرده و id
را بر اساس طول آرایه فیلمها تولید میکند.
برای مدیریت خطاها و وضعیت بارگذاری:
اگر isError
مقدار true
داشته باشد، پیام خطا نمایش داده میشود.
اگر isLoading
مقدار a-enlighter-language="generic">true
داشته باشد، پیام «Loading...
» نمایش داده میشود.
در نهایت، اگر همه چیز به درستی پیش برود ساختار JSX بازگردانده میشود که شامل موارد زیر است:
MovieCard
نمایش داده میشوند. هر MovieCard
دادههای یک movie
را دریافت کرده و تابع deleteMovie
را برای حذف فیلم اجرا میکند.در پوشه CardComponent
، یک فایل جدید به نام MovieCard.tsx
ایجاد میکنیم. کد زیر را در این فایل قرار میدهیم:
import { useRef, useState } from "react"; import EditModal from "../Modal/EditModal"; import { Movie } from "../Movies"; type DeleteMovie = (movie:{id:string}) => void; interface MovieCardProps { movie: Movie; deleteMovie: DeleteMovie; } function MovieCard({ movie, deleteMovie }: MovieCardProps) { const dialogRef = useRef<HTMLDialogElement | null>(null); const [selectedMovie, setSelectedMovie] = useState<Movie>(movie); const handleSelectedMovie = () => { setSelectedMovie(movie); dialogRef.current?.showModal(); document.body.style.overflow = 'hidden'; } const closeDialog = (): void => { dialogRef.current?.close(); document.body.style.overflow = 'visible'; } return ( <div className="movie-wrapper" key={movie.id}> <div className="img-wrapper"> <img src={movie.thumbnail} alt={`${movie.title} poster`} /> </div> <h3> {movie.title} ({movie.year}) </h3> <p>{movie.description}</p> <div className="button-wrapper"> <button onClick={handleSelectedMovie}>Edit</button> <button onClick={() => deleteMovie({ id: movie.id })}>Delete</button> </div> <EditModal dialogRef={dialogRef} selectedMovie={selectedMovie} closeDialog={closeDialog} /> </div> ); } export default MovieCard;
در کد بالا، ما یک کامپوننت MovieCard
برای نمایش فیلمها بر روی صفحه ایجاد میکنیم.
کدی که داریم کارهای زیر را انجام میدهد:
از useRef
و useState
برای مدیریت state و رفرنسهای کامپوننت استفاده میکند.
کامپوننت EditModal
را import میکنیم که برای ویرایش اطلاعات فیلم مورد استفاده قرار میگیرد.
MovieCard
دو prop دریافت میکند:
movie
: اطلاعات فیلمdeleteMovie
: تابعی برای حذف فیلممدیریت نمایش Modal:
dialogRef
برای مدیریت رفرنس Modal استفاده میکنیم.selectedMovie
با مقدار prop movie
مقداردهی اولیه میشود و فیلمی را که برای ویرایش انتخاب شده، ذخیره میکند.تابع handleSelectedMovie
با کلیک روی دکمه Edit
فراخوانی میشود و کارهای زیر را انجام میدهد:
selectedMovie
را روی آبجکت فیلم فعلی تنظیم میکند.EditModal
را با استفاده از dialogRef.current?.showModal()
باز میکند.document.body.style.overflow
روی 'hidden'
از پیمایش صفحه در زمانی که Modal باز است، جلوگیری میکند.تابع closeDialog
با استفاده از dialogRef.current?.close()
Modal باز را می بندد و با تنظیم document.body.style.overflow
به 'visible'
رفتار اسکرول صفحه را مجددا فعال میکند.
در دستور return
، یک ساختار JSX بازگردانده میشود که:
h3
قرار داد نشان میدهد،handleSelectedMovie
را برای باز کردن EditModal
فعال میکند.deleteMovie
را فراخوانی میکند و فیلم مشخص شده را از API حذف مینماید.کامپوننت EditModal
در اینجا نمایش داده شده و dialogRef
، closeDialog
و selectedMovie
را دریافت میکند.
در پوشه Modal
، فایلی به نام EditModal.tsx
ایجاد کرده و کد زیر را در آن قرار میدهیم:
import { useUpdateMovieMutation } from "../../state/movies/moviesApiSlice"; import { Movie } from "../Movies"; import "./modal.css"; import { useState, RefObject, FormEvent } from "react"; interface EditModalProps { dialogRef: RefObject<HTMLDialogElement>; selectedMovie: Movie; closeDialog: () => void; } function EditModal({ dialogRef, selectedMovie, closeDialog }: EditModalProps) { const [title, setTitle] = useState<string>(selectedMovie.title); const [year, setYear] = useState<string | number>(selectedMovie.year); const [description, setDescription] = useState<string>(selectedMovie.description); const [thumbnail, setThumbnail] = useState<string>(selectedMovie.thumbnail); const [updateMovie] = useUpdateMovieMutation(); async function handleUpdateMovie(e: FormEvent<HTMLFormElement>){ e.preventDefault(); try { await updateMovie({title, description, year: Number(year), thumbnail, id: selectedMovie.id}); closeDialog(); } catch (error) { alert(`${error} occurred`); } } return ( <dialog ref={dialogRef} className="modal-dialog"> <form onSubmit={handleUpdateMovie}> <div className="form-group"> <label htmlFor="title">Title:</label> <input type="text" id="title" value={title} onChange={(e) => setTitle(e.target.value)} /> </div> <div className="form-group"> <label htmlFor="year">Year of release:</label> <input type="text" id="year" value={year} onChange={(e) => setYear(e.target.value)} /> </div> <div className="form-group"> <label htmlFor="thumbnail">Image URL:</label> <input type="text" id="thumbnail" value={thumbnail} onChange={(e) => setThumbnail(e.target.value)} /> </div> <div className="form-group"> <label htmlFor="description">Description:</label> <textarea id="description" value={description} onChange={(e) => setDescription(e.target.value)} ></textarea> </div> <button type="submit">Save</button> </form> <button className="close-btn" onClick={closeDialog}> Close </button> </dialog> ); } export default EditModal;
کدی که داریم کارهای زیر را انجام میدهد:
<dialog>
برای ایجاد Modal ویرایش فیلم استفاده میکند.form
داخل المنت dialog
، اطلاعات فیلم انتخاب شده را نمایش میدهد.useUpdateMovieMutation
برای ارسال درخواست ویرایش به API مورد استفاده قرار میگیرد.handleUpdateMovie
:
updateMovie
بهروزرسانی میکند.closeDialog
Modal را میبندد.به فایل App.tsx
میرویم و کامپوننت Movies
را با استفاده از کد زیر، به آن اضافه میکنیم:
import "./App.css"; import Movies from "./components/Movies"; function App() { return ( <div> <Movies /> </div> ); } export default App;
تا این بخش از مقاله توانستیم با موفقیت RTK Query را با Redux Toolkit ادغام کنیم.
در این بخش، یاد میگیریم که Caching در RTK Query چگونه کار میکند و چطور میتوانیم کشها را invalidate کنیم.
در برنامهنویسی، Caching یکی از سختترین مفاهیم است. اما RTK Query این کار را برای ما سادهتر میکند.
هنگامی که API خود را فراخوانی میکنیم، RTK Query به طور خودکار نتیجه درخواست موفقیتآمیز را در کش ذخیره میکند. این یعنی برای درخواستهای بعدی به همان API، نتیجه کش شده را return میکند.
برای مثال، اگر یک فیلم را در اپلیکیشن خود ویرایش کنیم، ممکن است متوجه شویم که تغییری در ظاهر دادهها ایجاد نشده است. این به این معنی نیست که عملیات ویرایش کار نمیکند، بلکه نتیجهای که دریافت میکنیم همان نسخه کش شده میباشد.
برای جلوگیری از این رفتار، باید هر بار که تغییری در بکاند ایجاد میکنیم، کش را invalidate نماییم. این کار باعث میشود RTK Query به طور خودکار دادهها را دوباره از سرور دریافت کند تا تغییرات ما اعمال شوند.
به فایل moviesApiSlice.ts
میرویم و کد آن را با نسخه زیر جایگزین مینماییم:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; export const moviesApiSlice = createApi({ reducerPath: "movies", baseQuery: fetchBaseQuery({ baseUrl: "http://localhost:8080", }), tagTypes: ['Movies'], endpoints: (builder) => { return { getMovies: builder.query({ query: () => `/movies`, providesTags: ['Movies'] }), addMovie: builder.mutation({ query: (movie) => ({ url: "/movies", method: "POST", body: movie, }), invalidatesTags: ['Movies'] }), updateMovie: builder.mutation({ query: (movie) => { const { id, ...body } = movie; return { url: `movies/${id}`, method: "PUT", body } }, invalidatesTags: ['Movies'] }), deleteMovie: builder.mutation({ query: ({id}) => ({ url: `/movies/${id}`, method: "DELETE", body: id, }), invalidatesTags: ['Movies'] }), }; }, }); export const { useGetMoviesQuery, useAddMovieMutation, useDeleteMovieMutation, useUpdateMovieMutation, } = moviesApiSlice;
در کد بالا، ویژگی tagTypes
را به moviesApiSlice
اضافه میکنیم و مقدار آن را [Movies]
تنظیم مینماییم. این ویژگی امکان invalidate کردن نتایج کش شده را هنگام اعمال تغییرات در بکاند فراهم میکند.
در تابع getMovies
، ویژگی providesTags
را اضافه میکنیم. این ویژگی باعث میشود که API درخواستها را با یک برچسب مشخص کش کند، که میتوانیم هنگام انجام عملیات mutation آن را invalidate کنیم.
در توابع addMovie
، updateMovie
و deleteMovie
، ویژگی invalidatesTags
را با مقدار tagTypes
تنظیم میکنیم. این ویژگی، کش را هر بار که این توابع mutation فراخوانی شوند، invalidate میکند و باعث میشود RTK Query به طور خودکار دادهها را دوباره دریافت کند.
با این تغییرات، اکنون میتوانیم فیلمها را ویرایش و حذف کنیم و تغییرات را بلافاصله مشاهده نماییم.
در زمان ساخت اپلیکیشن، ما مدیریت خطاها را با نمایش یک متن ساده مانند “Error...
” انجام دادیم.
در دنیای واقعی، بهتر است UI مناسبی طراحی کنیم که خطاها را به صورت دقیق نمایش دهد و مشخص کند چه مشکلی پیش آمده است.
به همین ترتیب، هنگام بارگذاری دادهها، بهتر است به جای یک متن ساده، از یک Loading Spinner یا Skeleton UI استفاده کنیم تا کاربران متوجه شوند که دادهها در حال دریافت هستند.
در این مقاله، به جزئیات مدیریت خطاهای پیشرفته یا مدیریت پیشرفته وضعیت بارگذاری نمیپردازیم، اما اینها مواردی هستند که باید در اپلیکیشنهای واقعی به آنها توجه داشته باشیم.
در ادامه، چند مورد از بهترین روشها هنگام کار با RTK Query را باهم بررسی میکنیم که عبارتند از:
usePrefetch
برای دریافت دادهها قبل از اینکه کاربر به صفحهای خاص برود، استفاده کنیم. این کار باعث کاهش زمان بارگذاری صفحه و بهبود تجربه کاربری میشود.useMutation
برای بهروزرسانی دادهها، میتوانیم از Optimistic Update استفاده کنیم تا تغییرات در UI بلافاصله اعمال شوند. اگر درخواست با خطا مواجه شد، میتوانیم تغییرات را به حالت قبل برگردانیم.در این مقاله، یاد گرفتیم که RTK Query چیست و چگونه میتوانیم آن را با Redux Toolkit ادغام کنیم. همچنین یک اپلیکیشن CRUD برای مدیریت فیلمها با React ساختیم. علاوه بر این، مفاهیم caching و invalidate کردن کش در RTK Query را نیز باهم بررسی کردیم.
۵۰ درصد تخفیف ویژه نوروز فرانت کست تا ۱۵ فروردین
کد تخفیف: spr