بارگذاری صفحات بدون < Link > + درک Route های تو در تو

22 بهمن 1399
بارگذاری صفحات بدون   + درک Route های تو در تو

بارگذاری صفحات بدون <Link>

در قسمت قبل برایتان توضیح دادیم که می توانیم با استفاده از <Link> بین صفحات مختلف جابجا شویم:

posts = this.state.posts.map(post => {
    return (
        <Link to={'/' + post.id} key={post.id}>
            <Post
                title={post.title}
                author={post.author}
                clicked={() => this.postSelectedHandler(post.id)} />
        </Link>);
});

اما روش دیگری نیز وجود دارد که بدون استفاده از <Link> می باشد. استفاده از <Link> هیچ مشکلی ندارد اما برخی اوقات نیاز است که با استفاده از روش های برنامه نویسی این کار را انجام دهید. به طور مثال زمانی که می خواهیم پس از پایان یک عملیات به صفحه ی دیگری برویم (مثلا پس از اتمام درخواست HTTP) بنابراین آشنایی با این روش به شما کمک خواهد کرد.

برای آشنایی با این روش ابتدا <Link> در کد بالا را کامنت کرده و key را به <Post> برگردانید:

posts = this.state.posts.map(post => {
    return (
        // <Link to={'/' + post.id} key={post.id}>
        <Post
            key={post.id}
            title={post.title}
            author={post.author}
            clicked={() => this.postSelectedHandler(post.id)} />
        // </Link>
    );
});

اگر به کد بالا نگاه کنید متوجه متد postSelectedHandler خواهید شد. ما قصد داریم به تعریف این متد رفته و کار دیگری را انجام دهیم. در حال حاضر محتوای این متد به شکل زیر است:

postSelectedHandler = (id) => {
    this.setState({ selectedPostId: id });
}

همانطور که می دانید دیگر state ای به این نام نداریم و ساختار پروژه را تغییر دادیم بنابراین این کد هیچ کاری انجام نمی دهد. به همین خاطر باید ابتدا کد درون این متد را حذف کرده و سپس کدهای جدید را در آن بنویسیم. برای نوشتن کدهای این متد می توانیم از شیء history استفاده کنیم که از props دریافت می کردیم:

ما می خواهیم از متد push استفاده کنیم که به ما اجازه می دهد یک صفحه ی دیگر را به صفحات خود اضافه کنیم:

postSelectedHandler = (id) => {
    this.props.history.push();
}

به عنوان پارامتر باید یک رشته یا یک شیء جاوا اسکریپتی به push داده شود و دقیقا مانند to عمل می کند. به طور مثال می گوییم:

postSelectedHandler = (id) => {
    this.props.history.push({ pathname: '/' + id });
}

همانطور که گفتم می توانید کد بالا را به شکل رشته نیز بنویسید:

postSelectedHandler = (id) => {
    this.props.history.push('/' + id);
}

هیچ تفاوتی ندارد. حالا اگر به مرورگر بروید و کدها را تست کنید، با کلیک روی هر پست محتوای آن پست را مشاهده می کنید بنابراین هیچ مشکلی نیست و هر دو روش کار می کنند.

مشکل لینک فعال در آدرس /

در حال حاضر اگر روی پستی کلیک کنیم محتوای آن باز می شود اما تا زمانی که در صفحه ی محتوا باشیم لینک Home نارنجی رنگ نیست که البته صحیح هم هست اما اگر نام لینک Home را به Posts تغییر دهیم چطور؟ به Blog.js بروید و نام این لینک را به Posts تغییر بدهید چرا که در صفحه ی اصلی برنامه فقط عناوین پست ها را داریم:

<li><NavLink
    exact
    to="/"
    activeClassName="my-active"
    activeStyle={{
        color: '#fa923f',
        textDecoration: 'underline'
    }}
>Posts</NavLink></li>

