1. Что такое axios и каковы его характеристики
описывать
аксиос - этоpromiseизHTTPБиблиотека, которая может быть использована в浏览器илиnode.jsсередина. Эта статья посвящена XHR.
axios предоставляет два адаптера HTTP-запросов, XHR и HTTP. Ядром XHR является объект XMLHttpRequest на стороне браузера, ядром HTTP является метод http.request узла.
характеристика:
- Создание XMLHttpRequests из браузера
- Создать http-запрос из node.js
- API обещания поддержки
- Перехват запросов и ответов
- Преобразование данных запроса в данные ответа
- отменить запрос
- Автоматически преобразовывать данные JSON
- Поддержка клиентов для защиты XSRF
задний план
посколькуVueНачиная с 2.0, Youda объявил об отменеvue-resourceОфициальная рекомендация , которая, в свою очередь, рекомендуетaxios. Сейчас axiosстало большинствомVueПервый выбор разработчика, в настоящее время имеет 87,3 тысячи звезд на github.axiosКвалифицированное использование и базовая инкапсуляция Vue также стали неотъемлемой частью серии технологических стеков Vue. Если вы еще не знаете axios, рекомендуется сначала с ним ознакомиться.документация официального сайта axios.
основное использование
Установить
npm install axios -S
использовать
import axios from 'axios'
// 为给定ID的user创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 上面的请求也可以这样做
axios.get('/user', {
params: {ID: 12345}})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
2. Зачем инкапсулировать аксиомы в проекте Vue
axiosAPI очень удобный и может использоваться непосредственно в проекте. Однако в больших проектах много http-запросов, и нужно различать окружение.
Каждый сетевой запрос имеет похожие части, которые необходимо обрабатывать следующим образом, что приведет к избыточности кода и повреждению проекта.可维护性,扩展性
axios('http://www.kaifa.com/data', {
// 配置代码
method: 'GET',
timeout: 3000,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
},
// 其他请求配置...
})
.then((data) => {
// todo: 真正业务逻辑代码
console.log(data);
}, (err) => {
// 错误处理代码
if (err.response.status === 401) {
// handle authorization error
}
if (err.response.status === 403) {
// handle server forbidden error
}
// 其他错误处理.....
console.log(err);
});
- Экологическое отличие
- запрашивать информацию заголовка
- тип запроса
- тайм-аут запроса
- timeout: 3000
- Разрешить файлы cookie
- withCredentials: true
- Обработка результатов ответа
- Не удалось подтвердить вход
- Отсутствует разрешение
- успех
- ...
3. Как инкапсулировать аксиомы в проекте Vue
файлы axios упакованы в каталогsrc/utils/https.js, выставленный наружуcallApiфункция
1. Экологическое отличие
callApiфункциональная экспозицияprefixUrlПараметры для настройки URL-адреса API前缀, значение по умолчаниюapi
// src/utils/https.js
import axios from 'axios'
export const callApi = ({
url,
...
prefixUrl = 'api'
}) => {
if (!url) {
const error = new Error('请传入url')
return Promise.reject(error)
}
const fullUrl = `/${prefixUrl}/${url}`
...
return axios({
url: fullUrl,
...
})
}
Увидев это, вы можете спросить, а почему бы не использовать параметры конфигурации, предоставляемые axios.baseURL, Причина вbaseURLСоответствующий префикс будет добавлен к каждому интерфейсу, и в реальном сценарии проекта есть интерфейсный проект, соответствующий нескольким服务место действия. Нужно проксировать на разные сервисы через разные префиксы,baseURLХотя это можно реализовать, для этого требуется вторичный префикс, что не элегантно, и вы не можете увидеть, какой реальный адрес API при его использовании, потому что префикс прокси смешивается с реальным адресом.
использоватьbaseURL, эффект следующий
Функция устанавливает параметр prefixUrl, эффект следующий
использовать环境变量а такжеwebpack代理(Здесь используется конфигурация vuecli3), чтобы различать среды разработки и тестирования. Та же конфигурация для производственной средыnginxиграет роль
// vue.config.js
const targetApi1 = process.env.NODE_ENV === 'development' ? "http://www.kaifa1.com" : "http://www.ceshi1.com"
const targetApi2 = process.env.NODE_ENV === 'development' ? "http://www.kaifa2.com" : "http://www.ceshi2.com"
module.exports = {
devServer: {
proxy: {
'/api1': {
target: targetApi1,
changeOrigin: true,
pathRewrite: {
'/api1': ""
}
},
'/api2': {
target: targetApi2,
changeOrigin: true,
pathRewrite: {
'/api2': ""
}
},
}
}
}
2. Заголовок запроса
Следующие три являются общими
(1)application/json
Параметры будут размещены непосредственно в теле запроса и отправлены на сервер в формате JSON. Это также способ по умолчанию для запросов axios. Этот тип является наиболее широко используемым.
(2)application/x-www-form-urlencoded
Данные в теле запроса отправляются на бэкэнд в обычной форме (пара ключ-значение).
(3)multipart/form-data
Параметры будут в теле запроса, в единицах тегов, разделенных разделителями (настраиваемые границы). Вы можете загружать как пары ключ-значение, так и файлы. Формат, обычно используемый для загрузки файлов.
callApiфункциональная экспозицияcontentTypeпараметры для настройки请求头, значение по умолчаниюapplication/json; charset=utf-8
Увидев это, все могут запутаться, пройдите сразуoptionsнастроитьheadersНет, ответ да, вы можете видетьnewOptionsПорядок значений: сначала принимается значение по умолчанию, затем принимается заданное значение.options, и, наконец, взятьcontentType,contentTypeМожет соответствовать большинству сценариев, может использоваться в сценариях, которые не могут быть удовлетвореныoptionsнастроить
пройти черезoptionsнастроитьheaders, написать n разheaders: {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}; и черезcontentTypeнастроить, передать параметрыjson || urlencoded || multipartТолько что
когдаcontentType === urlencodedчас,qs.stringify(data)
// src/utils/https.js
import axios from 'axios'
import qs from 'qs'
const contentTypes = {
json: 'application/json; charset=utf-8',
urlencoded: 'application/x-www-form-urlencoded; charset=utf-8',
multipart: 'multipart/form-data',
}
const defaultOptions = {
headers: {
Accept: 'application/json',
'Content-Type': contentTypes.json,
}
}
export const callApi = ({
url,
data = {},
options = {},
contentType = 'json', // json || urlencoded || multipart
prefixUrl = 'api'
}) => {
...
const newOptions = {
...defaultOptions,
...options,
headers: {
'Content-Type': options.headers && options.headers['Content-Type'] || contentTypes[contentType],
},
}
const { method } = newOptions
if (method !== 'get' && method !== 'head') {
if (data instanceof FormData) {
newOptions.data = data
newOptions.headers = {
'x-requested-with': 'XMLHttpRequest',
'cache-control': 'no-cache',
}
} else if (options.headers['Content-Type'] === contentTypes.urlencoded) {
newOptions.data = qs.stringify(data)
} else {
Object.keys(data).forEach((item) => {
if (
data[item] === null ||
data[item] === undefined ||
data[item] === ''
) {
delete data[item]
}
})
// 没有必要,因为axios会将JavaScript对象序列化为JSON
// newOptions.data = JSON.stringify(data);
}
}
return axios({
url: fullUrl,
...newOptions,
})
}
Обратите внимание, что вapplication/jsonВ этом формате JSON.stringify не имеет смысла обрабатывать параметры, потому что axios будет сериализовать объекты JavaScript в JSON, а это значит, что это JSON независимо от того, конвертируете вы его или нет.
3. Тип запроса
Параметр типа запросаaxiosизoptionsизmethodвведите соответствующий тип запроса, напримерpost,getПросто подожди
Не инкапсулируйте, используйте роднойaxiosчас,发送带参数的get请求следующим образом:
// src/service/index.js
import { callApi } from '@/utils/https';
export const delFile = (params) => callApi({
url: `file/delete?systemName=${params.systemName}&menuId=${params.menuId}&appSign=${params.appSign}`,
option: {
method: 'get',
},
});
// 或者
export const delFile = (params) => callApi({
url: 'file/delete',
option: {
method: 'get',
params
},
});
Официальные документы следующие
callApiфункциональная экспозицияmethodпараметры для настройки请求类型, значение по умолчаниюget
когда тип запросаgetкогда будетcallApiфункция выставленаdataпараметр, установленный наoptions.params, чтобы параметры автоматически стыковались после url-адреса
// src/utils/https.js
import axios from 'axios'
export const callApi = ({
url,
data = {},
method = 'get',
options = {},
...
prefixUrl = 'api'
}) => {
...
const newOptions = {
...,
...options,
method
}
...
if(method === 'get'){
newOptions.params = data
}
...
return axios({
url: fullUrl,
...newOptions,
})
}
4. Тайм-аут запроса
// src/utils/https.js
const defaultOptions = {
timeout: 15000,
}
5. Разрешить файлы cookie
// src/utils/https.js
const defaultOptions = {
withCredentials: true,
}
6. Обработка результатов ответа
пройти через.then,.catch()иметь дело с
Это необходимо согласовать с сервером.接口响应全局码, так что единая трактовка登录校验失败,无权限,成功ждать результата
Например, некоторые серверы登录校验失败,无权限,成功Все возвращаемые коды ответов равны 200, а коды состояния, возвращаемые в тексте ответа, — 20001, 20002 и 10000 соответственно.then()обработка
Например, некоторые серверы登录校验失败,无权限,成功Коды ответов возвращают 401, 403, 200, вcatch()обработка
// src/utils/https.js
import axios from 'axios'
import { Message } from "element-ui";
export const callApi = ({
...
}) => {
...
return axios({
url: fullUrl,
...newOptions,
})
.then((response) => {
const { data } = response
if (data.code === 'xxx') {
// 与服务端约定
// 登录校验失败
} else if (data.code === 'xxx') {
// 与服务端约定
// 无权限
router.replace({ path: '/403' })
} else if (data.code === 'xxx') {
// 与服务端约定
return Promise.resolve(data)
} else {
const { message } = data
if (!errorMsgObj[message]) {
errorMsgObj[message] = message
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
return Promise.reject(data)
}
})
.catch((error) => {
if (error.response) {
const { data } = error.response
const resCode = data.status
const resMsg = data.message || '服务异常'
// if (resCode === 401) { // 与服务端约定
// // 登录校验失败
// } else if (data.code === 403) { // 与服务端约定
// // 无权限
// router.replace({ path: '/403' })
// }
if (!errorMsgObj[resMsg]) {
errorMsgObj[resMsg] = resMsg
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
const err = { code: resCode, respMsg: resMsg }
return Promise.reject(err)
} else {
const err = { type: 'canceled', respMsg: '数据请求超时' }
return Promise.reject(err)
}
})
}
Вышеуказанная схема находится вMessage.error(xx), когда информация об ошибке, возвращаемая несколькими интерфейсами, непротиворечива, будет重复提示проблема, как показано на рисунке ниже
схема оптимизации, использующая防抖, реализовать подсказку об ошибке один раз, более элегантно
В-четвертых, полная упаковка и конкретное использование
код доступенмой гитхаб
полный пакет axios-ajax
// src/utils/https.js
import axios from 'axios'
import qs from 'qs'
import { debounce } from './debounce'
const contentTypes = {
json: 'application/json; charset=utf-8',
urlencoded: 'application/x-www-form-urlencoded; charset=utf-8',
multipart: 'multipart/form-data',
}
function toastMsg() {
Object.keys(errorMsgObj).map((item) => {
Message.error(item)
delete errorMsgObj[item]
})
}
let errorMsgObj = {}
const defaultOptions = {
withCredentials: true, // 允许把cookie传递到后台
headers: {
Accept: 'application/json',
'Content-Type': contentTypes.json,
},
timeout: 15000,
}
export const callApi = ({
url,
data = {},
method = 'get',
options = {},
contentType = 'json', // json || urlencoded || multipart
prefixUrl = 'api',
}) => {
if (!url) {
const error = new Error('请传入url')
return Promise.reject(error)
}
const fullUrl = `/${prefixUrl}/${url}`
const newOptions = {
...defaultOptions,
...options,
headers: {
'Content-Type':
(options.headers && options.headers['Content-Type']) ||
contentTypes[contentType],
},
method,
}
if (method === 'get') {
newOptions.params = data
}
if (method !== 'get' && method !== 'head') {
newOptions.data = data
if (data instanceof FormData) {
newOptions.headers = {
'x-requested-with': 'XMLHttpRequest',
'cache-control': 'no-cache',
}
} else if (newOptions.headers['Content-Type'] === contentTypes.urlencoded) {
newOptions.data = qs.stringify(data)
} else {
Object.keys(data).forEach((item) => {
if (
data[item] === null ||
data[item] === undefined ||
data[item] === ''
) {
delete data[item]
}
})
// 没有必要,因为axios会将JavaScript对象序列化为JSON
// newOptions.data = JSON.stringify(data);
}
}
axios.interceptors.request.use((request) => {
// 移除起始部分 / 所有请求url走相对路径
request.url = request.url.replace(/^\//, '')
return request
})
return axios({
url: fullUrl,
...newOptions,
})
.then((response) => {
const { data } = response
if (data.code === 'xxx') {
// 与服务端约定
// 登录校验失败
} else if (data.code === 'xxx') {
// 与服务端约定
// 无权限
router.replace({ path: '/403' })
} else if (data.code === 'xxx') {
// 与服务端约定
return Promise.resolve(data)
} else {
const { message } = data
if (!errorMsgObj[message]) {
errorMsgObj[message] = message
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
return Promise.reject(data)
}
})
.catch((error) => {
if (error.response) {
const { data } = error.response
const resCode = data.status
const resMsg = data.message || '服务异常'
// if (resCode === 401) { // 与服务端约定
// // 登录校验失败
// } else if (data.code === 403) { // 与服务端约定
// // 无权限
// router.replace({ path: '/403' })
// }
if (!errorMsgObj[resMsg]) {
errorMsgObj[resMsg] = resMsg
}
setTimeout(debounce(toastMsg, 1000, true), 1000)
const err = { code: resCode, respMsg: resMsg }
return Promise.reject(err)
} else {
const err = { type: 'canceled', respMsg: '数据请求超时' }
return Promise.reject(err)
}
})
}
// src/utils/debounce.js
export const debounce = (func, timeout, immediate) => {
let timer
return function () {
let context = this
let args = arguments
if (timer) clearTimeout(timer)
if (immediate) {
var callNow = !timer
timer = setTimeout(() => {
timer = null
}, timeout)
if (callNow) func.apply(context, args)
} else {
timer = setTimeout(function () {
func.apply(context, args)
}, timeout)
}
}
}
конкретное использование
api файл в управлении каталогамиsrc/serviceВниз,index.jsфайл выставляет другие модули, другие файлы нажимают功能模块划分документ
получить запрос с параметрамиПользовательский префикс прокси для разных сервисов
обработка типов файлов
V. Резюме
axiosАбсолютного стандарта для упаковки нет, и его нужно совмещать с проектом实际场景дизайну, но нет сомнений, что инкапсуляция axios-ajax очень нужна
Оригинальный адрес:GitHub.com/в следующем месяце 25/asy…
Другие статьи:Рекомендации по работе с общими модулями между несколькими проектами Vue
Колонка [Бизнес-резюме], включенная в эту статью, направлена наУпреждать проблемы, возникающие в работе, и обобщать их как лучшие практики, добро пожаловать в подписку ✨