Микро интерфейс от размышлений до практики онлайн (1)

внешний интерфейс
Микро интерфейс от размышлений до практики онлайн (1)

Эта статья от товарищей по команде@OnWorkОбмен знаниями, я надеюсь поделиться и обсудить с вами.

Стремитесь накопить кремния на тысячу миль и имейте смелость исследовать красоту жизни.

предисловие

С наложением трафика проект перестал быть маленьким и красивым, который постепенно раздувался. Особенно, когда несколько линий трафика совместно поддерживают гигантское приложение, влияние, вызванное цитированием друг друга, нелегко разделить, и нелегко различить графики, а неудобство разработки и отладки также увеличит рабочую нагрузку. одноклассник по тесту.

Когда был популяризирован внутренний «микросервис», концепция «микро» также повлияла на внешний интерфейс, и появился «микро интерфейс».

В этой статье кратко описывается наш опыт работы в реальных проектах.

Просто делайте то, что говорите, засучите рукава, возьмите клавиатуру и молча введите три символа «микро интерфейс» в хроме.

Появляется в результатах поиска: iframe / single-spa / qiankun

выбор плана

iframe

существуетMDNопределяется в: элементе iframe HTML, представляющем вложенныйbrowsing context. Он может встроить другую HTML-страницу в текущую страницу. Каждый встроенный контекст просмотра имеет свою историю сеансов и дерево DOM. Контекст просмотра, содержащий встроенное содержимое, называется родительским контекстом просмотра. Контекст просмотра верхнего уровня (без родителя) обычно представляет собой окно браузера, представленное объектом Window.

Проще говоря, iframe — это встроенный фрейм, используемый для встраивания другого документа в текущий HTML-документ. А у iframe есть атрибут sandbox, то есть атрибут песочницы.

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

--Энциклопедия Baidu

«Песочница атрибута песочницы iframe не поддерживается в Internet Explorer 9 и более ранних версиях».

Преимущество

  • Встроенная веб-страница может отображаться без изменений;
  • Упомянутая выше песочница, полная изоляция css/js;
  • Загружать скрипты параллельно

недостаток

  • Избыточные запросы внешней цепочки css/js;
  • Если в проекте используется несколько iframe, на оси x и оси y будет много полос прокрутки;
  • Недружественный для поисковых систем (iframe обычно имеет только ссылки без отображения innerText или innerHtml);
  • Некоторые мобильные устройства не могут отображать фрейм iframe, а совместимость устройств плохая;
  • Блокировать событие загрузки главной страницы

Если разделить наше огромное приложение, то там 20 меню первого уровня, и надо встроить 20 iframe, что является фатальной проблемой для скорости загрузки. Так есть ли лучшее решение?

single-spa

single-spa — это фреймворк микроинтерфейса JavaScript, который объединяет несколько одностраничных приложений в монолитное приложение. Использование single-spa для проектирования архитектуры внешнего интерфейса может принести много преимуществ, таких как:

  • Agile: независимая разработка, более эффективное развертывание;
  • Меньшие единицы: более быстрое тестирование без необходимости обновлять все приложение для каждого обновления;
  • Снижение риска: снижает риск ошибок и проблем с регрессией, а также сокращает цикл устранения неполадок;
  • CI/CD: более эффективная непрерывная интеграция, непрерывное развертывание и непрерывная поставка

Посмотреть >>>Пример официального сайта

single-spa

Видно, что когда мы переключаем меню, div на панели Elements справа меняется, отображая содержимое соответствующего меню.

Войдите НАЧАТЬ, следуйтеcreate-single-spaПройдите процесс.

single-spa

В процессе сборки я обнаружил, что мне все еще нужно самостоятельно изучить различные элементы конфигурации.Есть ли лучшее и более лаконичное решение?

qiankun

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

Посмотреть >>>Пример официального сайта qiankun

