react-router详解
react-router详解
React Router
是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。
路由的基本使用
安装react-router
cnpm install react-router-dom -D
使用react-router
环境准备:我使用antd的UI组件,所以需要下载依赖,并配置主题,==>Ant Design of React
需求
- 点击头部导航链接组件,在Context里面展示对应的组件内容
- 点击录入成绩,显示对应的组件,输入相关信息,提交信息到后台,并跳转到查询成绩中,显示信息
- 点击查询全部成绩,对应组件显示所有学生成绩信息
导航区
导航区的a标签改为Link标签
import {Link} from 'react-router-dom'
<Link to="/xxxxx">Demo</Link>
如:
import {Link} from 'react-router-dom'
(
<>
<h1>学生成绩管理系统</h1>
<ul>
<li><Link to="/insetScore">录入成绩</Link></li>
<li><Link to={{pathname:"/findScore",state:{}}}>查询成绩</Link></li>
<li><Link to="/updateScore">修改成绩</Link></li>
<li><Link to="/deteleScore">删除成绩</Link></li>
<li><Link to="/getAllScore">查看全部成绩</Link></li>
</ul>
</>
)
头部一般组件(<Link/>
组件的使用)
在components
文件夹新建Header/index.jsx
和Header/index.css
文件
/*Header/index.jsx*/
import React, { Component } from 'react'
import {Link} from 'react-router-dom'
import './index.css'
export default class Header extends Component {
render() {
return (
<>
<h1>学生成绩管理系统</h1>
<ul>
<li><Link to="/insetScore">录入成绩</Link></li>
<li><Link to={{pathname:"/findScore",state:{}}}>查询成绩</Link></li>
<li><Link to="/updateScore">修改成绩</Link></li>
<li><Link to="/deteleScore">删除成绩</Link></li>
<li><Link to="/getAllScore">查看全部成绩</Link></li>
</ul>
</>
)
}
}
/*Header/index.css*/
h1{
font: 30px bold 仿宋;
color: chocolate;
text-align: center;
}
ul{ background-color: #dddddd;
}
li{ display: inline-block;
margin-left: 5px;
border:3px ;
padding:5px;
width: 17%;
height: 30px;
}
a{ display:block;
text-decoration: none;
color: chocolate;
width: 100%;
height: 30px;
text-align: center;
}
li:hover { background-color: white;
border-top: 3px solid #ff7f00;
}
a:hover{color: #ff7f00; }
使用react-router-dom中的<Link/>
标签包裹链接,浏览渲染为超链接a标签
展示区
展示区写Route标签进行路径的匹配。
<Route path='/xxxx' component={Demo}/>
如:
import {Route,Redirect,Switch} from 'react-router-dom'
{/*
Switch可以提高路由匹配效率(单一匹配)。
*/}
<Switch>
<Route path="/insetScore" component={AddStudent}/>
<Route path="/findScore" component={Message}/>
<Route path="/getAllScore" component={AllStudent}/>
{/* 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由 */}
<Redirect to="/insetScore"/>
</Switch>
<Route path="/insetScore" component={AddStudent}/>:
注册路由<Switch> :
通常情况下,path和component是一一对应的关系。Switch
可以提高路由匹配效率(单一匹配)。<Redirect to="/insetScore"/>:
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
主体内容路由组件
在
src
目录下新建pages
目录用于存放路由组件
在pages
目录下创建主体内容Context/AllStudent.jsx
和Context/AddStudent.jsx
,如果需要样式可以在这里创建,我这里没有所以不用创建
Context/AllStudent.jsx
import React, { Component } from 'react'
import axios from 'axios'
import { Table } from 'antd';
import uuid from 'react-uuid'
const { Column } = Table;
export default class AllStudent extends Component {
state = {
data: []
}
componentDidMount(){
//没有请求参数
axios.get('http://localhost:8080/api/getAllStudent')
.then((response)=>{
this.setState({data:response.data});
console.info(response.data)
})
.catch((error)=>{
console.log(error);
});
}
render() {
return (
<>
<h2 style={{textAlign:'center'}}>学生所有成绩信息显示</h2>
<Table dataSource={this.state.data}>
<Column title="姓名" dataIndex="name" key="name" />
<Column title="数据库原理" dataIndex="databaseScore" key="databaseScore" />
<Column title="大学英语" dataIndex="englishScore" key="englishScore" />
<Column title="高等数学" dataIndex="mathScore" key="mathScore" />
<Column title="程序设计" dataIndex="programScore" key="programScore" />
<Column title="总成绩" key="sumScore"
render={(text, record) => (
Object.keys(text).length !== 0?
(Number.parseInt(record.databaseScore)+
Number.parseInt(record.englishScore)+
Number.parseInt(record.mathScore)+
Number.parseInt(record.programScore))
:
" "
)}
/>
<Column title="平局成绩" key="avgScore"
render={(text, record) => (
Object.keys(text).length !== 0?
(Number.parseInt(record.databaseScore)+
Number.parseInt(record.englishScore)+
Number.parseInt(record.mathScore)+
Number.parseInt(record.programScore))/4
:
" "
)}
/>
</Table>
</>
)
}
}
Context/AddStudent.jsx
import React, { Component } from 'react'
import { Form, Input, Button } from 'antd';
import axios from 'axios'
import { message } from 'antd';
const layout = {
labelCol: {
span: 10,
},
wrapperCol: {
span: 4,
},
};
const tailLayout = {
wrapperCol: {
offset: 10,
span: 16,
},
};
export default class Context extends Component {
onFinish = (values) => {
//有请求参数
axios.post('http://localhost:8080/api/addStudent', {
id: values.id,
name: values.name,
databaseScore: values.databaseScore,
englishScore: values.englishScore,
mathScore: values.mathScore,
programScore: values.programScore,
})
.then(function (response) {
if(response.data === "200"){
console.info("=====================")
message.success('添加学生消息成功!');
}
})
.catch(function (error) {
message.error('添加学生消息失败!');
});
this.props.history.push('/findScore',values);
//console.log('Success:', values);
};
onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
render() {
return (
<>
<h2 style={{textAlign:'center'}}>学生成绩信息输入</h2>
<Form
{...layout}
name="basic"
initialValues={{
remember: true,
}}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
>
<Form.Item
label="学号"
name="id"
rules={[
{
required: true,
message: '请输入你的学号!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="姓名"
name="name"
rules={[
{
required: true,
message: '请输入你的姓名!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="高等数学"
name="mathScore"
rules={[
{
required: true,
message: '请输入你的高等数学成绩!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="大学英语"
name="englishScore"
rules={[
{
required: true,
message: '请输入你的大学英语成绩!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="程序设计"
name="programScore"
rules={[
{
required: true,
message: '请输入你的程序设计成绩!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="数据库原理"
name="databaseScore"
rules={[
{
required: true,
message: '请输入你的数据库原理成绩!',
},
]}
>
<Input />
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</>
)
}
}
pages/Message/index.jsx
注意: 如果组件下是index.jsx的形式,引入时可以导入到文件夹目录即可。默认为index.jsx
ex:import Message from './pages/Message'
import React, { Component } from 'react'
import { Table } from 'antd';
import uuid from 'react-uuid'
const { Column } = Table;
export default class Message extends Component {
render() {
const data = [];
const data1 = this.props.location.state || {};
if( Object.keys(data1).length !== 0 ){
const item = {};
item['key'] = uuid();
item['name'] = data1['name']
item['databaseScore'] = data1['databaseScore']
item['englishScore'] = data1['englishScore']
item['mathScore'] = data1['mathScore']
item['programScore'] = data1['programScore']
data.push(item)
}
// console.info(data)
return (
<>
<h2 style={{textAlign:'center'}}>学生成绩信息显示</h2>
<Table dataSource={data}>
<Column title="姓名" dataIndex="name" key="name" />
<Column title="数据库原理" dataIndex="databaseScore" key="databaseScore" />
<Column title="大学英语" dataIndex="englishScore" key="englishScore" />
<Column title="高等数学" dataIndex="mathScore" key="mathScore" />
<Column title="程序设计" dataIndex="programScore" key="programScore" />
<Column title="总成绩" key="sumScore"
render={(text, record) => (
Object.keys(text).length !== 0?
(Number.parseInt(record.databaseScore)+
Number.parseInt(record.englishScore)+
Number.parseInt(record.mathScore)+
Number.parseInt(record.programScore))
:
" "
)}
/>
<Column title="平局成绩" key="avgScore"
render={(text, record) => (
Object.keys(text).length !== 0?
(Number.parseInt(record.databaseScore)+
Number.parseInt(record.englishScore)+
Number.parseInt(record.mathScore)+
Number.parseInt(record.programScore))/4
:
" "
)}
/>
</Table>
</>
)
}
}
App.js
import React from 'react';
import './App.less';
import {Route,Redirect,Switch} from 'react-router-dom'
import Header from './components/Header'
// import Footer from './components/Footer'
import AddStudent from './pages/Context/AddStudent'
import AllStudent from './pages/Context/AllStudent'
import Message from './pages/Message'
const App = () => (
<>
<Header/>
{/*
Switch可以提高路由匹配效率(单一匹配)。
*/}
<Switch>
<Route path="/insetScore" component={AddStudent}/>
<Route path="/findScore" component={Message}/>
<Route path="/getAllScore" component={AllStudent}/>
{/* 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由 */}
<Redirect to="/insetScore"/>
</Switch>
{/* <Footer/> */}
</>
);
export default App;
index.js
<App/>
组件的最外侧包裹了一个<BrowserRouter>或<HashRouter>
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById('root'));
项目结构如下:
路由组件与一般组件
- 写法不同:
- 一般组件:
<Demo/>
- 路由组件:
<Route path="/demo" component={Demo}/>
- 一般组件:
- 组件存放的位置不同:
- 一般组件:存放在components文件夹下
- 路由组件:存放在pages文件夹下
- 接收到的props不同:
- 一般组件:写组件标签时传递了什么,就能收到什么
- 路由组件:接收到三个固定的属性
history: go: ƒ go(n) goBack: ƒ goBack() goForward: ƒ goForward() push: ƒ push(path, state) replace: ƒ replace(path, state) location: pathname: "/about" search: "" state: undefined match: params: {} path: "/about" url: "/about"
NavLink与封装NavLink
components/MyNavLink/index.jsx
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
console.log(this.props);
return (
<NavLink activeClassName="zysheep" className="list-group-item" {...this.props}/>
)
}
}
- NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
使用:App.jsx
import React, { Component } from 'react'
import {Route} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
- 标签体内容是一个特殊的标签属性
- 通过
this.props.children
可以获取标签体内容
Switch的使用
import {Route,Redirect,Switch} from 'react-router-dom'
<Switch>
<Route path="/insetScore" component={Context}/>
<Route path="/findScore" component={Message}/>
<Redirect to="/insetScore"/>
</Switch>
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
- public/index.html 中 引入样式时不写 ./ 写
%PUBLIC_URL%
(常用) - 使用HashRouter
路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect的使用
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由
ex: Home组件下有News组件和Message组件
import React, { Component } from 'react'
import MyNavLink from '../../components/MyNavLink'
import {Route,Switch,Redirect} from 'react-router-dom'
import News from './News'
import Message from './Message'
export default class Home extends Component {
render() {
return (
<div>
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
</div>
</div>
)
}
}
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
params参数
- 路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
- 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
- 接收参数:
this.props.match.params
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
//Detail组件中接收params参数
const {id,title} = this.props.match.params
search参数
- 路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情</Link>
- 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.search
- 获取到的search是urlencoded编码字符串,如
(key=value&key=value)
,需要借助querystring
解析(parse) (stringify)
import qs from 'querystring'
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
//Detail组件中接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
state参数
- 路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
- 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
this.props.location.state
- 注意:刷新也可以保留住参数
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
// 接收state参数
const {id,title} = this.props.location.state || {}
编程式路由导航
借助
this.props.history
对象上的API对操作路由跳转、前进、后退
params参数
this.props.history.push()+携带params参数
//push跳转+携带params参数
this.props.history.push(`/home/message/detail/${id}/${title}`)
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
//Detail组件中接收params参数
const {id,title} = this.props.match.params
this.props.history.replace+携带params参数
//replace跳转+携带params参数
this.props.history.replace(`/home/message/detail/${id}/${title}`)
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
//Detail组件中接收params参数
const {id,title} = this.props.match.params
search参数
this.props.history.push()+携带search参数
//push跳转+携带search参数
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
{/* search参数无需声明接收,正常注册路由即可 */}
Route path="/home/message/detail" component={Detail}/>
//Detail组件中接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
this.props.history.replace+携带search参数
//replace跳转+携带search参数
this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
{/* search参数无需声明接收,正常注册路由即可 */}
Route path="/home/message/detail" component={Detail}/>
//Detail组件中接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
state参数
this.props.history.push()+携带state参数
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
// 接收state参数
const {id,title} = this.props.location.state || {}
this.props.history.replace+携带state参数
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
const {id,title} = this.props.location.state || {}
withRouter
的使用
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API,withRouter的返回值是一个新组件
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'
class Header extends Component {
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
console.log('Header组件收到的props是',this.props);
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
BrowserRouter与HashRouter的区别
- BrowserRouter没有任何影响,因为state保存在history对象中。
底层原理不一样:- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
- HashRouter使用的是URL的哈希值。
- path表现形式不一样
- BrowserRouter的路径中没有#,例如localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为state保存在history对象中。
- HashRouter刷新后会导致路由state参数的丢失!!!
- 备注:HashRouter可以用于解决一些路径错误相关的问题。
react中使用axios
- 安装:
cnpm i axios -D
- 导入使用
import axios from 'axios'
// get
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
// get带参数
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
// post请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
react中解决跨域问题
在项目中,我使用了axios进行后端API请求,代码如下
componentDidMount(){
//没有请求参数
axios.get('http://localhost:8080/api/getAllStudent')
.then((response)=>{
this.setState({data:response.data});
console.info(response.data)
})
.catch((error)=>{
console.log(error);
});
}
之所以可以请求到数据是因为我在后端允许跨域,而一般前后端分离开发是要前端人员自己配置跨域的。
解决方案:
- 使用第三方的WEB服务器Nginx做方向代理,解决跨域
- 在React-cli脚手架中修改配置信息解决跨域,有两种解决方案
React-cli配置代理解决跨域
方案一
在项目根目录的package.json
中添加配置
"proxy":"http://localhost:8080"
这个方案配置起来较简单,前端请求资源时可以不加任何前缀。但是它的缺点很明显不能配置多个代理。
工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方案二
使用代理配置文件,在src下创建配置文件src/setupProxy.js
,名字固定写法,不能更换,否则不生效。
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:8080', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:8081',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
配置相比方案一较繁琐,前端请求资源时必须加前缀/api1或/api2。但是有点明显可以配置多个代理,可以很灵活的控制请求是否走代理。