React:六、axios请求接口

React:六、axios请求接口

1 前言

vue和react均可使用axios进行接口数据请求。axios官网文档地址:

http://www.axios-js.com/docs/index.html

React项目,先执行如下命令安装axios库:

npm i axios

2 使用

2.1 接口准备

编写react前,先准备好请求接口,如下搭建起水果查询接口:

mapper文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.xiaoxu.dao.QueryFruitDao">

    <select id="queryFruits"
            parameterType="map"
            resultType="com.xiaoxu.entity.Fruit.Fruit">
        select
        fruit_id,create_time,extra_info,
        modify_time,cross_out_price,fruit_stock,name,sales,
        sup_id,unit_price,unit_weight
        from my_fruit
        <where>
            <if test = "fruitId!=null">
                and fruit_id = #{fruitId}
            </if>
            <if test = "name!=null">
                and name like concat('%',#{name},'%')
            </if>
            <if test = "supId!=null">
                and sup_id like concat('%',#{supId},'%')
            </if>
        </where>
    </select>
</mapper>

测试数据如下:

在这里插入图片描述

controller:

package com.xiaoxu.controller.Fruit;

import com.google.common.collect.Maps;
import com.xiaoxu.base.FruitMallExceptions.CommExp;
import com.xiaoxu.base.Request.QueryFruitRequest;
import com.xiaoxu.base.Result.CommResult;
import com.xiaoxu.base.Result.results.QueryFruitsResults;
import com.xiaoxu.base.template.ProcessorCallBack;
import com.xiaoxu.base.template.impl.ProcessorImpl;
import com.xiaoxu.service.QueryFruitService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.xiaoxu.vo.Fruit.FruitVo;

import java.util.List;
import java.util.Map;

/**
 * @author xiaoxu
 * @date 2022-03-26 12:50
 * FruitMall:com.xiaoxu.controller.Fruit.FruitController
 */
@Controller
@RequestMapping(value = "/fruit")
@Slf4j
public class FruitController {
    @Autowired
    QueryFruitService queryFruitService;

    @Autowired
    ProcessorImpl<QueryFruitRequest, QueryFruitsResults> processor;

    @ResponseBody
    @RequestMapping(value = "/fruitsBySup",method = RequestMethod.GET )
    public List<FruitVo> queryFruitsBySupplier(){
        return null;
    }

    @ResponseBody
    @RequestMapping(value = "/queryFruits", method = RequestMethod.GET)
    public CommResult<QueryFruitsResults> queryFruits(QueryFruitRequest queryFruitRequest){
        Map<String,Object> params = Maps.newHashMap();

        return processor.process(queryFruitRequest, new ProcessorCallBack<QueryFruitRequest, QueryFruitsResults>() {
            @Override
            public void validate(QueryFruitRequest params1) throws CommExp {

            }

            @Override
            public QueryFruitsResults doProcess() throws CommExp {
                log.info(String.format("请求进来了,传入的参数是:%s",queryFruitRequest));
                List<FruitVo> fruitVos = queryFruitService.queryFruitsByCondition(queryFruitRequest);
                QueryFruitsResults queryFruitsResults = new QueryFruitsResults();
                if (!CollectionUtils.isEmpty(fruitVos)) {
                    queryFruitsResults.setFruitVoList(fruitVos);
                }
                return queryFruitsResults;
            }
        });
    }
}

配置文件中的url注意时区问题:

serverTimezone改为中国时区Asia/Shanghai,如果是serverTimezone=UTC,那么接口返回json数据中,时间会增加8小时(因为若服务器时区为UTC,那么中国时间为东8区,即UTC+8,那么转换成本地时间会增加8小时):

url: jdbc:mysql://localhost:3306/fruitmall?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai

postman请求:

在这里插入图片描述

接口返回数据无误:

在这里插入图片描述

2.2 react接口请求

2.2.1 src下封装接口请求api:axios

在这里插入图片描述

import axios from "axios"

// 请求响应超时时间
axios.defaults.timeout=5000;

let {keys,values,entries} = Object;

// 封装get/post请求
export default{
    get:(path="",params={})=>{
        let request_url="";
        request_url=path?request_url+path:request_url;
        if(keys(params).length>0){
            request_url+="?"
            let count=0;
            for(let k of keys(params)){
                if(count!=keys(params).length-1){
                    request_url+=k+"="+params[k]+"&";
                    count+=1;
                }else{
                    request_url+=k+"="+params[k];
                }
            }
        }
        axios.get(request_url).then(res=>{
            console.log("get接口请求成功!");
            console.log(res);
            console.log(typeof res);
        }).catch(err=>{
            console.log("get接口报错!")
            console.log(err)
        }).then(r=>{
            console.log("get请求接口调用结束!");
        });
    },
    post:(path="",data={})=>{
        let request_url="";
        request_url=path?request_url+path:request_url;
        axios.post(request_url,data).then(res=>{
            console.log("post接口请求成功!");
            console.log(res);
            console.log(typeof res);
        }).catch(err=>{
            console.log("post接口报错!")
            console.log(err)
        }).then(r=>{
            console.log("post请求接口调用结束!");
        });
    }
}

2.2.2 组件请求接口

App.js:

import React, { Component } from 'react'
import Search from './Components/Search'
import Table from './Components/Table'

export default class App extends Component {
  render() {
    return (
      <div>
        <Search/>
        <Table/>
      </div>
    )
  }
}

在这里插入图片描述

Search:

index.jsx:

import React, { Component } from 'react'
import Axios from '../../api/axios'


export default class Search extends Component {
    componentDidMount(){
        Axios.get("http://localhost:8800/fruit/queryFruits")
        // console.log(Axios)
    }
    render() {
    return (
        <div>index</div>
    )
    }
}

页面出现跨域错误:Access-Control-Allow-Origin

在这里插入图片描述

跨域是浏览器的行为,即浏览器不允许一个aaa.com,能够直接去访问bbb.com的资源,这样别人拿到你网站的资源后,可能会攻击你的网站等。而服务器之间的访问则没有跨域的说法,不同的服务器之间是可以访问的,之所以没有增加服务器间访问的限制,因为一般服务器间都有网关、鉴权等功能,别人也无法轻易访问到你的服务器内容。

如下可证明跨域是浏览器拦截的,即网站可发送请求,但是接受服务器请求时,被浏览器发现跨域,再次做了拦截。

如下服务器打印日志,说明是接收到了前端的请求,只是因为跨域,故而被浏览器拦截了。

在这里插入图片描述
注意:在修改跨域前,先引入react-ui组件库:antd

官方文档:https://ant.design/docs/react/introduce-cn

或antd官网镜像地址:https://ant-design.gitee.io/index-cn

npm i antd --save

2.2.3 解决跨域-脚手架配置代理

通过代理,浏览器将XXX:3000的请求,转发到代理服务器XXX:3000,由代理服务器发送请求到服务器端8800,最后返回给代理服务器,代理服务器通过XXX:3000再次转发给浏览器XXX:3000,即不会发生跨域问题。

找到脚手架下的package.json,在最后修改如下:

"proxy":"http://localhost:8800"

在这里插入图片描述

另外接口请求的路径,需要去掉前面的http://localhost:8800,修改Search的index.jsx如下:

import React, { Component } from 'react'
import Axios from '../../api/axios'
import {Form,Input,Button} from 'antd'

export default class Search extends Component {
    componentDidMount(){
        // Axios.get("http://localhost:8800/fruit/queryFruits")
        // Axios.get("http://localhost:3000/fruit/queryFruits")
        Axios.get("/fruit/queryFruits")
        // console.log(Axios)
    }
    render() {
        return (
            <div>
                <Form>
                    <Form.Item
                    label = "水果名称">
                        <Input/>
                    </Form.Item>
                    <Form.Item
                    label = "水果id">
                        <Input/>
                    </Form.Item>
                    <Form.Item
                    label = "供应商id">
                        <Input/>
                    </Form.Item>
                    <Form.Item className='flex_btn'>
                        <Button type = "primary"  style={{"fontSize":"12px"}}>搜索</Button>
                        <Button  style={{"fontSize":"12px"}}>重置</Button>
                    </Form.Item>
                </Form>
            </div>
        )
    }
}

或者如下也可:

Axios.get("http://localhost:3000/fruit/queryFruits")

修改了package.json,一定注意重启项目!再次执行npm start,此时接口请求,返回成功:

在这里插入图片描述

tips:
修改如下请求:

在这里插入图片描述

这样默认优先访问前端public下的资源文件:

在这里插入图片描述

如果前端资源不存在,再访问服务器资源,服务器也无资源,则直接报错:

在这里插入图片描述

2.2.4 解决跨域-脚手架配置代理二

如上解决跨域的方式,有一个弊端是如果前端请求的接口的ip+端口号来自不同的服务器,那么直接在package.json中配置是无法办到的(package.json中只能配置一个代理),于是更为通用的做法是在setupProxy.js中配置代理。

先注释package.json下的代理,在react的脚手架的src目录下,新建一个setupProxy.js:

在这里插入图片描述

setupProxy.js中,使用CommonJS语法(http-proxy-middleware在react脚手架中已经配置了,无需重复npm i http-proxy-middleware):

createProxyMiddleware是需要重写的接口路径(必须要有),然后下面的pathRewrite,再将/api1开头这个路径去掉,这样才能正确访问到后端的接口路径。

const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function(app){
    app.use(
        createProxyMiddleware('/api1',{
            target:"http://localhost:8800",
            changeOrigin:true,
            pathRewrite:{'^/api1':""}
        })
    )
}

这种代理方式配置好后,注意原先请求的路径要更改下,否则会报错404,接口路径找不到:

可以使用http://localhost:3000/api1/fruit/queryFruits,或者/api1/fruit/queryFruits(一定要带上重写的接口的前缀路径/api1,即上述createProxyMiddleware的第一个参数),app.use中配置的代理,只要接口请求前缀遇到/api1,就会触发相应的代理:

    componentDidMount(){
        // Axios.get("http://localhost:8800/fruit/queryFruits")
        // Axios.get("http://localhost:3000/fruit/queryFruits")
        Axios.get("http://localhost:3000/api1/fruit/queryFruits")
        // Axios.get("/api1/fruit/queryFruits")
        // console.log(Axios)
    }

其中,changeOrigin的默认值为false,改为true后,可以控制服务器端收到的请求头的Host值。即为false时,服务器收到请求头的host是localhost:3000,但是改为true后,服务器收到的host就是localhost:8800了(即changeOrigin为true时,会模拟相同的服务器地址来请求后端接口,避免后端有请求来源限制)。

如果涉及到多个后端请求的ip+端口号路径,app.use中可以传多个createProxyMiddleware()参数,逗号隔开即可。

说明:

(1)优点:可以配置多个代理,灵活控制请求是否走代理(请求接口前缀增加/api1与否决定)

(2)缺点:配置繁琐,前端请求资源时必须加前缀

2.2.5 举个搜索的例子

文件目录如下:

在这里插入图片描述
api:

参考python的位置参数和关键字参数(args和kwargs),因为axios为异步请求api,直接在then中return结果,定义变量去接收axios请求返回数据会拿到undefined(异步请求的结果还没拿到,变量就已经赋值了),故而增加回调函数参数供axios封装方法使用,此时为了方便get请求等可以不传参,所以回调参数作为axios封装方法的第一个参数,而关键字参数如params有默认值,故而可留空。

axios.js:

import axios from "axios"

// 请求响应超时时间
axios.defaults.timeout=5000;

let {keys,values,entries} = Object;

// 封装get/post请求
export default{
    get:(callBack,path="",params={})=>{
        let request_url="";
        request_url=path?request_url+path:request_url;
        if(keys(params).length>0){
            request_url+="?"
            let count=0;
            for(let k of keys(params)){
                if(count!=keys(params).length-1){
                    request_url+=k+"="+params[k]+"&";
                    count+=1;
                }else{
                    request_url+=k+"="+params[k];
                }
            }
        }
        axios.get(request_url).then(res=>{
            console.log("get接口请求成功!");
            console.log(res);
            console.log(typeof res);
            callBack(res.data)
        }).catch(err=>{
            console.log("get接口报错!")
            console.log(err)
        }).then(r=>{
            console.log("get请求接口调用结束!");
        });
    },
    post:(callBack,path="",data={})=>{
        let request_url="";
        request_url=path?request_url+path:request_url;
        axios.post(request_url,data).then(res=>{
            console.log("post接口请求成功!");
            console.log(res);
            console.log(typeof res);
            callBack(res.data)
        }).catch(err=>{
            console.log("post接口报错!")
            console.log(err)
        }).then(r=>{
            console.log("post请求接口调用结束!");
        });
    }
}

commonTool.js:

// 接口请求参数为小驼峰写法,在这里需要转换下
export function mapToRequestData(mapObj){
    if(typeof(mapObj)=="undefined"){
        throw Error("请传入非undefined对象")
    }
    var finalObj = {}
    for(let i in mapObj){
        if(typeof(mapObj[i])!=="undefined"){
            const value = mapObj[i].replace(/\s/g,"")
            const newKeyArr = i.split("_")
            let objKey = "";
            let newVal = ""
            for(let k=0;k<newKeyArr.length;k++){
                let oldVal = newKeyArr[k];
                if(k>=1&&k<=newKeyArr.length-1){
                    newVal = oldVal.substring(0,1).toUpperCase()+oldVal.substring(1)
                }else{
                    newVal = oldVal;
                }
                objKey+=newVal
            }
            finalObj[objKey] = value
        }
    }
    return finalObj
}

Components:

Search(index.css为空):

index.jsx:

import React, { Component } from 'react'
import Axios from '../../api/axios'
import {Form,Input,Button} from 'antd'
import {mapToRequestData} from '../../api/commonTool'


export default class Search extends Component {
    fRef = React.createRef()

    componentDidMount(){
        // Axios.get("http://localhost:8800/fruit/queryFruits")
        // Axios.get("http://localhost:3000/fruit/queryFruits")
        // Axios.get("/api1/fruit/queryFruits")
        // console.log(Axios)
        this.onFinish()
    }

    OnReSet = ()=>{
        const form = this.fRef.current
        form.resetFields();
        // form.getFieldsValue()是不需要校验的,所以可以直接获取值
        this.onFinish()
    }

    // form表单校验通过后的回调
    onFinish = async()=>{
        // console.log("组件校验成功")
        const {searchToFather} = this.props
        const form = this.fRef.current
        const values = form.getFieldsValue(this.names.slice(1))

        // console.log("我是值哈哈哈",values,typeof values)
        // form.validateFields返回的是Promise对象
        // const values2 = await form.validateFields(this.names.slice(1))
        // console.log("我是校验后的值哈哈哈",values2) 在onfinish中,结果
        // 和values一样
        var params = mapToRequestData(values)
        // console.log("我是请求的参数:",params)
        const {changeLoad} = this.props
        changeLoad(true)
        Axios.get((data)=>{
            // console.log("哈哈哈,我是回调",data)
            const {success} = data
            // const {resData:{fruitVoList:value}} = data
            // 对象的解构赋值,上面fruitVoList:value,就是对fruitVoList
            // 取别名,可以直接使用value,代表的变量就是fruitVoList
            const {resData:{fruitVoList}} = data
            // &&fruitVoList!==null
            if(success===true&&typeof(fruitVoList)!=="undefined"
            &&fruitVoList!==null){
                // console.log("请求数据成功~",fruitVoList,typeof fruitVoList)
                searchToFather(fruitVoList)
            }else{
                // 其余场景全部默认没查到数据,返回空
                searchToFather([])
            }
            // 组件数据返回给父组件后,更新加载状态
            changeLoad(false)
        },"http://localhost:3000/api1/fruit/queryFruits",params)
    }
    names = ["form","name","fruit_id","sup_id"]
    // 使用form的validateMessages,可作为全部限制字段的模板,
    // 对应字段加上name,以及rules配置required:true即可
    validateMessages = {
        required: "这个#${label}#,即${name}是必选字段",
    };
    render() {
        // console.log(this.fRef.current)
        // console.log(this.fRef)
        return (
            <div>
                <Form
                ref = {this.fRef}
                labelCol={{
                    span: 5,
                }}
                name = {this.names[0]}
                validateMessages = {this.validateMessages}
                scrollToFirstError
                onFinish = {this.onFinish}
                >
                    <Form.Item
                    label = "水果名称"
                    name = "name"
                    >
                        <Input/>
                    </Form.Item>
                    <Form.Item
                    label = "水果id"
                    name = "fruit_id"
                    rules={[
                        {
                          required: true,
                          message: '请输入水果id~',
                        },
                    ]}>
                        <Input/>
                    </Form.Item>
                    <Form.Item
                    label = "供应商id"
                    name = "sup_id"
                    rules = {[{
                        required:true
                    }]}
                    >
                        <Input/>
                    </Form.Item>
                    <Form.Item className='flex_btn'>
                        <Button 
                        type = "primary"  
                        style={{"fontSize":"12px"}}
                        htmlType="submit"
                        >搜索</Button>
                        {/* 注意上面搜索按钮,必须加上 htmlType="submit",
                        否则校验文本不会生效,并且对应的Form.Item的
                        rules和name都要有,才能生效*/}
                        <Button  
                        style={{"fontSize":"12px"}}
                        onClick = {this.OnReSet}
                        >重置</Button>
                    </Form.Item>
                </Form>
            </div>
        )
    }
}

Table(index.css为空):

index.jsx:

import React, { Component,Fragment } from 'react'
import { Divider,Table } from 'antd'


const tableColumns = [
    {
        title:"水果编号",
        dataIndex:"fruitNumber"
    },
    {
        title:"水果名称",
        dataIndex:"fruitName"
    },
    {
        title:"供应商编号",
        dataIndex:"supplierId"
    },
    {
        title:"库存",
        dataIndex:"fruitStockCount"
    },
    {
        title:"销量",
        dataIndex:"fruitSaleCount"
    },
    {
        title:"水果单价",
        dataIndex:"unitPrice",
        render:(data)=>{
            return <>{data} /</>
        }
    },
    {
        title:"水果划线价",
        dataIndex:"crossOutPrice",
        render:(data)=>{
            return (<>
                    <div style = {{"textDecoration":"line-through",
                    "display":"inline-block",
                    "textDecorationColor":"red"}}>{data}</div> /</>);
        }
    },
    {
        title:"水果单位质量",
        dataIndex:"unitWeight",
        render:(data)=>{
            return <>{data} / g</>
        }
    },
    {
        title:"上架时间",
        dataIndex:"createTime"
    },
]

const data = []

export default class FruitQueryTable extends Component {
    constructor(props){
        super(props)
        this.fruitData = data
    }
    render() {
        var {lists,isLoad} = this.props
        this.fruitData = lists
        // console.log("table的数据为:",this.fruitData)
        return (
            <div>
                <Divider/>
                <Table
                columns={tableColumns}
                loading = {isLoad}
                locale = {{emptyText:'现在暂无数据哦~'}}
                bordered
                dataSource={this.fruitData}
                >
                </Table>
            </div>
        )
    }
}

App.css:

#root .wrapper_input{
    width:400px;
    height: 266px;
    margin: auto;
    padding:20px;
    border: 1px solid rgb(111, 197, 239);
    border-radius: 10px;
}

#root .wrapper_input:hover{
    box-shadow: 2px 1px 1px 1px rgb(111, 197, 239);
}