Текущие основные микроинтерфейсные фреймворки делятся на два лагеря: single-spa/qiankun, и qiankun основан на single-spa и имеет следующие особенности:

  • Независимо от стека технологий, можно использовать/доступ к любому приложению стека технологий, будь то React/Vue/Angular/JQuery или другие фреймворки;
  • Метод доступа к записи HTML, что позволяет получить доступ к микроприложениям так же просто, как с помощью iframe;
  • изоляция стиля, чтобы стили между микроприложениями не мешали друг другу;
  • JS-песочница, чтобы гарантировать, что «глобальные переменные/события» между микроприложениями не конфликтуют;
  • Предварительная загрузка ресурсов, предварительно загружать неоткрытые ресурсы микроприложений во время простоя браузера для ускорения открытия микроприложений;
  • уми плагин,при условии@umijs/plugin-qiankunДля приложений umi, чтобы переключиться на систему микроинтерфейсной архитектуры одним щелчком мыши

Таким образом, iframe — это самое простое и прямое решение, но его пропускают из-за его ограничений. При выборе qiankun / single-spa qiankun основан на single-spa и расширяет его.Он поддерживается большой муравьиной фабрикой, имеет лучшую экологию, более сильное сообщество и очень удобен для устранения проблем. относительно прост, и элементы конфигурации аналогичны, гораздо проще, чем single-spa.

Google почти закончил, а остальное вот-вот начнется, начните думать, как это сделать?

Прежде всего, мы должны посмотреть на реальную ситуацию с проектом.Если наш проект небольшой и имеет всего несколько страниц с несколькими функциональными точками, не рекомендуется обращаться к интерфейсу pit micro. Если ваш проект в настоящее время очень большой, в нем уже много функциональных точек, много страниц, много сложных взаимодействий, или если вы планируете масштабный проект, то рекомендуется попробовать микроинтерфейсный фреймворк qiankun.

Предыстория проекта

Потребность в болевых точках

Вообще говоря, если проекту необходимо использовать архитектуру микроинтерфейса, то будет много бизнес-модулей, на примере системы электронной коммерции: модуль входа в систему, модуль личного центра, модуль заказа, модуль корзины, модуль товаров, модуль продаж, модуль инвентаризации, модуль логистики, системный модуль, модуль отчетов и т. д. Существуют также различные библиотеки классов lodash, qs, bigNum, dayjs, echarts, stompjs и т. д., а также vue, поддерживающий пользовательский интерфейс, утилиты...

Вспомните, что мы использовали vue-cli для создания проекта, а затем переходили кyarn serve / yarn buildНасколько это быстро, но с доступом к нашим модулям, работой проекта, становится ли упаковка все медленнее и медленнее, а развертывание становится все более и более громоздким, у нас такое же чувство, как у партнеров.

Итак, после запуска примера с qiankun, давайте подумаем, как подключиться к qiankun и какие работы по инфраструктуре необходимо выполнить?

Совокупный анализ

Наш проект создан на базе vue-cli, а версия Vue 2.6.x, учитывая разные затраты, мы не распространялись на другие front-end фреймворки. Проект включает в себя библиотеку утилит, аксиомы сетевой связи, vuex для хранения состояния, левую строку меню, верхнюю панель навигации, интернационализацию, eslint, внешние ресурсы и т. д. Видя это, думаем ли мы, что это очень распространено в приложении vue, и почти не о чем беспокоиться, просто возьмите клавиатуру и переместите ее 😏, «Программирование какое-то время круто, крематорий BUG» 😂.

Далее мы будем шаг за шагом углубляться в практику микро-интерфейса (копания).

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

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

разделенный пакет Функция
@xxx/xxx-core Используется для хранения процесса инициализации субприложения и некоторых важных общедоступных методов.
@xxx/xxx-ui Универсальная библиотека компонентов пользовательского интерфейса, основанная на вторичной инкапсуляции компонентов element-ui.
@xxx/xxx-business Библиотека бизнес-компонентов используется для хранения компонентов, которые больше связаны с бизнесом. В отличие от компонентов пользовательского интерфейса, сильно связанная бизнес-логика
@xxx/xxx-util Библиотека функций инструмента, аналогичная библиотеке функций lodash, в основном остальные библиотеки будут представлять эту библиотеку утилит.
@xxx/xxx-http инкапсуляция запроса axios, унифицированное управление перехватом, ответным запросом и обработкой ошибок
@xxx/xxx-config Связанные с конфигурацией приложения
@xxx/xxx-mixins связанные с миксином

