如何将 URLSearchParams.get() 返回值的类型缩小为联合类型?

发布于 2025-01-10 18:02:16 字数 1187 浏览 1 评论 0原文

URL 中有一个搜索参数,如下所示:?mode=viewmode 的有效值应该是 'edit''view',因此我创建了一个 ModeTuple 类型并使用索引访问类型 ModeTuple[number] 将其转换为联合类型。

type ModeTuple = ['view', 'edit'];

const searchParams = new URLSearchParams();
const modes: ModeTuple = ['view', 'edit'];

const m1: ModeTuple[number] = modes.includes(searchParams.get('mode')) ? searchParams.get('mode') : 'view';

我想使用 modes.includes(...) 检查 mode 的值是否有效,然后将类型缩小为联合类型 'edit' | '查看'

出现错误:

“字符串”类型的参数不可分配给““视图”类型的参数 | “编辑”'

看来 Array.prototype.includes 方法不会将类型从 string 缩小到 “view” | “编辑”成功。从JS代码逻辑来看,mode值必须是'view''edit'。但 TSC 并不知道这一点。

<一href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAsg9gEwgFQK5gDbQLxQNoDkAbgJYQDuBANFARAicAQLoDcAUOwMZwB2AzsCj8IAQwBOXABYAFCaIC2-KLl4UoAVQ BKAGQDKYybPlKAFAEoOPAUIWII-AFyx7aTDnzEylGnQZM2TmtBKAUARmd4JDcsPF5UBQAjCHFmFVD7fgA6El4uDFQkflMRCWk5cUVsgHMIYFMCOyQCc3MoAH5hQ3KTGrqGpogWqGcvCgIOIA" rel="nofollow noreferrer">TypeScript 游乐场

There is a search parameter in URL like this: ?mode=view. The valid value of mode should be 'edit' and 'view', so I create a ModeTuple type for it and convert it to union type using indexed access types ModeTuple[number].

type ModeTuple = ['view', 'edit'];

const searchParams = new URLSearchParams();
const modes: ModeTuple = ['view', 'edit'];

const m1: ModeTuple[number] = modes.includes(searchParams.get('mode')) ? searchParams.get('mode') : 'view';

I want to check if the value of mode is valid using modes.includes(...), then narrow the type to union type 'edit' | 'view'.

Got error:

Argument of type 'string' is not assignable to parameter of type '"view" | "edit"'

It seems Array.prototype.includes method will NOT narrow the type from string to "view" | "edit" successfully. From the logic of the JS code, the mode value must be 'view' or 'edit'. But TSC doesn't know that.

TypeScript Playground

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

寄风 2025-01-17 18:02:16

const m1: ModeTuple[number] = models.includes(searchParams.get('mode')) ? searchParams.get('mode') : 'view'; 不起作用有两个原因:

  1. mode.includes 需要一个 ModeTuple[number] 参数,但 searchParams.get('mode') 返回 string |空

  2. TypeScript 对缩小类型的流分析是有限的。即使 #1 不是问题,它也不会从 modes.includes(searchParams.get('mode')) 得知后续调用 searchParams.get('mode) ') 将返回一个 ModeTuple[number] 值。 (尤其是因为并非所有函数都是纯函数,因此 TypeScript 在一般情况下不能假设函数在使用相同参数调用两次时会返回相同的内容两次。)

对于这样的事情,我喜欢从一个常量开始数组,然后从中派生类型:

const modes = ["view", "edit"] as const;
type ModeTuple = typeof modes; // If you want it
type Mode = ModeTuple[number];

这样,这些值仅列出一次,并且如果我需要添加一个等,我只有一个地方可以更改它们。

然后,您可以替换原始值m1 代码如下:

const m1 =
    modeString !== null && (modes as readonly string[]).includes(modeString)
    ? modeString as ModeTuple[number]
    : "view";

...但是我不喜欢那样做内联类型断言,因为它们很冗长并且重复它们给了我很多出错的机会。

相反,我更喜欢定义一个 类型谓词 (又名“类型保护”)我可以重用的函数:

function isMode(mode: string | null): mode is Mode {
    return mode !== null && (modes as readonly string[]).includes(mode);
}

那么你可以这样做:

const modeString = new URLSearchParams(location.search).get("mode");
const m1 = isMode(modeString) ? modeString : "view";

或者你可以定义一个函数来执行它,如果你可能需要在多个地方执行它:

function getMode(searchParams: URLSearchParams): Mode | null {
    const mode = searchParams.get("mode");
    // Or if you didn't define `isMode`, just do the check here
    return isMode(mode) ? mode : null;
}

那么获取它只是:

const m1 = getMode(theSearchParams):

游乐场链接

const m1: ModeTuple[number] = modes.includes(searchParams.get('mode')) ? searchParams.get('mode') : 'view'; won't work for two reasons:

  1. mode.includes expects a ModeTuple[number] argument, but searchParams.get('mode') returns string | null.

  2. TypeScript's flow analysis for narrowing types is limited. Even if #1 weren't an issue, it wouldn't know from modes.includes(searchParams.get('mode')) that a subsequent call to searchParams.get('mode') will return a ModeTuple[number] value. (Not least because not all functions are pure, so TypeScript can't assume in the general case that a function will return the same thing twice when called twice with the same arguments.)

For things like this, I like to start with a constant array, then derive the types from it:

const modes = ["view", "edit"] as const;
type ModeTuple = typeof modes; // If you want it
type Mode = ModeTuple[number];

That way, the values are only listed once and I only have one place to change them if I need to add one, etc.

Then, you could replace your original m1 code with:

const m1 =
    modeString !== null && (modes as readonly string[]).includes(modeString)
    ? modeString as ModeTuple[number]
    : "view";

...but I don't like doing inline type assertions like that, because they're verbose and repeating them gives me multiple chances to get it wrong.

Instead, I prefer defining a single type predicate (aka "type guard") function that I can reuse:

function isMode(mode: string | null): mode is Mode {
    return mode !== null && (modes as readonly string[]).includes(mode);
}

Then you could do:

const modeString = new URLSearchParams(location.search).get("mode");
const m1 = isMode(modeString) ? modeString : "view";

Or you could define a function to do it, if you may need to do it in more than one place:

function getMode(searchParams: URLSearchParams): Mode | null {
    const mode = searchParams.get("mode");
    // Or if you didn't define `isMode`, just do the check here
    return isMode(mode) ? mode : null;
}

Then getting it is just:

const m1 = getMode(theSearchParams):

Playground link

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文