《React后台管理系统实战:十三》部署准备:路由改用HashRouter,并解决产生的bug

49 篇文章 6 订阅

一、hashRouter

为什么要用hashrouter

项目中控制路由跳转使用的是BrowserRouter
在开发过程中使用是没有问题的,但是将页面上传至服务器之后,问题就来了:用户访问的资源不存在,页面是空白的。

[原因]:
在browserHistory 模式下,URL 是指向真实 URL 的资源路径,当通过真实 URL 访问网站的时候,由于路径是指向服务器的真实路径,但该路径下并没有相关资源,所以用户访问的资源不存在。

经过排查是路径的问题,将BrowserRouter 改为 HashRouter之后,问题解决

一、从原理上

HashRouter在路径中包含了#,相当于HTML的锚点定位。(# 符号的英文叫hash,所以叫HashRouter,和散列没关系哦))

而BrowserRouter使用的是HTML5的新特性History,没有HashRouter(锚点定位)那样通用,低版本浏览器可能不支持。

二、从用法上

BrowserRouter进行组件跳转时可以传递任意参数实现组件间的通信,而HashRouter不能(除非手动拼接URL字符串),因此一般配合Redux使用,实现组件间的数据通信。

三、生产实践

1.HashRouter

HashRouter相当于锚点定位,因此不论#后面的路径怎么变化,请求的都相当于是#之前的那个页面。可以很容易的进行前后端不分离的部署(也就是把前端打包后的文件放到服务器端的public或static里),

因为请求的链接都是ip地址:端口/#/xxxx,因此请求的资源路径永远为/,相当于index.html,而其他的后端API接口都可以正常请求,不会和/冲突,由于前后端不分离也不会产生跨域问题。

缺点就是丑,路径里总有个#,宝宝表示强迫症犯了…

2.BrowserRouter

因为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服务器即可

二、实现

src/app.js

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】删掉<BrowserRouter>,并改成HashRouter
        <HashRouter>
           <Switch>
               <Route path='/login' component={Login}></Route>
               <Route path='/' component={Admin}></Route>
           </Switch>
        </HashRouter>
        //</BrowserRouter> 
        )
    }
}
export default App

效果地址栏任何地方都会加个#号

http://localhost:3000/home/#/product

三、问题及解决

1.问题:访问商品详情页报错

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里的<linkButton 中的proObj无法传到商品详情页,商品详情页解构数据时并没有数据,导致最终报错

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.解决

思路1:可以用Redux来管理数据
思路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>
                            {/* <LinkButton onClick={()=>this.props.history.push('/product/detail',{proObj})}>详情</LinkButton> 
                            <LinkButton onClick={()=>this.props.history.push('/product/add-update',proObj)}>修改</LinkButton>*/}
                        </span>
                    )
                }
            },
        ]
    }

    //【2】把当前产品对象保存到memoryUtils.js里,并跳转到产品详情
    showDetail=(proObj)=>{
        memoryUtils.product=proObj
        this.props.history.push('/product/detail')
    }

    //【3】并把当前产品对象保存到memoryUtils.js里,并跳转到产品详情
    showUpdate=(proObj)=>{
        memoryUtils.product=proObj
        this.props.history.push('/product/add-update')
    }

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.效果

http://localhost:3000/home/#/product 访问【详情】、【修改】都能正常显示,效果同之前一样

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值