Напишите библиотеку наподобие axios

JavaScript
Напишите библиотеку наподобие axios

предисловие

Эта статья должнаjs,es6,webpack,网络请求Базовые знания и т. д. имеют базовое понимание

ПолагатьaxiosВсе использовали это или что-то网络请求Соответствующие библиотеки, чтоajax,fly.jsПодождите, подождите, я использовал только семь или восемь библиотек запросов, и все они похожи

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

Не говори глупостей, давай сделаем это~

Построить проект

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

yarn init -y

или

cnpm init -y

webpack

затем импортироватьwebpack, хотя этот раздел не посвященwebpack, позвольте мне упомянуть немного здесь,webpackиwebpack-cliНе только в проекте скачать, но и глобально, т.е.yarn global add webpack webpack-cli

Установить зависимости

Для выполнения команд эти пакеты в основном нужны для компиляции и отладки.babelпомощьпопробуйСовместимость с браузерами, в конце концов, мы должны писать код, полныйes6

yarn add webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env

настроить веб-пакет

Далее создайте его в корневом каталогеwebpack.config.jsнастроитьwebpack, а затем построить еще одинsrcдиректория для хранения кода нашей библиотеки, текущая директория будет выглядеть так

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

~ webpack.config.js

const path = require('path');

module.exports = function() {
  const dev = true;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
  };
};


В настоящее времяsrcВнутри постройтеindex.js, а затем просто напишите что-то вроде этого

Затем терминал выполняетwebpackЗаказ

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

Затем, после компиляции, вы можете видеть, что результат все ещеlet, что точно не работает

Итак, следующий шаг — настроитьbabel, тут нечего сказать, тут код прямо ставится, тут и говорить нечего

const path = require('path');

module.exports = function() {
  const dev = true;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
  };
};

Тогда вам точно не захочется вручную переходить к каждой модификацииwebpackмомент, не так ли? Пучокwebpack-dev-serverпринести

~ webpack.config.js

const path = require('path');

module.exports = function() {
  const dev = true;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
    devServer: {
      port: 8000,
      open: true,
    },
  };
};

В это время запустите прямо в терминалеwebpack-dev-serverНа самом деле он автоматически найдет глобальный модуль, что нехорошо, т.к. . . Знаешь

непосредственныйpackage.jsonдобавить команду

~ package.json

{
  "name": "axios",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server"
  },
  "dependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "babel-loader": "^8.0.6",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  }
}

потомyarn start

Появитсяhtml

Конечно, по умолчанию нужно найти кореньindex.html, у нас его нет, поэтому создайте его под корнем, а затем введите нашaxios.js

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>axios</title>
</head>
<body>
  <script src="/axios.js"></script>
</body>
</html>

Обновите страницу и вы увидитеsrc/index.jsвнутреннийalertвступает в силу

иwebpack-dev-serverТакже возможно, если вы измените код, страница автоматически обновится

Тогда давайте сопоставимbuild

Здесь не так много ерунды, сразу переходим к коду

~ package.json

{
  "name": "axios",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server --env.dev",
    "build": "webpack --env.prod"
  },
  "dependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "babel-loader": "^8.0.6",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  }
}

~ webpack.config.json

const path = require('path');

module.exports = function(env={}) {
  const dev = env.dev;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
    devServer: {
      port: 8000,
      open: true,
    },
  };
};

Видно, что все в порядке~

Хорошо, так вот я былwebpackСвязанные с этим дела почти закончены, и тогда мы начнем заниматься~

Код проекта Axios

Сначала построимcommon.js, используемый для размещения общедоступных методов, сначала напишите утверждение

~ /src/common.js

export function assert(exp, msg = 'assert faild') {
  if (!exp) {
    throw new Error(msg);
  }
}

Затем создайте еще один файл (личная привычка)/src/axiosЗдесь размещены основные файлы

