در قسمت قبل، نحوهی برپایی و تنظیمات اولیهی کتابخانهی مسیریابی react-router-dom را بررسی کردیم. در ادامه نحوهی دریافت پارامترهای مسیریابی و سایر جزئیات این کتابخانه را مرور میکنیم.
دریافت پارامترهای مسیریابی
گاهی از اوقات نیاز به ارسال پارامترهایی به مسیریابیهای تعریف شدهاست. برای مثال در لیست محصولات تعریف شده، بسته به انتخاب هر کدام، باید id متناظری نیز در URL نهایی ظاهر شود. به این Id، یک Route parameter گفته میشود. برای پیاده سازی این نیازمندی به صورت زیر عمل میکنیم:
در فایل app.js، یک مسیریابی جدید را برای نمایش جزئیات یک محصول اضافه میکنیم:
در اینجا برای تعریف یک پارامتر مسیریابی، آنرا با : شروع میکنیم؛ مانند id:، در مسیریابی جدید فوق. البته امکان تعریف چندین پارامتر هم در اینجا وجود دارد؛ مانند تعریف پارامترهای سال و ماه برای مسیریابی مطالب. به علاوه چون این نوع مسیریابیها ویژهتر هستند، باید در ابتدا قرارگیرند. برای مثال اگر مسیریابی products/ را در اول لیست قرار دهیم، دیگر کار به انتخاب products/:id/ نخواهد رسید.
کامپوننت جدید src\components\productDetails.jsx نیز به صورت زیر تعریف شدهاست:
پس از این تغییرات و ذخیره سازی برنامه، با بارگذاری مجدد برنامه در مرورگر، ابتدا صفحهی products را از منوی راهبری سایت انتخاب کرده و سپس بر روی یکی از محصولات لیست شده کلیک میکنیم. سپس در افزونهی react developer tools، کامپوننت نمایش داده شدهی ProductDetails را انتخاب میکنیم:
در اینجا با گشودن اطلاعات خاصیت match تزریق شدهی به کامپوننت ProductDetails، میتوان اطلاعاتی مانند پارامترهای دریافتی مسیریابی را دقیقا مشاهده کرد. برای مثال در این تصویر id=1 از URL بالای صفحه که به http://localhost:3000/products/1 تنظیم شدهاست، دریافت میشود.
بنابراین امکان خواندن اطلاعات پارامترهای مسیریابی، توسط شیء match تزریق شدهی به یک کامپوننت وجود دارد. به همین جهت کامپوننت ProductDetails را ویرایش کرده و المان h1 آنرا جهت نمایش id محصول به صورت زیر تغییر میدهیم که در آن شیء match.params، از props کامپوننت تامین میشود:
برای آزمایش آن مجددا از صفحهی products شروع کرده و بر روی لینک یکی از محصولات، کلیک کنید. در اینجا هرچند id محصول به درستی نمایش داده میشود، اما ... نمایش جزئیات آن به همراه بارگذاری کامل و مجدد صفحهی آن است که از حالت SPA خارج شدهاست. برای رفع این مشکل به کامپوننت products مراجعه کرده و anchorهای تعریف شده را همانطور که در قسمت قبل نیز بررسی کردیم، تبدیل به کامپوننت Link میکنیم.
از حالت قبلی:
به حالت جدید:
با این تغییر دیگر در حین نمایش یک کامپوننت، بارگذاری کامل صفحه رخ نمیدهد.
پارامترهای اختیاری مسیریابی
به تعریف مسیریابی زیر، دو پارامتر سال و ماه، اضافه شدهاند:
و برای مثال اگر بر روی لینک posts در منوی راهبری کلیک کنیم، آدرسی مانند http://localhost:3000/posts/2018/06 ایجاد شده و سپس کامپوننت Posts رندر میشود. حال اگر پارامتر ماه را حذف کنیم http://localhost:3000/posts/2018 چه اتفاقی رخ میدهد؟ در این حالت برنامه کامپوننت Home را نمایش خواهد داد. علت اینجا است که پارامترهای تعریف شدهی در مسیریابی، به صورت پیشفرض اجباری هستند. به همین جهت URL وارد شده، چون با الگوی تعریفی Route فوق بدلیل نداشتن قسمت ماه، انطباق نیافته و تنها مسیریابی / که کامپوننت Home را نمایش میدهد، با آن تطابق یافتهاست.
برای رفع این مشکل میتوان با اضافه کردن یک ? به هر پارامتر، پارامترهای تعریف شده را اختیاری کرد:
در regexهای جاوا اسکریپتی زمانیکه یک ? را به یک عبارت باقاعده اضافه میکنیم، یعنی آن عبارت اختیاری است.
با این تغییرات اگر مجددا آدرس http://localhost:3000/posts/2018 را درخواست کنیم، کامپوننت Posts بجای کامپوننت Home نمایش داده میشود.
اکنون کامپوننت Posts را به صورت زیر تغییر میدهیم تا پارامترهای مسیریابی را نیز درج کند:
پارامتر ({ match }) در اینجا به این معنا است که شیء props ارسالی به آن، توسط Object Destructuring تجزیه شده و خاصیت match آن در اینجا به صورت یک پارامتر در اختیار کامپوننت بدون حالت تابعی قرار گرفتهاست.
پس از ذخیره سازی این تغییرات و بارگذاری مجدد برنامه در مرورگر، اگر آدرس http://localhost:3000/posts/2018/1 را وارد کنیم، خروجی زیر حاصل میشود:
کار با پارامترهای کوئری استرینگهای مسیریابی
پارامترهای اختیاری، جزو قابلیتهایی هستند که باید تا حد ممکن از بکارگیری آنها اجتناب و آنها را با کوئری استرینگها تعریف کرد. کوئری استرینگها با یک ? در انتهای URL شروع میشوند و میتوانند چندین پارامتر را داشته باشند؛ مانند: http://localhost:3000/posts?sortBy=newest&approved=true و یا حتی میتوان آنها را با پارامترهای اختیاری نیز ترکیب کرد مانند: http://localhost:3000/posts/2018/05?sortBy=newest&approved=true
برای استخراج کوئری استرینگها در برنامههای React باید از شیء location استفاده کرد:
در اینجا مقدار خاصیت search، کل قسمت کوئری استرینگها را به همراه دارد. البته ما قصد پردازش آنرا به صورت دستی نداریم. به همین جهت از کتابخانهی زیر برای انجام اینکار استفاده خواهیم کرد:
پس از نصب کتابخانهی بسیار معروف query-string، به کامپوننت Posts مراجعه کرده و تغییرات زیر را اعمال میکنیم:
- پیشتر ذکر پارامتر ({ match }) را بررسی کردیم. در اینجا خاصیت location نیز به آن اضافه شدهاست تا پس از Object Destructuring شیء props ارسالی به کامپوننت، بتوان به مقدار شیء location نیز دسترسی یافت.
- سپس شیء queryString را از ماژول مرتبط، import میکنیم. در ادامه به کمک متد parse آن، میتوان location.search را آنالیز کرد که خروجی آن، یک شیء جاوا اسکریپتی به صورت زیر است:
بنابراین در اینجا هم میتوان توسط Object Destructuring، به این خواص دسترسی یافت:
یک نکته: باید دقت داشت که کتابخانهی query-string، همیشه مقادیر خواص را رشتهای بازگشت میدهد؛ حتی اگر عدد باشند.
مدیریت مسیرهای نامعتبر درخواستی
فرض کنید کاربری آدرس غیرمعتبر http://localhost:3000/xyz را که هیچ نوع مسیریابی را برای آن تعریف نکردهایم، درخواست میکند. در این حالت برنامه کامپوننت home را رندر میکند که مرتبط است با تعاریف مسیریابی برنامه در فایل app.js. تنها path تعریف شدهای که با این آدرس تطابق پیدا میکند، / متناظر با کامپوننت home است.
بجای این رفتار پیشفرض، مایل هستیم که کاربر به یک صفحهی سفارشی «پیدا نشد» هدایت شود. به همین جهت ابتدا کامپوننت جدید تابعی بدون حالت src\components\notFound.jsx را با محتوای زیر ایجاد میکنیم:
سپس ابتدا به مسیریابی /، ویژگی exact را هم اضافه میکنیم تا دیگر بجز ریشهی سایت، به مسیر دیگری پاسخ ندهد:
اکنون اگر مجددا مسیر xyz را درخواست کنیم، فقط کامپوننت NavBar در صفحه ظاهر میشود. برای بهبود این وضعیت و نمایش کامپوننت NotFound، مراحل زیر را طی میکنیم:
- ابتدا شیء Redirect را از react-router-dom باید import کنیم.
- همچنین import کامپوننت NotFound نیز باید ذکر شود.
- سپس پیش از مسیریابی کلی /، مسیریابی جدید not-found را که به کامپوننت NotFound اشاره میکند، اضافه میکنیم.
- اکنون در انتهای Switch تعریف شده (جائی که دیگر هیچ مسیریابی تعریف شدهای، با مسیر درخواستی کاربر، تطابق نداشته)، باید کامپوننت Redirect را جهت هدایت به این مسیریابی جدید، تعریف کرد:
پس از این تغییرات، اگر آدرس نامعتبر http://localhost:3000/xyz درخواست شود، بلافاصله به آدرس http://localhost:3000/not-found هدایت میشویم.
کاربرد دیگر کامپوننت Redirect، هدایت کاربران از یک آدرس قدیمی، به یک آدرس جدید است که نحوهی تعریف آن به صورت زیر میباشد:
با این تنظیم اگر کاربری مسیر http://localhost:3000/messages را درخواست دهد، به صورت خودکار به http://localhost:3000/posts هدایت خواهد شد.
هدایت کاربران به آدرسهای مختلف با برنامه نویسی
گاهی از اوقات پس از تکمیل فرمی و یا کلیک بر روی دکمهای، میخواهیم کاربر را به آدرس خاصی هدایت کنیم. برای مثال در برنامهی جاری میخواهیم زمانیکه کاربری صفحهی جزئیات یک محصول را مشاهده و بر روی دکمهی فرضی Save کلیک کرد، دوباره به همان صفحهی لیست محصولات هدایت شود؛ برای این منظور از شیء history استفاده خواهیم کرد:
در اینجا متدها و خواص شیء history را مشاهده میکنید. برای نمونه توسط متد push آن میتوان آدرس جدیدی را به تاریخچهی آدرسهای مرور شدهی توسط کاربر، اضافه کرد و کاربر را با برنامه نویسی، به صفحهی جدیدی هدایت نمود:
یک نکته: اگر به تصویر دقت کنید، متد replace هم در اینجا قابل استفاده است. متد push با افزودن رکوردی به تاریخچهی آدرسهای مرور شدهی در مرورگر، امکان بازگشت به محل قبلی را با کلیک بر روی دکمهی back مرورگر، فراهم میکند؛ اما replace تنها رکورد آدرس جاری را در تاریخچهی مرورگر به روز رسانی میکند. یعنی از داشتن تاریخچه محروم خواهیم شد. عمدهی کاربرد این متد، در صفحات لاگین است؛ زمانیکه کاربر به سیستم وارد میشود و به صفحهی جدیدی مراجعه میکند، با کلیک بر روی دکمهی back، دوباره نمیخواهیم او را به صفحهی لاگین هدایت کنیم.
تعریف مسیریابیهای تو در تو
قصد داریم به صفحهی admin، دو لینک جدید به مطالب و کاربران ادمین را اضافه کنیم، به نحوی که با کلیک بر روی هر کدام، محتوای هر صفحهی متناظر، در همینجا نمایش داده شود (تصویر فوق). به عبارتی در حال حاضر، مسیریابی تعریف شده، جهت مدیریت لینکهای NavBar بالای صفحه کار میکند. اکنون میخواهیم مسیریابی دیگری را داخل آن برای پوشش منوی کنار صفحهی ادمین اضافه کنیم. به اینکار، تعریف مسیریابیهای تو در تو گفته میشود و پیاده سازی آن توسط کامپوننت react-router-dom بسیار سادهاست و شامل این موارد میشود:
- ابتدا مسیریابیهای جدید را همینجا داخل کامپوننت src\components\admin\dashboard.jsx تعریف میکنیم:
در اینجا محتوای کامپوننت بدون حالت تابعی Dashboard را ملاحظه میکنید که از یک کامپوننت منوی SideBar و سپس در ستونی دیگر، از 2 کامپوننت Route تشکیل شدهاست که بر اساس URL رسیده، سبب رندر کامپوننتهای جدید Users و Posts خواهند شد.
تنها نکتهی جدید آن، امکان درج کامپوننت Route در قسمتهای مختلف برنامه، خارج از app.js میباشد و این امکان محدود به app.js نیست. در این حالت اگر مسیر /admin/posts توسط کاربر وارد شد، دقیقا در همان محلی که کامپوننت Route درج شدهاست، کامپوننت متناظر با این مسیر یعنی کامپوننت Posts، رندر میشود.
در ادامه محتوای این کامپوننتهای جدید را نیز ملاحظه میکنید:
محتوای کامپوننت src\components\admin\sidebar.jsx
محتوای کامپوننت src\components\admin\users.jsx
محتوای کامپوننت src\components\admin\posts.jsx
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-15-part-02.zip
دریافت پارامترهای مسیریابی
گاهی از اوقات نیاز به ارسال پارامترهایی به مسیریابیهای تعریف شدهاست. برای مثال در لیست محصولات تعریف شده، بسته به انتخاب هر کدام، باید id متناظری نیز در URL نهایی ظاهر شود. به این Id، یک Route parameter گفته میشود. برای پیاده سازی این نیازمندی به صورت زیر عمل میکنیم:
در فایل app.js، یک مسیریابی جدید را برای نمایش جزئیات یک محصول اضافه میکنیم:
import ProductDetails from "./components/productDetails"; // ... class App extends Component { render() { return ( <div> <NavBar /> <div className="container"> <Switch> <Route path="/products/:id" component={ProductDetails} /> <Route path="/products" render={props => ( <Products param1="123" param2="456" {...props} /> )} /> <Route path="/posts/:year/:month" component={Posts} /> <Route path="/admin" component={Dashboard} /> <Route path="/" component={Home} /> </Switch> </div> </div> ); } }
کامپوننت جدید src\components\productDetails.jsx نیز به صورت زیر تعریف شدهاست:
import React, { Component } from "react"; class ProductDetails extends Component { handleSave = () => { // Navigate to /products }; render() { return ( <div> <h1>Product Details - </h1> <button className="btn btn-primary" onClick={this.handleSave}> </div> ); } } export default ProductDetails;
در اینجا با گشودن اطلاعات خاصیت match تزریق شدهی به کامپوننت ProductDetails، میتوان اطلاعاتی مانند پارامترهای دریافتی مسیریابی را دقیقا مشاهده کرد. برای مثال در این تصویر id=1 از URL بالای صفحه که به http://localhost:3000/products/1 تنظیم شدهاست، دریافت میشود.
بنابراین امکان خواندن اطلاعات پارامترهای مسیریابی، توسط شیء match تزریق شدهی به یک کامپوننت وجود دارد. به همین جهت کامپوننت ProductDetails را ویرایش کرده و المان h1 آنرا جهت نمایش id محصول به صورت زیر تغییر میدهیم که در آن شیء match.params، از props کامپوننت تامین میشود:
<h1>Product Details - {this.props.match.params.id} </h1>
برای آزمایش آن مجددا از صفحهی products شروع کرده و بر روی لینک یکی از محصولات، کلیک کنید. در اینجا هرچند id محصول به درستی نمایش داده میشود، اما ... نمایش جزئیات آن به همراه بارگذاری کامل و مجدد صفحهی آن است که از حالت SPA خارج شدهاست. برای رفع این مشکل به کامپوننت products مراجعه کرده و anchorهای تعریف شده را همانطور که در قسمت قبل نیز بررسی کردیم، تبدیل به کامپوننت Link میکنیم.
از حالت قبلی:
{this.state.products.map(product => ( <li key={product.id}> <a href={`/products/${product.id}`}>{product.name}</a> </li> ))}
import { Link } from "react-router-dom"; // ... <Link to={`/products/${product.id}`}>{product.name}</Link>
پارامترهای اختیاری مسیریابی
به تعریف مسیریابی زیر، دو پارامتر سال و ماه، اضافه شدهاند:
<Route path="/posts/:year/:month" component={Posts} />
برای رفع این مشکل میتوان با اضافه کردن یک ? به هر پارامتر، پارامترهای تعریف شده را اختیاری کرد:
<Route path="/posts/:year?/:month?" component={Posts} />
با این تغییرات اگر مجددا آدرس http://localhost:3000/posts/2018 را درخواست کنیم، کامپوننت Posts بجای کامپوننت Home نمایش داده میشود.
اکنون کامپوننت Posts را به صورت زیر تغییر میدهیم تا پارامترهای مسیریابی را نیز درج کند:
import React from "react"; const Posts = ({ match }) => { return ( <div> <h1>Posts</h1> Year: {match.params.year} , Month: {match.params.month} </div> ); }; export default Posts;
پس از ذخیره سازی این تغییرات و بارگذاری مجدد برنامه در مرورگر، اگر آدرس http://localhost:3000/posts/2018/1 را وارد کنیم، خروجی زیر حاصل میشود:
کار با پارامترهای کوئری استرینگهای مسیریابی
پارامترهای اختیاری، جزو قابلیتهایی هستند که باید تا حد ممکن از بکارگیری آنها اجتناب و آنها را با کوئری استرینگها تعریف کرد. کوئری استرینگها با یک ? در انتهای URL شروع میشوند و میتوانند چندین پارامتر را داشته باشند؛ مانند: http://localhost:3000/posts?sortBy=newest&approved=true و یا حتی میتوان آنها را با پارامترهای اختیاری نیز ترکیب کرد مانند: http://localhost:3000/posts/2018/05?sortBy=newest&approved=true
برای استخراج کوئری استرینگها در برنامههای React باید از شیء location استفاده کرد:
در اینجا مقدار خاصیت search، کل قسمت کوئری استرینگها را به همراه دارد. البته ما قصد پردازش آنرا به صورت دستی نداریم. به همین جهت از کتابخانهی زیر برای انجام اینکار استفاده خواهیم کرد:
> npm i query-string --save
import queryString from "query-string"; const Posts = ({ match, location }) => { const result = queryString.parse(location.search); console.log(result); // ...
- سپس شیء queryString را از ماژول مرتبط، import میکنیم. در ادامه به کمک متد parse آن، میتوان location.search را آنالیز کرد که خروجی آن، یک شیء جاوا اسکریپتی به صورت زیر است:
{approved: "true", sortBy: "newest"}
const { approved, sortBy } = queryString.parse(location.search);
یک نکته: باید دقت داشت که کتابخانهی query-string، همیشه مقادیر خواص را رشتهای بازگشت میدهد؛ حتی اگر عدد باشند.
مدیریت مسیرهای نامعتبر درخواستی
فرض کنید کاربری آدرس غیرمعتبر http://localhost:3000/xyz را که هیچ نوع مسیریابی را برای آن تعریف نکردهایم، درخواست میکند. در این حالت برنامه کامپوننت home را رندر میکند که مرتبط است با تعاریف مسیریابی برنامه در فایل app.js. تنها path تعریف شدهای که با این آدرس تطابق پیدا میکند، / متناظر با کامپوننت home است.
بجای این رفتار پیشفرض، مایل هستیم که کاربر به یک صفحهی سفارشی «پیدا نشد» هدایت شود. به همین جهت ابتدا کامپوننت جدید تابعی بدون حالت src\components\notFound.jsx را با محتوای زیر ایجاد میکنیم:
import React from "react"; const NotFound = () => { return <h1>Not Found</h1>; }; export default NotFound;
<Route path="/" exact component={Home} />
- ابتدا شیء Redirect را از react-router-dom باید import کنیم.
- همچنین import کامپوننت NotFound نیز باید ذکر شود.
- سپس پیش از مسیریابی کلی /، مسیریابی جدید not-found را که به کامپوننت NotFound اشاره میکند، اضافه میکنیم.
- اکنون در انتهای Switch تعریف شده (جائی که دیگر هیچ مسیریابی تعریف شدهای، با مسیر درخواستی کاربر، تطابق نداشته)، باید کامپوننت Redirect را جهت هدایت به این مسیریابی جدید، تعریف کرد:
import { Redirect, Route, Switch } from "react-router-dom"; //... import NotFound from "./components/notFound"; //... class App extends Component { render() { return ( <div> <NavBar /> <div className="container"> <Switch> //... <Route path="/not-found" component={NotFound} /> <Route path="/" exact component={Home} /> <Redirect to="/not-found" /> </Switch> </div> </div> ); } }
کاربرد دیگر کامپوننت Redirect، هدایت کاربران از یک آدرس قدیمی، به یک آدرس جدید است که نحوهی تعریف آن به صورت زیر میباشد:
<Redirect from="/messages" to="/posts" />
هدایت کاربران به آدرسهای مختلف با برنامه نویسی
گاهی از اوقات پس از تکمیل فرمی و یا کلیک بر روی دکمهای، میخواهیم کاربر را به آدرس خاصی هدایت کنیم. برای مثال در برنامهی جاری میخواهیم زمانیکه کاربری صفحهی جزئیات یک محصول را مشاهده و بر روی دکمهی فرضی Save کلیک کرد، دوباره به همان صفحهی لیست محصولات هدایت شود؛ برای این منظور از شیء history استفاده خواهیم کرد:
در اینجا متدها و خواص شیء history را مشاهده میکنید. برای نمونه توسط متد push آن میتوان آدرس جدیدی را به تاریخچهی آدرسهای مرور شدهی توسط کاربر، اضافه کرد و کاربر را با برنامه نویسی، به صفحهی جدیدی هدایت نمود:
class ProductDetails extends Component { handleSave = () => { // Navigate to /products this.props.history.push("/products"); };
یک نکته: اگر به تصویر دقت کنید، متد replace هم در اینجا قابل استفاده است. متد push با افزودن رکوردی به تاریخچهی آدرسهای مرور شدهی در مرورگر، امکان بازگشت به محل قبلی را با کلیک بر روی دکمهی back مرورگر، فراهم میکند؛ اما replace تنها رکورد آدرس جاری را در تاریخچهی مرورگر به روز رسانی میکند. یعنی از داشتن تاریخچه محروم خواهیم شد. عمدهی کاربرد این متد، در صفحات لاگین است؛ زمانیکه کاربر به سیستم وارد میشود و به صفحهی جدیدی مراجعه میکند، با کلیک بر روی دکمهی back، دوباره نمیخواهیم او را به صفحهی لاگین هدایت کنیم.
تعریف مسیریابیهای تو در تو
قصد داریم به صفحهی admin، دو لینک جدید به مطالب و کاربران ادمین را اضافه کنیم، به نحوی که با کلیک بر روی هر کدام، محتوای هر صفحهی متناظر، در همینجا نمایش داده شود (تصویر فوق). به عبارتی در حال حاضر، مسیریابی تعریف شده، جهت مدیریت لینکهای NavBar بالای صفحه کار میکند. اکنون میخواهیم مسیریابی دیگری را داخل آن برای پوشش منوی کنار صفحهی ادمین اضافه کنیم. به اینکار، تعریف مسیریابیهای تو در تو گفته میشود و پیاده سازی آن توسط کامپوننت react-router-dom بسیار سادهاست و شامل این موارد میشود:
- ابتدا مسیریابیهای جدید را همینجا داخل کامپوننت src\components\admin\dashboard.jsx تعریف میکنیم:
const Dashboard = ({ match }) => { return ( <div> <h1>Admin Dashboard</h1> <div className="row"> <div className="col-3"> <SideBar /> </div> <div className="col"> <Route path="/admin/users" component={Users} /> <Route path="/admin/posts" component={Posts} /> </div> </div> </div> ); };
تنها نکتهی جدید آن، امکان درج کامپوننت Route در قسمتهای مختلف برنامه، خارج از app.js میباشد و این امکان محدود به app.js نیست. در این حالت اگر مسیر /admin/posts توسط کاربر وارد شد، دقیقا در همان محلی که کامپوننت Route درج شدهاست، کامپوننت متناظر با این مسیر یعنی کامپوننت Posts، رندر میشود.
در ادامه محتوای این کامپوننتهای جدید را نیز ملاحظه میکنید:
محتوای کامپوننت src\components\admin\sidebar.jsx
import React from "react"; import { Link } from "react-router-dom"; const SideBar = () => { return ( <ul className="list-group"> <li className="list-group-item"> <Link to="/admin/posts">Posts</Link> </li> <li className="list-group-item"> <Link to="/admin/users">Users</Link> </li> </ul> ); }; export default SideBar;
محتوای کامپوننت src\components\admin\users.jsx
import React from "react"; const Users = () => { return <h1>Admin Users</h1>; }; export default Users;
محتوای کامپوننت src\components\admin\posts.jsx
import React from "react"; const Posts = () => { return ( <div> <h1>Admin Posts</h1> </div> ); }; export default Posts;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-15-part-02.zip