为什么这种字符串类型保护不起作用?

Why isn't this type guard for string working?

我有一个接受参数的 TypeScript 方法。参数可以是 string 类型或 Object 类型。当我在方法主体中使用 typeof 检查类型 string 时,我仍然收到错误消息,指出 indexOfsubstr 未在类型 string | Object 上找到。

据我了解,if 块体应该由条件进行类型保护。我做错了什么?

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {

  var sourceParamObject: Object;
  if (typeof sourceParameters === "string") {
    if (sourceParameters.indexOf("?") >= 0)
      sourceParameters = sourceParameters.substr(sourceParameters.indexOf("?") + 1);
    sourceParamObject = $.deparam(sourceParameters, true);
  } else {
    sourceParamObject = sourceParameters;
  }

  ...
}

我正在使用 Visual Studio 2015 并安装了最新的 TypeScript (1.8.x)。

更新

事实证明,sourceParameters 变量的重新分配导致类型保护失败。我已经发布了关于此行为的相关潜在错误报告 here。同时,有几个简单的解决方法可用,我选择使用 ? 条件表达式内联检查和子字符串调用。

您需要将其转换为合适的类型:

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {
    var sourceParamObject: Object;

    if (typeof sourceParameters === "string") {
        if ((<string> sourceParameters).indexOf("?") >= 0) {
            sourceParameters = (<string> sourceParameters).substr((<string> sourceParameters).indexOf("?") + 1);
        }
        sourceParamObject = $.deparam((<string> sourceParameters), true);
    } else {
        sourceParamObject = sourceParameters;
    }

    ...
}

编辑

如果你想避免这种转换,你可以创建你自己的类型保护(你的代码中没有):

function isString(x: any): x is string {
    return typeof x === "string";
}

然后:

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {
    var sourceParamObject: Object;

    if (isString(sourceParameters)) {
        if (sourceParameters.indexOf("?") >= 0) {
            sourceParameters = sourceParameters.substr(sourceParameters.indexOf("?") + 1);
        }
        sourceParamObject = $.deparam(sourceParameters, true);
    } else {
        sourceParamObject = sourceParameters;
    }

    ...
}

更多信息在TypeScript Handbook | typeof type guards


第二次编辑

好的,玩了一会儿我明白你的代码是什么编译器问题了。
您的代码的这种变体编译没有错误:

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {
    var sourceParamObject: Object;

    if (typeof sourceParameters === "string") {
        sourceParamObject = $.deparam(sourceParameters.indexOf("?") >= 0 ? 
                sourceParameters.substr(sourceParameters.indexOf("?") + 1)
                : sourceParameters
            , true);
    } else {
        sourceParamObject = sourceParameters;
    }
}

主要区别在于您在代码中将值重新分配给 sourceParameters,这可能会使编译器感到困惑。

为了说明这个问题,考虑这个简单的例子:

function x(param: number | string): number {
    var num: number;

    if (typeof param === "string") {
        num = parseInt(param);
    } else {
        num = param;
    }

    return num;
}

function y(param: number | string): number {
    if (typeof param === "string") {
        param = parseInt(param);
    } 

    return param;
}

函数x编译没有错误,但是y有如下错误:

Argument of type 'number | string' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'

(在 the playground 中查看)