项目中控制路由跳转使用的是BrowserRouter
在开发过程中使用是没有问题的,但是将页面上传至服务器之后,问题就来了:用户访问的资源不存在,页面是空白的。
[原因]:
在browserHistory 模式下,URL 是指向真实 URL 的资源路径,当通过真实 URL 访问网站的时候,由于路径是指向服务器的真实路径,但该路径下并没有相关资源,所以用户访问的资源不存在。
经过排查是路径的问题,将BrowserRouter 改为 HashRouter之后,问题解决
HashRouter在路径中包含了#,相当于HTML的锚点定位。(# 符号的英文叫hash,所以叫HashRouter,和散列没关系哦))
而BrowserRouter使用的是HTML5的新特性History,没有HashRouter(锚点定位)那样通用,低版本浏览器可能不支持。
BrowserRouter进行组件跳转时可以传递任意参数实现组件间的通信,而HashRouter不能(除非手动拼接URL字符串),因此一般配合Redux使用,实现组件间的数据通信。
HashRouter相当于锚点定位,因此不论#后面的路径怎么变化,请求的都相当于是#之前的那个页面。可以很容易的进行前后端不分离的部署(也就是把前端打包后的文件放到服务器端的public或static里),
因为请求的链接都是ip地址:端口/#/xxxx,因此请求的资源路径永远为/,相当于index.html,而其他的后端API接口都可以正常请求,不会和/冲突,由于前后端不分离也不会产生跨域问题。
缺点就是丑,路径里总有个#,宝宝表示强迫症犯了…
因为BrowserRouter模式下请求的链接都是ip地址:端口/xxxx/xxxx,因此相当于每个URL都会访问一个不同的后端地址,如果后端没有覆盖到路由就会产生404错误。
可以通过加入中间件解决,放在服务器端路由匹配的最后,如果前面的API接口都不匹配,则返回index.html页面。但这样也有一些小问题,因为要求前端路由和后端路由的URL不能重复。
比如商品列表组件叫/product/list,而请求商品列表的API也是/product/list,那么就会访问不到页面,而是被API接口匹配到。
进行前后端分离的部署,比如前端地址ip1:端口1,后端接口地址ip2:端口2,使用Nginx反向代理服务器进行请求分发。前端向后端发起请求的URL为nginx所在的服务器+/api/xxx,通过NGINX的配置文件判断,如果URL以api开头则转发至后端接口,否则转发至前端的地址,访问项目只需访问Nginx服务器即可
import React,{Component} from 'react'
import {HashRouter,Route,Switch} from 'react-router-dom' //【1】删BrowserRouter加HashRouter
import Admin from './pages/admin/admin'
import Login from './pages/login/login'
class App extends Component{
constructor(props){
super(props);
}
render(){
return(
//【2】删掉,并改成HashRouter
<HashRouter>
<Switch>
<Route path='/login' component={Login}></Route>
<Route path='/' component={Admin}></Route>
</Switch>
</HashRouter>
//
)
}
}
export default App
http://localhost:3000/home/#/product
TypeError: Cannot read property 'proObj' of undefined
Detail.render
E:/a/web/reactAdmin/src/pages/admin/product/detail.jsx:52
49 | render(){
50 |
51 | //接收前一步传过来的商品对象数据
> 52 | const {name, desc, price, detail, imgs}=this.props.location.state.proObj
| ^ 53 | const {cName1,cName2}=this.state
54 | const title=(
55 | <span>
用browserRouter时可以直接把参数传给其它组件,而hashRouter不行,这就导致:product/home.js里的 思路1:可以用Redux来管理数据 http://localhost:3000/home/#/product 访问【详情】、【修改】都能正常显示,效果同之前一样initColumns=()=>{
...
render:(proObj)=>{//proObj当前商品对象
return(
<span>
{/*将product对象使用state传递给目标路由组件*/}
<LinkButton onClick={()=>this.props.history.push('/product/detail',{proObj})}>详情</LinkButton>
<LinkButton onClick={()=>this.props.history.push('/product/add-update',proObj)}>修改</LinkButton>
</span>
2.解决
思路2:简单些,home.js页用memoryUtils.js来保存数据,detail.js页去直接取来用即可utils/memoryUtils.jsx
/*
用于在内存中保存数据的工具模块
*/
export default{
user:{}, //保存用户字典
product:{} //【1】保存产品字典
}
product/home.jsx只写改动部分,其它略过
import memoryUtils from '../../../utils/memoryUtils' //【0】引入工具
...略过
//Table的列名及对应显示的内容渲染
initColumns=()=>{
this.columns=[
{
title:'商品名称',
dataIndex:'name'
},
{
title:'商品描述',
dataIndex:'desc'
},
{
title:'价格',
dataIndex:'price',
render:(price)=>'¥'+price //把price渲染进对应的行,并加上¥符号
},
{
width:100,
title:'商品状态',
//dataIndex:'status',//注释掉
render:(proObj)=>{//传入当前的商品对象
const {_id,status}=proObj //解构商品id和status
const newStatus=status===1?2:1//把商品的状态2换1,1换2
return(
<span>
<Button
type='primary'
/*调用更新状态函数把当前商品id及要更新的状态传过去*/
onClick={()=>this.updateStatus(_id,newStatus)}>
{status===1 ? '下架' : '上架'}</Button>
<span>{status===1 ? '在售':'已下架'}</span>
</span>
)
}
},
{
width:100,
title:'操作',
render:(proObj)=>{//proObj当前商品对象
return(
<span>
{/*将product对象使用state传递给目标路由组件【1】改为如下*/}
<LinkButton onClick={()=>this.showDetail(proObj)}>详情</LinkButton>
<LinkButton onClick={()=>this.showUpdate(proObj)}>修改</LinkButton>
{/*
product/detail.jsx
import memoryUtils from '../../../utils/memoryUtils' //【0】引入工具
async componentDidMount () {
// 得到当前商品的分类ID
const {pCategoryId, categoryId} = memoryUtils.product //【1】=后改为当前,删除this.props.location.state.proObj
if(pCategoryId==='0') { // 一级分类下的商品
const result = await reqCategory(categoryId)
const cName1 = result.data.name
this.setState({cName1})
} else { // 二级分类下的商品
/*
//通过多个await方式发多个请求: 后面一个请求是在前一个请求成功返回之后才发送
const result1 = await reqCategory(pCategoryId) // 获取一级分类列表
const result2 = await reqCategory(categoryId) // 获取二级分类
const cName1 = result1.data.name
const cName2 = result2.data.name
*/
// 一次性发送多个请求, 只有都成功了, 才正常处理
const results = await Promise.all([reqCategory(pCategoryId), reqCategory(categoryId)])
const cName1 = results[0].data.name
const cName2 = results[1].data.name
this.setState({
cName1,
cName2
})
}
}
//【3】组件卸载之前清除数据
componentWillUnmount=()=>{
memoryUtils.product={}
}
render(){
//接收前一步传过来的商品对象数据
const {name, desc, price, detail, imgs}= memoryUtils.product //【2】this.props.location.state.proObj
const {cName1,cName2}=this.state
const title=(
<span>
{/* 跳转回商品列表页 */}
<LinkButton onClick={()=>this.props.history.goBack()}>
<Icon type='arrow-left' />
</LinkButton>
<span>产品详情</span>
</span>
)
return(
...略过
product/add-update.jsx只写变动,其它略过
import memoryUtils from '../../../utils/memoryUtils' //【0】引入
componentWillMount(){
//取出产品列表修改按钮传过来的state
const product= memoryUtils.product //【1】换成从memory中读取。this.props.location.state //如果是添加的就没有值,否则就有值
//保存是否更新标识到this
this.isUpdate=!!product //双取反,若product有值,结果为ture
//保存商品到this(如果没有,则保存空对象)
this.product=product || {}
}
// 【2】在卸载之前清除保存的数据
componentWillUnmount () {
memoryUtils.product = {}
}
3.效果