React Router
在React
应用中常用的路由是React Router
,React Router
分两个库,用在网站中的库是react-router-dom
,在React Native
中用的库是react-router-native
.还有一个是react-router-config
,此库是用于通过配置生成路由
主要的组件
BrowserRouter
使用
history
模式的路由器,即路由地址不带#
basename
:基础路径,所有路径前缀都默认加上此路径getUserConfirmation
:确认导航forceRefresh
:强制刷新浏览器,类型:Boolean
HashRouer
使用
hash
模式的路由器,路由地址上带#
属性同BrowserRouter
Route
路由匹配器,通过指定
path
属性指定路由,通过子组件来指定路由显示的内容
路由渲染方式有三种:
<Route component>
:通过component
属性指定渲染的组件<Route render>
:通过render
属性指定渲染的内容,接收一个函数,函数返回值为渲染内容Route children>
:通过children
属性或者用Route
包裹组件指定渲染的内容,chilren
属性接收一个函数,函数返回值为渲染内容
Route
其他属性:
path
:指定路由路径,动态属性可通过:
来指定,如:/user/:id
exact
:精确匹配strict
:严格匹配sensitive
:区分大小写
Switch
匹配唯一路由,如果不使用
Switch
则会匹配所有满足的路由,当使用Switch
时则从上到下一次匹配,匹配到则不继续匹配
Link
导航,可通过
to
属性指定跳转地址,to
属性可以是string
(路由),或者为一个对象或者函数(函数需要返回一个对象),对象属性如下:
pathname
:跳转地址search
:链接后的search
字符串,类型是string
hash
:链接后的hash
字符串,类型是string
,需要手动加#
state
:状态
Link
属性:
replace
:替换路由,即指定的路由替换掉当前路由innerRef
:a 标签的ref
component
:指定路由显示的组件内容
NavLink
导航,可通过
to
属性指定跳转地址,与Link
不同的是当地址匹配到指定路由时,则会激活当前NavLink
,通过activeClassName
指定激活样式
activeClassName
:路由激活时的class
activeStyle
:路由激活时的style
exact
:是否精准匹配strict
:是否严格匹配,即:路由字符串完全一致,如果不严格匹配,则当前后有/
时一样可以匹配isActive
:确定链接是否处于激活状态的额外逻辑操作函数
Redirect
重定向导航,通过
to
属性制定重定向的地址,可为string
或对象或函数(返回一个对象),对象属性同Link
的to
属性指定
Redirect
属性:
push
:向历史记录中推入一条记录,而不是替换,类型:Boolean
from
:精准匹配,同Rtoue.exact
,当from
是当前路由时才可重定向strict
:严格匹配,用于指定from
属性的严格匹配sensitive
:区分大小写,用于指定from
属性区分大小写
import React from 'react';
import { BrowserRouter, Link, NavLink, Redirect, Route, Switch } from 'react-router-dom';
import classes from './app.module.css';
const Home = () => {
return <h1>Home</h1>;
};
const About = () => {
return <h1>About</h1>;
};
const App = () => {
return (
<BrowserRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<NavLink to="/" exact activeClassName={classes.active}>
NavLink-Home
</NavLink>
</li>
<li>
<NavLink to="/about" activeClassName={classes.active}>
NavLink-About
</NavLink>
</li>
</ul>
</nav>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/home">
<Redirect to="/" />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
</BrowserRouter>
);
};
export default App;
app.module.css
.active {
color: orange;
font-weight: 700;
}
withRouter
通过
withRouter
将路由相关数据传递给组件,withRouter
是一个高阶组件,传入的组件内部可通过props
访问match
、location
、history
对象
import React from 'react';
import {
BrowserRouter,
Link,
NavLink,
Redirect,
Route,
Switch,
withRouter
} from 'react-router-dom';
import classes from './app.module.css';
const Home = () => {
return <h1>Home</h1>;
};
const About = withRouter((props: any) => {
const { match, location, history } = props;
console.log(match, location, history);
return <h1>About</h1>;
});
const App = () => {
return (
<BrowserRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/home">
<Redirect to="/" />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
</BrowserRouter>
);
};
export default App;
Hooks
react-router-dom
也提供了一些Hooks
用于函数组件中
useHistory
获取
history
对象,一般用于编程式导航
useLocation
获取当前
URL
对象
useParams
获取
URL
参数的键/值对对象
useRouteMatch
用于匹配路由,匹配成功之后返回匹配到的路由信息,否则返回
null
import React, { useEffect } from 'react';
import {
BrowserRouter,
Redirect,
Route,
Switch,
useHistory,
useLocation,
useParams,
useRouteMatch
} from 'react-router-dom';
const Home = () => {
return <h1>Home</h1>;
};
const About = () => {
return <h1>About</h1>;
};
const UserDetails = () => {
const params = useParams<{ id: string }>();
return <h1>用户详情:{params.id}</h1>;
};
const Laytout = () => {
const history = useHistory();
const location = useLocation();
const match = useRouteMatch('/user/:id');
useEffect(() => {
console.log(location);
console.log(match);
}, [location]);
return (
<>
<nav>
<ul>
<li>
<button onClick={() => history.push('/')}>Home</button>
</li>
<li>
<button onClick={() => history.push('/about')}>About</button>
</li>
<li>
<button onClick={() => history.push('/user/1')}>用户1</button>
</li>
<li>
<button onClick={() => history.push('/user/2')}>用户2</button>
</li>
</ul>
</nav>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/user/:id" component={UserDetails} />
</Switch>
</>
);
};
const App = () => {
return (
<BrowserRouter>
<Laytout />
</BrowserRouter>
);
};
export default App;
react-router-config
react-router-config
是react-router
的辅助库,用于集中式配置路由,通过配置文件生成路由的一个库。提供两个方法:
renderRoutes
:接收一个路由配置的数组,返回渲染的路由matchRoutes
:匹配路由,第一个参数传入路由配置数组,第二个参数传入要匹配的路由,返回匹配到的路由信息
App.tsx
import React, { Suspense } from 'react';
import { BrowserRouter, Link, Switch } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './routes';
const App = () => {
return (
<Suspense fallback={<div>loading...</div>}>
<BrowserRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/user">User</Link>
</li>
</ul>
</nav>
{renderRoutes(routes)}
</BrowserRouter>
</Suspense>
);
};
export default App;
因为需要将路由做懒加载处理,所以用了
Suspense
边界处理,懒加载路由可以使用React.lazy
routes/index.ts
import { lazy } from 'react';
import { RouteConfig } from 'react-router-config';
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
exact: true,
component: lazy(() => import('../pages/home'))
},
{
path: '/login',
name: 'Login',
component: lazy(() => import('../pages/login'))
},
{
path: '/user',
component: lazy(() => import('../pages/user')),
routes: [
{
path: '/user/:id',
component: lazy(() => import('../pages/user/user-details'))
}
]
}
];
export default routes;
page/home.tsx
import React from 'react';
const Home = () => {
return <h1>Home</h1>;
};
export default Home;
page/login.tsx
import React from 'react';
const Login = () => {
return <h1>login</h1>;
};
export default Login;
page/user/index.tsx
import React, { useState, useEffect } from 'react';
import { renderRoutes } from 'react-router-config';
import { Link } from 'react-router-dom';
interface User {
id: number;
name: string;
}
const userList: Array<User> = [
{
id: 1,
name: '用户001'
},
{
id: 2,
name: '用户002'
},
{
id: 3,
name: '用户003'
}
];
const User = (props: any) => {
const [list, setList] = useState<Array<User>>([]);
const getList = async () => {
const data = await new Promise((resolve) => setTimeout(() => resolve(userList), 1000));
setList(data as Array<User>);
};
useEffect(() => {
getList();
}, []);
return (
<>
<div>列表:</div>
<ul>
{list.map((item) => (
<li key={item.id}>
<Link to={`/user/${item.id}`}>{item.name}</Link>
</li>
))}
</ul>
{renderRoutes(props.route.routes)}
</>
);
};
export default User;
用户详情使用了嵌套路由,所以在此处需要再次使用
renderRoutes(props.route.routes)
来渲染子路由
page/user/user-details.tsx
import React from 'react';
const UserDetails = (props: any) => {
return <div>用户详情:{props.match.params.id}</div>;
};
export default UserDetails;
从上述列子可以总结出:首先需要一个Router(BrowserRouter/HashRouter)
以及根据需求使用Switch
来包裹路由,然后将所有的路由信息放到一个文件进行配置,并通过renderRoutes
来生成并渲染Route
。如果需要使用嵌套则需要在嵌套路由的父路由中使用renderRoutes
生成渲染出子路由。若需要路由懒加载处理,则需要通过Suspence
和React.lazy
结合使用。
路由配置属性:
path
:路由路径exact
:是否精准匹配strict
:是否严格匹配component
:路由组件render
:路由渲染函数
路由鉴权
其实,react-router-config
的代码量并不大,其renderRoutes
的原理就是解析配置文件并渲染成Route
组件,但是不支持路由鉴权,如果需要路由鉴权的话,可以通过withRouter
封装一个鉴权组件,然后用其包裹
import React from 'react';
import { withRouter } from 'react-router';
import { Redirect } from 'react-router-dom';
const AuthRoute = withRouter((props: any) => {
const { children, history } = props;
const token = localStorage.getItem('token');
if (history.location.pathname !== '/login' && !token) {
return <Redirect to="/login" />;
}
return children;
});
export default AuthRoute;
调整App.tsx
文件,用AuthRoute
组件包裹renderRoutes
import React, { Suspense } from 'react';
import { BrowserRouter, Link, Switch } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './routes';
+import AuthRoute from './components/AuthRoute';
const App = () => {
return (
<Suspense fallback={<div>loading...</div>}>
<BrowserRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/user">User</Link>
</li>
</ul>
</nav>
<Switch>
- {renderRoutes(routes)}
+ <AuthRoute>{renderRoutes(routes)}</AuthRoute>
</Switch>
</BrowserRouter>
</Suspense>
);
};
export default App;