【React Native】从React开始——网络请求之axios

1. 前言

偶然看到了这个axios这个框架,当然也找到了一个官方的文档说明

Axios 是一个基于 promiseHTTP 库,可以用在浏览器和 node.js 中。

了解一些前端开发的大多都使用过AJAX来进行网络请求,那么为什么还需要axios这个库。我们知道AJAX的引入是为了页面的局部刷新,即:AJAX = 异步 JavaScriptXMLAsynchronous Javascript And XML)。而axios 可以看作是对AJAX的进一步封装,提供了更多的封装以及更加安全,可以防御CSRF/XSRF攻击。当然首先还是需要了解一下promise 是什么?

1.1 promise 是什么?

可以查看JavaScript Promise 对象这篇文章。Promise 对象是ES6中提供的,用来确保在JavaScript中只有异步操作的结果可以决定哪种状态,其他任何操作都无法改变其状态。Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,表示进行中;
  • fulfilled: 操作成功;
  • rejected: 操作失败;

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来。相比较于AJAX而言,有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。比如下面的简单案例:

function test(){
    // Promise对象上有then、catch方法
    return new Promise(function (resolve, reject) {
      // 异步操作
      // 代码正常执行
      resolve("Message");
      // 非正常执行
      reject(new Error());
    });
}

test().then(function(message){
    // resolve
}).catch(function(error){
    // reject
});

上面代码中,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例,而 resolve 方法的参数除了正常的值以外,还可能是另一个 Promise 实例。

  • resolve 方法的作用是将Promise对象的状态从pending变为resolved
  • reject 方法的作用是将Promise对象的状态从pending变为rejected

当然在其回调方法中还有allrace,这里不再介绍,感兴趣可以查看博客:ES6 Promise用法小结

2. axios

使用命令

yarn add axios

// 我本地安装了两个Node,这里需要切换node版本
nvm use 16.13.0

当然如果安装过慢可以换个源:

yarn config set registry https://registry.npm.taobao.org
// 如果有问题,还是改为默认算了
// yarn config set registry https://registry.yarnpkg.com

当然这里还是在react项目中进行测试,比如这里简单请求一个get方式的请求,如下:

import React from 'react'
import axios from 'axios'

export default function Test() {
  axios
    .get('https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json')
    .then(function (response) {
      console.log(response) // 处理成功情况
    })
    .catch(function (error) {
      console.log(error) // 处理错误情况
    })
    .then(function () {
      console.log('finish') // 总是会执行
    })

  return <div>Test</div>
}

结果:
在这里插入图片描述

在这里其实也就是跨域问题。

2.1 解决跨域问题

可以使用http-proxy-middleware,参考网址:Create React App。首先安装这个插件:

yarn add http-proxy-middleware

这里我的安装版本为^2.0.1。然后在src目录下创建一个setupProxy.js,然后在这个文件中写入:

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

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
    })
  );
};

因为在请求的链接地址中开始的时候为api,所以这里修改为:

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

module.exports = function(app) {
  app.use(
    '/api',        // 代理路径
    createProxyMiddleware({
      target: 'https://i.maoyan.com', // 真实地址
      secure: true, // 如果是https接口 需要配置这个参数为true
      changeOrigin: true,
    })
  );
  // 可配置多个
};

然后我们把请求域名删除掉,即将react修改为:

import React from 'react'
import axios from 'axios'

export default function Test() {
  axios
    .get('/api/mmdb/movie/v3/list/hot.json')  // 去掉非同源域
    .then(function (response) {
      console.log(response) // 处理成功情况
    })
    .catch(function (error) {
      console.log(error) // 处理错误情况
    })
    .then(function () {
      console.log('finish') // 总是会执行
    })

  return <div>Test</div>
}

值得注意的是如果访问的还是http://localhost/域名下的东西,还是会报错403。这里需要使用网络IP地址,比如下面两个测试效果图:

在这里插入图片描述
可以看到这里的返回了403,即:

403 Forbidden:服务器收到请求,但是拒绝提供服务。

所以这里需要使用非localhost域名:
在这里插入图片描述

2.2 Get请求

其实在前面的案例中已经使用过了,这里再次摘抄一下:

