Как-то я увидел в проекте соседней команды код, который генерировал строку с 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.
gogolor
Только URLSearchParams плохо обрабатывает массивы
даст "arr=1%2C2", а хотелось бы "arr=1&arr=2"
Carduelis Автор
На самом деле, обработка массива вполне допустимая и корректная.
Например, в node этот вариант кодирования массивов поддерживается.
При этом сторонняя и очень популярная библиотека qs имеет специальный параметр
{ comma: true }
Также стоит отметить, что URLSearchParams использует RFC1738 стандарт, а не RFC3986. То есть пробел кодируется символом плюса
+
, а не%20
.Например, из документации к того же qs пакета, можно увидеть это различие:
dmitriym09
«arr=1&arr=2» — так тоже возможно