меню

Для тех, кто только начинает изучать React, сталкивается с проблемами организации роутов в приложении, т.к. в интернете много материалов по работе react-router более ранних версий. В данной статье будут описаны основная разница и новшества работы с react-router v6.

И так приступим, что нового в релизе 6-ой версии react-router. Вообще создатели react-router часто меняют подходы, используемые в библиотеке, но в этот раз они объединили лучшее, что было в прошлых версиях.

В статье приведу краткий обзор того, что поменялось.

Основные изменения и нововведения react-router v6

  • Switch -> Routes;
  • Относительные пути;
  • Element вместо Component / render;
  • Отказ от withRouter и новые хуки;
  • Вложенные роуты;
  • Индексные роуты;
  • useRoutes вместо react-router-config.

Switch ? Routes

Вместо компонента Switch теперь появился компонент Routes. Но это не просто переименование — Routes более функционален. Основное отличие в том, что Routes не требует жесткого порядка роутов внутри.

Switch обходил роуты в строгом порядке сверху вниз и при первом совпадении пути рендерил заданный компонент. Поэтому важно было определить порядок: например, выносить вниз наиболее общий роут. Рассмотрим пример:

  
const Main = () => (
  <main>
    <Switch>
      <Route path='/' component={Home}/> // Switch всегда будет попадать в этот роут
      <Route path='/students' component={Students}/>
    </Switch>
  </main>
)
 

В таком случае по любом URL рендерился бы компонент Home. Чтобы этого избежать, пришлось бы поставить <Route path='/' component={Home}/> в конец Switch.

В случае с Routes этого делать не нужно. Компонент «более умный» и сматчит наиболее подходящий роут:

 
<Routes>
  <Route path='/' element={<Home />}/>
  <Route path='/students' element={<Students/>}/>
</Routes>
  

Это срабатывает даже с более сложными кейсами, например с именованными параметрами. Пример из документации:

  
<Route path="teams/:teamId" element={<Team />} />
<Route path="teams/new" element={<NewTeamForm />} />
 

По урлу teams/new откроется не компонент Team, а NewTeamForm, хотя путь teams/:teamId тоже матчится с урлом teams/new.

Относительные пути

Следующее важное нововведение — относительные пути. Раньше в пропсе path у компонентов Route нужно было указывать полный путь, например:

 
<Route path=”/courses” component={Courses} />
 

И в роутах внутри компонента Courses пришлось бы тоже указывать полные пути:

 
<Switch>
 <Route path=”/courses/list” component={CoursesList} />
 <Route path=”/courses/:id” component={CoursePage} />
</Switch>
 

Теперь вам достаточно указать относительный путь в пропсе path. Он будет автоматически добавлен к пути родительского роута. Например, компонент Courses теперь можно написать так:

 
<Routes>
 <Route path=”list” element={<CoursesList />} />
 <Route path=”:id” element={<CoursePage />} />
</Routes>
 

То же самое правило действует для компонентов Link, которые отвечают за ссылки. В пропсе "to" можно указать относительный путь, который сработает по той же схеме, что и "path" в Route. Например, если в компоненте Courses сделать <Link to="123">id=123</Link>, то ссылка будет вести на /courses/123.

Element вместо Component / render

Это, пожалуй, одна из самых удобных фичей, которые появились в новом роутере.

Раньше в компоненте Route был выбор: либо указать компонент для рендера, либо передать render-prop функцию. В первом случае код выглядит красиво:

  
<Route path={urls.courses} component={CoursesList} />
  

Но у него есть существенный недостаток. Вы не могли прокинуть дополнительные пропсы в компонент из родителя. Проблема решалась использованием render-функции:

  
<Route path={urls.courses} render={props => <CoursesList {...props} otherProp={myProp} />} />
 

В новой версии роутера даже этого делать не придется. Оба этих пропса заменены на один — element. В него можно передать любой JSX-элемент. Пример выше в таком случае будет выглядеть так:

 
<Route path={urls.courses} element={<CoursesList otherProp={myProp} />} />
 

