【问题标题】:Programmatically generate url from path and params以编程方式从路径和参数生成 url
【发布时间】:2020-01-13 20:51:35
【问题描述】:

我正在使用 Next.js,需要以编程方式从路径和参数生成一个 url。

例子:

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 是否提供任何此类方法?如果没有,实现此结果的最佳方法是什么?

【问题讨论】:

  • “如果没有,达到此结果的最佳方法是什么?” - 你自己尝试过什么吗?遍历params 的属性,将path 中的任何属性替换为其值,并从其余属性生成查询字符串应该不会那么复杂。
  • 基于“期望类似”,您似乎有意从路径中省略了一些属性,但您没有告诉我们是什么决定了是否包含属性。
  • @T.J.Crowder 包含所有属性。 url 变量被替换,其余的将作为查询参数添加。我给出的示例有 2 个 url 变量([id] 和 [code])和 2 个查询参数(第一个,第二个)
  • @Andreas 是的,我正在考虑使用正则表达式替换 [value] -> :value,然后使用 path-to-regexp 库或像你说的那样手动进行。但如果 Next.js 提供任何这样的方法,我宁愿使用它。

标签: javascript next.js


【解决方案1】:

更新答案:

在你说过的评论中:

查询参数的顺序确实无关紧要,但路径变量对于不同的路径是不同的(因此不可能只获取 id 和代码 - 我假设必须使用正则表达式或字符串匹配)

是的,您可以从路径中提取 [] 中的位并使用它们,稍后请记住您不希望它们出现在查询字符串中:

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);
}

现场示例:

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 且不想使用 polyfill 的过时环境,则可以使用对象来跟踪使用过的键:

// 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;
}

原答案:

如果查询参数的顺序无关紧要(或者如果对象始终以相同的方式创建并且属性添加到它的顺序与您希望它们在查询参数中的顺序相匹配),您可以抓住 @ 987654328@ 和 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 回调中。

【讨论】:

  • 感谢您的详细回答。查询参数的顺序确实无关紧要,但路径变量对于不同的路径是不同的(因此不可能只获取 id 和代码 - 我假设必须使用正则表达式或字符串匹配)
猜你喜欢
  • 2016-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多