Тогда давайте посмотримaxiosКак его использовать, можно ли его использовать напрямую?axios({...})илиaxios.getи т.д

существуетindex.jsВведите непосредственно результат, напишите ожидаемое использование, а затем дополните внутреннее, как написать

~ index.js

import axios from './axios';

console.log(axios);
axios({ url: '1.txt', method: 'post' });
axios.get('1.txt', { headers: { aaa: 123 } });
axios.post(
  '1.txt',
  { data: 123 },
  {
    headers: {
      bbb: 123,
    },
  },
);

В это время мы должны подумать об этом. Мы можем напрямую писать функции. Это не проблема, но это слишком разбросано. Мне лично это не нравится, но это тоже возможно, поэтому я напишу это как класс здесь. Поскольку он изменен на класс, он будет выводиться. должен быть один实例, поскольку это экземпляр, то его нельзя запускать напрямую, как функцию ()

Правильно, мы можем использовать нашproxyв настоящее время,jsизclassвнутреннийconstructorдаreturnЕсли вы не знакомы с этой вещью, я предлагаю вам сначала пойти и увидеть ее.jsизclass, я не буду здесь вдаваться в подробности, а в основном объясню идею

Проще говоря, мы можемreturnОдинproxyобъект для проксирования результатов, которые мы возвращаем, чтобы мы могли напрямую использоватьclassЕго можно записать так же, как функцию непосредственно при использовании.()перечислить

Затем распечатайте его

~

Глядя на страницуconsole, тогда вы можете увидетьaxiosтолько одинproxyобъект, как это

В это время вы все еще можете увидеть ошибку, потому что сейчас мы возвращаемproxyОбъект, больше не класс экземпляра, нетgetЭто также разумно

Некоторые могут задаться вопросом, почему этоproxyКонтролируемый объект должен контролироваться отдельноproxyфункция, прямой мониторингthisНе правда ли, обратите внимание, это на самом деле невозможно, поймитеproxyдрузья должны знать,proxyТо, что вы используете для наблюдения за созданием, очень важно.Если вы следите за объектом, вы все равно не можете вызвать его напрямую.Если вы хотите вызвать его напрямую, как функцию, то вы также должны следить за функцией.

нравится

Тогда давайте решим этоgetПроблема в том, что функция не может быть найдена, приходите кproxyДобавить метод, очень простой, вы можете датьproxyдобавить однуgetметод, кто-то приходит к нему, просто вернись и найди его из моего класса, разве это не конец, но обратите внимание на это немногоthis, пишите прямоthisэто указывает на этоproxy

function request() {}

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

let axios = new Axios();

export default axios;

Посмотрите еще раз на этот раз, ошибки нет, иgetиpostтакже можетconsoleпублично заявить

В это время мы можем продолжать писать запросы данных. . . . . ? Далеко не достаточно

axiosВ параметрах много разнообразия, хотим подытожить

axios('1.txt',{})
axios({
    url: '1.txt',
    method
})

axios.get('1.txt')
axios.get('1.txt',{})

axios.post....

Подождите, как можно единообразно обрабатывать такие сложные параметры с несколькими источниками?

ВторойaxiosПараметры могут быть настроены очень глубоко, как глобально, так и индивидуально, перехватчики,transfromчто, подождите, дефолты и т.д.


параметр

первыйdefault, чтобы определить эти значения по умолчанию, вот немного об этомX-Request-ByЭто неписаная спецификация, и это то, что готова сделать библиотека общих запросов, чтобы фон мог судить, исходит ли ваш запрос отajaxвсе ещеfromили браузерurl

function request() {}