Преимущество разделения набора инструментов с помощью npm позволяет более точно контролировать детализацию. Как самая основная библиотека, на библиотеку util/config могут ссылаться другие библиотеки для формирования зависимостей. Что ж, теперь есть сразу 6 библиотек, и количество библиотек может увеличиваться по мере роста потребностей бизнеса.Не слишком ли много для поддержки такого количества npm-пакетов? Как удобно обрабатывать зависимости между библиотеками, обновлениями номеров версий и локальной разработкой и отладкой? Это наш следующий вопрос.

Управление пакетами npm

управление пакетами npm, рекомендуетсяlernaэтот инструмент.

установить лерна

Поскольку вам нужно будет часто использовать команду lerna, чтобы работать позже, вы не будете использовать команду npx для ее создания, а для ее глобальной установки.

npm install -g lerna

проект инициализации lerna

После установки создайте новый каталог и выполните

lerna init --independent

вывод после завершения

➜  xxx lerna init --independent
lerna notice cli v3.22.1
lerna info Initializing Git repository
lerna info Creating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files

independentУказывает на использование отдельного номера версии, а не единого номера версии (относится к публикации на npmjs.com). у нас есть папкаpackages,Одинlerna.jsonконфигурационный файл, аpackage.jsonдокумент.

Настроить lerna.json

{
  "npmClient": "yarn",             // 用 yarn 代替 npm
  "useWorkspaces": true,           // 启用工作区
  "packages": [
    "packages/@xxx/*"              // 库路径
  ],
  "version": "independent",
  "command": {
    "publish": {
      "ignoreChanges": [           // 忽略改动的文件
        "**/node_modules/**",
        //...
      ],
      "message": "chore: publish"
    }
  }
}

настроить package.json

{
  "name": "xxx",                  // 最外层的这个name可以随意取,不过还是建议语义化一点
  "private": true,                // 必须是true 才能启用 workspaces
  "workspaces": [
    "packages/@xxx/*"             // 库路径和 lerna.json 保持一致
  ],
  "scripts": {
    "cz": "cz",                   // changelog 的命令,和普通的开发业务逻辑不一样 我们需要详细的记录改动点
    "build-core": "lerna run build --scope @xxx/xxx-core", // 核心库打包命令
    "build-http": "lerna run build --scope @xxx/xxx-http", // 网络通讯库打包命令
    "build-util": "lerna run build --scope @xxx/xxx-util", // 工具库打包命令
    "build": "concurrently \"npm run build-core\" \"npm run build-http\" \"npm run build-util\"" // 一起执行命令
  },
  "keywords": [                   // 关键词
    "micro",
    "util",
    //...
  ],
  "license": "ISC",
  "devDependencies": {
    "@commitlint/cli": "^11.0.0",
    "@commitlint/config-conventional": "^11.0.0",
    "commitizen": "^4.2.3",
    "commitlint-config-cz": "^0.13.2",
    "concurrently": "^5.3.0",
    "cz-customizable": "^6.3.0",
    "husky": "^4.3.8",
    "inquirer": "^7.3.3",
    "lerna-changelog": "^1.0.1",
    "shelljs": "^0.8.4"
  },
  "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    },
    "cz-customizable": {
      "config": ".cz-config.js"
    }
  },
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "version": "0.0.1"
}

Структура каталогов пакетов npm

npm 包目录结构

Разрабатывать npm-пакеты

  1. Войдите в каталог xxx-util;
  2. бегатьnpm init -y;
  3. Измените package.json, измените имя
- "name": "xxx-util",
+ "name": "@xxx/xxx-util", // 发包之后人家安装就是 import xxx from '@xxx/xxx-util' 这个样子

Вы можете обратиться к этой статьеКак написать свою библиотеку и опубликовать в npm? 》" писать и публиковать свои пакеты, потому что тут главное, что lerna не будет кратко описывать "выпуск npm пакетов с 0 по 1 до npm релиза".

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

Итак, что мне делать, если я столкнулся с местом, которое нужно отладить? мы можем использоватьnpm linkОтладка по ссылке. Конкретная операция: запустить в каталоге packages/@xxx/xxx-utilsudo npm linkэтой команде требуется разрешение, поэтому добавьтеsudo.

