Разговор об инкапсуляции аксиом во Vue

внешний интерфейс Vue.js

axios — это HTTP-библиотека, официально рекомендованная Vue, которая представлена ​​официальным введением axios, а именно:

Axios — это HTTP-библиотека на основе обещаний, которую можно использовать в браузерах и node.js.

Будучи отличной HTTP-библиотекой, axios победил vue-resource, который когда-то поддерживался официальной командой Vue и был настоятельно рекомендован Ю Сяою, автором Vue, и стал лучшим выбором для HTTP-библиотек в проектах Vue.

Хотя axios и является отличной HTTP-библиотекой, использовать ее непосредственно в проекте не так удобно, поэтому нам необходимо ее настроить и в определенной степени инкапсулировать, чтобы уменьшить повторяющийся код и облегчить вызовы. Далее поговорим об инкапсуляции аксиом в Vue.

Начинать

На самом деле, в Интернете есть много кода об инкапсуляции axios, но большинство из них настроены в виде определения атрибута глобального объекта axios в файле ввода (main.js), подобно следующему коду:

axios.defaults.timeout = 10000

У этого решения есть два недостатка: во-первых, код инкапсуляции axios связан с входным файлом, что неудобно для последующего обслуживания, во-вторых, использование глобального метода определения атрибута объекта axios для настройки, код слишком фрагментирован.

Для проблемы 1 я использовал основную идею структуры исходного кода Vue — разделение функций на файлы для облегчения последующего обслуживания. создать отдельныйhttp.jsилиhttp.tsфайл, добавить в файл axios, упаковать и настроить его и, наконец, экспортировать и смонтировать на прототипе Vue. На этом этапе каждый раз, когда вы изменяете конфигурацию axios, вам нужно только изменить соответствующий файл, и это не повлияет на несвязанные функции.

Для проблемы 2 мы используем официальную рекомендацию axios для настройки и инкапсуляции путем создания экземпляра axios с помощью элементов конфигурации.

код показывает, как показано ниже:

// http.js
import axios from 'axios'
// 创建 axios 实例
const service = axios.create({
  // 配置项
})

Установите baseURL в соответствии со средой

Свойство baseURL — это префикс адреса запроса, который будет автоматически добавляться к URL-адресу, если только URL-адрес не является абсолютным адресом. В обычных условиях в среде разработки и рабочем режиме используются разные базовые URL-адреса, поэтому нам нужно переключать разные базовые URL-адреса в соответствии с разными средами.

В режиме разработки из-за существования DevServer адрес запроса должен быть переписан в соответствии с фиксированным префиксом URL. Следовательно, в среде разработки устанавливают BaseURL на фиксированное значение, например:/apis.

В производственном режиме могут быть установлены разные базовые URL-адреса в зависимости от префикса запроса модуля Java.

Конкретный код выглядит следующим образом:

// 根据 process.env.NODE_ENV 区分状态,切换不同的 baseURL
const service = axios.create({
	baseURL: process.env.NODE_ENV === 'production' ? `/java` : '/apis',
})

Установите заголовки запроса единообразно

Позвольте мне поговорить с вами о вопросе, что такое инкапсуляция? На мой взгляд, инкапсуляция охватывает больше сценариев вызова с меньшим количеством вызывающего кода.

Поскольку в большинстве случаев заголовки запросов являются фиксированными, и только в некоторых случаях требуются некоторые специальные заголовки запросов, поэтому здесь я принял решение использовать универсальный заголовок запроса в качестве базовой конфигурации. Когда требуется специальный заголовок запроса, специальный заголовок запроса передается в качестве параметра, переопределяя базовую конфигурацию.

код показывает, как показано ниже:

const service = axios.create({
    ...
	headers: {
        get: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
          // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
        },
        post: {
          'Content-Type': 'application/json;charset=utf-8'
          // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
        }
  },
})

Междоменная обработка, тайм-аут, обработка кода ответа

В axios он предоставляет атрибут разрешать ли междоменный - withCredentials, и атрибут для настройки времени тайм-аута - тайм-аут, Через эти два атрибута можно легко справиться с проблемой междоменного и тайм-аута.

Далее поговорим об обработке кода ответа:

axios предоставляет свойство validateStatus, чтобы определить, разрешить или отклонить обещание для данного кода состояния ответа HTTP. Поэтому при обычных настройках мы установим запрос с кодом состояния 2 серии или 304 в состояние разрешения, а остальные в состояние отклонения. В результате мы можем использовать catch в бизнес-коде для унифицированного захвата запросов, которые реагируют на ошибки, чтобы обрабатывать их единообразно.

Однако, поскольку я использую async-await в коде, и, как мы все знаем, способ, которым async-await перехватывает catch, чрезвычайно проблематичен, поэтому здесь я решил установить для всех ответов состояние разрешения и обрабатывать их единообразно в then .

Эта часть кода выглядит следующим образом:

const service = axios.create({
	// 跨域请求时是否需要使用凭证
	withCredentials: true,
    // 请求 30s 超时
	timeout: 30000,
	validateStatus: function () {
		// 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常
		return true
	},
})

Запрос, обработка ответа

Axios без использования, в ответ на каждый запрос или приема, запрос или ответ должен быть сериализован.

И в аксиомахtransformRequestПозволяет модифицировать данные запроса перед отправкой запроса на сервер;transformResponseПозволяет изменять данные ответа перед передачей в then/catch.

С помощью этих двух хуков можно сэкономить много повторяющегося кода сериализации.

код показывает, как показано ниже:

const service = axios.create({
    // 在向服务器发送请求前,序列化请求数据
    transformRequest: [function (data) {
        data = JSON.stringify(data)
        return data
    }],
    // 在传递给 then/catch 前,修改响应数据
    transformResponse: [function (data) {
        if (typeof data === 'string' && data.startsWith('{')) {
            data = JSON.parse(data)
        }
        return data
    }]
})

перехватчик

Блокировщик в запрос и перехватчики перехватчика ответа перехватывает, затем перехватываются до того, как они будут обработаны или уволить запрос или ответ.

Как упоминалось ранее, из-за проблемы, связанной с трудностью обработки catch в асинхронном ожидании, состояние ошибки также обрабатывается как состояние разрешения. Но тут возникает проблема, в случае ошибки в запросе или ответе результат не имеет поля msg (сообщения), определенного в протоколе данных. Поэтому нам нужно вручную сгенерировать возвращаемые данные, которые соответствуют формату возврата при возникновении ошибки.

Поскольку в бизнесе нет необходимости выполнять дополнительную обработку в перехватчике запросов, состояние разрешения перехватчика запросов нужно возвращать только напрямую.

Код перехватчика запросов выглядит следующим образом:

// 请求拦截器
service.interceptors.request.use((config) => {
	return config
}, (error) => {
	// 错误抛到业务代码
    error.data = {}
    error.data.msg = '服务器异常,请联系管理员!'
    return Promise.resolve(error)
})

Давайте еще раз поговорим о перехватчике ответа.Это та же проблема, что и раньше.Помимо ошибки запроса или ответа, есть еще одна ситуация, которая приведет к несоответствию возвращаемого тела сообщения спецификации протокола, то есть когда код состояния это не 2 серия или 304. На этом этапе нам все еще нужно выполнить ту же обработку — вручную сгенерировать возвращаемые данные, которые соответствуют возвращаемому формату. Однако есть одно отличие: нам также необходимо генерировать разную оперативную информацию в соответствии с разными кодами состояния, чтобы упростить обработку проблем после выхода в онлайн.

Код перехватчика ответа выглядит следующим образом:

// 根据不同的状态码,生成不同的提示信息
const showStatus = (status) => {
    let message = ''
    // 这一坨代码可以使用策略模式进行优化
    switch (status) {
        case 400:
            message = '请求错误(400)'
            break
        case 401:
            message = '未授权,请重新登录(401)'
            break
        case 403:
            message = '拒绝访问(403)'
            break
        case 404:
            message = '请求出错(404)'
            break
        case 408:
            message = '请求超时(408)'
            break
        case 500:
            message = '服务器错误(500)'
            break
        case 501:
            message = '服务未实现(501)'
            break
        case 502:
            message = '网络错误(502)'
            break
        case 503:
            message = '服务不可用(503)'
            break
        case 504:
            message = '网络超时(504)'
            break
        case 505:
            message = 'HTTP版本不受支持(505)'
            break
        default:
            message = `连接出错(${status})!`
    }
    return `${message},请检查网络或联系管理员!`
}

// 响应拦截器
service.interceptors.response.use((response) => {
    const status = response.status
    let msg = ''
    if (status < 200 || status >= 300) {
        // 处理http错误,抛到业务代码
        msg = showStatus(status)
        if (typeof response.data === 'string') {
            response.data = { msg }
        } else {
            response.data.msg = msg
        }
    }
    return response
}, (error) => {
    // 错误抛到业务代码
    error.data = {}
    error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
    return Promise.resolve(error)
})

подсказки 1: Дружеское напоминание, приведенный выше код переключателя может быть оптимизирован с использованием режима стратегии~

подсказки 2: Если есть какие-то требования, связанные с бизнесом, вы можете добавить их в перехватчик, например: загрузка, аутентификация и т. д. ~

Поддержка TypeScript

Потому что некоторое время назад я пропихнул в отдел TypeScript, чтобы удовлетворить свое обсессивно-компульсивное расстройство, я переписал все файлы js в файлы ts. Поскольку сам axios имеет поддержку TypeScript, вам нужно только импортировать соответствующий тип и назначить его.

полный код

// http.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'

const showStatus = (status: number) => {
  let message = ''
  switch (status) {
    case 400:
      message = '请求错误(400)'
      break
    case 401:
      message = '未授权,请重新登录(401)'
      break
    case 403:
      message = '拒绝访问(403)'
      break
    case 404:
      message = '请求出错(404)'
      break
    case 408:
      message = '请求超时(408)'
      break
    case 500:
      message = '服务器错误(500)'
      break
    case 501:
      message = '服务未实现(501)'
      break
    case 502:
      message = '网络错误(502)'
      break
    case 503:
      message = '服务不可用(503)'
      break
    case 504:
      message = '网络超时(504)'
      break
    case 505:
      message = 'HTTP版本不受支持(505)'
      break
    default:
      message = `连接出错(${status})!`
  }
  return `${message},请检查网络或联系管理员!`
}

const service = axios.create({
  // 联调
  baseURL: process.env.NODE_ENV === 'production' ? `/` : '/apis',
  headers: {
    get: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
    },
    post: {
      'Content-Type': 'application/json;charset=utf-8'
    }
  },
  // 是否跨站点访问控制请求
  withCredentials: true,
  timeout: 30000,
  transformRequest: [(data) => {
    data = JSON.stringify(data)
    return data
  }],
  validateStatus () {
    // 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常
    return true
  },
  transformResponse: [(data) => {
    if (typeof data === 'string' && data.startsWith('{')) {
      data = JSON.parse(data)
    }
    return data
  }]
})

// 请求拦截器
service.interceptors.request.use((config: AxiosRequestConfig) => {
    return config
}, (error) => {
    // 错误抛到业务代码
    error.data = {}
    error.data.msg = '服务器异常,请联系管理员!'
    return Promise.resolve(error)
})

// 响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {
    const status = response.status
    let msg = ''
    if (status < 200 || status >= 300) {
        // 处理http错误,抛到业务代码
        msg = showStatus(status)
        if (typeof response.data === 'string') {
            response.data = {msg}
        } else {
            response.data.msg = msg
        }
    }
    return response
}, (error) => {
    // 错误抛到业务代码
    error.data = {}
    error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
    return Promise.resolve(error)
})

export default service