const _default = {
  method: 'get',
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

let axios = new Axios();

export default axios;

Конечно картинка здесь простая, просто напишите несколько параметров, при желании можно много чего добавить, напримерdataСначала достаточно значения по умолчанию и т. д., а затем, если его недостаточно


Теперь давайте подумаем об этомdefaultКого добавить напрямуюaxios.default = _defaultНу, конечно, нет, потому что мыaxiosЭто будет необходимо в концеaxios.createЕсли экземпляров несколько, то в это время работать не будет, и они будут влиять друг на друга.prototypeне говоря уже о

На самом деле, это тоже очень просто, каждый раз, когда вы создаете его из_defaultПросто сделайте новую копиюJSON.parse(JSON.stringify(_default))Просто упакуйте его, что также является наиболее эффективным способом, а затем немного измените код.

function request() {}

const _default = {
  method: 'get',
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

Axios.create = Axios.prototype.create = function() {
  let axios = new Axios();

  axios.default = JSON.parse(JSON.stringify(_default));

  return axios;
};

export default Axios.create();

Вот дополнение и к прототипу и к экземпляруcreateметод, потому что мы можем напрямую использоватьaxios.create()Также можно использовать напрямуюaxios(), Простое добавление статических методов или методов экземпляра не может удовлетворить наши потребности.

Теперь давайте поэкспериментируем, сначалаconsoleнемногоaxios.default

ты найдешь,undefined,зачем это,явно сюда добавили

потому что на этот разaxiosне предмет, аproxy, мы не далиproxyдобавлятьsetВерен ли метод? Я ничего не могу добавить. Давайте сейчас изменим код.

function request() {}

const _default = {
  method: 'get',
  baseUrl: "",
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

Axios.create = Axios.prototype.create = function() {
  let axios = new Axios();

  axios.default = JSON.parse(JSON.stringify(_default));

  return axios;
};

export default Axios.create();

Затем снова посмотрите в браузере, вы найдете этоdefaultТам есть

и мы приходимcreateдваaxios, попробуйте изменить параметры

Два параметра экземпляра также не имеют значения, что также хорошо для начала.nшаг, теперь мы закончилиaxiosчетверть


Теперь у нас нет эффекта между экземплярами, но когда мы меняем параметры, это не только напрямуюaxios.default.xxxИтак, измените, у нас также должны быть параметры, подобные этому

Здесь мы можем напрямую изменитьaxios.createметод

~ axios.js

...
Axios.create = Axios.prototype.create = function(options={}) {
  let axios = new Axios();

  axios.default = {
    ...JSON.parse(JSON.stringify(_default)),
    ...options
  };

  return axios;
};

...

Просто разверните и замените его, не так ли?

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

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

~ axios.js

...
Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };
  function merge(dest, src) {
      for (let name in src) {
        if (typeof src[name] == 'object') {
          if (!dest[name]) {
            dest[name] = {};
          }
    
          merge(dest[name], src[name]);
        } else {
          dest[name] = src[name];
        }
      }
    }

  merge(res, options);

  axios.default = res;
  return axios;
};
...

Посмотри сейчас, все будет хорошо


Сортировка и разделение кода

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

Нынешний сплит можно разделить на несколько пунктов

  1. defaultМожно ли использовать отдельный файл для установки
  2. этоmergeФункция определенно общедоступна и может быть размещена в нашемcommon.jsвнутри
  3. этоrequestтакже должны быть помещены в отдельныйjsопределять

Не говорите ерунды, сразу переходите к коду

~ request.js

export default function request() {
  
}

~ default.js

export default {
  method: 'get',
  baseUrl: '',
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

~ common.js

export function assert(exp, msg = 'assert faild') {
  if (!exp) {
    throw new Error(msg);
  }
}

export function merge(dest, src) {
  for (let name in src) {
    if (typeof src[name] == 'object') {
      if (!dest[name]) {
        dest[name] = {};
      }

      merge(dest[name], src[name]);
    } else {
      dest[name] = src[name];
    }
  }
}

~ axios.js

import _default from './default';
import { merge } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

Теперь он кажется намного чище, верно?


Обработка параметров запроса

Прежде чем писать, давайте посмотримaxiosКакие поддерживаются способы записи, а потом рассмотрим, как писать

В дополнение к общемуaxios({...})Похожи ли эти три метода?axiosВ ней слишком много всего, поэтому я просто реализую здесь эти три.Главная проблема в том, чтобы объяснить проблему.Если вам интересно, вы можете добавить ее сами.Это не более чем физический труд.

Конечно, вы можете видетьaxiosЕсть еще довольно много параметров, в этот раз мы должны работать с ними напрямую, независимо от того, какие параметры будут переданы, мы вернемaxios({})Это окончательное унифицированное лечение, разве это не удобно?

Здесь мы непосредственно судим о первых двух случаях

Вы найдете первые два случая в дополнение к этомуmethodодинаковы, то мы можем извлечь метод, чтобы работать с ним единообразно

~ axios.js

import _default from './default';
import { merge } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  _preprocessArgs(method, ...args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
    } else {
      return undefined;
    }
    return options;
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

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

мы были раньшеcommon.jsнаписано наassertВот когда я использовал его

...
get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      // ...
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );
      
    }
  }
}
...

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

