【问题标题】:Typeguard for generic union type通用联合类型的类型保护
【发布时间】:2020-01-14 08:49:30
【问题描述】:

我创建了一个联合类型:

type RequestParameterType = number | string | boolean | Array<number>;

我有一个类,它是一个持有该联合类型的键/值对:

class RequestParameter
{
    constructor(name: string, value: RequestParameterType)
    {
        this.Name = name;
        this.Value = value;
    }

    public Name: string;
    public Value: RequestParameterType;
}

然后我可以创建一个包含该 RequestParameter 的数组来保存键/值:

let parameters: Array<RequestParameter> = new Array<RequestParameter>();
parameters.push(new RequestParameter("one", 1));
parameters.push(new RequestParameter("two", "param2"));

我的想法是我可以编写一个GetParameter 函数来从该数组返回类型化的值,实际上我可能会这样使用:

// should return number type, with value 1
let numberParam: number | undefined = this.GetParameter<number>("one", parameters);

// should return string type, with value "param2"
let stringParam: string | undefined = this.GetParameter<string>("two", parameters);

// should return undefined, because param named 'two' is not number type
let undefinedParam: number | undefined = this.GetParameter<number>("two", parameters);

但是,我的函数获取类型参数时遇到问题,因为我不知道如何检查泛型类型是否与值类型匹配:

function GetParameter<T extends RequestParameterType>(parameterName: string, parameters: Array<RequestParameter>): T | undefined
{
    let result: T | undefined = undefined;

    for (let parameter of parameters)
    {
        // Type check fails: 'T' only refers to a type, but is being used as a value here.
        if (parameter.Name === parameterName && parameter.Value instanceof T )
        {
            // Possibly an issue here too: 
            // Type 'RequestParameterType' is not assignable to type 'T | undefined'.  
            // Type 'string' is not assignable to type 'T | undefined'.
            result = parameter.Value;
        }
    }

    return result;
}

我相信我可能需要编写一个类型保护函数,但我在编写类型保护时以同样的方式努力检查泛型类型。这有可能解决吗?

示例如下:in the Playground

【问题讨论】:

标签: typescript typeguards


【解决方案1】:

TypeScript 编译为 JavaScript,这是实际运行的。 T 类型及其规范如 &lt;number&gt;&lt;string&gt; 在编译时将是 erased,因此在运行时没有任何名为 T 的内容可供使用。 instanceof 运算符仅在检查类构造函数时才有效,并且由于您可能的 T 值主要是 stringboolean 之类的原始值,因此无论如何您都不想使用 instanceof ("foo" instanceof Stringfalse)。

相反,您可能需要将type guard function 作为参数传递给GetParameter(),因为这样的函数将在运行时存在。

也就是说,您可以将GetParameter() 更改为

function GetParameter<T extends RequestParameterType>(
  parameterName: string,
  parameters: Array<RequestParameter>,
  guard: (x: RequestParameterType) => x is T // new param
): T | undefined {
  let result: T | undefined = undefined;

  for (let parameter of parameters) {
    // new check using guard() instead of instanceof
    if (parameter.Name === parameterName && guard(parameter.Value)) {
      result = parameter.Value; // no error
    }
  }

  return result;
}

其中guard() 必须是一个函数,它可以获取一些RequestParameterType 的对象并将其缩小到T。以下是您可以使用的一组:

const guards = {
  number: (x: RequestParameterType): x is number => typeof x === "number",
  string: (x: RequestParameterType): x is string => typeof x === "string",
  boolean: (x: RequestParameterType): x is boolean => typeof x === "boolean",

  // the only array matching RequestParameterType is number[], so we can
  // just check to see if x is an array  without needing to inspect elements
  numberArray: (x: RequestParameterType): x is number[] => Array.isArray(x) 
};

然后你可以像这样调用GetParameter()

let numberParam = GetParameter("one", parameters, guards.number);
console.log(numberParam); // 1

let stringParam = GetParameter("two", parameters, guards.string);
console.log(stringParam); // param2

let undefinedParam = GetParameter("two", parameters, guards.number);
console.log(undefinedParam); // undefined

注意guards.number 是如何代替&lt;number&gt; 的。如果您检查numberParam 的类型,它是number | undefined,返回的值就是您所期望的。

好的,希望对您有所帮助;祝你好运!

Link to code

【讨论】:

    猜你喜欢
    • 2017-05-18
    • 1970-01-01
    • 2019-02-13
    • 1970-01-01
    • 1970-01-01
    • 2020-04-09
    • 1970-01-01
    • 2020-02-05
    • 1970-01-01
    相关资源
    最近更新 更多