React Router更新到4.0,使用起来发生了很大的变化,这个变化只要是指完全的组件化-----嵌套的JSX路由器!接下来,像刚接触路由一样的姿势,一探究竟(简书文字太小,建议大家调整一下页面比例)
使用React脚手架搭建项目结构
create-react-app
内在使用的npm
,所以速度会很慢,你cnpm
的大刀已经饥渴难耐,可以考虑设置npm
为cnpm镜像源:
npm config set registry http://registry.npm.taobao.org/
//通过get registy检测是否成功
npm get registry
>http://registry.npm.taobao.org/
生成的项目结构src
目录如下
因为来回切换页面可能会破坏读文的愉悦心情,所以咱们主要在app.js
内部大动手脚
app.js基本内容
import React,{Component} from "react";
//创建App根组件
class App extends Component{
render(){
return(
<h1>hello React!</h1>
)
}
}
export default App;
控制台输入
npm start
项目已经启动,不接受任何形式的报错
下载react-router
cnpm install react-router-dom --save
在app.js内部引入
import React,{Component} from "react";
//引入路由组件
import { BrowserRouter, Route,Link} from 'react-router-dom';
//创建App根组件
class App extends Component{
render(){
return(
<h1>hello React!</h1>
)
}
}
export default App;
接下来我创建函数式组件作为页面级组件
//首页组件
let Index=()=>{
return(
<h2>我是首页内容</h2>
)
}
//列表组件
let List=()=>{
return(
<div>
<h2>我是列表页</h2>
<ul>
<li>我是列表元素</li>
<li>我是列表元素</li>
<li>我是列表元素</li>
</ul>
</div>
)
}
在App组件内部配置路由组件
class App extends Component{
render(){
return(
<BrowserRouter>
<div>
<h1>hello React!</h1>
<Link to="/index">首页</Link>
<Link to="/list">列表页</Link>
<Route path="/index" component={Index}></Route>
<Route path="/list" component={List}></Route>
</div>
</BrowserRouter>
)
}
}
注意:
- BrowserRouter内部只能放一个子元素
- Route组件类似于判断,路径符合的会被显示
1.如果没有匹配到路径怎么办?
当然是显示空白,但是使用<Redirect>
组件
路由引入改为
//引入路由组件
import { BrowserRouter, Route,Link,Redirect} from 'react-router-dom';
App写成
<BrowserRouter>
<div>
<h1>hello React!</h1>
<Link to="/index">首页</Link>
<Link to="/list">列表页</Link>
<Route path="/index" component={Index}></Route>
<Route path="/list" component={List}></Route>
<Redirect to="/index" />
</div>
</BrowserRouter>
这样当匹配不到路径的时候会被重定向到/index
2.我想把Index
组件path
定义为/
可以吗?
当然可以,只不过需要注意一些问题
<BrowserRouter>
<div>
<h1>hello React!</h1>
<Link to="/">首页</Link>
<Link to="/list">列表页</Link>
<Route path="/" component={Index}></Route>
<Route path="/list" component={List}></Route>
</div>
</BrowserRouter>
浏览器访问,大家会发现,两个组件同时出现,这是因为路由进行的是非精准匹配
//假设为组件path
let str="/list/hot";
let str2="/list/discount";
//假设为真实访问路径
let path="/list";
//假设为Route组件判断
if(str.indexOf(path+"/")==0){
alert("str")
}
if(str2.indexOf(path+"/")==0){
alert("str2")
}
// 真实路径 / path:/
// 真实路径 /list path:/ /list
// 真实路径 /list/abc path:/ /list /list/abc
毫无悬念,会弹出两次,相信也都已经理解了,怎么解决这个问题?
1.exact
给Route组件添加exact进行精准匹配,相当于
path==str1
path==str2
你可能觉得非精准匹配的存在是一个烦恼,其实到嵌套路由时它的作用就凸显出来了
App组件代码
<BrowserRouter>
<div>
<h1>hello React!</h1>
<Link to="/">首页</Link>
<Link to="/list">列表页</Link>
<Route path="/" exact component={Index}></Route>
<Route path="/list" exact component={List}></Route>
</div>
</BrowserRouter>
2.Switch
只显示一个,匹配成功之后不再往下查找,但是不是精准查找,实际开发过程中需要加上exact
路由引入改为
//引入路由组件
import { BrowserRouter, Route,Link,Redirect,Switch} from 'react-router-dom';
App写成
<BrowserRouter>
<div>
<h1>hello React!</h1>
<Link to="/">首页</Link>
<Link to="/list">列表页</Link>
<Switch>
<Route path="/list" component={List}></Route>
<Route path="/" component={Index}></Route>
<Route path="/list" component={List}></Route>
<Route path="/list" component={List}></Route>
</Switch>
</div>
</BrowserRouter>
说完Route组件再来看一下Link,这个组件最终会被解析成a标签,在有些场景下使用会很受限,例如程序执行到某个环节需要路由进行跳转,类似location.href或者说Vue中的router.push。BrowserRouter组件外层包括,将路由信息注入到每个路由组件的props上。接下来将Link换成button
注意:因为App组件不是路由组件,所以该组件内部的props访问不到路由信息
对组件嵌套结构进行改造:
//创建Content组件
class Content extends Component{
constructor(props){
super(props);
console.log(props);
}
render(){
return(
<div>
<h1>hello React!</h1>
<button onClick={()=>{this.props.history.push("/")}}>Index</button>
<button onClick={()=>{this.props.history.push("/list")}}>List</button>
<Switch>
<Route path="/" exact component={Index}></Route>
<Route path="/list" component={List}></Route>
</Switch>
</div>
)
}
}
//创建App根组件
class App extends Component{
render(){
return(
<BrowserRouter>
<Route path="/" component={Content}></Route>
</BrowserRouter>
)
}
}
withRouter
如上代码,用Route组件包括Content组件将路由信息注入组件。组件内部需要访问路由对象时这样做就太过麻烦了,默认情况下,只有经过路由匹配渲染的组件才能访问路由对象下的属性和方法,然而不是所有组件都与路由相连,我们可以使用withRouter将路由对象注入到组件
首先引入withRouter
//引入路由组件
import { BrowserRouter, Route,Link,Redirect,Switch,withRouter} from 'react-router-dom';
上述代码修改如下
//创建Content组件
class Content extends Component{
constructor(props){
super(props);
console.log(props);
}
render(){
return(
<div>
<h1>hello React!</h1>
<button onClick={()=>{this.props.history.push("/")}}>Index</button>
<button onClick={()=>{this.props.history.push("/list")}}>List</button>
<Switch>
<Route path="/" exact component={Index}></Route>
<Route path="/list" component={List}></Route>
</Switch>
</div>
)
}
}
//将路由对象注入到组件
Content=withRouter(Content)
//创建App根组件
class App extends Component{
render(){
return(
<BrowserRouter>
<Content/>//这里直接使用组件即可
</BrowserRouter>
)
}
}
Link or NavLink
在4.0版本中,这两个组件都可以解析为a链接进行页面跳转,工作方式相同,但是NavLink可以根据路由匹配提供一些样式功能,代码如下
<BrowserRouter>
<div>
<h1>hello React!</h1>
<NavLink to="/" exact activeClassName="active">Index</NavLink>
<NavLink to="/list" activeClassName="active">List</NavLink>
<Switch>
<Route path="/list" component={List}></Route>
<Route path="/" component={Index}></Route>
<Route path="/list" component={List}></Route>
<Route path="/list" component={List}></Route>
</Switch>
</div>
</BrowserRouter>
当匹配路径为/
时对应a
链接会有active
类名,生效对应样式
Route
Route组件通过上面的使用,应该不陌生了,匹配路径显示对应组件,基础用法存在一些局限性:
1.当需要往路由组件额外传一些props
时
这种写法在List
组件内部是接收不到age
的
<Route path="/list" component={List} age="20"></Route>
需要借助render
属性
<Route path="/list" render={(props)=>{
return <List age="20"/>
}}></Route>
render
指向一个函数式组件将List组件返回,这样就可以在List组件上随意定义props,此种情况下List不再属于路由组件,所以访问不到路由相关对象,但是箭头函数props中包含路由相关信息,所以
<Route path="/list" render={(props)=>{
return <List {...props} age="20"/>
}}></Route>
一切照常进行
2.当在匹配跳转之前进行一些拦截时(如登录验证)
<Route path="/list" render={(props)=>{
if(this.state.login){//根据登录状态决定如何响应
return (<List {...props} age="20"></List>)
}else{
return(<Redirect to="/login">)
}
}}></Route>
加油!