Совсем недавно состоялся релиз 6-ой версии react-router. Вообще создатели react-router часто меняют подходы, используемые в библиотеке, но в этот раз они объединили лучшее, что было в прошлых версиях.
В статье приведу краткий обзор того, что поменялось.
Поиграть с новой версией можно тут.
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 часто меняют парадигмы, требуя от разработчиков изучения новых подходов, но с каждым разом библиотека становится только удобнее и функциональнее.
ссылка на оригинал статьи https://habr.com/ru/company/kts/blog/598835/
Добавить комментарий