Программно генерировать URL из пути и параметров - PullRequest
2 голосов
/ 10 января 2020

Я использую Next. js и мне нужно программно сгенерировать URL-адрес из path и params.

Пример:

path = "/test/[id]/[code]/path" // (the format Router.pathname returns from next/router)
params = {id: 1, first: 'xyz', second: 2, code: 'abc'}
// Expecting something like: generateUrl(path, params) -> "/test/1/abc/path?first=xyz&second=2"

Я попытался просмотреть следующий маршрутизатор, но это не помогло. Кажется, у меня нет такого метода. Я также посмотрел, как это реализовано в React Router и в котором используется библиотека path-to-regexp, которая, кажется, ожидает путь в формате /user/:name, а не в квадратных скобках, как Next. js возвращает.

Предоставляет ли Next. js такой метод? Если нет, то как лучше всего достичь этого результата?

1 Ответ

3 голосов
/ 11 января 2020

Обновленный ответ:

В комментарии вы сказали:

порядок параметров запроса действительно не имеет значения, но переменные пути различны для разных путей (поэтому невозможно просто взять идентификатор и код - я предполагаю, что необходимо использовать регулярное выражение или сопоставление строк)

Да, вы можете извлечь биты в [] из путь и используйте их, помня для дальнейшего, что вы не хотите, чтобы они были в строке запроса:

function buildPath(path, params) {
    const used = new Set();
    // Replace the parts in [xxx]
    path = path.replace(/\[([^\]]+)]/g, (m, c0) => {
        used.add(c0);
        return c0 in params ? params[c0] : "";
    });
    // Add query string if there are any left over
    let qstr = Object.entries(params)
        .filter(([key]) => !used.has(key))
        .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
        .join("&");
    return path + (qstr && "?" + qstr);
}

Live Пример:

function buildPath(path, params) {
    const used = new Set();
    // Replace the parts in [xxx]
    path = path.replace(/\[([^\]]+)]/g, (m, c0) => {
        used.add(c0);
        return c0 in params ? params[c0] : "";
    });
    // Add query string if there are any left over
    let qstr = Object.entries(params)
        .filter(([key]) => !used.has(key))
        .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
        .join("&");
    return path + (qstr && "?" + qstr);
}

console.log("Your example:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, first: 'xyz', second: 2, code: 'abc'}));

console.log("Example without any query string:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, code: 'abc'}));

console.log("Your example with `first` and `second` reversed:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, second: 2, first: 'xyz', code: 'abc'}));

console.log("An example requiring URI encoding:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, blah: "value with & that needs encoding", code: 'abc'}));
.as-console-wrapper {
    max-height: 100% !important;
}

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

Если вам нужно поддерживать устаревшие среды, которые не имеют Set и не хотят использовать полифилл, вы можете использовать объект для отслеживания вместо используемых ключей:

// ES5 version
function buildPath(path, params) {
    var used = Object.create(null); // So it doesn't inherit from Object.prototype and have "toString", etc.
    // Replace the parts in [xxx]
    path = path.replace(/\[([^\]]+)]/g, function(m, c0) {
        used[c0] = true;
        return c0 in params ? params[c0] : "";
    });
    // Add query string if there are any left over
    let qstr = Object.keys(params)
        .filter(function(key) { return !used[key]; })
        .map(function(key) {
            return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
        })
        .join("&");
    return path + (qstr && "?" + qstr);
}

console.log("Your example:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, first: 'xyz', second: 2, code: 'abc'}));

console.log("Example without any query string:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, code: 'abc'}));

console.log("Your example with `first` and `second` reversed:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, second: 2, first: 'xyz', code: 'abc'}));

console.log("An example requiring URI encoding:");
console.log(buildPath("/test/[id]/[code]/path", {id: 1, blah: "value with & that needs encoding", code: 'abc'}));
.as-console-wrapper {
    max-height: 100% !important;
}

Оригинальный ответ:

Если порядок параметров запроса не имеет значения (или если объект всегда создается одинаково, и порядок добавления к нему свойств соответствует порядку, который вы хотите использовать в параметрах запроса), вы можете получить id и code от объекта и захватить все остальное для построения Строка запроса, что-то вроде этого:

function buildPath(path, params) {
    const {id, code, ...query} = params;
    return path.replace(/\[id]/, id)
               .replace(/\[code]/, code) +
               "?" +
               Object.entries(query).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
               .join("&");
}

// Your example
console.log(buildPath("/test/[id]/[code]/path", {id: 1, first: 'xyz', second: 2, code: 'abc'}));

// Your example with `first` and `second` reversed:
console.log(buildPath("/test/[id]/[code]/path", {id: 1, second: 2, first: 'xyz', code: 'abc'}));

// An example requiring URI encoding
console.log(buildPath("/test/[id]/[code]/path", {id: 1, blah: "value with & that needs encoding", code: 'abc'}));

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

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

{id: 1, code: 'abc', blah: ["one", "two", "three"]}

как /test/1/abc/path?blah=one%2Ctwo%2Cthree. В некоторых средах вы хотите повторить имя параметра запроса blah. Если это так, вам нужно обнаружить массив и обработать его:

function buildPath(path, params) {
    const {id, code, ...query} = params;
    const qstr = Object.entries(query).map(([key, value]) => {
        key = encodeURIComponent(key) + "[]";
        if (Array.isArray(value)) {
            value = value.map(e => encodeURIComponent(e)).join(`&${key}=`);
        } else {
            value = encodeURIComponent(value);
        }
        return `${key}=${value}`;
    }).join("&");
    return path.replace(/\[id]/, id)
               .replace(/\[code]/, code) +
               qstr;
}

// Your example
console.log(buildPath("/test/[id]/[code]/path", {id: 1, first: 'xyz', second: 2, code: 'abc'}));

// Your example with `first` and `second` reversed:
console.log(buildPath("/test/[id]/[code]/path", {id: 1, second: 2, first: 'xyz', code: 'abc'}));

// An example requiring URI encoding
console.log(buildPath("/test/[id]/[code]/path", {id: 1, blah: "value with & that needs encoding", code: 'abc'}));

// An example with an array converted to repeated query params
console.log(buildPath("/test/[id]/[code]/path", {id: 1, code: 'abc', blah: ["one", "two", "three"]}));

Некоторые среды хотят видеть [] после клавиши, когда она используется таким образом. Если это так, просто измените

key = encodeURIComponent(key);

на

key = encodeURIComponent(key) + "[]";

в обратном вызове map.

...