react-router React路由,让父组件动态去挂载不同的子组件,本文以4.x为例;
4.x 对依赖包的划分:
-
react-router路由基础库 -
react-router-dom适用于浏览器环境的再次封装 -
react-router-native适用于原生开发环境的再次封装 -
react-router-config静态路由配置助手
v4与v5用法差不多,但与v2/v3差异较大,v4/v5采用的是动态路由,而v2/v3采用的是静态路由。
v5 新增了一些新特性,向后兼容:
- 消除了严格模式的警告
- 对
react v16有了更好的支持 - 升级了
react context api - 预优化
build,无需考虑生产环境和开发环境
react-router 主张 一切皆组件,不像vue-router那样做中心化配置(集中式路由)。当然,react也支持集中式路由。
- 前端路由的实现:
history库,管理浏览器会话历史的工具库。本质上还是包装了原生BOM中的window.history和window.location.hash - 核心组件:
<BrowserRouter>、<HashRouter>、<Route>、<Redirect>、<Link>、<NavLink>、<Switch>-
Hash模式:<HashRouter> -
HTML5:history模式:<BrowserRouter> -
react native开发使用的是MemoryHistory
-
- 对象:
history、match、withRouter
引入路由之后,可以把组件分为两类:普通组件(components目录)、路由组件(views目录)
基本配置
- 安装
react-router-dom模块:npm install react-router-dom -S - 在根组件
App.js中引入路由相关的组件,加载的子组件包括Home.js、News.js、Product.js、NotFound.jsimport { BrowserRouter, Route, NavLink, Switch } from 'react-router-dom' render() { return(<BrowserRouter> <div> <header> <NavLink to="/">首页</NavLink> <NavLink to="/news">新闻</NavLink> <NavLink to="/product">商品</NavLink> <NavLink to="/notfound">一个不存在的路由</NavLink> </header> <Switch> <Route exact path="/" component={Home} /> <Route path="/news" component={News} /> <Route path="/product" component={Product} /> <Route component={NotFound} /> // 404组件 </Switch> </div> </BrowserRouter>) }- 在根组件中,把Home组件的路由配置为
path="/",表示默认加载Home组件; -
exact:表示严格匹配模式,必须完全匹配,如果设置为false,每次都会匹配path="/"的路由; - 404组件不需要声明任何
path,且必须放在最后面,当上面的所有路由都没有匹配时,则匹配404路由。
- 在根组件中,把Home组件的路由配置为
-
Redirect表示重定向,可用于指定默认路由import { BrowserRouter, Route, NavLink, Switch, Redirect } from 'react-router-dom' render() { return(<BrowserRouter> <div> <header> <NavLink to="/">首页</NavLink> <NavLink to="/news">新闻</NavLink> <NavLink to="/product">商品</NavLink> </header> <Switch> <Route path="/home" component={Home} /> <Route path="/news" component={News} /> <Route path="/product" component={Product} /> <Redirect to="/home" /> //默认路由,加载Home组件 </Switch> </div> </BrowserRouter>) } - 一个项目中只有一个
<BrowserRouter>,所以<BrowserRouter>包裹根组件即可! -
<NavLink>和<Route>必须包裹在<BrowserRouter>中,且<BrowserRouter>只能有一个直接子节点; -
NavLink与Link:它们最终都被解析为HTML中的<a>,但内部会阻止浏览器的默认行为;-
to属性用于指定跳转的路由,也可以是一个对象<Link to={{ pathname: '/courses', search: '?sort=name', hash: '#the-hash', state: { fromDashboard: true } }}/> -
<NavLink>是<Link>的一个特定版本,它会在匹配上当前的url时给已经渲染的元素添加参数; -
<NavLink>上的属性:activeClassName、activeStyle、exact、strict ...<NavLink className="normal" activeClassName="active" to="/news"> //NavLink的默认样式className="",匹配时的激活样式:activeClassName="" 或 activeStyle={} - 自定义
NavLink:MyNavLink.jsx,统一使用一个activeClassNameimport React, {Component} from 'react' import {NavLink} from 'react-router-dom' export default class MyNavLink extends Component { render() { return <NavLink {...this.props} activeClassName='active'> } }
-
-
Route:path指定路由,component指定路由对应的组件; -
Switch:从上往下匹配时,只要匹配成功,就不会再向下匹配,即只显示与当前路由匹配的Route; -
<NavLink>和<Route>可以不在同一个组件中,只要路由匹配,都可以实现路由跳转。
路由传值
- 动态路由传参:
<Route path="/info/:id" component={Info} />this.state = { id:12 }-
:id表示占位符,对应的NavLink:<NavLink to={`/info/${this.state.id}`}>详情</NavLink> - 在
Info.js组件中获取动态路由的参数id:this.props.match.paramscomponentDidMount() { let { id } = this.props.match.params }
-
-
GET方式传参<Route path="/info" component={Info} /> <NavLink to={`/info?id=${this.state.id}`}>详情</NavLink>- 在
Info.js组件中,获取参数的对象:this.props.locationconst { search } = this.props.location - 但是,GET参数并没有被解析,仍是一个原始字符串:
?id=12 - 借助第三方模块url解析get参数:
npm install url --saveimport url from 'url' //url模块是node上的内置模块,npm上的url模块并不是node的 componentDidMount() { let { id } = url.parse(this.props.location.search, true).query } - React不建议使用
?xxx=xxx的方式传参,而是把to设置为对象<NavLink to={ { pathname:'/info', id:this.state.id } }>详情</NavLink>
- 在
- React解析原始HTML字符串:
dangerouslySetInnerHTML属性this.state = { content: '<p>原始HTML字符串</p>' } <div dangerouslySetInnerHTML={{__html: this.state.content}}></div>
路由嵌套
-
User.js组件是根路由App.js的动态子组件,路由名称为/user- 在
User组件中再配置两个动态子组件:Main.js和Info.jsimport { Route, NavLink, Redirect, Switch } from 'react-router-dom' render() { return(<div> <div className="left"> <NavLink to="/user/">个人中心</NavLink> <NavLink to="/user/info">用户详情</NavLink> </div> <div className="right"> <Switch> <Route exact path="/user/" component={Main} /> <Route path="/user/info" component={Info} /> <Switch> </div> </div>) } -
User组件的路由为/user,则把子组件Main的路由设置为/user/,表示默认加载Main组件; - 亦或者,
Redirect重定向的方式实现默认加载的路由组件render() { return(<div> <div className="left"> <NavLink to="/user/center">个人中心</NavLink> <NavLink to="/user/info">用户详情</NavLink> </div> <div className="right"> <Switch> <Route exact path="/user/center" component={Main} /> <Route path="/user/info" component={Info} /> <Redirect to='/user/center' /> <Switch> </div> </div>) } -
Redirect的to属性也可以是一个对象:pathname表示路由地址,其他的是参数;<Redirect to={ {pathname:'/', state:{foo:'bar'}} } />
- 在
- 动态获取父级的路由,再拼接成自己组件的路由
<Route exact path={`${this.props.match.url}/`} component={Main} /> <Route path={`${this.props.match.url}/info`} component={Info} />
JS控制路由跳转
- 借助对象:
this.props.history中的方法 -
push()、replace()、goBack()、goForward() ......this.props.history.push('/home/user'); this.props.history.push({ pathname: '/home/user', state: { foo:'bar' } }); - 获取传递的参数:
this.props.locationconst { foo } = this.props.location.state;
路由守卫
- 路由守卫其实就是路由拦截,可以做权限控制,它其实也是一个组件,返回值是一个
Route组件;
class RouteGuard extends Component {
state = {
isLogin: false
}
render() {
const {component:Component, ...otherProps} = this.props
return (
<Route {...otherProps} render={props => (
//只有已经登录了才允许进入守卫路由对应的组件,没有登录则重定向到登录页
this.state.isLogin ? <Component {...props}></Component> :
(<Redirect to={{ pathname:'/login', state:{from:props.location.pathname} }} />)
)}></Route>
)
}
}
- 在需要控制权限的地方,应用路由守卫
render() {
return(<BrowserRouter>
<div>
<header>
<NavLink to="/">首页</NavLink>
<NavLink to="/news">新闻</NavLink>
<NavLink to="/mine">我的</NavLink>
</header>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/news" component={News} />
<RouteGuard path="/mine" component={Mine} /> // 应用路由守卫
</Switch>
</div>
</BrowserRouter>)
}
路由的模块化
- 用一个数组管理项目中的所有路由,在把这个数组放在一个独立的
JS文件中- 在
router.js中引入相关组件,管理路由const routes = [ { path:'/', component:Home, exact:true }, { path:'/user', component:User } ] export default routes; - 在根组件
App中引入路由模块import routes from './route/router.js' render(){ return(<BrowserRouter> <header> <NavLink to="/">首页</NavLink> <NavLink to="/user">用户</NavLink> </header> { routes.map((route, key) => { if(route.exact) { return <Route key={key} exact path={route.path} component={route.component} /> } else { return <Route key={key} path={route.path} component={route.component} /> } }) } </BrowserRouter>) }
- 在
- 嵌套路由的管理
- 在
router.js中const routes = [ { path:'/', component:Home, exact:true }, { path:'/user', component:User, routes:[ { path:'/user/', component:Main }, { path:'/user/info', component:Info } ] } ] - 在根组件
App中,通过<Route>的render属性,把子路由传递给子组件<Route key={key} path={route.path} render={props => ( <route.component {...props} routes={route.routes} /> )} /> - 在
User组件中获取子路由:this.props.routesrender() { return(<div> <div className="left"> <NavLink to="/user/">个人中心</NavLink> <NavLink to="/user/info">用户详情</NavLink> </div> <div className="right"> { this.props.routes.map((route, key) => { return <Route key={key} exact path={route.path} component={route.component} /> }) } </div> </div>) }
- 在
- 子路由的
<Route />上可以都加上exact属性,而根路由的<Route />上不行!