English | 简体中文
react-router-manage
is based on react-router
v6
. It can realize the function of authentication, guard, add, delete and check of route by configuration.
Because react-router
v5
upgrades to v6
have a high cost, react-router-manage
provides the API from the original v5
section for compatible projects that use v5
for smooth upgrades to v6
- 🛠 [
config router
] - Centralized router configuration, fast and convenient management. - + [
addRoutes
] - Dynamically addroutes
: you can use hookuseAddRoutes
to add routes and automatically refresh the view. - ➖ [
removeRoutes
] - Dynamically delete routes: you can use hookuseRemoveRoutes
to delete routes and automatically refresh the view. - 🖇 [
updateRoutes
] - Dynamically modify routes: you can use hookuseUpdateRoutes
to modify routes. - 🔐 [permission] - Permission control: configure the code of the route and automatically manage the permission of the route
- 👨
✈️ [route guard] - Provide hooks for route entry configbeforeEnter
andbeforeEachMount
, route exit hookuseBeforeLeave
- 🌲 [navigation] - level navigation: supports level navigation, and automatically generates navigation bars for parent-child routes, such as breadcrumbs and menu navigation
- Breadcrumb navigation based on
antd
automatic generationantd-breadcrumbs
- Breadcrumb navigation based on
npm install react-router-manage --save
- basic
- config children routes, eg:
Outlet
children level routes - global guard
beforeEachMount
- routes operation
useAddRoutes
,useUpdateRoutes
,useRemoveRoutes
- auth-basic
- @rrmc/antd-breadcrumbs
field name | description | type | is required |
---|---|---|---|
basename |
the routing prefix of the route | string |
not required , default is / |
routes |
hierarchical configuration of routes | RouteTypeI[] |
required |
beforeEachMount |
each route is called before rendering | `(to: RouteTypeI | undefined, next: ({path?: string; name: string} | React.ComponentType) => void): void` |
autoDocumentTitle |
the title of the document changes depending on the route switch | boolean | (RouteTypeI[]) => string |
not required , default is false |
LoadingComponent |
Used for react Suspend component to configure fallback when loading asynchronous components or before next called |
React.FunctionComponent | not required |
There are currently two router modes:
history
hash
A simple global configuration(history
mode)
import React from 'react';
import { MRouter, defineRouterConfig } from 'react-router-manage';
const routerConfig = defineRouterConfig({
basename: '/',
routes: [{...}], // Please check the routes configuration below
// autoDocumentTitle: true, // if true,the transform is automatically set document.title,
// autoDocumentTitle: (routes) => return `网易云商-${routes.map((i) => i.title).join('-')}`, // custom configuration document.title
// beforeEachMount: (to, next) => { // configure the global route entry guard. You can see the introduction of the global route guard below
// console.log(to)
// next();
// }
})
function App () {
return (
<MRouter routeConfig={routeConfig}>
{(children) => children}
</MRouter>
)
}
A simple global configuration(hash
mode)
import React from 'react';
import { MHRouter, defineRouterConfig } from 'react-router-manage';
const routerConfig = defineRouterConfig({
basename: '/',
routes: [{...}], // Please check the routes configuration below
// autoDocumentTitle: true, // if true,the transform is automatically set document.title,
// autoDocumentTitle: (routes) => return `网易云商-${routes.map((i) => i.title).join('-')}`, // custom configuration document.title
// beforeEachMount: (to, next) => { // configure the global route entry guard. You can see the introduction of the global route guard below
// console.log(to)
// next();
// }
})
function App () {
return (
<MHRouter routeConfig={routeConfig}>
{(children) => children}
</MHRouter>
)
}
field name | description | type | is required |
---|---|---|---|
name |
the name of the route, the name is globally unique and non-repeatable, and is used to obtain the route info | string |
required |
path |
the path of the route, the combined full path is globally unique and can not be repeated。 But if it is a nested sub route, it can not be configured, it is equivalent to setting the index attribute in the react-route Route component |
string |
required |
title |
the Chinese name or other name of the route, the name of the display, used to automatically generate navigation and breadcrumbs | string |
not required |
index |
the first route | boolean |
not required |
component |
current route matching component, if it is not configured, it will jumps to the next level of privileged route | React.Component | React.FunctionComponent |
not required |
items |
the visual sub-level routes, used for navigation parent-child relationship, is actually the same level of route | RouteTypeI[] |
not required |
children |
sub-routing, rendering in react-router V6 in Outlet component |
RouteTypeI[] |
not required |
props |
when rendering the route, the props content is automatically injected,, <Component {...props}/> |
Record<string, any> |
not required |
code |
used for permission verification, will be compared permissionList |
string | string[] | (route: RouteTypeI) => boolean |
not required |
redirect |
route redirect to the specified route with priority over component | string |
not required |
beforeEnter |
render the method called by the routing money. if a component is passed in the call next function, the component will be rendered. if next function not call, The component configured by the route will not be rendered |
`(to: RouteTypeI | undefined, next: (options?: {name?: string; path?: string} | React.ComponentType) => void): void` |
beforeLeave |
The callback called before leaving the route needs to be actively called | (to: RouteTypeI | undefined,from: RouteTypeI | undefined, next: () => void): void |
not required |
meta |
Some custom information can be put here,,you call use currentRoute.meta get meta info |
Record<string, any> |
not required |
hidden |
display and hidden of navigation | boolean |
not required, default is false |
fullscreen |
You can hidden navigation ui, fullscreen set true ,navigation is hidden, the current configuration is use in router-base-nav |
boolean |
not required |
icon |
Icon for displaying navigation, the current configuration is use in router-base-nav |
string |
not required |
type |
if type is null string, this route is not really rendered, but the correct currentRoute can be set, the current configuration is use in router-base-nav |
real | null |
not required, default is real |
bredcrumbs |
used to configure breadcrumbs in routing, antd-breadcrumbs |
BreadcrumbsI | not required |
field name | description | type | is required |
---|---|---|---|
isRoot |
Is it the root node of breadcrumbs? If it is, it will be calculated from the next level | boolean |
false |
text |
The name of breadcrumbs. If it is not configured, it will be used by defaultroute.title |
string | React.ReactNode | (route: RouteTypeI) => React.ReactNode |
not required |
hidden |
Whether to hide the display of bread crumbs at this level | boolean |
false |
NOTE
- If a function is configured in
code
, since it will be called in batch during route initialization, please do not call asynchronously. If necessary, i recommended to use 'beforeEnter' to achieve the same effect - If
component
is not configured for the parent route, jump to the route to find the first route with permission under items and children. If it's not found, the page without permission will be displayed - If
redirect
andcomponent
are configured at the same time, thecomponent
will be ignored - If
beforeEnter
andbeforeEachMount
,next
function called acomponent
, the Component will be rendered, ifStrictMode
is used in react, the function may be called twice, which is normal
The react-router-manage
,you can configuration children
, items
, to express the parent-child relationship of route context.
/**
* The article list page and the article details page are on different pages
* /user/article/list The article list page
* /user/article/detail The article detail page
* */
/user/article/list /user/article/detail
+------------------+ +-----------------+
| +--------------+ | | +-------------+ |
| | ------------ | | +------------> | | content | |
| | ------------ | | | | | |
| | ------------ | | | | | |
| | ------------ | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
An example with basic router configuration
import React from 'react';
import { MRouter, defineRouterConfig } from 'react-router-manage';
const Users = () => {
return <div>Users</div>
}
const Profile = () => {
return <div>Profile</div>
}
const appRouterConfig = defineRouterConfig({
basename: '/',
// Configure level navigation
routes: [
{
name: 'user', // Each route corresponds to a globally unique name
path: 'user', // The path will be automatically converted to '/user'. Since there is no component configured here, entering '/user' will be redirected to '/user/list'
items: [ // Items is used to configure navigation with a level structure, such as breadcrumb navigation,
{
name: 'userList',
path: 'list', // The path is automatically converted to `/user/list`
component: Users,
},
{
name: 'profile',
path: 'profile', // The path is automatically converted to `/user/list`
component: Profile,
},
]
}
],
});
function App () {
return (
<MRouter routeConfig={routeConfig}>
{(children) => children}
</MRouter>
)
}
The UI of some applications consists of multiple nested components. In this case, the fragment of the URL usually corresponds to a specific nested component structure, for example:
/**
* The article list page and the article detail page are on the same page
* /user/article/list The article list page
* /user/article/detail The article detail page
* */
/user/article/list /user/article/detail
+------------------+ +-----------------+
| user | | user |
| +--------------+ | | +-------------+ |
| | list | | +------------> | | content | |
| | ------------ | | | | | |
| | ------------ | | | | | |
| | ------------ | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
An example with nested route configuration
import React from 'react';
import Outlet from 'react-router';
import { MRouter, defineRouterConfig } from 'react-router-manage';
const Users = () => {
return <div>
<div>Users</div>
<Outlet />
</div>
}
const Profile = () => {
return <div>Profile</div>
}
const UserProfile = () => {
return <div>UserProfile</div>
}
const UserArticles = () => {
return <div>UserArticles</div>
}
const appRouterConfig = defineRouterConfig({
basename: '/',
// Configure level navigation
routes: [
{
name: 'user', // Each route corresponds to a globally unique name
path: 'user', // The path will be automatically converted to '/user'. Since there is no component configured here, entering '/user' will be redirected to '/user/list'
items: [ // Items is used to configure navigation with a level structure, such as breadcrumb navigation,
{
name: 'userList',
path: 'list', // The path is automatically converted to `/user/list`
component: Users,
children: [
{
name: 'userProfile',
path: 'profile',
component: UserProfile,
},
{
name: 'userArticle',
title: 'user articles',
component: UserArticles,
}
]
},
{
name: 'profile',
path: 'profile', // The path is automatically converted to `/user/list`
component: Profile,
},
]
}
],
});
function App () {
return (
<MRouter routeConfig={routeConfig}>
{(children) => children}
</MRouter>
)
}
- If
permissionMode
isparent
, if the parent route does not have permission, then the child routes do not have permission - If
permissionMode
ischildren
, if the child route has permission, the parent route will automatically change to have permission regardless of whether it has permission configured
- You need to pass in the
permissionList
in theMRouter
component, and set thehasAuth
totrue
, which is true by default - You need to configure the
code
in the routing configuration. If it is not configured, you have permission by default
An example of authentication configuration
const permissionList = [`admin`, 'staff'] // Represents that the current user is admin
// const permissionList = ['staff'] // Represents that the current user is an employee
const appRouterConfig = defineRouterConfig({
basename: '/',
// Configure level navigation
routes: [
{
name: 'user', //Each route corresponds to a globally unique name
path: 'user', // // The path will be automatically converted to '/user'. Since there is no component configured here, entering '/user' will be redirected to '/user/list'
code: [`admin`, 'staff'],
items: [ // Items is used to configure navigation with a level structure, such as breadcrumb navigation,
{
name: 'userList',
path: 'list', // // The path is automatically converted to `/user/list`
component: Users,
code: 'admin', // This route will be filtered if the current user is an employee
// code: (currentRoute) => {
// // You can also perform custom verification here
// // Do not check here, because this is a batch check during reinitialization. If you want to check only after entering this route, please use `beforeeachmount`
// return getHasAuth(currentRoute);
// }
},
{
name: 'profile',
path: 'profile', // // The path is automatically converted to `/user/list`
component: Profile,
code: [`admin`, 'staff'], // Have a common route personal Center
},
]
}
],
});
// `hasAuth` It can not be configured. The default value is `true`
function App () {
return (
<MRouter routeConfig={routeConfig} permissionList={permissionList} hasAuth={true} permissionMode="parent">
{(children) => children}
</MRouter>
)
}
Modify the above appRouterConfig
const NoAuth = () => {
return <div>NoAuth</div>
}
const appRouterConfig = defineRouterConfig({
basename: '/',
// Configure level navigation
routes: [
{
name: 'user', //Each route corresponds to a globally unique name
path: 'user', // // The path will be automatically converted to '/user'. Since there is no component configured here, entering '/user' will be redirected to '/user/list'
items: [ // Items is used to configure navigation with a level structure, such as breadcrumb navigation,
{
name: 'userList',
path: 'list', // // The path is automatically converted to `/user/list`
component: Users,
},
{
name: 'profile',
path: 'profile', // // The path is automatically converted to `/user/list`
component: Profile,
},
]
}
],
beforeEachMount: (to, next) => {
if (to.name === 'userList') {
next();
} else {
next(NoAuth)
}
}
});
hook name |
type | describe |
---|---|---|
useAddRoutes |
() => (routes: RouteTypeI[]) => void |
Dynamically add routes |
useRemoveRoutes |
() => (routes: { routeName: string; routeData: Partial<RouteTypeI> }[]) => void |
Dynamically update routes |
useUpdateRoutes |
() => (routeNames: string[]) => void |
Dynamically remove routes |
useBeforeLeave |
(fn: BeforeLeaveI) => void |
The guard when the route leaves needs to call next to jump normally |
useRouter |
() => RoutesStateStruct |
state of route storage |
useHistory |
() => BrowserHistory |
get history ,react-router v6 There is no exposure. The user's V5 upgrade to V6 is too smooth. It is not recommended |
Route navigation by useRouter
useRouter
state data can be obtained in all components
useRouter()
is return state:RoutesStateStruct
field name | describe | type |
---|---|---|
currentRoute |
Current route object | RouteTypeI |
routesMap |
All routes corresponding to route name and path are stored in this object | Record<string, RouteTypeI> |
navigate |
Used to jump route | (to: string, {query: Record<string, any>; params: Record<string, any>; state: any}) => void} |
authRoutes |
Routes objects with permission after authentication | RouteTypeI[] |
routes |
Incoming routes | RouteTypeI[] |
query |
Current address bar query parameters | Record<string, string> |
params |
Current address bar dynamic route query parameters | Record<string, string> |
useRouter
called return navigate is base on react-router
export useNavigate
,some interception processing has been done for route jump,so Do't use navigate
in import react router
navigate
has two parameters. The first parameter is the path to jump, and the second parameter is the route configuration to jump. The type is as follows:
(to: string, {query: Record<string, any>; params: Record<string, any>; state: any}) => void}
query
, Thequery
parameters will be automatically added to the address when jumping the route, for example:navigate('/user/detail', { query: {id: 13}})
, It will turn into/user/detail?id=13
- params, When a dynamic route with parameters is configured, it will be automatically replaced. For example:
navigate('/user/detail/:id', { params: {id: 13}})
, It will turn into/user/detail/13
- state, 这个是history原始的state
currentRoute
the route information passed in when the configuration is included will be automatically added internally parent
, parent
is used to identify the parent route, for example: parentRoute = currentRoute.parent
...
import { useRouter } from 'react-router-manage'
...
function Item() {
const { currentRoute, routesMap } = useRouter();
const onClick = () => {
navigate(routesMap.LIST.path); // navigate接收一个字符串
}
return (
<div><Button onClick={onClick}>跳转到LIST</Button></div>
)
}
useBeforeLeave
You need to call next to jump normally
import { useBeforeLeave, useRouter } from 'react-router-manage';
import { Modal } from 'ppfish';
const function Item() {
const {navigate, routesMap} = useRouter();
useBeforeLeave((to, from, next) => {
Modal.confirm({
title: 'Are you sure you want to jump ?',
onOk: () => {
next();
}
})
})
const onClick = () => {
navigate(routesMap.List.path);
}
return (<div>
<Button onClick={onClick}>jump</Button>
</div>)
}
useAddRoutes
Add routes
const AddRoutesWrapComponent = ({children}) => {
const addRoutes = useAddRoutes();
useEffect(() => {
addRoutes([{
parentName: 'PAGE1', // 'parentName' needs to be passed in, otherwise it will be inserted under the first level
title: 'Dynamically added pages',
name: 'add',
path: 'add',
component: Page,
code: 'staff',
}])
}, [])
return <div data-testid='__router-children'>
{children}
</div>
}
useUpdateRoutes
update routes
const UpdateRoutesWrapComponent = ({children}) => {
const updateRoutes = useUpdateRoutes();
useEffect(() => {
updateRoutes([{
routeName: 'PAGE1',
routeData: {
title: 'Modified page' // Modified title
}
}])
}, [updateRoutes])
return <div data-testid='__router-children'>
{children}
</div>
}
useRemoveRoutes
delete routes
const RemoveRoutesWrapComponent = ({children}) => {
const removeRoutes = useRemoveRoutes();
useEffect(() => {
removeRoutes(['PAGE1']) // 传入要删除的 route的name字段
}, [])
return <div data-testid='__router-children'>
{children}
</div>
}
name | describe | type |
---|---|---|
beforeEachMount |
Called before each route rendering, next Must be called to render the component |
`(to: RouteTypeI | undefined, next: {name?: string; path?: string} |
import NoAuth from './NoAuth', // No permission component
const appRouterConfig = {
basename: '/',
routes: [
{
name: 'root',
path: '/',
items: [
{
name: 'page1',
path: 'page1',
components: Page,
custom: 'aaa',
},
{
name: 'page2',
path: 'page2',
components: Page2,
custom: 'bbb',
}
]
}
],
beforeEachMount(to, next) {
if (to.custom === 'aaa) {
next(); // if call, The component corresponding to the route will be rendered normally
} else {
next(NoAuth) // render components without permission
}
}
}
name | describe | type |
---|---|---|
beforeEnter |
Called before rendering the current route (after beforeEachMount ), next must be called before rendering the component |
`(to: RouteTypeI | undefined, next: {name?: string; path?: string} |
beforeLeave |
The callback called before leaving the route needs to actively call 'next' to jump normally | `(to: RouteTypeI | undefined,from: RouteTypeI | undefined, next: {name?: string; path?: string} |
import NoAuth from './NoAuth', // No permission component
const appRouterConfig = {
basename: '/',
routes: [
{
name: 'root',
title: '根路径',
path: '/',
items: [
{
name: 'page1',
path: 'page1',
components: Page,
custom: 'aaa',
beforeEnter: (to, next) => {
//...
next(); // 需要跳转则调用
},
beforeLeave: (to, next) => {
//...
next(); // 需要跳转则调用
}
},
{
name: 'page2',
path: 'page2',
components: Page2,
custom: 'bbb',
}
]
}
],
beforeEachMount(to, next) {
if (to.custom === 'aaa) {
next(); // 调用,则会正常渲染该路由对应的组件
} else {
next(NoAuth) // 则渲染无权限组件
}
}
}
- Keepalive support
- Improvement of sample code
- Route switching transition animation
react-router-manage
is used in many projects within Netease BizEase. In order to give back to the community, it has decided to open source, and is willing to build a useful react router management library with friends in the community