➜  xxx-util (develop) ✗ sudo npm link
/usr/local/lib/node_modules/@xxx/xxx-util -> /Users/xxx/Desktop/micro/xxx/packages/@xxx/xxx-util

При входе в ваш проект, то есть вам необходимоimport xxx from '@xxx/xxx-util'проект

➜  xxx-web-main (develop) ✗ npm link @xxx/xxx-util
/Users/xxx/Desktop/micro/xxx-web/xxx-web-main/node_modules/@xxx/xxx-util -> /usr/local/lib/node_modules/@xxx/xxx-util -> /Users/xxx/Desktop/micro/xxx/packages/@xxx/xxx-util

Когда мы его используем, нам не нужно добавлятьsudoДа, тогда еще раз проверьте структуру каталогов node_modules/@xxx/xxx-util.Если она совпадает со структурой каталогов, которую вы разрабатываете, это означает, что ссылка успешна, и позже будет намного проще отлаживать код. .

lerna публикует пакет npm

lerna publish patch --canaryПакет для выпуска альфа-версии, подобный следующему:

- @xxx/xxx-util => 0.0.30-alpha.34+d78bade

Если вы хотите отправить официальную версию, удалите ее--canaryВот и все, отправляйте другие пакеты по очереди, используя те же действия.

Теперь мы можем поместить некоторые классы инструментов, которые нужны нашему микроинтерфейсу, в @xxx/xxx-util, такие как: связанные с браузером, связанные с типом, связанные с проверкой, связанные с числами, связанные с датами и другие связанные с проектом функции инструментов. , все, что доступно в этом пакете Inside, поддерживать, как небольшой проект.

Единое сетевое сообщение

Как упоминалось выше, нам нужно разделить некоторые общие части на пакеты npm в соответствии с агрегацией функций, что удобно для обслуживания и справки. В качестве примера мы возьмем обычно используемую инкапсуляцию запросов axios.

Инкапсулируйте его в @xxx/xxx-http, затем введите

➜  xxx (develop) ✔ cd packages/@xxx/xxx-http
➜  xxx (develop) mkdir src // 创建 src 文件夹用于存放源码
➜  xxx (develop) mkdir lib // 创建 lib 文件夹用于存放打包之后的文件 打包的东西有很多 我选择的是 "build": "./node_modules/.bin/babel src --out-dir lib --extensions '.ts' --extensions '.js'" 进行打包
➜  xxx (develop) cd src
➜  src (develop) mkdir config // 创建配置文件夹
➜  src (develop) mkdir core // 封装的核心逻辑
➜  src (develop) mkdir utils // 存放导出的文件
➜  src (develop) pwd
/Users/xxx/Desktop/micro/xxx/packages/@xxx/xxx-http/src
➜  config (develop) cd config // 进入配置文件夹
➜  config (develop) touch HttpCode.ts // 创建状态码映射文件
➜  config (develop) touch settings.js // 创建配置

Сохраните конфигурацию по умолчанию и некоторые типы в HttpCode.ts:

export enum HttpCode {
  "e400" = 400,
  "e401" = 401,
  "e403" = 403,
  "e405" = 405,
  "e408" = 408,
  "e500" = 500,
  "e501" = 501,
  "e502" = 502,
  "e503" = 503,
  "e504" = 504,
  "e505" = 505,
}
export class StatusCode {
  static 400 = '请求无效';
  static 401 = '由于长时间未操作,登录已超时,请重新登录';
  static 403 = '拒绝访问';
  static 405 = '未授权';
  static 408 = '请求超时';
  static 500 = '服务器内部错误';
  static 501 = '服务未实现';
  static 502 = '网关错误';
  static 503 = '服务不可用';
  static 504 = '网关超时';
  static 505 = 'HTTP版本不受支持';
}

Сохраните некоторую конфигурацию в settings.js:

export const _httpOptions = {
  baseURL: '',   // api 的 base_url
  retry: 3,
  retryDelay: 1000,
  withCredentials: true,
  headers: {
    "Content-Type": "application/json;charset=UTF-8"
  },
  timeout: 5000, // request timeout
  method: 'post' // 默认请求方法
}