axios
  .get('/api/mmdb/movie/v3/list/hot.json')  // 去掉非同源域
  .then(function (response) {
    console.log(response) // 处理成功情况
  })
  .catch(function (error) {
    console.log(error) // 处理错误情况
  })
  .then(function () {
    console.log('finish') // 总是会执行
  })

这种情况也就是直接请求,通常我们需要传入一些参数信息,可以按照下面的方式进行,这里以百度的为例,比如请求链接https://www.baidu.com/s?wd=React

export default function Test() {
  axios
    .get('/baidu/s', {
      params: {
        wd: 'React',
      },
    })
    .then(function (response) {
      console.log(respons)
    })
    .catch(function (error) {
      console.log(error)
    })
    .then(function () {
      console.log('finish')
    })

  return <div>Test</div>
}

对应的在setupProxy.js文件中添加一个配置:

// 可配置多个
app.use(
  '/baidu', // 代理路径
  createProxyMiddleware({
    target: 'https://www.baidu.com', // 真实地址
    secure: true, // 如果是https接口 需要配置这个参数为true
    pathRewrite: { '^/baidu': '' }, // 重写映射
    changeOrigin: true,
  })
)

这里重新添加了一个百度的映射。结果为:

在这里插入图片描述
当然其实get方式的请求参数可以直接拼接到URL链接中。

2.3 Post请求

类似的处理:

// https://passport.meituan.com/account/unitivelogin
axios
  .post(
    '/login/account/unitivelogin?risk_partner=0&risk_platform=1&risk_app=-1&uuid=1b6c5826fdad48a7833f.1638412798.1.0.0&token_id=DNCmLoBpSbBD6leXFdqIxA&service=maoyan&continue=https://www.maoyan.com/passport/login?redirect=%2F',
    {
      countrycode: '86',
      email: '15128459509',
      password: 'C7XRhr',
      origin: 'account-login',
    }
  )
  .then(function (response) {})
  .catch(function (error) {})

因为这个接口的参数还挺多的,这里我就没有拷贝完全,可以自行抓包查看。请求最后因为非法到了重定向:
在这里插入图片描述
侧面也说明了其实Post请求测试成功的。前面的这种配置方式为别名配置,即对应的请求方法getpostheadput等。当然,也可以向 axios 传递相关配置来创建请求:

// 发起一个post请求
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

比如:

function getMethod() {
  axios({
    method: 'get',
    url: '/baidu/s',
    params: {  // get使用params,post使用data
      wd: 'React',
    },
  })
    .then(function (response) {
      console.log(response)
    })
    .catch(function (error) {
      console.log(error)
    })
    .then(function () {
      console.log('finish')
    })
}

在使用别名方法时, urlmethoddata 这些属性都不必在配置中指定。

2.4 同时多个请求

比如:

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

Promise.all([getUserAccount(), getUserPermissions()])
  .then(function (results) {
    const acct = results[0];
    const perm = results[1];
  });

2.5 更多请求参数配置

在使用爬虫的时候我们知道可以传入请求头部信息等,在axios中也可以做相应的配置。

axios({
  method: 'get', // 默认值
  url: '/baidu/s',
  baseURL: 'https://www.baidu.com', // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  data: {
    // data` 是作为请求体被发送的数据,仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
    wd: 'React',
  },
  // 自定义请求头
  headers: {},
  timeout: 1000, // 默认值是 `0` (永不超时)
  // `responseType` 表示浏览器将要响应的数据类型
  // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认值
  responseEncoding: 'utf8', // 默认值
  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
  // 你可以修改请求头。
  transformRequest: [
    function (data, headers) {
      // 对发送的 data 进行任意转换处理
      return data
    },
  ],
  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [
    function (data) {
      // 对接收的 data 进行任意转换处理
      return data
    },
  ],
  // `auth` HTTP Basic Auth
  auth: {
    username: 'janedoe',
    password: 's00pers3cret',
  },
})

当然还有很多千奇百怪的设置,完整可查看网站:请求配置

2.6 拦截器

可以在请求或响应被 thencatch 处理前拦截它们。比如,请求拦截器

axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

响应拦截器

axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

使用案例:

function getMethod() {
  const myAxiosInstance = axios.create()

  myAxiosInstance.interceptors.request.use(
    function (config) {
      console.log('Before')
      return config // 在发送请求之前做些什么
    },
    function (error) {
      // 对请求错误做些什么
      return Promise.reject(error)
    }
  )

  myAxiosInstance({
    method: 'get', // 默认值
    url: '/baidu/s',
    params: {
      wd: 'React',
    },
  })
    .then(function (response) {
      console.log(response)
    })
    .catch(function (error) {
      console.log(error)
    })
    .then(function () {
      console.log('finish')
    })
}

当然这里还是非同源域,所以还是需要在setupProxy.js文件中配置代理。

2.7 请求体编码

默认情况下,axiosJavaScript 对象序列化为 JSON 。要以application/x-www-form-urlencoded格式发送数据,您可以使用以下选项之一。

2.7.1 浏览器

在浏览器中,可以使用URLSearchParams API,如下所示(qs):

import qs from 'qs';
const data = { 'bar': 123 };
const options = {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  data: qs.stringify(data),
  url,
};
axios(options);

2.7.2 Node.js

const querystring = require('querystring');
axios.post('http://something.com/', querystring.stringify({ foo: 'bar' }));

更多的方式请查阅:请求体编码

3. axios底层逻辑

github项目地址:https://github.com/axios/axios/
这里部分就来简单看下axios的底层源码实现。注意到在githubreadme文件中提到了使用CDN引入的方式为:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

所以在项目地址中,只需要关注于axios.js这个文件即可。在这个文件可以看到:

var Axios = require('./core/Axios');

所以其实真正的目录位于core/Axios.js文件中。

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

// 定义了原型 
Axios.prototype.request = function request(config) {
	...		
	// 验证config参数
	// 设置请求方法
	// 遍历拦截器链
	var requestInterceptorChain = [];
	var synchronousRequestInterceptors = true;
	 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
	   if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
	     return;
	   }
	
	   synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
	
	   requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
	 });
	 // 分发请求
	try {
		promise = dispatchRequest(newConfig);
	} catch (error) {
		return Promise.reject(error);
	}
	 ...
	 // 配置请求别名
	utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
	  /*eslint func-names:0*/
	  Axios.prototype[method] = function(url, config) {
	    return this.request(mergeConfig(config || {}, {
	      method: method,
	      url: url,
	      data: (config || {}).data
	    }));
	  };
	});
}
// 导出Axios
module.exports = Axios;

可以看到一个简略的实现流程,在这个文件中主要完成的是对上层逻辑的一些封装,包括默认请求方法,请求配置,验证,拦截器遍历,分发请求,提供请求方法别名等。

这里因为主要关注于底层的实现,所以这里我就直接找到dispatchRequest(newConfig)。注意到这个方法来自外部:

var dispatchRequest = require('./dispatchRequest');

所以这里就继续查看dispatchRequest.js这个文件。关注下面的代码即可:

module.exports = function dispatchRequest(config) {
	...
	var adapter = config.adapter || defaults.adapter;
	return adapter(config).then(function onAdapterResolution(response) {
	    throwIfCancellationRequested(config);
	
	    // Transform response data
	    response.data = transformData.call(
	      config,
	      response.data,
	      response.headers,
	      config.transformResponse
	    );
	
	    return response;
	  }, ...
  }
}

返回一个adapter,并传入了配置。如果这个adapter执行成功将返回一个response。也就是说其实我们的目标进一步定位到了adapter中。因为这里不清楚config中存放了什么东西,所以可以追踪defaults.adapter,注意到:

var defaults = require('../defaults');

可以查看defaults.js文件,可以看到:

adapter: getDefaultAdapter(),

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

终于在这里看到了adapter 来自于xhr.js文件,或者来自于http.js文件。我们通过注释也知道二者是如果浏览器端就使用XMLHttpRequest ,如果是node就使用node。也就是核心文件为:

在这里插入图片描述

这里仅以xhr.js为例。简略来说就是:

// 构建XMLHttpRequest对象
var request = new XMLHttpRequest();

// 使用open来打开链接
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

// 注册回调监听
request.onabort = function handleAbort() ...
request.onerror = function handleError() ...
request.ontimeout = function handleTimeout() ...

References

References

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦否

文章对你有用?不妨打赏一毛两毛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值