Интервьюер: Были ли axios включены в проект Vue/React? Как это упаковано?

Vue.js axios
Интервьюер: Были ли axios включены в проект Vue/React? Как это упаковано?

image.png

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, эффект следующий

image.png

Функция устанавливает параметр prefixUrl, эффект следующийimage.png

использовать环境变量а также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. Этот тип является наиболее широко используемым.

image

(2)application/x-www-form-urlencoded

Данные в теле запроса отправляются на бэкэнд в обычной форме (пара ключ-значение).

image

(3)multipart/form-data

Параметры будут в теле запроса, в единицах тегов, разделенных разделителями (настраиваемые границы). Вы можете загружать как пары ключ-значение, так и файлы. Формат, обычно используемый для загрузки файлов.

image 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)

image.png

// 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
  },
});

Официальные документы следующие

image.png

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), когда информация об ошибке, возвращаемая несколькими интерфейсами, непротиворечива, будет重复提示проблема, как показано на рисунке ниже

image.png

схема оптимизации, использующая防抖, реализовать подсказку об ошибке один раз, более элегантно

В-четвертых, полная упаковка и конкретное использование

код доступенмой гитхаб

полный пакет 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файл выставляет другие модули, другие файлы нажимают功能模块划分документ

получить запрос с параметрамиimage.pngПользовательский префикс прокси для разных сервисовimage.pngобработка типов файловimage.png

V. Резюме

axiosАбсолютного стандарта для упаковки нет, и его нужно совмещать с проектом实际场景дизайну, но нет сомнений, что инкапсуляция axios-ajax очень нужна

Оригинальный адрес:GitHub.com/в следующем месяце 25/asy…

Другие статьи:Рекомендации по работе с общими модулями между несколькими проектами Vue

Колонка [Бизнес-резюме], включенная в эту статью, направлена ​​наУпреждать проблемы, возникающие в работе, и обобщать их как лучшие практики, добро пожаловать в подписку ✨