Кстати, это место конечно можно переиспользовать, но не обязательно.После этого не сильно уменьшится код, и получится грязно.Это зависит от человека.Не нравится-ты можете изменить его самостоятельно.

Тогда давай разберемся с этимoptionsconsoleнемного

~ axios.js

import _default from './default';
import { merge, assert } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
    } else {
      return undefined;
    }

    console.log(options);
    return options;
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        console.log(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };

        console.log(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        console.log(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };

      console.log(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

Теперь давайте проверим

~ index.js

import Axios from './axios';

Axios.get('1.json');
Axios.get('1.json', { headers: { a: 12 } });

Axios.post('1.php');
Axios.post('1.php', { a: 12, b: 5 });
Axios.post('1.php', [12, 5, 6]);

let form = new FormData();
Axios.post('1.txt', form);
Axios.post('1.txt', 'dw1ewdq');

Axios.post('1.json', form, { headers: { a: 213, b: 132 } });

Axios.delete('1.json');
Axios.delete('1.json', { parmas: { id: 1 } });

Вы можете видеть это сейчас, верно?

Тогда что. . . Не забыли, нам еще нужно разобраться с прямымиapplyслучае, то есть непосредственноAxios()когда так зовут

Ничего лишнего, переходите сразу к коду, следуйтеgetНа самом деле почти

~ axios.js

import _default from './default';
import { merge, assert } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            console.log(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
    } else {
      return undefined;
    }

    console.log(options);
    return options;
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        console.log(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };

        console.log(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        console.log(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };

      console.log(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

тогда проверь это

нет проблем, конечно, почемуmethodдаundefined, потому что это время еще не пришло к намdefaultЧто ж, здесь мы определяем значение запроса по умолчанию, так что вот прямоеundefinedХорошо, тогда мы должны положить немногоoptionsвсе иdefaulftГотово, бросай намrequestфункция для запроса

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

этоrequestМетод в основном делает четыре вещи

  1. Объединить с this.default
  2. Проверьте правильность параметров
  3. запрос на слияние baseUrl
  4. Официальный запрос вызова (варианты)
import _default from './default';
import { merge, assert } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    console.log(options, 'request');
    // 1. 跟this.default进行合并
    // 2. 检测参数是否正确
    // 3. baseUrl 合并请求
    // 4. 正式调用request(options)
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

Это слияние очень простое, мы писали ранееmergeФункция снова пригодится, модифицируйте код

...
request(options) {
    console.log(options);
    // 1. 跟this.default进行合并
    merge(options, this.default);

    console.log(options);
    // 2. 检测参数是否正确
    // 3. baseUrl 合并请求
    // 4. 正式调用request(options)
  }
  ...

В это время видно, что данные до и после слияния уже доступны, но в это время нашиheaderЭто не должно быть все из них, это должно быть основано наmethodКак поставить соответствующийheaderиcommonсливаться

request(options) {
    // 1. 跟this.default进行合并
    let _headers = this.default.headers;
    delete this.default.headers;
    merge(options, this.default);
    this.default.headers = _headers;
    //合并头
    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    console.log(headers);
    console.log(options);

    // 2. 检测参数是否正确
    // 3. baseUrl 合并请求
    // 4. 正式调用request(options)
  }

Здесь немного грязно, так что давайте сначала посмотрим

Наша цель - сделатьheaderСлияние, но будет небольшая проблема со слиянием, прежде чем мы были вdefaultопределено вcommon,get... также будет скопировано, если мы используемifсудитьoptions.header.common == this.default.headers.commonпотомdeleteЕсли это так, вы обнаружите, что в настоящее время это невозможно, потому что мы также знаем, что если вы прямо пишете два суждения об объектах, это эквивалентно прямомуnewдва объекта, то суждение определенно не равно в это время, так когда же мы его скопировали?

Просто когда мы инкапсулируемmerge, и во многие другие места перенесли эту штуку

Затем мы должны выяснить, когда эта вещь отличается, на самом деле она наша.requestпервый раз в функцииmergeкогда

Так что мы играем здесь небольшую хитрость, потому чтоcommonЭти вещи были сделаны вручную ниже, поэтому ему не нужно копировать сюда, поэтому сначалаdeleteнемного

Пусть не влезет раньше,deleteПосле этого я заберу его обратно, и два конца не будут задерживаться. Это действительно хорошо~

Наконец, мы положилиheadersприсвоить нашемуoptions.headers

request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;
    merge(options, this.default);
    this.default.headers = _headers;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;
    console.log(options);

    // 3. baseUrl 合并请求
    

    // 4. 正式调用request(options)
  }

~ index.js

import Axios from './axios';

Axios('1.php');
Axios({
  url: '2.php',
  params: { a: 12, b: 3 },
  headers: {
    a: 12,
  },
});

проверить результаты

Как видишь, все в порядке~

Тогда давайте посмотрим на второй шаг, На самом деле, мы можем написать много вещей, которые стоит проверить для этой проверки, но здесь объясняется только смысл, даже если вы напишете несколько, вы можете сделать больше, если вам интересно.

...
assert(options.method, 'no method');
assert(typeof options.method == 'string', 'method must be string');
assert(options.url, 'no url');
assert(typeof options.url == 'string', 'url must be string');
...

Третий шаг непосредственно в коде

~ axios.js

options.url=options.baseUrl+options.url;
delete options.baseUrl; 

~ common.js

export function assert(exp, msg = 'assert faild') {
  if (!exp) {
    throw new Error(msg);
  }
}

export function merge(dest, src) {
  for (let name in src) {
    if (typeof src[name] == 'object') {
      if (!dest[name]) {
        dest[name] = {};
      }

      merge(dest[name], src[name]);
    } else {
      if (dest[name] === undefined) {
        dest[name] = src[name];
      }
    }
  }
}

~ index.js

import Axios from './axios';

Axios('1.php', {
  baseUrl: 'http://www.baidu.com/',
  headers: {
    a: 12,
  },
});
Ï

В это время протестируйте его снова, вы увидите, что проблем нет.

Вот почему вам нужно изменить егоmerge, добавил суждение, потому что мы заменили его непосредственно перед тем, заменили мы его или нет, это определенно не хорошо, если мы не добавим его, нашbaseUrlВыполнено

Конечно, есть одна маленькая вещь, с которой нам нужно разобраться, если человек, который использует вашу библиотеку, имеет больной разум (конечно, можно игнорировать больной разум), когда он пишет путь, он пишет его так:

Вы не можете сделать это снова, это очень просто,NodeJSсуществует одинurlпакет, вы можете использовать его напрямую,webpackпоможет вам упаковать его, но есть одно замечание,webpackНе все можно упаковать, напримерfsмодули и даже некоторые низкоуровневые функцииcиc++Написанный системный пакет работать точно не будет, ноurlнет проблем

~ axios.js

import _default from './default';
import { merge, assert } from './common';
import request from './request';
const urlLib = require('url');

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;
    merge(options, this.default);
    this.default.headers = _headers;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;
    console.log(options);

    // 2. 检测参数是否正确
    assert(options.method, 'no method');
    assert(typeof options.method == 'string', 'method must be string');
    assert(options.url, 'no url');
    assert(typeof options.url == 'string', 'url must be string');

    // 3. baseUrl 合并请求
    options.url = urlLib.resolve(options.baseUrl, options.url);
    delete options.baseUrl;

    // 4. 正式调用request(options)
    request(options);
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

Проверьте это снова

нет проблем

проблема слияния

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

Теперь мы вынуждены написатьif, поменяли на проверку на утечки и заполнение вакансий, то по факту порядок вещей с приоритетом должен быть обратным

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

В самом деле, что делать, порядок этих двух вещей должен быть обратным, но в этот раз это неправильно, потому что это приводит к этому.this.defaultОн изменился. На данный момент вам нужно сделать копию. Давайте напишем публичный метод и поместим его вcommon.jsвнутри

~ common.js

...

export function clone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

И давайте немного изменим порядок, а затем клонируем данные

Давайте проверим это сейчас

выяснит лиheaderЭти вещи здесь вернулись, и причина очень проста, потому что мыdefaultдаватьcloneвниз, так что давайте положитьdeleteПоднимите этот кусок вверх

теперь все будет хорошо


request

В это время мы должны написать четвертый шаг. напрямуюoptionsнамrequestЭто можно сделать в функции, и все мелочи решаются ею.

затем измените егоrequest.js

~ request.js

export default function request(options) {
  console.log(options);
  let xhr = new XMLHttpRequest();

  xhr.open(options.method, options.url, true);

  for (let name in options.headers) {
    xhr.setRequestHeader(name, options.headers[name]);
  }

  xhr.send(options.data);
}

Напишите пока простой запрос, а потом давайте проверим, можно ли его отправить

Сначала постройтеtxt, нам удобно тестить, вставлюdataкаталог, как это

затем измените егоindex.js

~ index.js

import Axios from './axios';

Axios('/data/1.txt', {
  headers: {
    a: 12,
  },
});

Теперь мы можем видеть, что,headerдобавляется, и то, что возвращается, является правильным

Конечно, это еще не конец, это также вопрос, чтобы пользователи не создавали проблем

Если пользователь дает вамheaderЭто как-то так, то это не очень хорошо, так что лучше дайте ему код

~ request.js

export default function request(options) {
  console.log(options);
  let xhr = new XMLHttpRequest();

  xhr.open(options.method, options.url, true);

  for (let name in options.headers) {
    xhr.setRequestHeader(
      encodeURIComponent(name),
      encodeURIComponent(options.headers[name]),
    );
  }

  xhr.send(options.data);
}

Это не проблема.В случае возникновения проблемы в фоновом режиме, как только вы столкнетесь с двоеточием, вы можете решить эту проблему и избежать ее.

Затем, когда мы его используем, нам определенно понадобится еще один.asyncиawaitпоэтому нам нужно использоватьPromiseупаковать

~ axios.js

export default function request(options) {
  console.log(options);
  let xhr = new XMLHttpRequest();

  xhr.open(options.method, options.url, true);

  for (let name in options.headers) {
    xhr.setRequestHeader(
      encodeURIComponent(name),
      encodeURIComponent(options.headers[name]),
    );
  }

  xhr.send(options.data);

  return new Promise((resolve, reject) => {
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(xhr);
        } else {
          reject(xhr);
        }
      }
    };
  });
}

