本文以 react-router-dom v5.3.0 学习。
模块简介
history 核心,提供两种路由 history 对象上的 api ,监听路由变化,改变路由等;
react-router 处理视图的渲染,提供一些组件;
react-router-dom 提供BrowserRouter,HashRouter,Link,NavLink,转发了 react-router 的组件:
export {
MemoryRouter,
Prompt,
Redirect,
Route,
Router,
StaticRouter,
Switch,
generatePath,
matchPath,
withRouter,
useHistory,
useLocation,
useParams,
useRouteMatch
} from "react-router";
export { default as BrowserRouter } from "./BrowserRouter.js";
export { default as HashRouter } from "./HashRouter.js";
export { default as Link } from "./Link.js";
export { default as NavLink } from "./NavLink.js";
两种路由
- history 路由
pushState
,replaceState
改变路由
popState
监听路由
popstate
事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()
、history.forward()
、history.go()
方法。
- hash 路由
location.hash
改变路由
hashchange
监听路由
路由组件
Router
function Login(props) {
console.log('login', props);
return <div>login</div>;
}
function Register(props) {
console.log('register', props);
return <div>register</div>;
}
function Foo(props) {
console.log('foo', props);
return <div>foo</div>;
}
function App() {
return (
<BrowserRouter>
<Route exact path="/" component={Login} />
<Route exact path="/foo" component={Foo} />
<Route exact path="/register" component={Register}></Route>
</BrowserRouter>
);
}
访问http://localhost:3000/
,匹配 Login 组件,打印的 props:
history、location、match 就是 RouterContext 内容
BrowserRouter/HashRouter 将 props.children 和内部的 history 传给 Router 组件返回;
Router 组件会调用 history.listen 监听 location ,给子组件提供 RouterContext、 HistoryContext上下文;
子组件 Route 会消费 RouterContext,render 时,会给 Route 的子组件提供 RouterContext 上下文,渲染子组件;
// BrowserRouter/HashRouter
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
// Router
this.unlisten = props.history.listen(location => {
if (this._isMounted) {
this.setState({ location });
} else {
this._pendingLocation = location;
}
});
render() {
return (
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}}
>
<HistoryContext.Provider
children={this.props.children || null}
value={this.props.history}
/>
</RouterContext.Provider>
);
}
// Route
<RouterContext.Consumer>
// 处理 context
<RouterContext.Provider value={props}>
// value 的 props 为处理后的 context ,根据 Route 的 props (children, component, render) 来渲染组件,它们会将 value.props 作为属性传给组件,所以组件里面 props 能拿到 RouterContext
</RouterContext.Provider>
</RouterContext.Consumer>
Route
- 说明
<Route exact path="/" component={Login}></Route>
exact 精确匹配
path 用于匹配路由,渲染组件
component 组件
render 函数形式返回元素
children 无论是否匹配成功都会渲染,匹配成功 match 有值
strict /foo -> /foo/ /foo 都能匹配;/foo/ -> /foo/ /foo/xx 匹配;
匹配路由,渲染组件,路由状态是通过 context 传递,Route 通过 Consumer 获取上一级的路由匹配,成功,渲染组件,并利用 context 逐层传递的特点,将自己的路由信息传给子路由,实现路由嵌套。
- 路由优雅的写法 react-router-config
function Login(props) {
console.log('login', props);
return <div>login</div>;
}
function Register(props) {
console.log('register', props);
return <div>register</div>;
}
function Foo(props) {
console.log('foo', props);
return <div>foo</div>;
}
const routes = [
{
path: '/',
component: Login,
exact: true
},
{
path: '/foo',
component: Foo,
exact: true
},
{
path: '/register',
component: Register,
exact: true
}
];
function App() {
return (
<BrowserRouter>
<div>{renderRoutes(routes)}</div>
</BrowserRouter>
);
}
其实就是 renderRoutes(routes) 帮我们生成 Route。源码如下:
function renderRoutes(routes, extraProps = {}, switchProps = {}) {
return routes ? (
<Switch {...switchProps}>
{routes.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props =>
route.render ? (
route.render({ ...props, ...extraProps, route: route })
) : (
<route.component {...props} {...extraProps} route={route} />
)
}
/>
))}
</Switch>
) : null;
}
Switch 包裹 Route ,渲染是通过 render 函数来渲染组件的。
Switch
Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route>
或 <Redirect>
,它里面不能放其他元素。
function App() {
return (
<HashRouter>
<div>
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/register" component={Register}></Route>
<Route
path="/foo"
render={props => {
return <Foo {...props} />;
}}
></Route>
</Switch>
</div>
</HashRouter>
);
}
没有 Switch exact 时,页面访问 /register 时,Login、Register 都会渲染。
+ Switch,页面展示 Login,
++ exact 页面展示 Register
Redirect
路由匹配不到时,重定向组件
function Login(props) {
console.log('login', props);
return <div>login</div>;
}
function Register(props) {
console.log('register', props);
return <div>register</div>;
}
function Foo(props) {
console.log('foo', props);
return <div>foo</div>;
}
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/foo" component={Foo} />
<Route exact path="/register" component={Register}></Route>
<Redirect to="/foo" />
</Switch>
</BrowserRouter>
);
}
不用 Switch 包裹时,页面组件都会加载,所以总是显示 foo 页面。
Redirect from 属性:是限制来自 from 下的找不到,才会重定向。
路由状态获取
- 被 Route 包裹的路由组件混入了路由对象,所以子组件可以通过
props
获取(history,location,match); - 距离路由组件比较远,层级比较深的类组件,可以通过
withRouter
获取; - 函数组件通过
useLocation
、useHistory
获取;
class Son extends React.Component {
render() {
console.log(this.props);
return <div>son</div>;
}
}
function Sonfn(props) {
console.log(props);
const location = useLocation();
const history = useHistory();
console.log(location, history);
return <div>son</div>;
}
const SonH = withRouter(Son);
function Login(props) {
console.log('login', props);
return (
<div>
<SonH />
<Sonfn />
</div>
);
}
function Register(props) {
console.log('register', props);
return <div>register</div>;
}
function Foo(props) {
console.log('foo', props);
return <div>foo</div>;
}
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/foo" component={Foo} />
<Route exact path="/register" component={Register}></Route>
<Redirect to="/foo" from="/register" />
</Switch>
</BrowserRouter>
);
}
路由跳转、传参
跳转
<NavLink>
、Link
跳转;history.push
函数跳转;
传参
- 直接 url 上面拼接;
- state 上传递,location 上面获取;
动态路由 <Route to="/login/:id"/>
可以用正则如下:
path="/login/:type(login|loginout)" ->/login/login /login/loginout
path="/login/:id?" -> /loign/1 /login
嵌套路由,子路由要跟随父路由,父路由不能加 exact
function Son(props) {
console.log(props);
const location = useLocation();
const history = useHistory();
console.log(location, history);
return <div>son</div>;
}
function Login(props) {
console.log('login', props);
return (
<div>
<Son />
</div>
);
}
function Address(props) {
const { history } = props;
const goHome = () => {
history.push({
pathname: '/',
state: {
info: 'address'
}
});
};
return (
<div>
<p>address</p>
<button onClick={() => goHome()}>首页</button>
</div>
);
}
function Register(props) {
console.log('register', props);
return (
<div>
<Route exact path="/register/address" component={Address} />
<NavLink to="/register/address">Address</NavLink>
</div>
);
}
function Foo(props) {
console.log('foo', props);
return <div>foo</div>;
}
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/foo" component={Foo} />
<Route path="/register" component={Register}></Route>
<Redirect to="/foo" />
</Switch>
<NavLink to="/foo">Foo</NavLink>
<NavLink to="/register">Register</NavLink>
</BrowserRouter>
);
}