Typeguard для универсального типа объединения

Я создал тип объединения:

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;
}

Я считаю, что мне может понадобиться написать функцию typeguard, но я пытаюсь точно так же проверить общий тип при написании typeguard. Возможно ли это решить?

Вот пример: на игровой площадке


person PaulG    schedule 12.09.2019    source источник
comment
соответствующий FAQ   -  person jcalz    schedule 12.09.2019


Ответы (1)


TypeScript компилируется в JavaScript, который и запускается. Тип T и его спецификация, например <number> или <string>, будут удалены при компиляции, поэтому во время выполнения нет ничего, называемого T. Оператор instanceof работает только при проверке функций конструктора классов, и поскольку ваши возможные значения T в основном являются примитивами, такими как string и boolean, вы все равно не захотите использовать instanceof ("foo" instanceof String это false).

Вместо этого вам, вероятно, потребуется передать тип защитную функцию в 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 занимает место <number>. И если вы проверите тип numberParam, это number | undefined, и возвращаемые значения соответствуют вашим ожиданиям.

Хорошо, надеюсь, что это поможет; удачи!

Ссылка на код

person jcalz    schedule 12.09.2019