И в это время у нас еще много-много вопросов

  1. 304 на самом деле является успешным, поэтому инкапсулировать его неправильно, и у пользователя могут быть какие-то нестандартныеcode, как это настроить
  2. наш текущийwebpackтолько совместимыеes6,asyncиawaitне совместимо, как это сопоставить

Давайте сначала решимwebpackПроблема, это на самом деле очень просто, нам нужно нажать другой пакетyarn add @babel/polyfill

затем откройтеwebpack.config.jsнемного отредактироватьentry

~ webpack.config.js

...
entry: ['@babel/polyfill','./src/index.js'],
...

Обратите внимание, что этот порядок нельзя изменить

Это совместимо, а затем давайте изменим его сноваindex.js

import Axios from './axios';

(async () => {
  let res = await Axios('/data/1.txt', {
    headers: {
      a: 12,
      b: '321fho:fdsf vfds; : ',
    },
  });

  console.log(res);
})();

Видно, что результатundefined, это потому, что мы вообще не вернули результат обработки

Измените это сейчасaxios.js

import _default from './default';
import { merge, assert, clone } from './common';
import request from './request';
const urlLib = require('url');

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            return _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;

    let result = clone(this.default);
    merge(result, this.default);
    merge(result, options);
    this.default.headers = _headers;

    options = result;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;

    // 2. 检测参数是否正确
    assert(options.method, 'no method');
    assert(typeof options.method == 'string', 'method must be string');
    assert(options.url, 'no url');
    assert(typeof options.url == 'string', 'url must be string');

    // 3. baseUrl 合并请求
    options.url = urlLib.resolve(options.baseUrl, options.url);
    delete options.baseUrl;

    // 4. 正式调用request(options)
    return request(options);
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      return this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

