前言
日常开发中,使用axios传递参数我们总是会混淆参数传递的位置,因为axios对我们传递的参数做了合并兼容,现在让我们从源码层面了解axios参数的使用。
axios的三种使用方式
- axios.request(config)
- axios.get(url,config)/axios.post(url,data,config)
- axios(config) axios是返回了一个方法,再遍历Axios.prototype上的所有方法,赋值给axios,所以axios可以支持多种调用方式,下面是入口参数的源码:
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
axios的参数合并
axios的最终接收的都只是一个config对象,里面包含一些我们经常使用的url、method、params、body、headers等参数,可以分开传递是因为内部对参数再做了一次合并,参照下面的部分源码:
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
// 合并为一个config对象
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
// 合并为一个config对象
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
axios的params和data参数
axios的params参数是通过简单处理,拼接到url后面,再去请求接口,data参数直接作为body,通过 xhr.send(data)传递给后台接口,源码如下:
- params参数处理
var request = new XMLHttpRequest();
var fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params), true);
// ...
function buildURL(url, params) {
var serializedParams;
var parts = [];
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === 'undefined') return;
// 对数组参数做处理
if (utils.isArray(val)) {
key = key + '[]';
} else {
val = [val];
}
// 对日期参数和普通对象处理
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString();
} else if (utils.isObject(v)) {
v = JSON.stringify(v);
}
parts.push(encode(key) + '=' + encode(v));
});
});
// 参数拼接到url
serializedParams = parts.join('&');
if (serializedParams) {
var hashmarkIndex = url.indexOf('#');
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex);
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
};
- data参数
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var request = new XMLHttpRequest();
// ...
request.send(requestData);
})
}
axios的headers默认参数
axios的headers的参数,axios支持根据你不同的请求方法,设置不同的默认参数,通过axios.defaults.headers.common = {xxx: "xxx"}设置,axios的参数都支持axios.defaults设置,但是优先级要低于传递的config参数,源码如下
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge({
'Content-Type': 'application/x-www-form-urlencoded'
});
});
axios的拦截器和适配器
基本使用
// 拦截器
axios.interceptors.request.use(config => {
config.headers = {
'Content-Type': 'application/x-www-form-urlencoded' //配置请求头
}
return config;
}, error => { });
// 适配器
axios.interceptors.response.use(response => {
response.data = "XXX"
return response
}, error => { })
实现思路:
- 通过use方法将传入的回调函数分别push到拦截器和适配器数组里面
- 创建一个数组,放一个ajax的方法,前面unshift拦截器的回调函数,后面push适配器的回调函数
- 通过promise.then的链式调用,保证调用顺序
- 这样,在每次发送请求之前(拦截器)和请求成功之后(适配器),分别去调用对应的参数。
核心源码如下:
// 适配器和拦截器使用同一个构造函数,实现一个use方法
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 实例挂载在axios.interceptors上
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(), // 拦截器
response: new InterceptorManager() // 适配器
};
}
Axios.prototype.request = function request(config) {
var requestInterceptorChain = [];
// 拦截器的函数收集到数组
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 适配器的函数收集到数组
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
// 中间是发送ajax请求
var chain = [dispatchRequest, undefined];
// 拦截器的函数放在ajax之前
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// 适配器的函数放在ajax之后
chain.concat(responseInterceptorChain);
// 一开始传入config作为函数参数,dispatchRequest之后的函数参数变为response
promise = Promise.resolve(config);
// 将所有函数通过promise.then连接起来
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
axios的取消请求
2种使用方式
- 传入一个cancelToken参数,值是CancelToken函数的一个实例
- 保存resolve函数,在必要的时候执行
// 方式一
const source = axios.CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
});
// 取消请求
source.cancel();
// 方式二
let cancel;
axios.get('/user/12345', {
cancelToken: new axios.CancelToken(function executor(c) {
cancel = c;
})
});
// 取消请求
cancel();
源码实现
核心思路就是promise+xhr.abort实现
- 创建一个CancelToken实例,传入一个回调函数
- 实例上挂载一个promise,promise的resolve方法,被作为参数传递出去
- 在发送xhr请求的时候,注册promise.then,中断请求
- 在外面执行promise的resolve方法,中断请求
function CancelToken(executor) {
var resolvePromise;
// 创建一个promise对象
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
// 将promise的resolve方法,在执行构造函数的时候,传递出去
executor(function cancel() {
resolvePromise();
});
}
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var request = new XMLHttpRequest();
request.open(config.method.toUpperCase(), true);
// 原生js的onabort方法
request.onabort = function handleAbort() {
reject();
};
// ...
if (config.cancelToken) {
// 创建的promise,在这里等待被执行
config.cancelToken.promise.then(function onCanceled(cancel) {
request.abort();
reject(cancel);
});
}
request.send(requestData);
});
};
上传与下载进度监控方法
基本使用
- 使用方法和传递其他headers等参数一样,最终都是去config里面取。
axios({
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
}
}
})
源码实现
这里的源码实现就很简单,直接调用xhr两个原生的对应的方法
function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var request = new XMLHttpRequest();
request.open(config.method.toUpperCase(), url, true);
// Handle progress if needed 下载进度
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events 上传进度
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
// Send the request
request.send(requestData);
});
};
最后
axios的实现,从源码角度来分析,其实并不是很难,文中贴出来的源码是经过一些删减,有兴趣的朋友,可以参考源码看一下具体实现,在工作中使用axios更加的顺滑,最后,文章对你有帮助的话,不要忘了点赞喔~