以下是可行的:
const equal = (privilege: string, whitelistVal: string) => privilege === whitelistVal;
const patternMatch = (privilege: string, whitelistPattern: RegExp) => whitelistPattern.test(privilege);
declare function getCompareFn(
whitelistItem: RegExp
): (string, RegExp) => boolean;
declare function getCompareFn(
whitelistItem: string
): (string, string) => boolean;
function getCompareFn(whitelistItem) {
return whitelistItem instanceof RegExp ? patternMatch : equal;
}
const whitelistItem1 = /test/;
const whitelistItem2 = "test";
const privilegeItem = "test";
getCompareFn(whitelistItem1)(privilegeItem, whitelistItem1);
getCompareFn(whitelistItem2)(privilegeItem, whitelistItem2);
链接到Try Flow
Flow 的declare 用于确定getCompareFn 的精确类型签名,否则难以表达。注意流程不会强制您声明的正确性,因此您有责任仔细分析并确保声明的类型确实与函数的实现相匹配,尽管在此示例中,这是相当微不足道的。
不使用declare,我们可以这样做:
// type alias to represent generic compare function
type CompareFn<T> = (string, T) => boolean;
function getCompareFn(
whitelistItem: RegExp | string
): CompareFn<string> | CompareFn<RegExp> {
return whitelistItem instanceof RegExp ? patternMatch : equal;
}
到目前为止一切顺利,它会进行类型检查。 (但请注意,getCompareFn 的返回值不是CompareFn<RegExp | string>)。不幸的是,getCompareFn 的这种类型规范不够精确。例如,我们知道以下是正确的:
const f: CompareFn<string> = getCompareFn('string');
然而,flow 不能断言。
更进一步,我们可能会尝试这样做来解决问题:
function getCompareFn<T: RegExp | string>(whitelistItem: T): CompareFn<T> {
return whitelistItem instanceof RegExp ? patternMatch : equal;
}
这比上面的要好得多,因为它更精确,通用函数getCompareFn 状态:
getCompareFn(string) -> CompareFn<string>
getCompareFn(RegExp) -> CompareFn<RegExp>
这正是代码的意图。一切都很好?不幸的是,这段代码没有进行类型检查。问题是T: string | RegExp,它指出:对于string | RegExp 类型的所有子类型,getCompareFn 返回CompareFn<T>。翻译为:
getCompareFn(string) -> CompareFn<string>
getCompareFn(RegExp) -> CompareFn<RegExp>
-
getCompareFn(string | RegExp) -> CompareFn<string | RegExp);
最后一种情况显然不正确,因为getCompareFn 永远无法返回同时接受字符串和正则表达式的比较函数。所以我们想表达案例1和2,但不允许案例3,我知道这样做的唯一工具是declare,因此解决方案首先在这个答案中概述。
最后,通过稍微更改代码,这是一个不使用declare 的解决方案,并且更习惯用法(在我看来):Link。
const whitelistItem1 = /test/;
const whitelistItem2 = "test";
const privilegeItem = "test";
interface TestString {
test(s: string): boolean;
};
// RegExp has method `test`, therefore structurally implements TestString
class EqualsString implements TestString {
_value: string;
constructor(value: string) {
this._value = value;
}
test(s) {
return s === this._value;
}
}
function getStringTester(whitelistItem: string | RegExp): TestString {
if (whitelistItem instanceof RegExp) {
return whitelistItem;
} else {
return new EqualsString(whitelistItem);
}
}
const t1 = getStringTester(whitelistItem1);
const t2 = getStringTester(whitelistItem2);
const b1: boolean = t1.test(privilegeItem);
const b2: boolean = t2.test(privilegeItem);