На этот раз давайте посмотрим на результаты, все ли в порядке? Конечно, мы не можем просто напрямую преобразовать исходныйxmlОбъект возвращается, и нам также нужно выполнить различную обработку возвращенных данных.


Обработка данных

Сделаем простое изменениеaxios.jsвнизrequest

вернуть другойpromise, вы можете увидеть результат в это время, нет никаких проблем

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

Создайте два файла один/src/response.jsодин/src/error.js

то здесьaxios.jsПредставьте его и передайте им отдельно при работе с ним.

потомresponse.jsПросто верните значение напрямую

Но этоheadersНемного особенный, вам нужно настроить метод отдельноxhr.getAllResponseHeaders(), но это возвращает оригиналxhrголову, это определенно не сработает, так что нам нужно ее отрезать

~ ответ.js

export default function(xhr) {
  let arr = xhr.getAllResponseHeaders().split('\r\n');
  let headers = {};

  arr.forEach(str => {
    if (!str) return;
    let [name, val] = str.split(': ');
    headers[name] = val;
  });

  return {
    ok: true,
    status: xhr.status,
    statusText: xhr.statusText,
    data: xhr.response,
    headers,
    xhr,
  };
}

это нормально

transformRequest && transformResponse

Конечно, это еще не конец, потому что наша нынешняяdataОбработки пока нет, поэтому это должна быть строка, и пользователи могут настраивать этот метод обработки, знакомый сaxiosдрузья должны знать,axiosимеютtransformRequestиtransformResponseметод

