روش محافظت از مسیریابیهای تعریف شدهی در برنامه
شبیه به روشی را که در قسمت قبل، برای انتقال شیء user، به مسیریابی کامپوننت Movies استفاده کردیم:
<Route path="/movies" render={props => <Movies {...props} user={this.state.currentUser} />} />
<Route path="/movies/:id" component={MovieForm} />
<Route path="/movies/:id" render={props => { if (!this.state.currentUser) { return <Redirect to="/login" />; } return <MovieForm {...props} />; }} />
اکنون اگر این تغییرات را ذخیره کرده و در حالت Logout، مسیر http://localhost:3000/movies/new را مستقیما درخواست دهیم، به صفحهی لاگین هدایت خواهیم شد.
ایجاد کامپوننتی با قابلیت استفادهی مجدد، برای محافظت از مسیریابیها
هرچند روشی که تا اینجا برای محافظت از مسیریابیها معرفی شد، بدون مشکل کار میکند، اما اگر قرار باشد برای تمام مسیریابیهای اینگونه، استفاده شود، به تکرار بیش از اندازهی کدهای یکسانی خواهیم رسید. به همین جهت میتوان این منطق را تبدیل به یک کامپوننت با قابلیت استفادهی مجدد کرد؛ تا دیگر نیازی به تکرار این if/elseها نباشد. برای این منظور، فایل جدید src\components\common\protectedRoute.jsx را ایجاد میکنیم. کامپوننت جدید protectedRoute را هم در پوشهی common قرار دادهایم؛ چون وابستگی به دومین این برنامه نداشته و میتواند در سایر برنامه نیز مورد استفاده قرار گیرد. سپس با استفاده از میانبرهای imrc و sfc، یک کامپوننت تابعی بدون حالت را به نام ProtectedRoute ایجاد کرده و در آن، همان کامپوننت اصلی Route را بازگشت میدهیم. بنابراین هر زمانیکه از ProtectedRoute استفاده شود، خروجی آن، همان کامپوننت استاندارد Route خواهد بود که اینبار قرار است از وضعیت کاربر جاری وارد شدهی به سیستم، مطلع باشد. به همین جهت در اولین قدم، همان قطعه کد Route فوق را که به همراه if/else نوشتیم، از فایل app.js کپی کرده و به اینجا، داخل متد رندر کامپوننت، منتقل میکنیم. سپس شروع میکنیم به متغیر کردن عباراتی که در آن به صورت صریح و ثابت، مقدار دهی شدهاند تا به یک کامپوننت با قابلیت استفادهی مجدد برسیم:
import React from "react"; import { Route, Redirect } from "react-router-dom"; import * as auth from "../../services/authService"; const ProtectedRoute = ({ path, component: Component, render, ...rest }) => { return ( <Route {...rest} render={props => { if (!auth.getCurrentUser()) return ( <Redirect to={{ pathname: "/login", state: { from: props.location } }} /> ); return Component ? <Component {...props} /> : render(props); }} /> ); }; export default ProtectedRoute;
- در این کامپوننت نیاز است اطلاعات کاربر جاری وارد شدهی به سیستم در دسترس باشد. یا میتوان آنرا به عنوان یکی از خواص props دریافت کرد و یا همانند این مثال، امکان دریافت مستقیم آن از authService نیز وجود دارد.
- در ادامه اگر CurrentUser مقدار دهی نشده باشد، کامپوننت Redirect را که کاربر را به صفحهی لاگین هدایت میکند، بازگشت میدهیم. در غیراینصورت نیاز است یک کامپوننت را بجای برای مثال MovieForm، بازگشت دهیم. علت استفادهی از component: Component این است که React انتظار دارد، کامپوننتها با نام بزرگ شروع شوند. به همین جهت خاصیت component را از props دریافت کرده و آنرا به Component تغییر نام میدهیم.
- زمانیکه از کامپوننت Route استاندارد استفاده میشود، یا از ویژگی component آن استفاده میشود و یا از ویژگی render آن که یک تابع است، تا بتوان داخل آن، کدهای پویایی را درج کرد. به همین جهت ممکن است که مقدار متغیر کامپوننت دریافت شده، نال باشد. بنابراین در اینجا بررسی میشود که آیا Component، مقدار دهی شدهاست یا خیر؟ اگر بله، همان کامپوننت را به همراه props آن بازگشت میدهیم. در غیراینصورت، متد render مقدار دهی شده را به همراه props ارسالی به آن، بازگشت خواهیم داد.
- علت وجود پارامتر rest نیز این است که این کامپوننت علاوه بر ویژگیهایی که تاکنون پیش بینی کردهایم، ممکن است در آینده ویژگیهای دیگری را نیز نیاز داشته باشد. به همین جهت مابقی آنها را توسط {rest...}، به صورت خودکار در اینجا درج میکنیم. برای نمونه در اینجا ذکر path={path} را مشاهده نمیکنید؛ چون توسط همان {rest...} به صورت خودکار تامین میشود.
اکنون به app.js بازگشته و کدهای قبلی را با این کامپوننت جدید ProtectedRoute، جایگزین میکنیم:
import ProtectedRoute from "./components/common/protectedRoute"; // ... <ProtectedRoute path="/movies/:id" component={MovieForm} />
مدیریت بازگشت کاربران، پس از لاگین به سیستم
پس از خروج از برنامه، اگر سعی در ویرایش یکی از فیلمهای موجود کنیم، به صفحهی لاگین هدایت خواهیم شد. پس از لاگین موفق، مجددا به ریشهی سایت بازگشت داده میشویم و نه به صفحهای که پیش از لاگین، مدنظر کاربر بودهاست. برای رفع این مشکل نیاز است بتوان به آدرس قبلی درخواستی، دسترسی یافت و این مورد توسط سیستم مسیریابی، به کامپوننتها به صورت خودکار تزریق میشود. برای مثال اگر در کامپوننت ProtectedRoute، مقدار شیء props دریافتی را لاگ کنیم:
return ( <Route {...rest} render={props => { console.log(props);
همانطور که مشخص است، شیء location دریافتی از props، به همراه اطلاعات آدرسی است که پیش از هدایت خودکار به صفحهی لاگین، درخواست کرده بودیم. به همین جهت یک چنین تنظیمی، در تعاریف کامپوننت ProtectedRoute درنظر گرفته شدهاند:
<Redirect to={{ pathname: "/login", state: { from: props.location } }} />
اکنون که این شیء، به کامپوننت لاگین، پس از Redirect خودکار ارسال میشود، نیاز است به src\components\loginForm.jsx مراجعه کرده و تغییرات زیر را اعمال کنیم:
doSubmit = async () => { try { const { data } = this.state; await auth.login(data.username, data.password); const { state } = this.props.location; window.location = state ? state.from.pathname : "/"; } catch (ex) { //...
تا اینجا اگر برنامه را ذخیره کرده، از سیستم خارج شویم و سعی در ویرایش اولین رکورد موجود در لیست فیلمها کنیم، ابتدا به صفحهی لاگین هدایت میشویم. پس از لاگین موفق، اینبار بجای مشاهدهی ریشهی سایت که در اینجا به لیست فیلمها تنظیم شده، دقیقا صفحهی ویرایش جزئیات اولین فیلم را مشاهده خواهیم کرد.
عدم نمایش مجدد صفحهی لاگین، به کاربران وارد شدهی به سیستم
آخرین تغییری را که در اینجا اعمال خواهیم کرد، رفع مشکل امکان مشاهدهی مجدد صفحهی لاگین، با وارد کردن مستقیم آدرس آن در مرورگر، پس از ورود موفقیت آمیز به سیستم است. برای این منظور، ابتدای متد رندر کامپوننت فرم لاگین را به صورت زیر تغییر میدهیم تا اگر کاربر، پیشتر به سیستم وارد شده بود، به صورت خودکار به ریشهی سایت هدایت شده و مجددا فرم لاگین برای او رندر نشود:
import { Redirect } from "react-router-dom"; //... render() { if (auth.getCurrentUser()) return <Redirect to="/" />;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-29-backend.zip و sample-29-frontend.zip