在弄清楚React中的Ajax的使用之前,首先需要理解如下的一些概念
异步&回调函数
异步
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。
function a(){
console.log('执行a');
setTimeout(function(){
console.log('a执行完毕')
},1000)
};
function b(){
console.log('执行b')
};
a();
b();
最终的运行结果为
执行a
执行b
a执行完毕
以上代码会先执行函数a,而且不会等到a中的延迟函数执行完才执行函数b, 在延迟函数被触发的过程中就执行了函数b,当js引擎的event 队列空闲时才会去执行队列里等待的setTimeout的回调函数,这就是一个异步的例子
即使时间设置为0,也是会照样先执行函数b
回调函数
在异步执行的模式下,每一个异步的任务都有其自己一个或着多个回调函数,这样当前在执行的异步任务执行完之后,不会马上执行事件队列中的下一项任务,而是执行它的回调函数,而下一项任务也不会等当前这个回调函数执行完,因为它也不能确定当前的回调合适执行完毕。例如:上述代码中的setTimeout就是一个回调函数。
在实际体验中,当我们访问购物网站时,浏览器请求加载图片资源是一个耗时操作,网络条件差时,会导致图片加载很慢。但是此时我们仍然可以点击购物按钮完成购物的操作。这是因为图片加载(Ajax)是一个异步请求。它并不会因为图片加载事件而阻塞其它JS事件
Promise
Ajax是一个典型的异步操作
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
}
}
以上代码,Ajax根据网络请求的返回的状态码(请求成功还是失败)来决定执行回调函数success(request.responseText)或fail(request.status)。
这样不好看,而且不利于代码复用。而且要想做多重的异步操作,会导致经典的回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
Promise的基本使用
doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了
Promise对象doSomething()是异步的,当他在某个时间点执行成功后,会去回调then后面的doSomethingElse();doSomethingElse()也是异步的,当他执行成功后,会去回调doThirdThing();以此类推,形成了一个Promise链。
在回调地狱示例中,有 3 次 failureCallback 的调用,而在 Promise 链中只有尾部的一次调用。通常,一遇到异常抛出,promise 链就会停下来,直接调用链式中的 catch 处理程序来继续当前执行。
Promise的并行执行
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。它依赖于前置的多个Promise对象,只有当这些Promise对象全部执行成功后,才会去执行then后面的回调函数。例如下面的promise对象p1和p2,这两个任务是可以并行执行的,用Promise.all()实现
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
有些时候,多个异步任务是为了容错。多个Promise对象只需要执行成功一个即可。只需要获得先返回的结果即可。例如下面例子,由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃这种情况下,用Promise.race()实现。
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
接下来进入正题
React AJAX请求
常见的五种请求方案:
- jQuery $.ajax
- Fetch API
- SuperAgent
- Axios
- Request
这五种方案我并未全部用过,具体用法请参见:
Axios初体验
axios基于promise用于浏览器和node.js的http客户端,具有如下特点:
- 支持浏览器和node.js
- 支持promise
- 能拦截请求和响应
- 能转换请求和响应数据
- 能取消请求
- 自动转换JSON数据
- 浏览器端支持防止CSRF(跨站请求伪造)
axios是基于ajax的一次封装。在实际工程中,接口统一规范好后,各组件axios的请求部分基本一致,因此可以将该部分分离出来做二次封装。被调用时接受组件传入的url等参数。其中baseURL会自动加入到url参数之前
import axios from 'axios'
import {Modal} from 'antd'
export default class Axios {
static ajax(options){
return new Promise((resolve,reject) => {
axios({
url: options.url,
baseURL:' https://www.easy-mock.com/mock/5d2bb8f85fb7810a6be03487/baseEndManager',
method: 'get',
timeout: 5000,
}).then((response) => {
if(response.status === 200){
let res = response.data;
resolve(res);
}else{
Modal.info({
title: "警告",
content: "请求失败"
})
reject(response.data);
}
})
})
}
}
然后在不同的组件中调用axios时只需要传入URL等参数即可。接着将返回的response根据不同组件进行不同的解析。然后传入state中,从而达到渲染组件的目的。本例使用的mock数据,接口地址在此
import { Table,Card,Button } from 'antd';
import React from 'react'
// import userList from './../../config/userConfig'
import axios from './../../axios/index.js'
export default class User extends React.Component {
constructor(props){
super(props);
this.state = {
datasource: [],
}
}
componentDidMount(){
this.request();
}
request = () => {
axios.ajax({
url: '/users',
}).then((res)=>{
let userlist = res.result.list.map((item) => {
item.key = item.id;
return item;
});
this.setState({
datasource: userlist,
});
})
}
render(){
const columns = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '姓名',
dataIndex: 'name',
},
{
title: '年龄',
dataIndex: 'age',
},
{
title: '籍贯',
dataIndex: 'city',
},
{
title: '电子邮件',
dataIndex: 'email',
},
{
title: '入职时间',
dataIndex: 'date',
},
]
return(
<div>
<Card>
<Button type='danger' size="small">编辑</Button>
</Card>
<Card>
<Table bordered columns={columns} dataSource={this.state.datasource}/>
</Card>
</div>
)
}
}
Axios实际体验
在实践过程中,体会到Axios基于ajax和promise的特点,决定了axios是一个异步的过程。这个特点在使用echarts时会踩坑。
假设需要使用echarts制作条形图,数据需要通过axios从接口获取,由于echarts的条形图是在componentDidMount()中初始化和设置数据的。如果在componentDidMount()中通过调用axios来获取数据。由于axios是异步执行的,axios还未从接口取回来数据,echarts已经初始化完毕了,这时由于this.state.obj还未通过axios调用的接口数据赋值。此时echarts会渲染出一个无数据的条形图。即使在componentWillMount()中调用axios,仍然会导致该情况发生。
class Bar extends Component {
constructor(props){
super(props);
this.state={
obj: {
week: [],
data: [],
}
};
}
componentDidMount() {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('bar'));
// 绘制图表
myChart.setOption({
title: { text: '每周平均交易量' },
tooltip: {},
xAxis: {
data: this.state.obj.week,
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
color: ['#dd6b66','#759aa0','#e69d87','#8dc1a9','#ea7e53','#eedd78','#73a373'],
data: this.state.obj.data,
}]
});
})
render() {
console.log(this.state.obj);
return (
<div id="bar" style={{ width: 800, height: 430 }}></div>
);
}
}
一种正确的做法是通过promise链,必须执行完axios的调用,才能执行echarts的数据初始化设置。核心代码如下,其余部分见上一个代码。
componentDidMount() {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('bar'));
// 绘制图表
axios.ajax({
url: '/charts',
}).then((res) => {
let weeks = res.result.list.map((item) =>{
return item.week;
});
let datas = res.result.list.map((item) => {
return item.data;
})
this.setState({
obj: {
week: weeks,
data: datas,
}
});
}).then(() => {
myChart.setOption({
title: { text: '每周平均交易量' },
tooltip: {},
xAxis: {
data: this.state.obj.week,
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
color: ['#dd6b66','#759aa0','#e69d87','#8dc1a9','#ea7e53','#eedd78','#73a373'],
data: this.state.obj.data,
}]
});
})
}
网友评论