export const _httpType = {
  DELETE: 'delete',
  GET: 'get',
  POST: 'post',
  PUT: 'put',
  PATCH: 'patch'
}

Перейдите в каталог утилит:

➜  config (develop) ✔ cd .. && cd utils
➜  utils (develop) ✔
➜  utils (develop) ✔ touch axios.js
➜  utils (develop) ✔ touch http.js

Напишите в axios.js:

import axios from "axios";
import { isObject, isArray } from "@xxx/xxx-util"
import { _httpOptions } from "../config/settings"; // 导入配置项
import { HttpCode, StatusCode } from "../config/HttpCode";
import NProgress from 'nprogress';

// 配置请求拦截器
const _configRequestInterceptor = (instance, reqInterceptSuccess) => {
  instance.interceptors.request.use(config => {
    if (reqInterceptSuccess) {
      const _config = reqInterceptSuccess(config);
      if (!isObject(_config)) {
        throw Error('reqInterceptSuccess必须返回一个config对象.')
      }
      return _config;
    }
    return config;
  }, error => {
    return Promise.reject(error);
  })
}

/**
 * @method 配置响应拦截器
 * @param {Object} instance axios实例
 * @param {Function} respInterceptSuccess 响应拦截器成功回调
 * @param {Function} respInterceptError 响应拦截器失败回调
 * @param {Number} retry 请求失败自动重试次数 默认2
 * @param {Number} retryDelay 请求失败自动重试时间间隔 默认1000ms
 */
const _configResponseInterceptor = (instance, respInterceptSuccess, respInterceptError, retry, retryDelay) => {
  // 自动重试机制
  instance.defaults.retry = retry;
  instance.defaults.retryDelay = retryDelay;
  // 响应拦截器
  instance.interceptors.response.use(
    res => {
      if (respInterceptSuccess) {
        const _res = respInterceptSuccess(res);
        return _res;
      }
      return res;
    },
    err => {
      NProgress.done();
      let config = err.config;
      let errres = err.response;
      let err_type = errres?.status ?? 0;
      // 处理状态码
      err.message = ErrorCodeHandler(err_type);
      // 收集错误信息
      if (!err.message) {
        switch (err_type) {
          case 404:
            err.message = `请求地址出错: ${errres?.config?.url ?? '/'}`;
            break;
          default:
            err.message = "未知异常,请重试";
            break;
        }
      }
      if (!config || !config.retry) return Promise.reject(err);
      config.__retryCount = config.__retryCount || 0;
      if (config.__retryCount >= config.retry) {
        // 自定义重复请求后失败的回调
        if (respInterceptError) {
          const _res = respInterceptError(err);
          if (!isObject(err?.config)) {
            throw Error('respInterceptError')
          }
          return Promise.reject(_res);
        }
        return Promise.reject(err);
      }
      config.__retryCount += 1;
      let backoff = new Promise((resolve) => {
        setTimeout(() => {
          resolve();
        }, config.retryDelay || 1);
      });
      return backoff.then(() => {
        if (config.baseURL) {
          config.url = config.url.replace(config.baseURL, "");
        }
        return instance(config);
      });
    }
  );
}

/**
 * @param {Number} error 错误码
 */
const ErrorCodeHandler = (error) => {
  return StatusCode[HttpCode[`e${error}`]]
}

export default class Axios {
  constructor() {
    this.httpInstance = null;
  }