Это намного более удобно, сокращает код и делает его визуально более понятным.

Отказ от withRouter и новые хуки

react-router появился давно, еще когда функциональные компоненты не имели возможности привязаться к жизненному циклу компонентов. Тогда для большинства компонентов (кроме совсем «глупых») использовались классовые компоненты. Единственным способом подмешать какую-то логику / пропсы в них была композиция, а именно использовался паттерн HOC (Higher Order Component).

В react-router был HOC withRouter, которым нужно было обернуть компонент, чтобы у него появились нужные для роутинга пропсы, например, объект истории, location, параметры и тд.

С появлением хуков в функциональных компонентах смысл использования HOC’а, который добавляет лишнюю обертку над компонентом, пропал. React-router добавил поддержку хуков useHistory, useLocation, useParams, которые отвечали за получение объекта истории, локейшна и сматченных параметров.

В 6 версии HOC withRouter вообще не упоминается, а все пропсы роутера рекомендуется получать через хуки useNavigate, useLocation, useParams.

Вложенные роуты

У этого изменения более глубокие корни. Чтобы понять мотивацию, придется вернуться аж к версии react-router v1.

В первой версии была возможность указать вложенные роуты прямо в конфиге:

  
const routes = {
  path: '/',
  component: App,
  childRoutes: [
    { path: 'about', component: About },
    { path: 'inbox', component: Inbox },
  ]
}

render(<Router routes={routes} />, document.body)
 

Либо в качестве children'ов в компоненте Route:

 
<Route path="/" component={App}>
  <Route path="about" component={About} />
  <Route path="inbox" component={Inbox} />
</Route>
 

В примере выше в обоих случаях в компоненте App нужно рендерить children:

  
<div>
  <h1>App</h1>
  {props.children}
</div>
  

Вложенные роуты позволяют перерендеривать только необходимую часть страницы при изменении урла. Например, оставлять шапку страницы с какими-нибудь табами, а перерисовывать только внутреннее содержимое.

В более поздних версиях роутера эту возможность убрали, заменив на возможность вставить Route на любом уровне вложенности в компонентах.

А теперь снова вернули, только в новом обличии. Теперь вы можете сделать ровно такой же «конфиг» роутов, но использовать компонент Outlet в родительском роуте. Пример из документации:

 
function App() {
 return (
   <BrowserRouter>
     <Routes>
       <Route path="/" element={<Home />} />
       <Route path="users" element={<Users />}>
         <Route path="me" element={<OwnUserProfile />} />
         <Route path=":id" element={<UserProfile />} />
       </Route>
     </Routes>
   </BrowserRouter>
 );
}

function Users() {
 return (
   <div>
     <nav>
       <Link to="me">My Profile</Link>
     </nav>
     <Outlet /> ? сюда подставится дочерний роут с path="me" или ":id"
   </div>
 );
}
  

Однако это не единственная возможность сделать вложенные роуты! Возможность использовать Routes на любом уровне вложенности остается:

  
// Main
<Route path='/students/*' element={<Students/>}/>

// Students
const Students = () => (
  <div>
    <h1>Страница студентов</h1>
    <Routes>
      <Route path=":id" element={<Student />} />
    </Routes>
  </div>
)
 

