меню

Для тех, кто только начинает изучать 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

Помощь сайту
ЮMoney:
4100 1180 7209 833
Карта Сбербанк:
2202 2080 6183 7127

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

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

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

Структура папок для React приложений

Какой должна быть структура папок на проекте React— это тема, которая часто вызывает жаркие споры. Мне и самому было непросто писать об этом, поскольку не существует универсального «правильного» подхода.

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

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

Лучшие практики ReactJS с TypeScript

ReactJS и TypeScript — это мощные технологии, которые можно объединить для создания надежных и типобезопасных приложений. В этом техническом документе изложены лучшие практики, которым нужно следовать при использовании ReactJS с TypeScript.