  /**
   * @method 创建axios实例
   * @param {Object} param 配置项
   * @description retry:Number 请求失败自动重连次数 默认2
   * @description retryDelay:Number 请求失败自动重连时间间隔 默认1000ms
   * @description timeout:Number 请求超时时间 默认5000
   * @description baseURL:String 请求地址前缀 默认''
   * @description expand:Object 其他需要扩展的配置项 other
   * @param {Function} reqInterceptSuccess 请求拦截器成功回调,必须返回一个config对象
   * @param {Function} respInterceptSuccess 响应拦截器成功回调,必须返回一个response对象
   * @param {Function} respInterceptError 响应拦截器失败回调,必须返回一个response对象
   * @returns 返回创建后的axios实例
   */
  static create({
    retry = _httpOptions.retry,
    retryDelay = _httpOptions.retryDelay,
    withCredentials = _httpOptions.withCredentials,
    headers = _httpOptions.headers,
    timeout = _httpOptions.timeout,
    baseURL = _httpOptions.baseURL,
    ...expand
  } = {}, reqInterceptSuccess, respInterceptSuccess, respInterceptError) {
    // 整理配置项
    const _options = {
      baseURL,
      withCredentials,
      headers,
      timeout,
      ...expand
    }
    // 创建axios实例
    const _http = axios.create(_options);
    // 注册请求拦截器
    _configRequestInterceptor(_http, reqInterceptSuccess);
    // 注册响应拦截器
    _configResponseInterceptor(_http, respInterceptSuccess, respInterceptError, retry, retryDelay);
    this.httpInstance = _http;
    return _http;
  }

  /**
   * 通过向 axios 传递相关配置来创建单个请求
   * @param {Object} param
   * @description url:String 请求地址
   * @description method:String 请求方法类型 默认post
   * @description params:Object 即将与请求一起发送的 URL 参数
   * @description data:Object 作为请求主体被发送的数据
   * @description instance:Object 外部传入的axios实例,默认使用内部创建,无特殊需求不得在外部创建多余实例
   * @description expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  static axios({
    url,
    method = _httpOptions.method,
    params,
    data,
    instance,
    ...expand
  } = {}) {
    // 废弃 返回一个新的promise,注意:此promise将把http错误和与create axios时
    // 整理请求参数
    const _options = {
      url,
      method,
      params,
      data,
      ...expand
    }
    // 处理请求并直接返回_http()
    const _http = instance ? instance() : this.httpInstance;
    return _http(_options);
  }

  /**
   * 执行多个请求
   * @param {Array} list axios Promise 对象
   */
  static all(list) {
    if (!isArray(list)) {
      throw Error('必须传入一个数组!');
    }
    return this.httpInstance.all(list)
  }
}

Напишите в http.js:

import Axios from './axios' // 导入Axios类
import { _httpType } from "../config/settings" // 导入配置项

export default class Http {
  /**
   * @param {Object} axios 外部axios实例 无特殊情况不要使用此参数; 如果传入则表示使用自定义axios实例,后续参数将不会产生作用
   * @param {Object} axiosOptions Axios.create
   * @description retry:Number 请求失败自动重连次数 默认2
   * @description retryDelay:Number 请求失败自动重连时间间隔 默认1000ms
   * @description withCredentials:Boolean 开启请求跨域 默认true
   * @description headers:Object 请求头配置 默认"Content-Type": "application/json;charset=UTF-8"
   * @description timeout:Number 请求超时时间 默认5000
   * @description baseURL:String 请求地址前缀 默认''
   * @description expand:Object 其他需要扩展的配置项
   * @param {Function} reqInterceptSuccess 请求拦截器成功回调
   * @param {Function} respInterceptSuccess 响应拦截器成功回调
   * @param {Function} respInterceptError 响应拦截器失败回调
   */
  constructor({ axios, axiosOptions, reqInterceptSuccess, respInterceptSuccess, respInterceptError } = {}) {
    this.__http__ = axios || Axios.create(axiosOptions, reqInterceptSuccess, respInterceptSuccess, respInterceptError);
  }

  /**
   * get方法请求
   * @param url:String 请求地址
   * @param params:Object 即将与请求一起发送的 URL 参数
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  get(url, params, expand) {
    return Axios.axios({ url, params, ...expand, method: _httpType.GET });
  }

  /**
   * post方法请求
   * @param url:String 请求地址
   * @param data:Object 作为请求主体被发送的数据
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  post(url, data, expand) {
    return Axios.axios({ url, data, ...expand, method: _httpType.POST })
  }

  /**
   * post方法请求,以url形式传参
   * @param url:String 请求地址
   * @param params:Object 即将与请求一起发送的 URL 参数
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  postQuery(url, params, expand) {
    return Axios.axios({ url, params, ...expand, method: _httpType.POST })
  }

  /**
   * 执行多个并发请求
   * @param {Array} list axios Promise 对象
   */
  all(list) {
    return Axios.all(list)
  }