.ant-form-item .ant-btn{
    margin-left: 10px;
}

.flex_btn .ant-form-item-control-input-content{
    display: flex;
    justify-content: flex-end;
}

/* table的样式 */
.wrapper_table{
    width: 80%;
    margin: auto;
    margin-top: 10px;
    border: 1px solid #ddd;
    border-radius: 10px;
    padding: 5px 15px 20px;
}

.wrapper_table .ant-table-wrapper{
    border-radius:5px;
    box-shadow: 1px 1px 1px 0 rgb(222, 219, 219);
}

App.js:

import React, { Component } from 'react'
import Search from './Components/Search'
import FruitQueryTable from './Components/Table'
import 'antd/dist/antd.css'
import './App.css'

export default class App extends Component {
  state = {fruitLists:[],isLoading:false}

  // 子组件search将查询出来的数据传递给父组件
  searchToFather = (fruitLists)=>{
    if(fruitLists instanceof Array&&typeof(fruitLists)!=="undefined"
    &&fruitLists!==null){
      // console.log("父组件开始啦啦啦")
      fruitLists.map(item=>{
        let keyVal = item["fruitNumber"]
        item["key"] = keyVal
        return item
      })
      this.setState({fruitLists})
      this.changeLoding()
    }
  }

  // changeLoding:当子组件Search查询时,通知子组件Table更新加载状态
  changeLoding = (flag)=>{
    this.setState({isLoading:flag})
  }

  render() {
    return (
      <div >
        <div className = "wrapper_input">
          <Search 
          searchToFather = {this.searchToFather}
          changeLoad = {this.changeLoding}/>
        </div>
        <div className = "wrapper_table">
          <FruitQueryTable 
          lists = {this.state.fruitLists}
          isLoad = {this.state.isLoading}/>
        </div>
      </div>
    )
  }
}

效果如下:

默认初始化页面时,无条件时查询全部数据:

在这里插入图片描述

无输入时限制表单提交,且不调用接口请求:

在这里插入图片描述
输入必填参数查询:

在这里插入图片描述

水果名称为模糊匹配:

在这里插入图片描述
条件不匹配,查询无数据展示:

在这里插入图片描述

点击重置,清空查询条件,并且查询全部数据:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值