Как-то я увидел в проекте соседней команды код, который генерировал строку с URL-параметрами для последующей вставки в iframe src-атрибут.


Эта статья может показаться лишней, очевидной или слишком простой, но раз такое встречается в живой природе, об этом не стоит молчать, а наоборот, поделиться best-practices.


Итак, вот он, оригинальный код:


const createQueryString = (param1, param2, objectId, timestamp, name) => {
  const encodedTimestamp = encodeURIComponent(timestamp);
  const delimiter = '&';
  const queryString = `${param1}${delimiter}
        param2=${param2}${delimiter}
        objectId=${objectId}${delimiter}
        itemTimestamp=${encodedTimestamp}${delimiter}
        itemName=${name}`;
  return queryString.replace(/ /g, '%20');
};

Для справки, param1 и param2 в оригинальном коде имеют говорящие названия. А их значения могут быть любыми строками с множеством невалидных для URL символов


Какие проблемы у этого кода?


  • Во-первых, это отсутствие encodeURIComponent для каждого значения, которое вполне может содержать совершенно любые символы. (в контексте данной задачи эти названия задаются пользователями и могут содержать всевозможные знаки вроде амперсанда, пробелов или диакритических символов);
  • Во-вторых, это лишние пробелы и символы переноса строки, которые появляются из-за оператора template string `; Их автор данного кода пытается исправить с помощью метода .replace(), но этот подход ничего не решает;
  • В-третьих, это трудночитаемый и нерасширяемый код из-за выбранного синтаксиса, подверженный ошибкам.

Как их исправить?


Первый подход:


const delimiter = '&'; // выносим константу из области видимости функции
const createQueryString = (param1, param2, objectId, timestamp, name) => {
  const queryString = [
    `param1=${encodeURIComponent(param1)}`,
    `param2=${encodeURIComponent(param2)}`,
    `objectId=${encodeURIComponent(objectId)}`,
    `itemTimestamp=${encodeURIComponent(timestamp)}`,
    `itemName=${encodeURIComponent(name)}`
  ].join(delimeter);
  return queryString;
};

Что мы добились здесь? Код теперь выдает корректную строку. Лишних символов вроде переносов строки теперь нет, а все значения закодированы нативной функцией encodeURIComponent. А еще вынесли константу, теперь она не объявляется каждый раз как вызывается функция. Код стал чуточку чище.


Можно лучше? Можно! Второй подход:


const createQueryString = (param1, param2, objectId, timestamp, name) => {
  const queryParams = {
     param1,
     param2,
     objectId,
     itemTimestamp: timestamp,
     itemName: name
  };
  const encodeAndJoinPair = pair => pair.map(encodeURIComponent).join('=');
  return Object.entries(queryParams).map(encodeAndJoinPair).join('&');
};

Мы избавились от константы. В данном контексте в этом нет ничего крамольного, так как оба символа — часть стандарта.


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


И еще разок

Обратим внимание на саму функцию и ее аргументы. Что, если нам понадобится больше аргументов? Нам надо будет их добавлять в конец функции, проставлять внутрь объекта queryParams. А затем вызывать функцию с этим новым новым аргументом. И так каждый раз, как добавится новый параметр. Перепишем функцию, и сделаем ее обобщенной:


const encodeAndJoinPair = pair => pair
  .map(encodeURIComponent)
  .join('=');

const createQueryString = objectParams => Object
  .entries(objectParams)
  .map(encodeAndJoinPair)
  .join('&');
};

Теперь функцию можно вынести в условный файл utils.js и использовать где угодно.


URLSearchParams


И тут на сцену выходит Web API. URLSearchParams нужен как раз в таких ситуациях.


И весь существующий код можно упростить до:


const createQueryString = objectParams => new URLSearchParams(objectParams).toString();

Ложкой дегтя является отсутствующая поддержка Internet Explorer, но мы всегда можем условно подключить полифилл, например, https://www.npmjs.com/package/url-search-params-polyfill .


Заключение


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