「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」
传统Ajax基于XMLHttpRequest 用的不多
我第一次接触ajax是从JQuery ajax开始的。但是对于现在的脚手架方式,不会有人往项目中引入jQuery。因为JQuery已经不太常用了。
基于Promise设计的,发送请求和获取数据进行了很好的分离。但也有缺点:它是一个较为底层的Api需要自己进行封装。
1️⃣ 比如需要自己配置Header的Content-Type等,不会默认携带cookie。这些都需要自己去封装。
2️⃣ 处理错误麻烦,只有网络错误才会reject(promise的api),Http状态码404、500不会被标记为reject
3️⃣ 不支持取消一个请求以及查看请求的进度。
可以理解成 ajax i/o system。前端使用较多。 特点:在浏览器中发送XMLHttpRequests 请求、在node中发送http请求,支持Promise API 、拦截请求和响应、转换请求和响应数据
总而言之,在脚手架项目中也就是在fetch 和 axios二者中选择。
其实没有必要新建,这是我项目的地址:✈️
我的项目是React Hooks的项目,我们在pages/Home/index.tsx
文件下新写一个请求。在这里我在useEffect()
钩子中发起请求。如果你是React Class
项目,可以在comoponentDidMount()
声明周期中发起请求。
import React, { useEffect } from "react";
import axios from 'axios'
const Home:React.FC = () =>{
useEffect(()=>{
axios.get('api/users/test').then((res)=>console.log(res))
// 再去修改数据
},[])
return(
<>
Home
</>
)
}
export default Home;
这个语法是TypeScript语法,但是并不是用TypeScript编写的。现在很多的库,在编写完之后写 d.ts文件。是TypeScript 定义文件。一些类型的定义文件。主要是为了我们跳转进去看到定义的类型。
按住Ctrl键 + 鼠标点击axios.get() 跳进去,可以发现它最主要让我们传入AxiosRequestConfig
那么,我们再同样操作,看一下AxiosRequestConfig
究竟是什么。这是一个接口,
刚才我们上面的请求中我请求的是我本地的node编写的后端,如需要可自行下载 🚁 当然,不是所有人都愿意去触摸后端,那么可以使用这个网站进行测试http://httpbin.org/#/
这里我们再说一下
正常来说,前端去调取非同源的后端,会产生跨域问题。解决跨域可以在前端处理,也可以再后端处理。在前端则需要安装http-proxy-middleware
。当然真实项目中,最常用的是用Nginx进行反向代理。
言归正传,如下是我们发起的一个get请求
import React, { useEffect } from "react";
import axios from 'axios'
const Home:React.FC = () =>{
useEffect(()=>{
axios.get('http://httpbin.org/get').then((res)=>console.log(res))
},[])
return(
<>
Home
</>
)
}
export default Home;
这个get请求到的其实是一个html页面
在axios中传递一个对象参数。请求的url、参数、请求方式(GET PUT POST DELETE),默认的请求是GET请求,method其实可以不写。
axios返回的是一个Promise的对象,要想获得返回结果需要在 then 中获得,catch 处理异常。
import React, { useEffect } from 'react';
import axios from 'axios';
const Home: React.FC = () => {
useEffect(() => {
axios({
url: 'http://httpbin.org/get',
params: {
name: 'sdss',
age: 18,
},
method: 'GET',
}).then(res => console.log(res))
.catch(err=>{ console.error(err) });
}, []);
return <>Home</>;
};
export default Home;
如下:其实get方式传参,是在URL中传递的。 使用params进行传参,但是在post中就不能使用params了。
axios.get("http://httpbin.org/get",{
params:{
name:'test',
age: 30
}
}).then(res=>console.log(res)).catch(err=>console.error(err))
post请求
这个传参我们就不能放到params中了 ,需要在data中传参数。
axios({
url:'http://httpbin.org/post',
data:{
name: 'koa',
age: 40
},
method: 'POST'
}).then(res=>{
console.log(res)
}).catch(err=>{
console.error(err)
})
}, []);
axios.post("http://httpbin.org/post",{
name:'lucky',
age:20
}).then(res=>console.log(res))
无论是get请求还是post请求,实质上都是request请求。
axios.request({
})
我们跳到index.d.ts文件,但是这并不是axios功能实现的源码,只是一个描述文件,index.js才是功能代码。 我们可以看到index.js中也并没有写具体代码,而是因我那个了lib下的axios
我们看一下 axios.js文件 可以看到我们的axios是通过createInstance创建的实例,在创建实例中 绑定了原型上的request。所以本质上请求方式都是request。因此可以 axios()这么写也就是第一种写法。
并没有 axios.get 那么为什么可以使用 axios.get那?我们选中Axios 然后 ctrl + 鼠标左键
跳入到了 core/Axios.js
,
'delete', 'get', 'head', 'options'
,并把它们添加到了原型上,可以看到这四种方式是没有传入data参数的只有url和config,return后面调用的request函数中的data是config或者是空对象的data。
'post', 'put', 'patch'
,这几中方式传入了url data 和 config,所以这几种请求是可以传data参数的。
我们用的是react hooks,正常来说,在useEffect的函数中写async关键字是可以的,
useEffect( async() => {
const result = await axios({
url: 'http://httpbin.org/post',
data: {
name: 'koa',
age: 40,
},
method: 'POST',
})
},[])
但是,我还用到了TypeScript,所以会报错。那如果我们还是想要在useEffect中使用的话,我们就再定义一个函数,然后再useEffect中使用就好了
const testAsync = async()=>{
// async await
const result = await axios({
url: 'http://httpbin.org/post',
data: {
name: 'koa',
age: 40,
},
method: 'POST',
});
console.log(result,'testAsync')
}
useEffect(() => {
testAsync()
},[])
同样也得到了我们想要的结果的。
在使用Promise的then() API的时候,我们是可以再通过catch API 获得错误异常的。那么 async await这种写法怎么获得错误异常呢?
可以使用try..catch..
const testAsync = async () => {
try {
// async await
const result = await axios({
url: 'http://httpbin.org/post',
data: {
name: 'koa',
age: 40,
},
method: 'POST',
});
console.log(result, 'testAsync');
} catch (error) {
console.log(error)
}
};
axios.all 需要传入一个数组[],数组
const testAsync1 = () => {
try {
// async await
return axios({
url: 'http://httpbin.org/post',
data: {
name: 'koa',
age: 40,
},
method: 'POST',
});
// console.log(result, 'testAsync');
} catch (error) {
console.log(error)
}
};
const testAsync2 =() => {
try {
// async await
return axios({
url: 'http://httpbin.org/get',
params: {
name: 'sdss',
age: 18,
},
method: 'GET',
})
} catch (error) {
console.log(error)
}
};
// 注意 这里是 testAsync1()函数需要带() 因为其返回值不是Promise对象
const testAsync = async ()=>{
const result = await axios.all([testAsync1(),testAsync2()])
console.log(result)
}
如图返回了一个数组,数组内分别是两个请求的结果对象
不能在request1 request2后面写then,因为有then的出现就说明已经执行完毕了。也就是Promise的resolve结束了。返回结果也就会是[undefined,undefined]
useEffect(() => {
// get请求
const request1 = axios({
url: 'http://httpbin.org/get',
params: {
name: 'sdss',
age: 18,
},
method: 'GET',
})
// post请求
const request2 =axios({
url: 'http://httpbin.org/post',
data: {
name: 'koa',
age: 40,
},
method: 'POST',
})
// 这里request1,request2返回的是Promise对象 所以不需要()
axios.all([request1,request2]).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
}, []);
要想把两个接口的返回结果分开的话,我们可以使用数组的解构
axios.all([request1,request2]).then(([res1,res2])=>{
console.log(res1,res2)
}).catch(err=>{
console.log(err)
})
看一下 源码其实axios.all本质上就是一个promise.all()
{
// `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) {
})
}
可以写到index.js
中
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// 如果只给post添加配置
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
再考虑一个问题,如果,去请求两台服务器 那么baseURL怎么办
可以创建多个实例 通过 axios.create
// 创建实例
const instance1 = axios.create({
baseURL:'http://www.baidu.com',
timeout:5000,
headers:{
}
})
const instance2 = axios.create({
baseURL:'http://www.hao123.com',
timeout:5000,
headers:{
}
})
// 使用
instance1 ({
url: '/post'
})
instance2({
url: '/get'
})
axios.interceptors.request.use(config=>{
// 1.发送请求时 在界面的中间位置显示loading的组件
// 2.请求的用户必须携带token
// 3.params/data做一些序列化的操作
return config
},err=>{
})
axios.interceptors.response.use(res=>{
return res.data
},err=>{
if(err && err.response){
switch(err.responseInterceptor.status){
case 400:
console.log('请求错误')
break;
case 401:
console.log('未授权')
break;
default:
console.log('其他错误')
}
}
return err
})
进行封装,如果需要修改,只需要改一出即可。
当然这个名字随便取,只要你自己能看懂就行。
在文件夹下新建 request.js。这个文件对axios进行封装。通过axios.create创建一个instance实例(这里用到的一些配置变量我们又定义一个文件然后再引用),然后分别对instance进行请求拦截处理 和 响应拦截处理。
import axios from 'axios'
import { BASE_URL, TIMEOUT } from './config'
const instance = axios.create({
baseURL: BASE_URL,
timeout: TIMEOUT
})
instance.interceptors.request.use(config => {
// 1.发送请求时 在界面的中间位置显示loading的组件
// 2.请求的用户必须携带token
// 3.params/data做一些序列化的操作
return config
}, err => {
})
axios.interceptors.response.use(res => {
return res.data
}, err => {
if (err && err.response) {
switch (err.responseInterceptor.status) {
case 400:
console.log('请求错误')
break;
case 401:
console.log('未授权')
break;
default:
console.log('其他错误')
}
}
return err
})
export default instance
定义一些配置变量,如baseUrl,timeout等。其中BASE_URL还对开发环境和正式环境进行了判断。
const devBaseURL = 'http://httpbin.org/'
const proBaseURL = 'http://production.org/'
// 进行修改
const BASE_URL = process.env.NODE_ENV === 'development' ? devBaseURL : proBaseURL
const TIMEOUT = 5000
export { BASE_URL , TIMEOUT}
我们打印 process.env看一下
import React, { useEffect } from 'react';
// 根据不同层级引用
import request from '../../service/request'
const Home: React.FC = () => {
useEffect(() => {
// get请求
request({
url: '/get',
params: {
name: 'sdss',
age: 18,
},
method: 'GET',
}).then((res)=>console.log(res))
}, []);
return <>Home</>;
};
export default Home;