  /**
   * delete方法请求
   * @param url:String 请求地址
   * @param params:Object 即将与请求一起发送的 URL 参数
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  delete(url, params, expand) {
    return Axios.axios({ url, params, ...expand, method: _httpType.DELETE })
  }

  /**
   * delete方法请求,以url形式传参
   * @param url:String 请求地址
   * @param data:Object 作为请求主体被发送的数据
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  deletePayload(url, data, expand) {
    return Axios.axios({ url, data, ...expand, method: _httpType.DELETE })
  }

  /**
   * put方法请求
   * @param url:String 请求地址
   * @param data:Object 作为请求主体被发送的数据
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  put(url, data, expand) {
    return Axios.axios({ url, data, ...expand, method: _httpType.PUT })
  }

  /**
   * put方法请求,以url形式传参
   * @param url:String 请求地址
   * @param params:Object 即将与请求一起发送的 URL 参数
   * @param expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  putQuery(url, params, expand) {
    return Axios.axios({ url, params, ...expand, method: _httpType.PUT })
  }

  /**
   * patch方法请求
   * @param {Object} options
   * @description url:String 请求地址
   * @description data:Object 作为请求主体被发送的数据
   * @description expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */
  patch(url, data, expand) {
    return Axios.axios({ url, data, ...expand, method: _httpType.PATCH })
  }

   /**
   * 通用请求方法
   * @param {Object} options
   * @description url:String 请求地址
   * @description params:Object 即将与请求一起发送的 URL 参数
   * @description data:Object 作为请求主体被发送的数据
   * @description instance:Object 外部传入的axios实例,默认使用内部创建,无特殊需求不得在外部创建多余实例
   * @description expand:Object 扩展对象,其他不常用的axios(options)配置项放在expand字段传入,key值和axios文档一致
   */

  request(options) {
    return Axios.axios(options)
  }
}

Введите ядро:

➜  config (develop) ✔ cd .. && cd core
➜  core (develop) ✔
➜  core (develop) ✔ touch service.js

Напишите в service.js:

import axios from 'axios';
import Http from '../utils/http';
import { LocalStorage, logout } from '@xxx/xxx-util';
import { Message, } from 'element-ui';
import NProgress from 'nprogress';

// 用于存储目前状态为pending的请求标识信息
const pendingRequest = [];
// VUE_APP_BASE_API_GW 参考 vuecli 文档中模式和环境变量一节
const { VUE_APP_BASE_API_GW, } = process.env;

export const NEED_MANUALLY_HANDLE_CODE_OBJ = { // 白名单
  SHOW_LOG_LINK: 'B18027',
  LOGISTICS_WAREHOUSE: 'B05017',
  LOGISTICS_PICKUPING: 'A05093',
  LOGISTICS_SOME_PICKUPING: 'A05028',
};

// NProgress 配置
NProgress.configure({ showSpinner: false, });

