Запрос данных - очень важная часть нашей разработки. Как элегантно выполнить абстрактную обработку - непростая задача, и это также то, что часто упускается из виду. Если это не обрабатывается должным образом, дублирующиеся коды будут разбросаны повсюду, и обслуживание стоимость будет чрезвычайно высока.
Поэтому нам необходимо разобраться с аспектами, связанными с запросом данных, и иметь общий контроль над ним, чтобы разработать масштабируемое решение.
анализ случая
Ниже мы используемaxios
Эта библиотека запросов объясняет это.
Если мы выдадимPOSTзапрос, что-то вроде этого:
axios.post('/user/create', { name: 'beyondxgb' }).then((result) => {
// do something
});
Позже выяснилось, что необходимо предотвратитьCSRF
, то нам нужно в запросеheaders
плюсX-XSRF-TOKEN
, поэтому становится так:
axios.post('/user/create', { name: 'beyondxgb' }, {
headers: {
'X-XSRF-TOKEN': 'xxxxxxxx',
},
}).then((result) => {
// do something
});
В это время вы можете обнаружить, что каждый раз, когда вы инициируетеpost
Все ли запросы должны быть настроены так? Поэтому я подумал об извлечении этой части конфигурации и абстрагировании такого метода:
function post(url, data, config) {
return axios.post(url, data, {
headers: {
'X-XSRF-TOKEN': 'xxxxxxxx',
},
...config,
});
}
Поэтому нам нужноконфигурация параметровАннотация.
Когда дело доходит до тестового процесса, обнаруживается, что запрос с сервера не всегда возвращаетуспехДа что мне делать? тогдаcatch
Проработай это:
post('/user/create', { name: 'beyondxgb' }).then((result) => {
// do something
}).catch((error) => {
// deal with error
// 200
// 503
// SESSION EXPIRED
// ...
});
Я всегда чувствую, что что-то не так, когда записываю.Оказывается, столько случаев ошибок запроса.Во всем моем проекте много мест для запроса данных.Эта часть кода должна быть общей и абстрактной!
function dealWithRequestError(error) {
// deal with error
// 200
// 503
// SESSION EXPIRED
// ...
}
function post(url, data, config) {
return axios.post(url, data, {
headers: {
'X-XSRF-TOKEN': 'xxxxxxxx',
},
...config,
}).catch(dealWithRequestError);
}
Поэтому нам нужноОбработка исключенийАннотация.
Перед запуском проекта бизнес-сторона может выдвигать требования к стабильности, в это время нам необходимо отслеживать запрос и фиксировать успех и неудачу запроса интерфейса. Аналогично пишем и эту часть кода в паблик, вот так:
function post(url, data, config) {
return axios.post(url, data, {
headers: {
'X-XSRF-TOKEN': 'xxxxxxxx',
},
...config,
}).then((result) => {
// 记录成功情况
...
return result;
})
.catch((error) => {
// 记录失败情况
...
return dealWithRequestError(error);
);
}
Поэтому нам нужномониторинг запросовАннотация.
Дизайн
Облицовка простой сверхуpost
В примере запроса мы видим, что запрос данных в основном включает три аспекта.конфигурация параметров,Обработка исключенийа такжемониторинг запросов. Обработка приведенного выше примера все еще относительно грубая, и все еще требуется общая организация кода и многоуровневость.
конфигурация параметров
Во-первых, мы имеем дело с конфигурацией параметров.Приведенный выше пример только дляpost
Запрос был проанализирован, фактически, для других, таких какget
,put
Тем не менее, мы можем обрабатывать эти запросы единообразно.
request.js
import axios from 'axios';
// The http header that carries the xsrf token value { X-XSRF-TOKEN: '' }
const csrfConfig = {
'X-XSRF-TOKEN': '',
};
// Build uniform request
async function buildRequest(method, url, params, options) {
let param = {};
let config = {};
if (method === 'get') {
param = { params, ...options };
} else {
param = JSON.stringify(params);
config = {
headers: {
...csrfConfig,
},
};
config = Object.assign({}, config, options);
}
return axios[method](url, param, config);
}
export const get = (url, params = {}, options) => buildRequest('get', url, params, options);
export const post = (url, params = {}, options) => buildRequest('post', url, params, options);
В этом случае мы выставляемget
а такжеpost
метод, другие запросы аналогичны, здесь только используйтеget
а такжеpost
Например, входные параметрыадрес API,данныеа такжеРасширенная конфигурация.
Обработка исключений
На самом деле сценарий обработки исключений будет сложнее, а не простоcatch
, часто в сопровожденииБизнес-логикаа такжеВзаимодействие с пользовательским интерфейсом, исключение в основном имеет два аспекта,глобальное исключениеа такжеделовое исключение.
глобальное исключение, можно также сказать, что это общее исключение, например, сервер возвращает 503, сетевое исключение, ошибка входа в систему, отсутствие разрешения и т. д. Эти исключения предсказуемы и управляемы, если формат согласован с сервером, а исключение захватывается и отображается.
деловое исключение, относится к событиям, тесно связанным с бизнес-логикой, таким как сбой отправки, сбой проверки данных и т. д. Эти исключения часто различаются для каждого интерфейса, и ошибки необходимо отображать по отдельности, поэтому эта часть может обрабатываться неодинаково. отправить сообщение об ошибке отображенияView
слой для реализации.
С точки зрения реализации, мы не будем напрямую в методе запроса вышеcatch
, но используяaxios
который предоставилinterceptors
функция, так что обработка исключений и основной метод запроса могут быть изолированы.В конце концов, эта часть должна бытьUI
интерактивный. Давайте посмотрим, как это сделать:
error.js
import axios from 'axios';
// Add a response interceptor
axios.interceptors.response.use((response) => {
const { config, data } = response;
// 和服务端约定的 Code
const { code } = data;
switch (code) {
case 200:
return data;
case 401:
// 登录失效
break;
case 403:
// 无权限
break;
default:
break;
}
if (config.showError) {
// 接口配置指定需要个性化展示错误
return Promise.reject(data);
}
// 默认展示错误
// ... Toast error
}, (error) => {
// 通用错误
if (axios.isCancel(error)) {
// Request cancel
} else if (navigator && !navigator.onLine) {
// Network is disconnect
} else {
// Other error
}
return Promise.reject(error);
});
axios
изinterceptors
Функция на самом деле является цепным вызовом, который может делать что-то до и после запроса.Здесь мы перехватываем запрос после запроса, проверяем возвращаемые данные и ловим исключения.Общие ошибки мы передаем напрямуюUI
Взаимодействие будет отображать ошибки. Для бизнес-ошибок мы проверяем, настроен ли интерфейс для индивидуального отображения ошибок. Если есть, мы передаем обработку ошибок на страницу. Если нет, мы обрабатываем ошибку.
мониторинг запросов
Мониторинг запросов похож на обработку исключений, за исключением того, что он только записывает ситуацию и не включаетUI
Поэтому можно прописать эту часть логики прямо в обработке исключений, либо добавить перехватчик после запроса и обрабатывать его отдельно.
monitor.js
axios.interceptors.response.use((response) => {
const { status, data, config } = response;
// 根据返回的数据和接口参数配置,对请求进行埋点
}, (error) => {
// 根据返回的数据和接口参数配置,对请求进行埋点
});
Сравнение рекомендует делать это, сохраняя независимость каждого модуля в соответствии с принципом единой функции (SRP).
Ну, пока,конфигурация параметров,Обработка исключенийа такжемониторинг запросовВсе оформлено, есть три файла:
-
request.js
: Запросить конфигурацию библиотеки, доступную для внешнего мира.get
,post
метод. -
error.js
: некоторая обработка исключений запроса, которая включает соединение с внешним миром, заключается в том, должен ли интерфейс отображать ошибки по отдельности. -
monitor.js
: Запись ситуации запроса, относительно независимая часть.
При вызове на странице это может быть так:
import { get, post } from 'request.js';
get('/user/info').then((data) => {});
post('/user/update', { name: 'beyondxgb' }, { showError: true }).then((data) => {
if (data.code !== 200) {
// 展示错误
} else {
// do something
}
});
Поразмыслив над этим хорошенько, я думаю, что он не самый совершенный.API
Имя прямо упоминается на странице, которая похоронит себя, если последняяAPI
Имя было изменено, и этоAPI
Он вызывается на нескольких страницах, и стоимость обслуживания высока. У нас есть два пути, первый - положить всеAPI
Независимая конфигурация находится в файле, а страница читается Второй способ — добавить слой перед библиотекой запроса и страницей, который называетсяservice
, то есть так называемый сервисный слой, который выставляет методы интерфейса на страницу, так что странице не нужно обращать внимание на то, что из себя представляет интерфейс или как интерфейс извлекает данные, и любая модификация интерфейса в будущем нужно изменить только на сервисном уровне. Никакого эффекта.
Конечно я выбираю второй способ, примерно так:
services.js
import { get, post } from 'request.js';
// fetch random data
export async function fetchRandomData(params) {
return get('https://randomuser.me/api', params);
}
// update user info
export async function updateUserInfo(params, options) {
return post('/user/info', params, { showError: true, ...options });
}
Таким образом, страница не будет напрямую взаимодействовать с библиотекой запросов, а получит соответствующий метод от сервисного слоя.
import { fetchRandomData, updateUserInfo } from 'services.js';
fetchRandomData().then((data) => {});
updateUserInfo({ name: 'beyondxgb' }).then((data) => {
if (data.code !== 200) {
// 展示错误
} else {
// do something
}
});
Посмотрим, как выглядит окончательное решение:
расширение
Все вышеперечисленноеaxios
Возьмем в качестве примера эту библиотеку запросов.На самом деле идеи совместимы, и один и тот же метод используется для изменения библиотеки запросов. Я не знаю, заметили ли вы, что два модуля конфигурации параметров библиотеки запросов и обработки исключений независимы, что полностью используется.interceptors
особенность, которая мне тоже нравитсяaxios
Одна из причин, я думаю, что это хорошо спроектировано, аналогично подходу промежуточного программного обеспечения, прежде чем данные запроса достигнут страницы, мы можем написать перехватчики для выполнения обработки данных на данных.фильтр,обработка,чек об оплате,мониторинг исключенийЖдать.
Я думаю, что любая библиотека запросов может реализовать эту функцию, даже если библиотека запросов имеет исторический багаж, она также может покрыть его сама. Например, есть библиотека запросовabc
, который имеетrequest
метод, вы можете переопределить его следующим образом:
import abc from 'abc';
function dispatchRequest(options) {
const reqConfig = Object.assign({}, options);
return abc.request(reqConfig).then(response => ({
response,
options,
})).catch(error => (
Promise.reject({
error,
options,
})
));
}
class Request {
constructor(config) {
this.default = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
}
Request.prototype.request = function request(config = {}) {
// Add interceptors
const chain = [dispatchRequest, undefined];
let promise = Promise.resolve(options);
// Add request interceptors
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// Add response interceptors
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Более
Мы хорошо решили проблему запроса данных спереди, а с другой стороны, она также тесно связана с запросом данных, т.е.Макет данных (макет)Теперь, пока данные не готовы на стороне сервера на ранней стадии разработки проекта, мы можем сделать это только локально самостоятельноMock
данные, или у многих компаний уже есть лучшая платформа для достижения этой функции, я представлю здесь без помощи платформы, просто запустите небольшой инструмент локально для достиженияMock
данные.
Здесь я сам написал небольшой инструмент@ris/mock, просто вставьте его как промежуточное ПО вwebpack-dev-server
Просто хорошо.
webpack.config.js
const mock = require('@ris/mock');
module.exports = {
//...
devServer: {
compress: true,
port: 9000,
after: (app) => {
// Start mock data
mock(app);
},
}
};
В это время создайте его в корневом каталоге проекта.mock
папка, создайте папкуrules.js
документ,rules.js
Он настраивает правила сопоставления интерфейса, например:
module.exports = {
'GET /api/user': { name: 'beyondxgb' },
'POST /api/form/create': { success: true },
'GET /api/cases/list': (req, res) => { res.end(JSON.stringify([{ id: 1, name: 'demo' }])); },
'GET /api/user/list': 'user/list.json',
'GET /api/user/create': 'user/create.js',
};
После настройки правил при запросе интерфейса он будет перенаправлен, и переадресация может быть对象
,函数
,文件
, подробности см.Документация.
Эпилог
При разработке схемы запроса данных также подтверждается, что наш «код написания» — это «программирование», а не «программирование». является основным качеством превосходных инженеров.
Вышеуказанная схема была урегулирована вRIS, включая структуру организации кода и техническую реализацию, вы можете инициализироватьStandardПодать заявку, чтобы увидеть, предыдущая статьяRIS, новый выбор для создания приложений ReactЭто было кратко упомянуто, и каждый может испытать это на себе.