Обратите внимание на "*" в пропсе path роута /students/*.

Звездочка означает, что данный роут будет матчиться с урлами вида /students/что-либо. При рендере компонента Students для урлов с параметров после /students/<тут> будет рендериться компонент Student. Основное отличие от того, что было раньше — звездочка.

Кстати, пропс exact исчез. Благодаря звездочке в нем пропала необходимость. Получается, что звездочка в пути отражает, что роут может матчиться с вложенными роутами. Если же звездочки нет — то только при точном совпадении пути. Это одна из важных особенностей при переводе проекта на v6, как и исключение регулярных выражений из пути, и опциональных параметров.

Примеры из документации:

Валидные пути:

 
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/
/files/:id/
 

Невалидные пути:

 
/users/:id?
/tweets/:id(\d+)
/files//cat.jpg
/files-*
 

Индексные роуты

Индексные роуты также вернулись к нам из v1-v3. В v4 индексные роуты были заменены на exact. Их назначение — отрендерить некоторый компонент по заданному пути в случае точного совпадения. Пример из документации:

 
function Layout() {
 return (
   <div>
     <GlobalNav />
     <main>
       <Outlet /> <- место для вложенных роутов
     </main>
   </div>
 );
}

function App() {
 return (
   <Routes>
     <Route path="/" element={<Layout />}>
       <Route index element={<Activity />} /> <- индексный роут
       <Route path="invoices" element={<Invoices />} />
       <Route path="activity" element={<Activity />} />
     </Route>
   </Routes>
 );
}
  

Компонент Activity будет отрендерен на месте <Outlet /> внутри компонента Layout.

Таким образом, получается удобная композиция вложенного роута, который нужно отрендерить, если путь полностью соответствует родительскому (path = “/” в примере выше отрендерит Layout c Activity внутри).

useRoutes вместо react-router-config

Как уже упоминалось выше, в роутере v1 существовала возможность задать конфиг роутера для всего приложения. Это было особенно удобно для серверного рендеринга, когда на сервере важно знать структуру роутов приложения.

Затем эта возможность переместилась в отдельный пакет react-router-config.

А теперь снова вернулась! Но уже в основной пакет.

Пример из документации:

  
function App() {
 let element = useRoutes([
   {
     path: "/",
     element: <Dashboard />,
     children: [
       {
         path: "messages",
         element: <DashboardMessages />
       },
       { path: "tasks", element: <DashboardTasks /> }
     ]
   },
   { path: "team", element: <AboutPage /> }
 ]);

 return element;
}
 

Такая конфигурация позволяет декларативно указать структуру роутов приложения.

Заключение

Есть и другие изменения, но я постарался подсветить наиболее интересные — на мой взгляд — и рассмотреть их через призму истории развития библиотеки. Создатели react-router часто меняют парадигмы, требуя от разработчиков изучения новых подходов, но с каждым разом библиотека становится только удобнее и функциональнее.

Материал частично был взят с сайта: habr.com


Возможно, вам будет интересно

Рендеринг нескольких макетов в React с помощью react-router-dom v6.

Пошаговое руководство по рендерингу нескольких макетов в React с использованием новой версии react-router-dom 6.

Руководство по внедрению Tailwind CSS в React JS

В этом руководстве мы расскажем, как настроить или добавить Tailwind CSS в приложение React JS. Мы также покажем Вам пример, как создать простой компонент с помощью фреймворка Tailwind CSS.

React useHooks. Часть 1 - полезные React хуки.

Хуки — это функции в React, которые позволяют Вам использовать состояние и другие функции React без написания классов. В этой статье мы будем рассматривать такие хуки, как useToggle, useFirestoreQuery, useMemoCompare, useAsync, useRequireAuth, useRouter, useAuth, useEventListener, useWhyDidYouUpdate, useDarkMode.

Что нового в react-router v6?

Для тех, кто только начинает изучать React, сталкивается с проблемами организации роутов в приложении, т.к. в интернете много материалов по работе react-router более ранних версий. В данной статье будут описаны основная разница и новшества работы с react-router v6.

Оформление заявки

Документы на создание сайта

Изучите наше коммерческое предложение, заполните БРИФ и отправьте его на почту maxidebox@list.ru. Изучив все пожелания из БРИФ-а, обратным ответом оповестим Вас по стоимости разработке, ответим на вопросы.

КП на создание сайта Коммерческое предложение на созданеи сайта

Мы берем на себя ответственность за все стадии работы и полностью избавляем клиентов от забот и необходимости вникать в тонкости.

Скачать БРИФ (акета) на создание сайта Скачать БРИФ (акета) на создание сайта

Зополните у БРИФ-а все необходимые поля. Сделайте краткое описание к каждому из пунктов анкеты, привидите примеры в соответсвующий пунктах - это позволит лучше понять Ваши ожидания и требования к сайту