В это время давайте сначала изменим егоaxios.jsвнутреннийrequestметод

Теперь нам нужно обработать запрос между шагами 3 и 4.

Сначала распечатайте параметры, а затем изменитеindex.jsконтрольная работаdemo

Для удобства тестирования я положил1.txtизменить на1.jsonХорошо, давай разберемся с этим позжеjsonДанные хороши, чтобы увидеть эффект

Видно, что этот параметр можно получить, тогда следующий шаг относительно прост, приходим сразу

Теперь посмотрите на запрос, этоheadersтолько что добавленное

Только немного здесь, почему я хочуdeleteпадать, хотя и неdeleteЭто не имеет значения, но я бы хотел, чтобы у меня было этоrequestне сорить, поддерживайте чистоту, содержите это в чистоте

Что касается этого пользовательского возвращаемого результата, он проще?

Вы можете увидеть результаты в это время, я не передал егоtransformResponse, результат такой

Вот и все

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

и между разными экземплярами можно


перехватчик

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

~ index.js

import Axios from './axios';

Axios.interceptors.request.use(function(config) {
  config.headers.interceptors = 'true';
  return config;
});

(async () => {
  let res = await Axios('/data/1.json', {
    headers: {
      a: 12,
      b: '321fho:fdsf vfds; : ',
    },
  });

  console.log(res);
})();

