【问题标题】:How to properly type the input for String.prototype.replace?如何正确键入 String.prototype.replace 的输入?
【发布时间】:2020-11-24 13:05:59
【问题描述】:

我需要为String.prototype.replace 保留一组输入。当尝试根据函数本身的类型定义一个接口以用作该数组的类型时,TypeScript 正在拒绝并出现 TS2769 错误。

代码:

interface Replacment {
  searchValue: RegExp;
  replacer: string | ((substring: string, ...args: any[]) => string);
}

const r1 = {searchValue: /bc/, replacer: 'de'};
const r2 = {searchValue: /bc/, replacer: () => 'de'};
const r3: Replacment = {searchValue: /bc/, replacer: 'de'};
const r4: Replacment = {searchValue: /bc/, replacer: () => 'de'}; // or (_substring: string, ..._args: any[]) => 'de'

'abc'.replace(/bc/, 'de');
'abc'.replace(/bc/, () => 'de');

'abc'.replace(r1.searchValue, r1.replacer);
'abc'.replace(r2.searchValue, r2.replacer);
'abc'.replace(r3.searchValue, r3.replacer); // <-- TS2769
'abc'.replace(r4.searchValue, r4.replacer); // <-- TS2769

错误:

(属性)Replacement.replacer:字符串 | ((子字符串:字符串,...args:任何[])=>字符串) 没有重载匹配此调用。 最后一个重载给出了以下错误。 'string | 类型的参数((substring: string, ...args: any[]) => string)' 不能分配给 '(substring: string, ...args: any[]) => string' 类型的参数。 类型 'string' 不可分配给类型 '(substring: string, ...args: any[]) => string'.(2769)

有什么想法吗?


replace 定义为:

replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
replace(searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string): string;

【问题讨论】:

标签: typescript


【解决方案1】:

r1r2 中,TypeScript 推断类型并且可以肯定地说您的replacerstring 或替换函数。所以在你使用它的那一刻,它就知道要使用replace 的哪个函数重载(还有更多,打开lib.es5.d.ts 可以查看其他函数。

在你注释的那一刻,TypeScript 不确定:它可以是string,也可以是替换函数。但它是什么? replacer 的所有重载都没有两种类型,因此 TypeScript 无法选择正确的类型。

免责声明:TypeScript 可以创建所有重载的叉积,但正如 Aplet123 指出的那样,这正在进行中:https://github.com/microsoft/TypeScript/issues/14107

您可以做的是明确说明您的Replacement 类型可以有一个带有string 的对象replacer,或者一个带有函数的对象replacer

type Replacment = {
  searchValue: RegExp;
  replacer: ((substring: string, ...args: any[]) => string);
} | {
  searchValue: RegExp,
  replacer: string
}

这是一个细微差别,但编译器会在您分配值时将类型缩小为其中一种联合类型。现在你可以再次通过它们了。

如果你没有赋值,TypeScript 仍然不知道如何处理它:

declare const r5: Replacment; // no idea what value is assigned, can be both!
'abc'.replace(r5.searchValue, r5.replacer)// <-- TS2769

你可以做两件事来对付它。 A: 缩小在辅助函数中

function replace(str: string, replacement: Replacment) {
   if(typeof replacement.replacer === "string") {
     // I know it's string!
     return str.replace(replacement.searchValue, replacement.replacer)
   }
   return str.replace(replacement.searchValue, replacement.replacer)
}

这是假的。或者自己修补 String.replace:

interface String {
  replace(searchValue: RegExp, replacer: string | ((substring: string, ...args: any[]) => string)): any
}

这可能很危险。或者你做一个组合:一个帮助函数,它只在你使用的模块范围内修补 String.replace。

【讨论】:

  • “TypeScript 可以创建所有重载的叉积,但故意不这样做,因为这对编译器来说太重太快了。” This is not a deliberate decision, but a design flaw in the process of being fixed.
  • 我认为修改全局 String 接口不是一个好主意。只是我的意见
  • @captain-yossarian 我同意!当我需要修补全局接口时,我所做的是在模块范围内执行此操作,因此很清楚在哪里发生了什么。
  • @ddprrt 顺便说一句,我发现你的博客和书非常有用!
  • @captain-yossarian 太棒了,非常感谢 :-) 能得到这样的反馈真是太好了!
【解决方案2】:

你应该给 TS 编译器一个提示,你有什么期望。 例如,您可以定义泛型参数:

interface Replacment<T> {
    searchValue: RegExp;
    replacer: T extends 'string' ? string : T extends 'fun' ? ((substring: string, ...args: any[]) => string) : never;
}

const r1 = { searchValue: /bc/, replacer: 'de' };
const r2 = { searchValue: /bc/, replacer: () => 'de' };
const r3: Replacment<'string'> = { searchValue: /bc/, replacer: 'de' };
const r4: Replacment<'fun'> = { searchValue: /bc/, replacer: () => 'de' }; // or (_substring: string, ..._args: any[]) => 'de'

'abc'.replace(/bc/, 'de');
'abc'.replace(/bc/, () => 'de');

'abc'.replace(r1.searchValue, r1.replacer);
'abc'.replace(r2.searchValue, r2.replacer);
'abc'.replace(r3.searchValue, r3.replacer); // no error
'abc'.replace(r4.searchValue, r4.replacer); // no error

更新

此外,您可以通过函数重载实现相同的行为:


interface ReplacmentA {
    searchValue: RegExp;
    replacer: string
}

interface ReplacmentB {
    searchValue: RegExp;
    replacer: ((substring: string, ...args: any[]) => string)
}

type Replacement = ReplacmentA | ReplacmentB

function replacer(arg: ReplacmentA): ReplacmentA['replacer'];
function replacer(arg: ReplacmentB): ReplacmentB['replacer'];
function replacer<T extends Replacement>(arg: T) {
    return arg
}

const r3 = { searchValue: /bc/, replacer: 'de' };
const r4 = { searchValue: /bc/, replacer: () => 'de' };

'abc'.replace(r3.searchValue, replacer(r3)); // no error
'abc'.replace(r4.searchValue, replacer(r4)); // no error

【讨论】:

    猜你喜欢
    • 2020-03-01
    • 2017-06-23
    • 2015-11-29
    • 2021-02-16
    • 2018-11-14
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 2016-05-09
    相关资源
    最近更新 更多