// 配置项
const options = {
  axiosOptions: { baseURL: VUE_APP_BASE_API_GW, },
  reqInterceptSuccess: config => {
    // 开启 progress bar
    NProgress.start();
    const token = LocalStorage.getToken();
    // 头部加入语言参数
    config.headers['x-ca-language'] = LocalStorage.getLanguage() === 'en' ? 'en_US' : 'zh_CN';
    // 加入请求的唯一ID
    config.headers['x-ca-reqid'] = Math.random() + new Date().getTime();
    // 加入请求的时间戳
    config.headers['x-ca-reqtime'] = new Date().getTime();
    if (token) {
      //让每个请求携带token--['Authorization']为自定义key 请根据实际情况自行修改
      config.headers['Authorization'] = 'bearer ' + token;
    }
    // 如果一个项目里有多个不同baseURL的请求,可以改成`${config.method} ${config.baseURL}${config.url}`
    if (config.cancelToken !== false) {
      const requestMark = `${config.method}-${config.url}`;
      // 找当前请求的标识是否存在pendingRequest中,即是否重复请求了
      const markIndex = pendingRequest.findIndex(item => {
          return item.name === requestMark;
      });
      // 存在,即重复了
      if (markIndex > -1) {
          // 取消上个重复的请求
          pendingRequest[markIndex].cancel();
          // 删掉在pendingRequest中的请求标识
          pendingRequest.splice(markIndex, 1);
      }
      //(重新)新建针对这次请求的axios的cancelToken标识
      const CancelToken = axios.CancelToken;
      const source = CancelToken.source();
      config.cancelToken = source.token;
      // 设置自定义配置requestMark项,主要用于响应拦截中
      config.requestMark = requestMark;
      // 记录本次请求的标识
      pendingRequest.push({
          name: requestMark,
          cancel: source.cancel,
      });
    }
    return config;
  },
  respInterceptSuccess: res => {
    //关闭 progress bar
    NProgress.done();
    if(res.config.cancelToken !== false){
      // 根据请求拦截里设置的requestMark配置来寻找对应pendingRequest里对应的请求标识
      const markIndex = pendingRequest.findIndex(item => {
        return item.name === res.config.requestMark;
      });
      // 找到了就删除该标识
      markIndex > -1 && pendingRequest.splice(markIndex, 1);
    }
    const { status, data, } = res;
    const { code, data: dataForm, msg, error_description: errorDescription, } = data
    // 如果是401则跳转到登录页面
    if (status === 401) {
      logout();
      location.reload();
    }
    // 如果请求为非200否者默认统一处理
    if (status !== 200) {
      const message = msg || errorDescription || '未知错误';
      return Promise.reject(new Error(message));
    }
    // B01032 登录超时
    if (code === 'B01032' || code === 'A00998') {
      logout();
      Message.error({
        message: '登录超时,请重新登陆',
        duration: 250,
        onClose: () => {
          location.reload();
        }
      });
      return Promise.reject(new Error('登录超时,请重新登陆'));
    }

    if (code !== '000000') {
        const message = msg || errorDescription || '未知错误';
        // 需要手动在页面处理的逻辑
        if (Object.values(NEED_MANUALLY_HANDLE_CODE_OBJ).includes(code)) {
            return Promise.reject({
                message,
                code,
            });
        }
        Message.error(message);
        return Promise.reject(new Error(message));
    }
    return dataForm;
  },
  respInterceptError: error => {
    NProgress.done();
    if (axios.isCancel(error)) {
        // 手动取消的请求 关闭promise链
        return new Promise(() => ({}));
    }
    return Promise.reject(new Error(error));
  },
};

// 实例化http
const http = new Http(options);

export default http;

сервис экспортирует экземпляр axios, нам нужен файл для размещения его для упаковки

➜  core (develop) ✔ pwd
/Users/xxx/Desktop/micro/xxx/packages/@xxx/xxx-http/src/core
➜  core (develop) ✔ cd .. && touch index.js

Экспортируйте этот экземпляр в index.js, чтобы другие проекты могли ссылаться на него.

import http from "./core/service"
export default http;

Точно так же мы можем использовать lerna для выпуска версии.

На данный момент мы написали библиотеку сетевых запросов. Мы можем запустить его снова по примеру qiankun, а заодно и по примеруyarn add -D @xxx/xxx-core, чтобы проверить, можно ли его использовать.

Простой пример, представленный на официальном веб-сайте qiankun, очевидно, не может быть использован напрямую, нам нужно сделать в нем много расширения, от входа в левую строку меню, до верхней панели навигации до правильного рендеринга контента. В следующей статье мы поговорим о том, как создать основное приложение/подприложение, а также об интеграции keep-alive и vuex и о том, как унифицировать версии публичных зависимостей.

По соглашению о неразглашении @xxx в тексте обозначает пакет с доменом, похожим на @vue, xxx — тот же английский.

Выше приведено все содержание этого обмена, я надеюсь, что это поможет вам ^_^

Если вам это нравится, не забудьте пошевелить пальцами, лайк, избранное, и следуйте трем последовательным волнам, чтобы забрать.


насчет нас

Мы являемся передовой командой Vantop Science and Technology, библиотеки компонентов для левой руки, библиотеки инструментов для правой руки и различных технологий, которые быстро растут.

Человек может бежать быстрее, чем группа людей может пробежать далеко. Добро пожаловать в нашу команду, и год Быка мчится вперед.

VANTOP前端团队