حالا اگر بخواهیم همزمان که در صفحه ی محتوای پست هستیم (مثلا پست http://localhost:3000/1) لینک Posts هم نارنجی باشد چطور؟ دلیل نارنجی نبودن این لینک کلیدواژه ی exact است. زمانی که از exact استفاده کرده ایم به این لینک می گوییم فقط در path ای فعال باش که دقیقا برابر با "/" باشد. از طرفی اگر exact را حذف کنیم در هنگام کلیک روی New Post، لینک Posts نیز فعال می شود. نکته ی مهم در این قسمت این است که اگر روی URL اصلی (/) کار می کنید استفاده از exact و نارنجی کردن لینک غیرممکن می شود مگر اینکه از پسوندی مانند /posts/ استفاده کنید. دوست داشتم این نکته را نیز به شما گوشزد کنم.

درک Route های تو در تو (nested)

در حال حاضر Routing ما در یک سطح (فقط در Blog.js) انجام می شود:

<Route path="/" exact component={Posts} />
<Switch>
    <Route path="/new-post" component={NewPost} />
    <Route path="/:id" exact component={FullPost} />
</Switch>

اما برخی اوقات لازم است که Route های تو در تو داشته باشیم؛ یعنی کامپوننتی را با routing درون یک کامپوننت دیگر بارگذاری کنیم که خودش با routing بارگذاری شده باشد. فرض کنید می خواهیم یک پست خاص را (متن پست) درون کامپوننت Posts.js بارگذاری کنیم نه در Blog.js. یعنی دقیقا زیر عنوان خودش بارگذاری شود. برای انجام این کار وارد Posts.js شده و Route خودمان به همراه بقیه ی کدهای JSX را درون یک div قرار می دهیم:

return (
    <div>
        <section className="Posts">
            {posts}
        </section>
        <Route path="/:id" exact component={FullPost} />
    </div>
);

حالا که این کار را انجام دادیم باید دستور import مربوط به Route و کامپوننت FullPost را نیز در بالای صفحه استفاده کنیم:

import { Route } from 'react-router-dom';
import FullPost from '../FullPost/FullPost';

در واقع شما می توانید از دستور <Route> در هر جایی از برنامه تان استفاده کنید، البته به یک شرط! به شرطی که آن کامپوننت توسط تگ های <BrowserRouter> احاطه شده باشد. در مورد پروژه ی ما هیچ مشکلی نیست چرا که در فایل App.js تمام کامپوننت Blog را درون آن ها قرار دادیم:

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div className="App">
          <Blog />
        </div>
      </BrowserRouter>
    );
  }
}

بنابراین تمام برنامه ی ما درون این تگ ها می باشد.

حالا درون فایل Blog.js فقط دو دستور <Route> باقی مانده است که یکی از آن ها درون دستور <Switch> است. دستور Switch در اینجا کاملا بی معنی است بنابراین Route ای که خارج از آن است را نیز داخل آن قرار می دهم:

<Switch>
    <Route path="/" exact component={Posts} />
    <Route path="/new-post" component={NewPost} />
</Switch>

گرچه از نظر فنی امکان ندارد آدرس های این دو <Route> (یعنی / و new-post/) با هم تداخل داشته باشند که بخواهیم آن ها را درون switch قرار دهیم، اما من <Switch> را نگه میدارم تا شما یادتان بماند که از Switch نیز می توان استفاده کرد. در یک برنامه ی واقعی Switch را حذف می کردیم چرا که هیچ استفاده ای ندارد. همچنین دستور import برای FullPost درون فایل Blog.js را نیز می توانید حذف کنید.

اگر فایل ها را به همین حالت ذخیره کرده و به مرورگر برویم، با کلیک روی عناوین پست ها هیچ پستی نمایش داده نمی شود. چرا؟ به دلیل اینکه یک Route تو در تو ساخته ایم. ما در بالاترین سطح این Route را داریم:

<Route path="/" exact component={Posts} />

این Route کامپوننت Posts را بارگذاری می کند و ما دوباره درون خود Posts (درون فایل Posts.js) از یک <Route> دیگر استفاده کرده ایم:

<Route path="/:id" exact component={FullPost} />

در اولین Route ای که گفتم کلیدواژه ی exact را داریم که باعث می شود کامپوننت Posts فقط زمانی بارگذاری شود که دقیقا / را داشته باشیم اما در Route دوم که nest شده است آدرس id:/ را داریم بنابراین هیچ وقت برابر با / نخواهد بود و کامپوننت Posts هیچ وقت بارگذاری نمی شود و اگر Posts بارگذاری نشود FullPost نیز بارگذاری نخواهد شد (FullPost درون Posts قرار دارد).

یکی از راه های حل این مشکل حذف کردن exact از Route اول (با آدرس /) است. بدین صورت 2/ یا 3/ نیز شامل آن می شوند و Posts بارگذاری می شود. البته اگر این کار را بکنیم باید ترتیب آن را بدین شکل عوض کنیم:

<Switch>
    <Route path="/new-post" component={NewPost} />
    <Route path="/" component={Posts} />
</Switch>

اگر / اول و new-post دوم باشد، دچار تداخل آدرس می شویم. گفته بودیم که ترتیب بارگذاری این Route ها مهم هستند. به طور مثال در شرایط بالا اگر new-post دوم باشد (از آنجایی که exact را حذف کرده ایم) / شامل new-post/ هم می شود! الان دستور <Switch> معنا پیدا می کند چرا که / برای new-post/ نیز بارگذاری می شود اما با دستور Switch تنها یکی از آن ها بارگذاری خواهد شد.

این برنامه به ظاهر به طور کامل کار می کند اما مشکلی دارد که با تغییر path ها مشخص می شود. در قسمت بعد در رابطه با این مشکل صحبت خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.