React-router
React-router
前言
React.js只提供 UI (view)层面的解决方案(MVC中的V)。在实际的项目当中,它并不能解决我们所有的问题,需要结合其它的库,例如 Redux、React-router 等来协助提供完整的解决方案。
介绍
React-router4(以下简称RR4)遵循React的设计万物皆组件的理念。所以只是一堆 提供了导航功能的组件,具有声明式(引入即用),可组合性的特点
- 组成部分:
react-router
核心,是浏览器和原生应用的通用部分,不提供dom操作进行跳转的api。react-router-dom
基于浏览器环境的开发。react-router-native
基于react-native环境的开发。
安装
浏览器开发就只需要安装react-router-dom
npm install react-router-dom --save
安装
react-router-dom
或react-router-native
时,都会自动将react-router
作为依赖安装
常用组件
路由类型
1、BrowserRouter
<BrowserRouter/>
是react-router-dom增加的组件,利用path来实现路由(类似与Vue中的history路由,使用pushState和replaceState事件构建路由)。使得页面和浏览器的history保持一致。如:http://localhost/home
需要服务器的配置
2、HashRouter
<HashRouter/>
是react-router-dom增加的组件,利用hash来实现路由(使用window.location.hash和hashchange事件构建路由),如:http://localhost/#/home
路由渲染方式
Route
<Route/>
是 React Router 中最重要的组件了,它最基本的职责就是:当页面的访问地址与 Route 上的 path 匹配时,就在<Route/>
所在位置渲染出对应的组件
<Route path="/home" component={Home} />
<Route path="/list" component={List} />
<Route path="/users" component={Users} />
- path(String):当浏览器地址与path地址匹配时,则渲染component对应的组件
如果不给path,那么路由将总是匹配
- component(Component):在path匹配成功之后会渲染这个组件
往组件props注入history,location,match参数
- render(Function):在path匹配成功之后会渲染这个函数返回的内容
- exact(Boolean):是否精确匹配
<!-- 浏览器地址为'/home' 和'/home/a',都能匹配 -->
<Route path="/home" component={Home}/>
<!-- 浏览器地址为'/home'时能匹配 ,浏览器地址为'/home/a'时不能匹配 -->
<Route path="/home" component={Home} exact />
Redirect
<Redirect/>
用于重定向页面
- from(String):浏览器地址为from的值时,重定向到to所在的地址
- to(String|Object):跳转的地址(如值为Object,有以下参数)
- pathname,跳转到的URL。
- search,跳转后的url参数。此例中,跳转后的url是
http://127.0.0.1:9090/p2?p1=1&p2=2
- state,会保存在this.history.location.state中,可以用于传递数据
Switch
多个Route可能会被同时渲染(如下),用<Switch/>
来包裹多个Route/Redirect组件,只渲染出第一个与浏览器访问地址匹配的 <Route>
或 <Redirect>
(注意顺序问题)
<!-- 以下可能会同时渲染两个组件 -->
<Route path="/" component={Home} />
<Route path="/home" component={Home} />
<Route path="/list" component={List} />
<!-- 以下只渲染一个组件 -->
<Switch>
<Route path="/" component={Home} />
<Route path="/home" component={Home} />
<Route path="/list" component={List} />
<Redirecct to="/404"/>
</Switch>
- 用途:
- 渲染单个组件
- 重定向
- 404页面
导航
声明式导航
利用组件(Link或NavLink)属性实现路由跳转
Link
<Link/>
为你的应用提供声明式,无障碍导航,默认解析成a标签
- to(String): 点击跳转到指定路径
如果只是单纯的跳转就直接用字符串形式的路径
<Link to="/home" />
- to(Object):携带参数跳转到指定路径(同Redirect)
跳转时携带详细信息(比如这是个支付跳转,需要把商品的价格等信息传递过去)
<Link to={{
pathname: '/pay',
search: '?id=123456',
state: { price: 998 }
}} />
- replace: bool
为 true 时,点击链接后将使用新地址替换掉上一次访问的地址
NavLink
<NavLink/>
是<Link/>
的特殊版,顾名思义这就是为页面导航准备的。因为导航需要有 “激活状态”
- activeClassName(String) 写法:activeClassName=""
导航选中激活时候应用的样式名,默认样式名为 active
- activeStyle(Object)
如果不想使用样式名就直接写style
- to: string|object
同
<Link/>
- isActive: func
通过返回值(boolean)决定导航是否激活,或者在导航激活时候做点别的事情。不管怎样,它不能决定对应页面是否可以渲染。
编程式导航
利用路由提供的history对象实现路由跳转
利用js实现跳转,编程式导航的三大对象:history,location,match
history.push(path|Object)
history.replace(path|Object)
获取history对象
- 1、利用
<Route />
渲染的组件
直接通过
props.history
/this.props.history
获取
组件只要是通过<Route component={组件}/>
方式渲染的组件,history对象自动传入组件的props(this.props.history访问)
- 2、
withRouter高阶组件
(推荐)
高阶组件不是一个React组件,而是一个函数,包装函数
利用withRouter高阶组件包装后,直接通过组件的props.history获取,就可以使用编程式导航进行点击跳转
高阶组件:一个包装函数
- 3、
Context
(了解,不推荐)
RR4 在 Router 组件中通过Contex暴露了一个router对象,router对象下包含history(即:this.context.router.history)
路由的Enter与Leave
相比之前的版本,RR4有了很大的改变,废除了之前版本onEnter、onLeave等路由钩子函数,利用组件生命周期函数来替代
- 使用componentDidMount或componentWillMount来代替onEnter
- 使用componentDidUpdate 或 componentWillUpdate来代替onUpdate
- 使用componentWillUnmount来代替onLeave
动态路由
在匹配路径path 的后面加上冒号 + 参数, 如
path ="goods/:id"
- 获取动态id方式:
当通过<Route/>
渲染组件时,路由会给我们组件注入3个参数(history,location,match),通过match.params
获取动态路由参数
嵌套路由
props.match是实现嵌套路由的对象,当我们在某个页面跳转到它的下一级子页面时,我们不会显显性地写出当前页面的路由,而是用match对象的path和url属性。
- match.path:是指写在
<Route/>
中的 path 参数; - match.url:是指在浏览器中显示的真实 URL。
match.path 可用于嵌套组件中的
<Route/>
,而 match.url 可用于嵌套组件中的<NavLink/>
<div className="subnav">
<NavLink to={props.match.url + "/computer"} activeClassName="active">电脑</NavLink>
<NavLink to={props.match.url + "/pad"} activeClassName="active">平板</NavLink>
<NavLink to={props.match.url + "/acc"} activeClassName="active">配件</NavLink>
</div>
<Switch>
<Route path={props.match.path + "/phone"} component={Phone}/>
<Route path={props.match.path + "/computer"} component={Computer}/>
<Redirect from={props.match.path} to={props.match.path + "/computer"} exact />
<Route path={props.match.path + "/pad"} component={Pad}/>
<Route path={props.match.path + "/acc"} component={Acc}/>
</Switch>
import { Layout, Menu } from 'antd'
import React from 'react'
import { Route } from 'react-router-dom'
import IQReact from './IQReact.jsx'
import IQVue from './IQVue.jsx'
import IQNode from './IQNode.jsx'
import IQJquery from './IQJquery.jsx'
const { Header, Footer, Sider, Content } = Layout;
// 类组件
class Category extends React.PureComponent {
state = {
current: '/react',
menu: [
{
name: 'React',
path: '/react',
component: IQReact
},
{
name: 'Vue',
path: '/vue',
component: IQVue
},
{
name: 'NodeJS',
path: '/node',
component: IQNode
},
{
name: 'Jquery',
path: '/jquery',
component: IQJquery
},
]
}
goto = ({key}) => {
this.setState({
current: key
})
this.props.history.push('/category' + key)
}
componentWillMount(){
const {pathname} = this.props.location
console.log(pathname)
this.setState({
current:pathname.replace(this.props.match.url,'')
})
}
render() {
const { menu, current } = this.state
const { match } = this.props
return <div>
<Layout>
<Sider>
<Menu onClick={this.goto} defaultSelectedKeys={[current]} mode="inline">
{
menu.map(item => <Menu.Item key={item.path} >
{item.icon}
{item.name}
</Menu.Item>)
}
</Menu>
</Sider>
<Content>
{
menu.map(item => <Route key={item.name} path={match.path + item.path} component={item.component}></Route>)
}
</Content>
</Layout>
</div>
}
}
export default Category
补充:
声明式导航
利用组件(Link或NavLink)属性实现路由跳转
main.js中的写法:这里是 < App/>
import React from 'react'
import ReactDOM from 'react-dom'
// 安装路由 react-router 和 react-router-dom
import {
HashRouter,
BrowserRouter,
Route
} from 'react-router-dom'
import App from './App'
const Router = process.env.NODE_ENV === 'product' ? BrowserRouter : HashRouter
ReactDOM.render(
<Router>
<App/>
</Router>,
document.querySelector('#app')
)
编程式导航
利用路由提供的history对象实现路由跳转
1、利用 < Route/> 渲染的组件 ,获取history对象
注意:设置方法goto()
, 事件中传入路径参数 onClick={this.goto.bind(this,item.path)}
,
goto=(path)=>{
console.log(this.props) // 这里的打印的是跳转路由后, 渲染的组件的 props
// 跳转路由 有浏览器记录
// this.props.history.push(path)
// 跳转路由 无浏览器记录
this.props.history.replace(path)
}
render(){
const {menu}=this.state
console.log('app.props=',this.props) // 这里的打印的是加载页面时,当前渲染的组件的 props
// 编程式导航时 利用 < Route/> 渲染的组件 ,获取history对象时:
// 1、当main.js中为<App /> 时打印里面为空的
///2、当main.js中为<Route component={App} /> 时打印 location、match、history
return (
<div>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/good" component={Good}></Route>
<Route path="/search" component={Search}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/login" component={Login}></Route>
<Route path="/reg" component={Reg}></Route>
<Route path="/notfound" render={()=> <div>404</div>}></Route>
{/* 重定向可以不加to */}
<Redirect from="/" to="/home" exact></Redirect>
{/* 404 */}
<Redirect to="/notfound"></Redirect>
</Switch>
<nav>
<ul>
{
// 声明式导航
// menu.map(item=><li key={item.name}>
// <NavLink to={item.path} activeClassName="active" activeStyle={{fontWeight:"bold"}}>{item.text}</NavLink>
// </li>)
// 编程式导航
menu.map(item=><li key={item.name} onClick={this.goto.bind(this,item.path)}>
{item.text}
</li>)
}
</ul>
</nav>
</div>
)
}
注意:main.js中需要把< App/>
修改为 < Route component={App} />
import React from 'react'
import ReactDOM from 'react-dom'
// 安装路由 react-router 和 react-router-dom
import {
HashRouter,
BrowserRouter,
Route
} from 'react-router-dom'
import App from './App'
const Router = process.env.NODE_ENV === 'product' ? BrowserRouter : HashRouter
ReactDOM.render(
<Router>
<Route component={App} />
{/* 这里不加 path 因为所有路径都要渲染这个App组件 */}
</Router>,
document.querySelector('#app')
)
2、利用 withRouter 高阶组件,获取history对象
利用withRouter高阶组件包装后,直接通过组件的props.history获取,就可以使用编程式导航进行点击跳转
1、首先引入withRouter : import {Route,Redirect,Switch,NavLink, withRouter} from 'react-router-dom'
2、使用高阶组件:App = withRouter(App)
import React from 'react'
// 安装react-router 和 react-router-dom
import {Route,Redirect,Switch,NavLink, withRouter} from 'react-router-dom'
// 导入路由
import Home from './pages/Home.jsx'
import Good from './pages/Good.jsx'
import Search from './pages/Search.jsx'
import Profile from './pages/Profile.jsx'
import Login from './pages/Login.jsx'
import Reg from './pages/Reg.jsx'
// 导入样式
import './App.css'
// 有状态改变用class组件
class App extends React.PureComponent {
state = {
menu:[{
text:'首页',
name:'home',
path:'/home'
},{
text:'商品',
name:'good',
path:'/good'
},{
text:'搜索',
name:'search',
path:'/search'
},{
text:'我的',
name:'profile',
path:'/profile'
}]
}
// 传入路径参数 path
goto = (path) => {
console.log(this.props) // 这里的打印的是跳转路由后, 渲染的组件的 props
// 跳转路由 有浏览器记录
// this.props.history.push(path)
// 跳转路由 无浏览器记录
this.props.history.replace(path)
}
render(){
const {menu}=this.state
console.log('app.props=',this.props) // 这里的打印的是当前渲染的组件的 props
// 这时当main.js中为 <App /> 时能打印出history对象,因为已经使用高阶组件进行了包装
return (
<div>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/good" component={Good}></Route>
<Route path="/search" component={Search}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/login" component={Login}></Route>
<Route path="/reg" component={Reg}></Route>
<Route path="/notfound" render={()=> <div>404</div>}></Route>
{/* 重定向可以不加to */}
<Redirect from="/" to="/home" exact></Redirect>
{/* 404 */}
<Redirect to="/notfound"></Redirect>
</Switch>
<nav>
<ul>
{
// 声明式导航
// menu.map(item=><li key={item.name}>
// <NavLink to={item.path} activeClassName="active" activeStyle={{fontWeight:"bold"}}>{item.text}</NavLink>
// </li>)
// 编程式导航
menu.map(item=><li key={item.name} onClick={this.goto.bind(this,item.path)}>
{item.text}
</li>)
}
</ul>
</nav>
</div>
)
}
}
// 高阶组件(包装函数)
App = withRouter(App)
export default App
注意main.js中的改变:
import React from 'react'
import ReactDOM from 'react-dom'
// 安装路由 react-router 和 react-router-dom
import {
HashRouter,
BrowserRouter,
Route
} from 'react-router-dom'
import App from './App'
const Router = process.env.NODE_ENV === 'product' ? BrowserRouter : HashRouter
ReactDOM.render(
<Router>
<App/>
{/* <Route component={App} /> */}
{/* 这里不加path 因为所有路径都要渲染这个App组件 */}
</Router>,
document.querySelector('#app')
)
使用高阶组件后,就不用把组件修改为 <Route component={App}
,还是之前的写法 < App />
3、利用 Context 获取history对象
RR4 在 Router 组件中通过Contex暴露了一个router对象,router对象下包含history(即:this.context.router.history)