React Router محبوبترین کتابخانه مسیریابی در React است اما استفاده از آن در کنار برخی از ویژگیها ممکن است کمی پییچدهتر باشد. در این مقاله قصد داریم تا همه مواردی که باید در مورد React Router بدانیم را باهم بررسی کنیم تا بتوانیم از پیشرفتهترین ویژگیها هم به راحتی استفاده کنیم.
قبل از شروع به بررسی ویژگیهای پیشرفته React Router ابتدا میخواهیم در مورد اصول اولیه آن صحبت کنیم. برای استفاده از React Router در وب، باید npm i react-router-dom
را برای نصب آن اجرا کنیم. این کتابخانه به طور خاص نسخه DOM از React Router را نصب میکند. اگر از React Native استفاده میکنیم به جای آن باید react-router-native
را نصب کنیم. به غیر از این تفاوت کوچک، کتابخانهها تقریباً یکسان کار میکنند.
در این مقاله ما روی react-router-dom
تمرکز خواهیم کرد، اما همانطور که گفتیم هر دو این کتابخانهها تقریباً یکسان هستند.
پس از نصب کتابخانه، برای استفاده از React Router باید سه کار انجام دهیم:
سادهترین مرحله تا کنون راهاندازی Router است. تنها کاری که باید انجام دهیم این است که روتر خاصی که نیاز داریم (BrowserRouter
برای وب و NativeRouter
برای موبایل) را وارد کرده و کل برنامه خود را در آن قرار دهیم.
import React from "react" import ReactDOM from "react-dom/client" import App from "./App" import { BrowserRouter } from "react-router-dom" const root = ReactDOM.createRoot(document.getElementById("root")) root.render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> )
به طور کلی، روتر خود را در صفحه index.js
برنامه وارد میکنیم و کامپوننت App
را داخل آن قرار میدهیم. روتر درست مانند یک context در React کار میکند و تمام اطلاعات لازم را در اختیار برنامه ما قرار میدهد تا بتوانیم مسیریابی را انجام داده و از تمام هوکهای سفارشی React Router استفاده کنیم.
مرحله بعدی در React Router این است که مسیرهای خود را تعریف کنیم. این کار معمولاً در سطح بالاتر برنامه ما انجام میشود، مانند کامپوننت App
، اما میتوانیم در هر جایی که بخواهیم این کار را انجام دهیم.
import { Route, Routes } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" export function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> </Routes> ) }
تعریف مسیرها به سادگی تعریف یک کامپوننت Route
برای هر مسیر در برنامه خود و سپس قرار دادن تمام آن کامپوننتهای Route
در یک کامپوننت Routes
است. زمانی که URL ما تغییر کند، React Router به مسیرهای تعریف شده در کامپوننت Routes
نگاه میکند و محتوای موجود در پراپ element
مربوط به Route
را که path
مطابق با URL دارد رندر میکند. در مثال بالا اگر URL ما /books
باشد، در این صورت کامپوننت BookList
رندر میشود.
نکته خوبی که در مورد React Router وجود دارد این است که وقتی بین صفحات جابهجا میشویم، فقط محتوای داخل کامپوننت Routes
رفرش میشود. بقیه مطالب موجود در صفحه یکسان باقی خواهند ماند که این موضوع به عملکرد و تجربه کاربری(UX) بسیار کمک میکند.
آخرین مرحله برای React Router مدیریت navigation است. به طور معمول ما در یک اپلیکیشن با استفاده از تگهای anchor بین صفحات جابهجا میشویم، اما React Router از کامپوننت Link
سفارشی خود برای مدیریت navigation استفاده میکند. این کامپوننت فقط یک wrapper در اطراف یک تگ anchor است که برای اطمینان از اینکه همه مسیریابیها و رندرهای مجدد شرطی به درستی انجام میشود مورد استفاده قرار میگیرد. بنابراین میتوانیم از آن همانند یک تگ anchor معمولی استفاده کنیم.
import { Route, Routes, Link } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" export function App() { return ( <> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/books">Books</Link></li> </ul> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> </Routes> </> ) }
در این مثال ما دو لینک به صفحه home و books اضافه کردیم. همچنین نکتهای که وجود دارد این است که ما به جای پراپ href
که با تگ anchor مورد استفاده قرار میگیرد، از پراپ to
برای تنظیم URL استفاده کردیم. این تنها تفاوت بین کامپوننت Link
و تگ anchor است که باید آن را به خاطر بسپاریم زیرا این احتمال وجود دارد که این دو به اشتباه بهجای هم استفاده شوند.
نکته دیگری که در مورد کد جدید باید به آن توجه داشته باشیم این است که navigationای که در بالای صفحه خود آن را میفرستیم خارج از کامپوننت Routes
است، به این معنی که وقتی صفحه را تغییر میدهیم این بخش navigation دوباره رندر نمیشود زیرا فقط محتوای موجود در داخل کامپوننت Routes
است که با تغییر URL تغییر خواهد کرد.
اینجاست که React Router واقعا جالب میشود. موارد زیادی وجود دارد که میتوانیم با کمک آنها مسیریابی انجام دهیم تا مسیرهای پیچیدهتر خوانایی بالاتر داشته و در کل بسیار کاربردیتر شوند. این کار را میتوانیم از طریق پنج تکنیک اصلی انجام دهیم:
useRoutes
سادهترین و رایجترین ویژگی پیشرفته در React Router مدیریت مسیرهای پویا است. در مثالی که داریم فرض کنید که میخواهیم یک کامپوننت را برای هر Book به شکل منحصربفرد در برنامه خود ارائه دهیم. ما میتوانیم هر یک از آن مسیرها را هاردکد کنیم اما اگر صدها کتاب داشته باشیم و یا اینکه کاربران توانایی ایجاد کتاب داشته باشند، هاردکد همه این مسیرها غیرممکن است. در چنین حالتی است که به یک مسیر پویا نیاز داریم.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> <Route path="/books/:id" element={<Book />} /> </Routes>
مسیر نهایی در مثال بالا یک مسیر پویا است که دارای پارامتر پویا :id
میباشد. تعریف مسیرهای پویا در React Router به سادگی قرار دادن یک :
در مقابل هر چیزی است که میخواهیم قسمت پویا مسیر ما باشد. در مورد مثالی که ما داریم مسیر پویا با هر URLای که با /book
شروع میشود و با یک value به پایان میرسد مطابقت دارد. برای مثال، //books/1
، /books/bookName
و /books/literally-anything
همگی با مسیر پویا ما مطابقت دارند.
تقریباً همیشه وقتی یک مسیر پویا مانند این مثال داریم و میخواهیم به مقدار پویا در کامپوننت سفارشی خود دسترسی پیدا کنیم جایی است که هوک useParams
وارد عمل میشود.
import { useParams } from "react-router-dom" export function Book() { const { id } = useParams() return ( <h1>Book {id}</h1> ) }
هوک useParams
هیچ پارامتری را دریافت نمیکند و یک آبجکت با کلیدهایی را برمیگرداند که با پارامترهای پویا در مسیر ما مطابقت دارند. در مثال ما پارامتر پویا :id
است، بنابراین هوک useParams
یک آبجکت برمیگرداند که دارای یک کلید id
بوده و مقدار آن کلید، شناسه واقعی در URL خواهد بود. برای مثال، اگر URL ما /books/3
بود، صفحه ما Book3 را رندر خواهد کرد.
زمانی که ما فقط با مسیرهای دارای هاردکد سروکار داشته باشیم فهمیدن اینکه کدام مسیر رندر میشود بسیار آسان است، اما وقتی با مسیرهای پویا کار میکنیم این موضوع ممکن است کمی پیچیدهتر باشد. برای مثال این مسیرها را در نظر بگیرید:
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> <Route path="/books/:id" element={<Book />} /> <Route path="/books/new" element={<NewBook />} /> </Routes>
اگر ما URL تحت عنوان /books/new
را داشته باشیم در این صورت با کدام مسیر مطابقت دارد؟ از نظر فنی، ما دو مسیر /books/:id
و /books/new
را داریم که باهم مطابقت دارند زیرا مسیر پویا فقط فرض میکند که new
بخش :id
در URL است، بنابراین React Router به روش دیگری برای تعیین مسیری که باید رندر شود نیاز دارد.
در نسخههای قدیمیتر React Router، هر مسیری که در ابتدا تعریف شده باشد همان مسیری است که رندر میشود، بنابراین در مورد مثالی که داریم مسیر /books/:id
رندر خواهد شد که البته بدیهی است این چیزی نیست که ما میخواهیم. خوشبختانه نسخه ۶ React Router این را تغییر داد، در نتیجه اکنون React Router از یک الگوریتم برای تعیین مسیری که ما میخواهیم استفاده میکند. در مورد مثال ما بدیهی است که میخواهیم مسیر /books/new
را رندر کنیم، بنابراین React Router آن مسیر را انتخاب میکند. روش کار این الگوریتم بسیار شبیه به ویژگی CSS است زیرا سعی میکند تعیین کند کدام مسیری که با URL ما مطابقت دارد خاصترین است (کمترین تعداد المنت پویا را دارد) و آن مسیر را انتخاب میکند.
در همین حین که درمورد اولویت مسیریابی صحبت میکنیم میخواهم چگونگی ایجاد مسیری که با هر چیزی مطابقت داشته باشد را هم باهم بررسی کنیم:
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> <Route path="/books/:id" element={<Book />} /> <Route path="/books/new" element={<NewBook />} /> <Route path="*" element={<NotFound />} /> </Routes>
یک *
با هر چیزی مطابقت دارد که آن را برای مواردی مانند صفحه ۴۰۴ به یک انتخاب عالی تبدیل میکند.
درنهایت در این بخش میخواهیم با نحوه مدیریت مسیرهای تودرتو آشنا شویم. در مثال بالا ما سه مسیر داریم که با /books
شروع میشوند، بنابراین میتوانیم آنها را به شکل تودرتو در داخل یکدیگر قرار دهیم تا مسیرهای تمیزتری داشته باشیم.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books"> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes>
انجام این کار بسیار ساده است. تنها کار لازم این است که یک Route
parent بسازیم که دارای پراپ path
در مسیر مشترک برای تمام کامپوننتهای Route
childها باشد. سپس میتوانیم تمام کامپوننتهای Route
child را در داخل Route
parent قرار دهیم. تنها تفاوت این است که پراپ path
کامپوننتهای Route
childها دیگر شامل مسیر مشترک /books
نمیشوند. همچنین مسیری که برای /books
وجود داشت با یک کامپوننت Route
جایگزین میشود که پراپ path
ندارد اما در عوض دارای یک پراپ index
میباشد. همه این موارد گویای این هستند که Route
ایندکس با Route
parent یکی است.
اگر از مسیرهای تودرتو فقط برای انجام این کار استفاده کنیم در این صورت به میزان کمی از مزایای آن بهرهمند میشویم، اما قدرت واقعی مسیرهای تودرتو در نحوه مدیریت Layoutهای مشترک است.
تصور کنید که میخواهیم یک بخش nav را با لینکهایی به هر کتاب و همچنین لینکهایی به فرم کتاب جدید از هر یک از صفحات کتاب خود ارائه کنیم. برای انجام این کار معمولاً باید یک کامپوننت مشترک ایجاد کنیم تا این navigation ذخیره شود و سپس آن را در هر کامپوننت مرتبط با تککتاب import کنیم. این راه کمی گیجکننده است، بنابراین React Router راه حل خود را برای این مشکل ایجاد کرده است. اگر یک پراپ element
را به یک مسیر parent ارسال کنیم، آن کامپوننت برای هر Route
child منفرد ارائه میشود، این بدان معناست که میتوانیم به راحتی یک nav مشترک یا سایر کامپوننتهای مشترک را در هر صفحه child قرار دهیم.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BooksLayout />}> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes>
import { Link, Outlet } from "react-router-dom" export function BooksLayout() { return ( <> <nav> <ul> <li><Link to="/books/1">Book 1</Link></li> <li><Link to="/books/2">Book 2</Link></li> <li><Link to="/books/new">New Book</Link></li> </ul> </nav> <Outlet /> </> ) }
روشی که کد جدید ما کار خواهد کرد به این صورت است که هرگاه مسیری را در داخل Route
parent/book
مطابقت دهیم، کامپوننت BooksLayout
را که شامل navigation مشترک ما است، رندر میکند. سپس هر Route
childای که مطابقت داشته باشد، در هر جایی که کامپوننت Outlet
در داخل کامپوننت layout قرار میگیرد، نمایش داده میشود. کامپوننت Outlet
اساساً یک کامپوننت placeholder است که محتوای صفحه فعلی ما را رندر میکند. این ساختار خیلی مفید است و اشتراکگذاری کد بین مسیرها را بسیار آسان میکند.
اکنون آخرین روشی که میتوانیم layoutها را با React Router به اشتراک بگذاریم این است که کامپوننتهای Route
child را داخل Route
parent قرار دهیم که به جای پراپ path
فقط یک پراپ element
تعریف میکند.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BooksLayout />}> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> </Route> <Route element={<OtherLayout />}> <Route path="/contact" element={<Contact />} /> <Route path="/about" element={<About />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes>
قسمت کد <Route element={<OtherLayout />}>
دو مسیر به نامهای /contact
و /about
ایجاد میکند که هر دو در داخل کامپوننت OtherLayout
رندر میشوند. اگر بخواهیم آن مسیرها یک layout واحد را به اشتراک بگذارند، حتی اگر مسیر مشابهی نداشته باشند، این تکنیک قرار دادن چندین کامپوننت Route
در یک کامپوننت Route
parent بدون پراپ path
میتواند بسیار مفید باشد.
آخرین نکته مهمی که باید در مورد کامپوننتهای Outlet
بدانیم این است که آنها میتوانند از یک پراپ context
استفاده کنند که درست مانند context در React کار میکند.
import { Link, Outlet } from "react-router-dom" export function BooksLayout() { return ( <> <nav> <ul> <li><Link to="/books/1">Book 1</Link></li> <li><Link to="/books/2">Book 2</Link></li> <li><Link to="/books/new">New Book</Link></li> </ul> </nav> <Outlet context={{ hello: "world" }} /> </> ) }
import { useParams, useOutletContext } from "react-router-dom" export function Book() { const { id } = useParams() const context = useOutletContext() return ( <h1>Book {id} {context.hello}</h1> ) }
همانطور که در مثال بالا داریم، ما یک مقدار context تحت عنوان { hello: "world" }
را ارسال کرده و سپس در کامپوننت child از هوک useOutletContext
برای دسترسی به مقدار context استفاده میکنیم. این یک الگوی بسیار رایج است زیرا اغلب ما دادههای مشترکی را بین تمام کامپوننتهای child خود خواهیم داشت که استفاده از context میتواند بهترین انتخاب برای این کار باشد.
یکی دیگر از کارهای فوق العاده قدرتمندی که میتوانیم با React Router انجام دهیم، استفاده همزمان از چندین کامپوننت Routes
است. این کار را میتوانیم به صورت دو کامپوننت جداگانه Routes
یا به عنوان Routes
های تودرتو انجام دهیم.
اگر میخواهیم دو بخش مختلف از محتوای برنامه را رندر کنیم که هر دو به URL برنامه وابسته هستند، به چندین کامپوننت Routes
نیاز داریم. یک مثال رایج برای این مفهوم به این صورت است که مثلا ما یک sidebar داریم که میخواهیم محتوای خاصی را برای URLهای خاص در آن رندر کنیم و همچنین یک صفحه اصلی که باید محتوای خاصی را براساس URL نشان دهد.
import { Route, Routes, Link } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" import { BookSidebar } from "./BookSidebar" export function App() { return ( <> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/books">Books</Link></li> </ul> </nav> <aside> <Routes> <Route path="/books" element={<BookSidebar />}> </Routes> </aside> <Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> </Routes> </> ) }
در مثال بالا دو Routes
داریم.Routes
اصلی تمام کامپوننتهای اصلی صفحه ما را تعریف میکند و سپس یک Routes
ثانویه در داخل aside
داریم که وقتی در /books
هستیم، sidebar صفحه کتابهای ما را نمایش میدهد. این بدان معناست که اگر URL ما /books
باشد، هر دو کامپوننت Routes
محتوایی که دارند را رندر میکنند زیرا هر دو در مسیرهای خود مطابقت منحصربهفردی برای /books
دارند.
یکی دیگر از کارهایی که میتوانیم با چندین کامپوننت Routes
انجام دهیم، هاردکد کردن پراپ location
است.
<Routes location="/books"> <Route path="/books" element={<BookSidebar />}> </Routes>
با هاردکد کردن پراپ location
رفتار پیشفرض یا React Router را نادیده میگیریم. بنابراین مهم نیست که URL صفحه ما چیست، این کامپوننت Routes
با Route
خود مطابقت دارد، مثل این است که URL آن /books
میباشد.
راه دیگر برای استفاده از چندین کامپوننت Routes
این است که آنها را در داخل یکدیگر قرار دهیم. اگر مسیرهای زیادی داریم و میخواهیم با انتقال مسیرهای مشابه به فایلهای خود کدی که داریم را تمیزتر کنیم، این روش بسیار رایج است.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books/*" element={<BookRoutes />} /> <Route path="*" element={<NotFound />} /> </Routes>
import { Routes, Route } from "react-router-dom" import { BookList } from "./pages/BookList" import { Book } from "./pages/Book" import { NewBook } from "./pages/NewBook" import { BookLayout } from "./BookLayout" export function BookRoutes() { return ( <Routes> <Route element={<BookLayout />}> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> ) }
Routes
های تودرتو در React Router بسیار ساده است. تنها کاری که باید انجام دهیم این است که یک کامپوننت جدید برای ذخیره Routes
های تودرتو ایجاد کنیم، این کامپوننت باید یک کامپوننت Routes
داشته باشد و در داخل آن باید تمام کامپوننتهای Route
ای باشند که با Route
parent مطابقت دارند. در مثالی که داریم تمام مسیرهای /books
خود را به کامپوننت BookRoute
منتقل میکنیم. سپس در Routes
parent، باید Route
ای را تعریف کنیم که path
آن برابر با path
ای که همه Routes
های تودرتو به اشتراک میگذارند، باشد. در مثال ما میتواند /books
باشد. با این حال، نکته مهمی که باید به آن توجه کنیم این است که باید path
مربوط به Route
parent خود را با یک *
پایان دهیم، در غیر این صورت مطابقت با مسیر childها به درستی صورت نمیگیرد.
اساساً، کدی که ما نوشتهایم میگوید زمانی که مسیری با /book/
شروع میشود، باید داخل کامپوننت BookRoutes
جستجو کند تا ببیند آیا Route
ای وجود دارد که با آن مطابقت داشته باشد یا خیر. همچنین یک مسیر *
دیگر در BookRoutes
داریم تا مطمئن شویم که اگر URL ما با هیچ یک از BookRoutes
مطابقت نداشته باشد، کامپوننت NotFound
به درستی رندر خواهد شد.
آخرین چیزی که باید در مورد تعریف مسیرها در React Router بدانیم این است که در صورت تمایل میتوانیم برای تعریف مسیرهای خود به جای JSX از یک آبجکت جاوااسکریپت استفاده کنیم.
import { Route, Routes } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" import { Book } from "./Book" export function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/books"> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> </Route> </Routes> ) }
import { Route, Routes } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" import { Book } from "./Book" export function App() { const element = useRoutes([ { path: "/", element: <Home /> }, { path: "/books", children: [ { index: true, element: <BookList /> }, { path: ":id", element: <Book /> } ] } ]) return element }
این دو کامپوننت هر دو دقیقاً مسیرهای مشابهی دارند و تنها تفاوت آنها در نحوه تعریف آنها است. اگر بخواهیم که از هوک useRoutes
استفاده کنیم، تمام پراپهایی که معمولاً به کامپوننتهای Route
ارسال میکنیم، فقط بهعنوان جفت key/value ارسال میشوند.
اکنون که میدانیم چگونه مسیرهای خود را تعریف کنیم، باید در مورد نحوه جابهجایی بین آنها هم صحبت کنیم. این بخش به سه قسمت تقسیم خواهد شد:
ابتدا میخواهیم درمورد link navigation صحبت کنیم زیرا سادهترین و رایجترین شکل navigation است که با آن روبهرو خواهیم شد. ما قبلاً درمورد ابتداییترین شکل link navigation با استفاده از کامپوننت Link
صحبت کردهایم.
<Link to="/">Home</Link> <Link to="/books">Books</Link>
این کامپوننتهای Link
میتوانند کمی پیچیدهتر شوند. به عنوان مثال میتوانیم لینکهای absolute مانند لینکهای بالا داشته باشیم یا اینکه میتوانیم لینکهایی داشته باشیم که نسبت به کامپوننت فعلی، در حال رندر شدن هستند.
<Link to="/">Home</Link> <Link to="../">Back</Link> <Link to="edit">Edit</Link>
برای مثال تصور کنید در مسیر /books/3
با لینکهای بالا هستیم. لینک اول به مسیر /
منتهی میشود زیرا یک مسیر absolute است. هر مسیری که با یک /
شروع میشود یک مسیر absolute بهشمار میآید. لینک دوم به مسیر /books
منتهی میشود زیرا یک لینک relative است که از /books/3
یک سطح بالاتر رفته و به /books
میرسد. در نهایت، لینک سوم به صفحه /books/3/edit
میرود، زیرا مسیری را در پراپ to
به انتهای لینک فعلی اضافه میکند، زیرا یک لینک relative میباشد.
علاوه بر پراپ to
، سه پراپ دیگر وجود دارد که برای کامپوننت Link
مهم هستند.
پراپ replace
یک مقدار بولین است که وقتی روی true
تنظیم شود باعث میشود این لینک جایگزین صفحه فعلی در تاریخچه مرورگر شود. تصور کنید تاریخچه مرورگر زیر را داریم:
/ /books /books/3
اگر روی لینکی کلیک کنیم که به صفحه /books/3/edit
میرود اما دارای ویژگی replace
میباشد که روی true
تنظیم شده است، در این حالت تاریخچه جدید ما به این شکل خواهد بود:
/ /books /books/3/edit
صفحهای که در حال حاضر در آن بودیم با صفحه جدید جایگزین شد. این به این معنی است که اگر روی دکمه بازگشت (Back) در صفحه جدید کلیک کنیم، به جای صفحه /books/3
به صفحه /books
برمیگردیم.
این پراپ یک مقدار بولین دیگر است و کار با آن بسیار ساده میباشد. اگر مقدار آن روی true
تنظیم شود، کامپوننت Link
مانند یک تگ anchor معمولی عمل میکند و به جای اینکه فقط محتوای داخل کامپوننت Routes
خود را مجدداً رندر کند، یک صفحه کامل را رفرش میکند.
پراپ نهایی state
نام دارد. state
به ما این امکان را میدهد تا دادههایی را به همراه Link
خود ارسال کنیم که در هیچ کجای URL نمایش داده نمیشود. این همان چیزی است که وقتی درمورد navigation data صحبت میکنیم به طور عمیقتر بررسی خواهیم کرد.
المنت بعدی که میخواهیم در مورد آن صحبت کنیم کامپوننت NavLink
است. این کامپوننت دقیقاً مانند کامپوننت Link
کار میکند، اما به طور خاص برای نشان دادن stateهای فعال روی لینکها، به عنوان مثال در nav barها است. به طور پیشفرض اگر ویژگی to
یک NavLink
با URL صفحه فعلی یکسان باشد لینک یک کلاس active
به آن اضافه میکند که میتوانیم برای استایلسازی از آن استفاده کنیم. اگر این کافی نباشد، میتوانیم تابعی با پارامتر isActive
را به className
یا style
props یا childهای NavLink
ارسال کنیم.
<NavLink to="/" style={({ isActive }) => ({ color: isActive ? "red" : "black" })} > Home </NavLink>
NavLink
همچنین دارای یک prop به نام end
است که برای کمک به مسیریابی تودرتو مورد استفاده قرار میگیرد. به عنوان مثال، اگر در صفحه /books/3
هستیم به این معنی است که در حال رندر کردن کامپوننت Book
ای هستیم که در مسیر /books
قرار دارد. این بدان معناست که اگر یک NavLink
با یک پراپ to
تنظیم شده روی /books
داشته باشیم، فعال در نظر گرفته میشود. این موضوع به این دلیل است که اگر URL با پراپ to
در NavLink
مطابقت داشته باشد یا اگر Route
فعلی رندر شده در داخل یک کامپوننت parent باشد که دارای path
ای باشد که منطبق بر پراپ NavLink
است، در این صورت یک NavLink
فعال در نظر گرفته میشود. اگر این رفتار پیشفرض را نمیخواهیم، میتوانیم پراپ end
را روی true
تنظیم کنیم. این کار باعث میشود تا URL صفحه دقیقاً با پراپ to
در NavLink
مطابقت داشته باشد.
گاهی اوقات میخواهیم براساس مواردی مانند ارسال فرم یا عدم دسترسی به یک صفحه خاص کاربر را به صورت دستی به یک مسیر خاصی هدایت کنیم. در چنین مواقعی باید از کامپوننت Navigate
و یا از هوک useNavigation
استفاده کنیم.
کامپوننت Navigate
یک کامپوننت واقعاً ساده است که وقتی رندر میشود کاربر را به طور خودکار به پراپ to
از کامپوننت Navigate
هدایت میکند.
<Navigate to="/" />
این کامپوننت تمام ویژگیهای کامپوننت Link
را به اشتراک میگذارد بنابراین به راحتی میتوانیم آن را به to
، replace
و state
ارسال کنیم.
استفاده از هوک useNavigation
بسیار ساده است زیرا هیچ پارامتری را دریافت نمیکند و یک تابع navigate
را برمیگرداند که میتوانیم از آن برای هدایت کاربر به صفحات خاص استفاده کنیم. این تابع navigate
دو پارامتر دارد. پارامتر اول لوکیشین to
است که میخواهیم کاربر را به آن هدایت کنیم و پارامتر دوم یک آبجکت است که میتواند کلیدهایی برای replace
و state
داشته باشد.
const navigate = useNavigate() function onSubmit() { // Submit form results navigate("/books", { replace: true, state: { bookName: "Fake Title" }}) }
کد بالا کاربر را به مسیر /books
هدایت میکند. همچنین جایگزین مسیر فعلی در تاریخچه مرورگر خواهد شد و برخی از اطلاعات state را نیز منتقل خواهد کرد.
راه دیگری که میتوانیم از تابع navigate
استفاده کنیم ارسال یک عدد به آن است. این کار به ما این امکان را میدهد تا کلیک کردن روی باتن forward/back را شبیه سازی کنیم.
navigate(-1) // Go back one page in history navigate(-3) // Go back three pages in history navigate(1) // Go forward one page in history
درنهایت وقت آن است که در مورد انتقال دادهها بین صفحات صحبت کنیم. ۳ روش اصلی برای انتقال داده بین صفحات وجود دارد که عبارتند از:
قبلاً در مورد نحوه استفاده از پارامترهای پویا در URLها با استفاده از هوک useParams
صحبت کردهایم. این بهترین راه برای مدیریت اطلاعات ارسالی مانند idها بهشمار میآید.
پارامترهای جستجو همه پارامترهایی هستند که بعد از ?
در یک URL قرار میگیرند، مثلا (?name=Kyle&age=27
). برای کار با پارامترهای جستجو، باید از هوک useSearchParams
استفاده کنیم که بسیار شبیه به هوک useState
عمل میکند.
import { useSearchParams } from "react-router-dom" export function SearchExample() { const [searchParams, setSearchParams] = useSearchParams({ n: 3 }) const number = searchParams.get("n") return ( <> <h1>{number}</h1> <input type="number" value={number} onChange={e => setSearchParams({ n: e.target.value })} /> </> ) }
در این مثال، یک مقدار input داریم که با تایپ کردن، بخش جستجوی URL ما بهروزرسانی میشود. به عنوان مثال، اگر ورودی ما دارای مقدار ۳۲ باشد URL ما شبیه http://localhost:3000?n=32
خواهد بود. هوک useSearchParams
همانند هوک useState
یک مقدار اولیه میگیرد که در مورد مثال ما این مقدار اولیه n
روی ۳ تنظیم شده است. این هوک سپس دو مقدار را برمیگرداند. مقدار اول تمام پارامترهای جستجوی ما است و مقدار دوم تابعی برای بهروزرسانی پارامترهای جستجوی میباشد. تابع set فقط یک آرگومان میگیرد که مقدار جدید پارامترهای جستجوی ما است. اولین مقداری که شامل پارامترهای جستجو میباشد کمی گیجکننده به نظر میرسد زیرا این مقدار از نوع URLSearchParams
میباشد. به همین دلیل است که در خط ۵ مثال بالا باید از دستور .get
استفاده کنیم.
آخرین نوع دادهای که میتوانیم آن را ذخیره کنیم دادههای state و location است. همه این اطلاعات از طریق هوک useLocation
قابل دسترسی هستند. استفاده از این هوک بسیار ساده است زیرا فقط یک مقدار برمیگرداند و هیچ پارامتری را نمیگیرد.
const location = useLocation()
اگر آدرس http://localhost/books?n=32#id
را داشته باشیم، مقدار بازگشتی هوک useLocation
به این شکل خواهد بود:
{ pathname: "/books", search: "?n=32", hash: "#id", key: "2JH3G3S", state: null }
این آبجکت location شامل تمام اطلاعات مربوط به URL ما میباشد. همچنین حاوی یک کلید منحصربهفرد است که اگر بخواهیم اطلاعات را برای زمانی که کاربر روی دکمه Back کلیک میکند تا به صفحه بازگردد ذخیره کنیم، میتوانیم آن را برای ذخیرهسازی کش مورد استفاده قرار دهیم. همینطور یک ویژگی State داریم که از هوک useLocation
بازگردانده شده است. این داده state میتواند هر چیزی باشد و بدون ذخیره شدن در URL بین صفحات ارسال میشود. برای مثال اگر روی Link
کلیک کنیم که به شکل زیر است:
<Link to="/books" state={{ name: "Kyle" }}>
در این صورت مقدار state در آبجکت location روی { name: "Kyle" }
تنظیم میشود.
به عنوان مثال اگر بخواهیم بین صفحاتی که نباید در URL ذخیره شوند پیامهای سادهای ارسال کنیم استفاده از این روش میتواند بسیار مفید باشد. یک مثال خوب برای این موضوع میتواند چیزی شبیه پیام موفقیتآمیز بودن ایجاد کتاب جدید باشد.
تاکنون در این مقاله حدود ۹۵٪ از آنچه که باید در مورد React Router بدانیم را باهم بررسی کردیم، اما این کتابخانه هنوز مفاهیمی دارد که باید به آنها نیز بپردازیم. در بخش مفاهیم اولیه در مورد تعریف router صحبت کرده و به BrowserRouter
و NativeRouter
اشاره کردیم، اما این دو تنها روترهای موجود نیستند. در واقع ۶ روتر وجود دارد که در این بخش هر یک از آنها را به طور کامل بررسی خواهیم کرد.
این روتر پیشفرضی است که اگر روی یک وب اپلیکیشن کار میکنیم باید از آن استفاده کنیم. BrowserRouter
روتری است که در ۹۹٪ از همه برنامههایی که داریم مورد استفاده قرار خواهد گرفت، زیرا همه موارد استفاده از مسیریابی معمولی را پوشش میدهد. هر یک از روترهای دیگری که در مورد آنها صحبت خواهیم کرد موارد استفاده مخصوص خود را دارند، بنابراین اگر با این موارد استفاده مناسب برنامه ما نباشند، BrowserRouter
همان چیزی است که باید آن را انتخاب کنیم.
NativeRouter
در اصل معادل BrowserRouter
است اما برای React Native اختصاص دارد. اگر از React Native استفاده میکنیم باید این روتر را به کار بگیریم.
این روتر بسیار شبیه به BrowserRouter
کار میکند، اما تفاوت اصلی این است که به جای اینکه URL را به چیزی مانند http://localhost:3000/books
تغییر دهد، آن را مانند http://localhost:3000/#/books
در هش ذخیره میکند. همانطور که میبینیم این URL دارای یک #
بعد از URL است که نشاندهنده بخش هش URL میباشد. هر چیزی در این بخش فقط اطلاعات اضافی است که معمولاً یک id در صفحه را برای اهداف مربوط به اسکرول کردن نشان میدهد. زیرا یک صفحه به طور خودکار به المنت با id نشان داده شده توسط هش هنگام لود شدن صفحه اسکرول میشود.
در React Router این هش در واقع برای ذخیره اطلاعات id برای اسکرول استفاده نمیشود، بلکه اطلاعات مربوط به URL فعلی را ذخیره میکند. دلیل اینکه React Router این کار را انجام میدهد این است که برخی از ارائهدهندگان هاست به ما اجازه نمیدهند تا URL صفحه خود را تغییر دهیم. ما در این شرایط بسیار نادر میخواهیم از HashRouter
استفاده کنیم زیرا این روتر URL واقعی صفحه ما را تغییر نمیدهد و فقط هش صفحه را عوض میکند. اگر میتوانید از هر URLای استفاده کنیم نباید این روتر را در برنامههای خود به کار بگیریم.
HistoryRouter
(که در حال حاضر unstable_HistoryRouter
نامیده میشود) روتری است که به ما این امکان را میدهد که به صورت دستی آبجکت historyای را که React Router برای ذخیره تمام اطلاعات مربوط به تاریخچه مسیریابی برنامه استفاده میکند، کنترل کنیم. این آبجکت history کمک میکند تا مطمئن شویم مواردی مانند باتن back و forward در مرورگر به درستی کار میکنند.
این روتری است که احتمالاً هرگز نباید از آن استفاده کنیم، مگر اینکه دلیل بسیار خاصی داشته باشیم که بخواهیم رفتار تاریخچه پیشفرض React Router را بازنویسی یا کنترل کنیم.
MemoryRouter
کمی متفاوت از بقیه روترهایی است که در مورد آنها صحبت کردیم، زیرا به جای ذخیره اطلاعات درمورد مسیر فعلی در URL مرورگر، اطلاعات مسیریابی را مستقیماً در حافظه ذخیره میکند. بدیهی است که استفاده از این روتر برای عملیات مسیریابی معمولی انتخاب درستی نیست اما استفاده از آن برای زمانی که در حال نوشتن تستهایی برای برنامه خود هستیم که به مرورگر دسترسی ندارند، میتواند بسیار مفید باشد.
به دلیل نحوه عملکرد React Router ما باید کامپوننتهای خود را داخل روتر قرار دهیم، در غیر این صورت تمام کد مسیریابی ما دچار خطا میشود. به این معنی که حتی اگر میخواهیم یک کامپوننت را تست کنیم، باید آن را داخل یک روتر قرار دهیم وگرنه خطاهایی ایجاد میکند. اگر کد خود را به گونهای تست میکنیم که به مرورگر دسترسی نداشته باشد (مانند unit testing)، روترهایی که تاکنون در مورد آنها صحبت کردهایم دچار خطا میشوند زیرا همه آنها برای URL به مرورگر وابسته هستند. از طرف دیگر MemoryRouter
تمام اطلاعات خود را در حافظه ذخیره میکند به این معنی که هرگز به مرورگر دسترسی پیدا نمیکند. درنتیجه زمانی که برای تست کردن کامپوننتهای خود از unit testing استفاده میکنیم این روتر یک انتخاب ایدهآل به شمار میآید. به غیر از این مورد خاص، MemoryRouter
هرگز استفاده نمیشود.
روتر نهایی StaticRouter
است. این روتر بهطور خاص برای سروری مورد استفاده قرار میگیرد که برنامههای React ما را رندر میکند، زیرا یک پراپ location
را میگیرد و برنامه ما را با استفاده از آن location
به عنوان URL نمایش میدهد. این روتر در واقع نمیتواند هیچ مسیریابی انجام دهد و فقط یک صفحه استاتیک را ارائه میدهد، اما برای رندر سرور یک انتخاب عالی محسوب میشود زیرا ما میخواهیم که فقط HTML برنامه خود را روی سرور رندر کنیم و سپس کلاینت میتواند تمام مسیریابیهای ما و موارد دیگر را تنظیم کند.
<StaticRouter location="/books"> <App /> </StaticRouter>
React Router یک کتابخانه بسیار بزرگ با هزاران ویژگی شگفتانگیز است و به همین دلیل است که اکثر افراد از این کتابخانه برای مسیریابی در برنامههای خود استفاده میکنند.