затем создайте новыйinterceptor.js

~interceptor.js

class Interceptor {
  constructor() {
    this._list = [];
  }

  use(fn) {
    this._list.push(fn);
  }

  list() {
    return this._list;
  }
}


export default Interceptor;

~ axios.js

import _default from './default';
import { merge, assert, clone } from './common';
import request from './request';
import createResponse from './response';
import createError from './error';
const urlLib = require('url');
import Interceptor from './interceptor';

class Axios {
  constructor() {
    this.interceptors = {
      request: new Interceptor(),
      response: new Interceptor(),
    };

    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            return _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;

    let result = clone(this.default);
    merge(result, this.default);
    merge(result, options);
    this.default.headers = _headers;

    options = result;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;

    // 2. 检测参数是否正确
    assert(options.method, 'no method');
    assert(typeof options.method == 'string', 'method must be string');
    assert(options.url, 'no url');
    assert(typeof options.url == 'string', 'url must be string');

    // 3. baseUrl 合并请求
    options.url = urlLib.resolve(options.baseUrl, options.url);
    delete options.baseUrl;

    // 4. 变换一下请求
    const { transformRequest, transformResponse } = options;
    delete options.transformRequest;
    delete options.transformResponse;

    if (transformRequest) options = transformRequest(options);

    let list = this.interceptors.request.list();
    list.forEach(fn => {
      options = fn(options);
    });

    // 5. 正式调用request(options)
    return new Promise((resolve, reject) => {
      return request(options).then(
        xhr => {
          let res = createResponse(xhr);
          if (transformResponse) res = transformResponse(res);
          
          let list = this.interceptors.response.list();
          list.forEach(fn => {
            res = fn(res);
          });
          resolve(res);
        },
        xhr => {
          let err = createError(xhr);
          reject(err);
        },
      );
    });
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      return this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = clone(_default);

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

Видно, что по сути это та же процедура, главное передать параметры и потом вызвать.

Тем не менее, есть две небольшие проблемы, которые необходимо решить

  1. Теперь мы даем пользователю много открытий, если он вернетсяconfigЕсли оно неправильное или не возвращено, мы должны вернуть ему сообщение об ошибке и проверить его снова.В это время все должны уметь думать об этом, верно?axiosТехнического содержания, чтобы сделать функцию для проверки этих параметров, нет, поэтому я не буду здесь вдаваться в подробности, если вам интересно, вы можете попробовать.
  2. То, что мы даем пользователю, это функция, которую мы используемforEach, это вызовет проблему, если пользователь даст вамasyncФункция , то работать не будет, надо еще добавитьasyncиawait,ноasyncиawaitкоторый возвращаетpromiseЭто очень странно.Если вам интересно, вы можете попробовать сами, или оставить сообщение в области комментариев.

Теперь давайте попробуем эффект

Видно, что наш перехватчик тоже завершен.

Суммировать

Окончательный код этой статьи загруженgithub, ссылка такаяGitHub.com/Mikey-9/Assi…

Тем не менее, это предложение, наша статья в основном не для достижения полной реализацииaxios, но идея реализации такой библиотеки.Конечно, есть и много проблем.Вы можете оставить сообщение в области комментариев или добавить меняqqили微信общаться вместе

Это немного длинно, спасибо за просмотр

Thank You

qq:

WeChat: