后端程序员入门react笔记(五)ajax请求

常见的ajax

  • Ajax 最原始的方式,基于原生的js

  • XmlHttpRequest 多个请求之间如果有先后关系,会存在很多层回调的问题,也是基于原生js

  • Jquery Ajax 基于原生XHR封装,依赖Jquery框架,由jquery 框架去封装原生的XML(Xml)封装的API,支持JSONP,如果只是用于发送Ajax而使用jQuery,不推荐。

  • Fetch Fetch是一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方案。Fetch不是ajax的进一步封装,它们是两个东西。Fetch函数就是原生js,和XMLHttpRequest是同级别的原生js的api,Fetch 的出现就是为了解决 XHR 的问题,它实现了 Promise 规范,返回 Promise 实例;而 Promise 是为解决异步回调问题而摸索出的一套方案。

  • axios 对原生XHR的封装,需要进行安装,可以在客户端使用,也可以在node端使用,Axios 是一个基于 promise (Promise 是异步编程的一种解决方案)的 HTTP 库,可以用在浏览器和 node.js 中。

Axios

接下来我们重点了解一下axios

特性

  • 可以从浏览器中创建 XMLHttpRequests
  • 可以从 node.js 创建 http 请求
  • 支持 Promise API
  • 可以拦截请求和响应
  • 可以转换请求数据和响应数据
  • 可以取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

基本使用

我们只需要在组件中引入axios组件即可使用,我们简单举个列子看一下,请求响应的处理在 then 和 catch 回调中,请求正常会进入 then ,请求异常则会进 catch

javascript 复制代码
import React, {Component} from 'react';
//引入组件
import axios from 'axios';

class App extends Component {
    getData = () => {
        //发送请求   请求响应的处理在 then 和 catch 回调中,请求正常会进入 then ,请求异常则会进 catch
        axios.get('http://localhost/data').then((res) => {
            console.log(res);
        }).catch((err) => {
            console.log(err);
        })
    }
    render() {
        return (
            <button onClick={this.getData}> 点击获取数据</button>
        );
    }
}
export default App;

发送请求的两种方式

axios提供了两种方式来发送请求, 一种是通过axios(config)来发送请求,另一种就是上面列子中的axios.get,

axios(config)

config是一个对象,里面配置了请求相关的信息,比如下面这个例子

javascript 复制代码
        axios(
            {
                //请求方法
                method: 'post',
                //请求url
                url: '/user/12345',
                //请求参数
                data: {
                    firstName: 'Fred',
                    lastName: 'Flintstone'
                }
            }
        ).then(res=>{//请求成功
            console.log(res);
        }).catch(err=>{//请求失败
            console.log(err);
        })
  • config可配置的相关项 config里面可以配置代理,超时,相应的最大尺寸,是否重定向等等
javascript 复制代码
{
   // `url` 是用于请求的服务器 URL
  url: '/user',
  // `method` 是创建请求时使用的方法
  method: 'get', // default
  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',
  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data, headers) {
    // 对 data 进行任意转换处理
    return data;
  }],
  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理
    return data;
  }],
  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},
  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },
   // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },
  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },
  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,
   // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default
  // `adapter` 允许自定义处理请求,以使测试更轻松
  // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },
 // `auth` 表示应该使用 HTTP 基础验证,并提供凭据
  // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },
   // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // default
  // `responseEncoding` indicates encoding to use for decoding responses
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // default
   // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default
  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default
   // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },
  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },
   // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,
  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // default
  },
  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // default
  // `socketPath` defines a UNIX Socket to be used in node.js.
  // e.g. '/var/run/docker.sock' to send requests to the docker daemon.
  // Only either `socketPath` or `proxy` can be specified.
  // If both are specified, `socketPath` is used.
  socketPath: null, // default
  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启用
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
  // 'proxy' 定义代理服务器的主机名称和端口
  // `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
  // 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },
  // `cancelToken` 指定用于取消请求的 cancel token
  // (查看后面的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
}
  • 配置全局默认值 我们可以给axios配置默认值
javascript 复制代码
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios.option

我们也可以使用axios.get()这种方式发送请求,里面定义data和config

javascript 复制代码
        axios.get('http://localhost/data').then().catch()
        axios.post('http://localhost/data',{name:'张三'},{timeout: 1000}).then().catch()

拦截器

axios提供了请求拦截器和响应拦截器,帮助我们在请求前对config做一些配置,判断是否重复提交,或者对config做一些检查以及配置纠正等,响应拦截器帮助我们根据不同的返回内容做不同的数据处理,相关案例如下

javascript 复制代码
import React, {Component} from 'react';
//引入组件
import axios from 'axios';
// 创建一个请求拦截器 请求头附加一个token 值是1111
const requestInterceptor = axios.interceptors.request.use(
    (config) => {
        config.headers['token'] = '1111111';
        return config;
    },
    (error) => {
        // 处理请求错误
        return Promise.reject(error);
    }
);
// 创建一个响应拦截器
const responseInterceptor = axios.interceptors.response.use(
    (response) => {
        // 处理响应数据
        return response;
    },
    (error) => {
        console.log(error);
        // 处理响应错误
        return Promise.reject(error);
    }
);
class App extends Component {

    // 添加请求拦截器

    getData = () => {
        axios.post('http://localhost/data',{name:'张三'},{timeout: 1000}).then(res=>{
            console.log(res);
        },err=>{
            console.log(err);
        }).catch(err=>{
            console.log(err);
        })
    }
    render() {
        return (
            <button onClick={this.getData}> 点击获取数据</button>
        );
    }
}
// 移除拦截器
axios.interceptors.request.eject(requestInterceptor);
axios.interceptors.response.eject(responseInterceptor);
export default App;

开启后端服务,发送请求

我们知道,我们前端的服务器是3000端口,但是我们后端的服务器不一定是3000,也可能是80端口,现在我启动一个go编写的后台服务,调用一下接口 我这现在有一个获取博客详情的接口,端口号是8080 接下来我们通过axios访问一下

javascript 复制代码
    getData = () => {
        axios.get('http://127.0.0.1:8080/v1/blog/detail?id=123').then(res=>{
            console.log(res);
        },err=>{
            console.log(err);
        }).catch(err=>{
            console.log(err);
        })
    }

问题来了,浏览器发送了请求,后端的gin服务收到了请求,但是结果没过来,浏览器报错说我跨域了。这说明主要问题在Ajax引擎对响应的处理那块。怎么办?作为后端人员最熟悉不过的方式就是做个代理。

配置代理

怎么配置代理呢?我们可以在react项目根目录下的package.json里面配置上这样一句声明,这表示请求的3000端口的请求会被转发到8080,对于后端人员都明白,这个转发规则是什么呢?优先匹配3000服务的路径,匹配不到再转去8080服务。

javascript 复制代码
"proxy": "http://127.0.0.1:8080"
  • 把axios的请求改为本域名下路径就可以了
javascript 复制代码
 axios.get('/v1/blog/detail?id=123')
  • 添加完毕后我们重启服务,响应数据回来了
  • 但是上面这种有一个问题,就是不够灵活,比如我们常用的/blog/v1/detail需要代理到blog服务,但是我/goods/v1/detail是要代理到goods服务器。我们需要更灵活的配置方式,那么我们就需要借助一个第三方库http-proxy-middleware,接下来我们在src目录下新建一个setupProxy.js文件,内容如下
javascript 复制代码
const {createProxyMiddleware} =require('http-proxy-middleware')
module.exports=function(app){
    app.use(
        createProxyMiddleware('/api',{ // api 是需要转发的请求(所有带/api 前缀的请求都会转发给8080)
            target:'http://localhost:8080', // 配置转发目标地址(能返回数据的服务器地址)
            changeOrigin:true, // 控制服务器接收到的请求头中host字段的值
            //设置为true,服务器端接收的host就是8080,如果为false,那么后端服务器接收到的host就是3000
            //  changeOrigin 默认值为false 但我们一般将changeOrigin值设为true
            pathRewrite:{'^/api':''} // 去请求前缀,保证交给后台服务器是正常请求地址(必须配置)
        }),

    )
}
  • 在发送请求的时候,如果是需要代理,我们只需要在原来的url上面加上/api/就可以了,或者和后端协商好某个统一前缀也能解决。
javascript 复制代码
 axios.get('/api/v1/blog/detail?id=123')//被转发后就是/v1/blog/detail?id=123

案例一 用户搜索

静态页面渲染

  • index.js
javascript 复制代码
import React from "react";
import ReactDOM  from "react-dom/client";
import App from "./App";
const content=<App/>
const root=ReactDOM.createRoot(document.getElementById("root"))
root.render(content)
  • app.jsx
javascript 复制代码
import React, {Component} from 'react';
import Search from './components/Search/Search'
import List from "./components/List/List";

class App extends Component {
    render() {
        return (
            <div>
                <div className="container">
                    <Search></Search>
                    <List></List>
                </div>
            </div>
        );
    }
}

export default App;
  • Search.jsx
javascript 复制代码
import React, {Component} from 'react';

class Search extends Component {
    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">Search Github Users</h3>
                    <div>
                        <input type="text" placeholder="enter the name you search"/>&nbsp;
                        <button>Search</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;
  • list.jsx
javascript 复制代码
import React, {Component} from 'react';
import './List.css'

class List extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="card">
                        <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
                            <img alt="head_protrait" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{width:'100px'}}/>
                        </a>
                        <p className="card-text">reactjs</p>
                    </div>
                </div>
            </div>
        );
    }
}

export default List;

配置代理 获取数据

真实接口地址如下https://api.github.com/search/users?q=react,我们先拿到数据吧,点击search获取到input的内容,并尝试请求数据

  • Search.jsx
javascript 复制代码
import React, {Component, createRef} from 'react';
import axios from "axios";

class Search extends Component {
    searchInput=createRef()
    searchData=()=>{
        //获取input的值
        const key=this.searchInput.current.value
        //发送Ajax请求

        axios.get(`/api/search/users?q=${key}`).then(res=>{
            console.log(res);
        },(err)=>{
            console.log(err);
        }).catch(err=>{
            console.log(err);
        })
        //
    }
    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">Search Github Users</h3>
                    <div>
                        <input type="text" ref={this.searchInput} placeholder="enter the name you search"/>&nbsp;
                        <button onClick={this.searchData}>Search</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;
  • 点击搜索后我们发现一个问题,接口返回的和axios里面的结构不一致,可以看出,axios对接口数据又做了封装

把数据传递给state,并渲染列表

  • list.jsx
javascript 复制代码
class List extends Component {
    render() {
        const data=this.props.data;
        return (
            <div>
                <div className="row">
                    {
                        data.map((item,index)=>{
                            return  <div className="card" key={item.html_url}>
                                <a rel="noreferrer" href={item.html_url} target="_blank">
                                    <img alt="head_protrait" src={item.avatar_url} style={{width:'100px'}}/>
                                </a>
                                <p className="card-text">{item.login}</p>
                            </div>
                        })
                    }
                </div>
            </div>
        );
    }
}
  • app.js
javascript 复制代码
    getData=(data)=>{
        console.log(data);
        this.setState(
            {
                list:data
            }
        )
    }

发布订阅和同步机制

我们从上一个搜索的例子其实可以看到,在app search list组件直接传递数据的时候,我们都绕不开父组件app,那么有没有一种方式,让search 和list直接沟通,而不需要再通过父组件App传递? 这就用到了发布订阅。 其实后端同学都很熟悉发布订阅模式,首先我们需要创建一个topic,然后有发布者,还得有订阅者。 接下来我们使用第三方组件pubsubjs来体验一下发布订阅,

  • search.jsx
javascript 复制代码
import React, {Component, createRef} from 'react';
import axios from "axios";
import PubSub from 'pubsub-js';


class Search extends Component {
    searchInput=createRef()
    searchData=()=>{
        //获取input的值
        const key=this.searchInput.current.value
        //发送Ajax请求
        axios.get(`/api/search/users?q=${key}`).then(res=>{
            //发布消息
            PubSub.publish('list',res.data.items);
        },(err)=>{
            console.log(err);
        }).catch(err=>{
            console.log(err);
        })
        //
    }
    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">Search Github Users</h3>
                    <div>
                        <input type="text" ref={this.searchInput} placeholder="enter the name you search"/>&nbsp;
                        <button onClick={this.searchData}>Search</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;
  • list.jsx
javascript 复制代码
import React, {Component} from 'react';
import './List.css'
import PubSub from 'pubsub-js';

class List extends Component {

    state ={
        data:[]
    }
    getData = (msg, data) => {
        this.setState({
            data:data
        })
    }

    //挂载完毕开始订阅
    componentDidMount() {//用一个函数处理订阅消息
       const token= PubSub.subscribe('list', this.getData);
       this.setState({
           token:token
       })
    }

    //卸载后取消订阅
    componentWillUnmount() {
        PubSub.unsubscribe(this.state.token);
    }

    render() {
        const data=this.state.data;
        return (
            <div>
                <div className="row">
                    {
                        data.map((item,index)=>{
                            return  <div className="card" key={item.html_url}>
                                <a rel="noreferrer" href={item.html_url} target="_blank">
                                    <img alt="head_protrait" src={item.avatar_url} style={{width:'100px'}}/>
                                </a>
                                <p className="card-text">{item.login}</p>
                            </div>
                        })
                    }
                </div>
            </div>
        );
    }
}
export default List;
相关推荐
weixin_382395233 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__3 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.4 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~5 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华5 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子8 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅8 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni8 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学9 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端