【问题标题】:Array of mixed types, but having minimum count of a certain type in Array混合类型的数组,但在 Array 中具有特定类型的最小计数
【发布时间】:2019-12-06 12:40:21
【问题描述】:

假设我想对给定数组强制执行严格类型,这样:

  • 它可以包含任意数量(也就是零或更多)number
  • 它必须包含一个或多个string

...和numberstring在数组中的顺序无关,所以下面的数组都是有效的:

  • ['foo', 1, 2, 3]
  • [1, 2, 'foo', 3]
  • ['foo']

但是下面的数组是无效的:

  • [1, 2, 3](因为它需要在数组中至少有一个string

几乎为我工作的最接近的解决方案是将类型定义为:

/**
 * Array must contain:
 * - One or more string
 * - Zero or more numbers
 * @type
 */
type CustomArray = [string] & Array<number | string>

const a: CustomArray = ['foo', 1, 2, 3]; // Should pass (works as expected)
const b: CustomArray = [1, 2, 'foo', 3]; // Should pass (but doesn't with my code)
const c: CustomArray = ['string'];       // Should pass (works as expected)
const d: CustomArray = [1, 2, 3];        // Should fail (works as expected)

但这意味着数组的第一个元素必须是字符串,而不是强制整个数组的最小计数为 1。 You can test it out on TypeScript Playround here.

【问题讨论】:

标签: javascript arrays typescript


【解决方案1】:

没有办法告诉 TypeScript 数组应该包含至少一个特定类型的元素。

你能做的最好的就是创建一个数字/字符串数组:

type CustomArray = (number | string)[];

或者

type CustomArray = Array<number | string>;

然后添加required checks,您正在向/从数组中添加或读取数据。

【讨论】:

  • 如何添加&amp; { _isCustomArray: true },然后使用检查进行类型转换。
  • @JonasWilms:这将如何验证至少有一个字符串条目的要求?
  • function asCustomArray(array: (string | number)[]): CustomArray | never { ... }
  • .... 还是只使用自定义类型保护?....虽然在运行前不会进行严格的类型检查。
【解决方案2】:

这是个主意吗?

/**
 * Array must contain:
 * - One or more string
 * - Zero or more numbers
 * @type
 */
console.clear();

const CustomArray = (arr: Array<string | number>) => {
  if (!arr.length) {
    return new TypeError("Invalid: array should contain at least one element");
  }

  const oneString = arr.filter(v => v.constructor === String).length;
  const wrongTypes = arr.filter(v => v.constructor !== Number && v.constructor !== String).length && true || false;
  
  if (wrongTypes) {
    return new TypeError(`Invalid: [${JSON.stringify(arr)}] contains invalid types`);
  }

  if (!oneString) {
    return new TypeError(`Invalid: [${JSON.stringify(arr)}] should contain at least one String value`);
  }
  
  return arr;
};

type MyArray = Array<string | number> | TypeError;

const a: MyArray = CustomArray(['foo', 1, 2, 3]); // Should pass (works as expected)
const b: MyArray = CustomArray([1, 2, 'foo', 3]); // Should pass (works as expected)
const c: MyArray = CustomArray(['string']);       // Should pass (works as expected)
const d: MyArray = CustomArray([1, 2, 3]);        // Should fail (works as expected)

const log = (v:any) => 
  console.log(v instanceof TypeError ? v.message : JSON.stringify(v));

log(a);
log(b);
log(c);
log(d);

【讨论】:

  • 那你为什么不直接使用CustomArray 而不是MyArray
  • arr as MyArray 这样就有意义了。还返回一个错误真的很丑
  • 感谢您的回答!但是,我不是在寻找一个可以进行检查的函数:只是一个满足要求的类型定义。看来现在不可能了。
  • @Terry,没问题。我必须承认,我很少觉得需要使用打字稿。
【解决方案3】:

可能有一个选项可以强制数组的最小大小,但它只适用于元组方法:

type OneOrMore<T> = {
    0: T
} & T[]
type Custom = [number[], OneOrMore<string>];

const bar: Custom = [
    [
        1,
        2,
        3
    ],
    ["bar"],
];

const foo: Custom = [
    [
        1,
        2,
        3
    ],
    [], // missing zero property
];

【讨论】:

    【解决方案4】:

    似乎没有办法对具有特定类型的最小大小的数组进行编译时检查。


    以下是基于运行时检查的解决方案:

    我将引入一个字符串和数字元组,而不是一个不同类型的数组。

    type Custom = [number[], string[]];
    

    然后您可以定义一个检查函数来查看它是否满足您的要求:

    const isValid = (c: Custom): boolean => {
        const [_, strings] = c;
    
        return strings.length >= 1;
    };
    

    如果您需要将输入作为数组,您可以将它们转换为元组:

    const toCustom = (a: (string | number)[]): Custom => {
        const numbers: number[] = [];
        const strings: string[] = [];
    
        a.forEach((element) => {
            if (typeof element === "string") {
                strings.push(element);
            } else if (typeof element === "number") {
                numbers.push(element);
            } else {
                throw new Error("Unexpected type given"); // for runtime errors
            }
        });
    
        return [
            numbers,
            strings
        ];
    }
    

    然后您可以强制执行运行时检查:

    const a = isValid(toCustom([
        "foo",
        1,
        2,
        3
    ])); // Should pass
    const b = isValid(toCustom([
        1,
        2,
        "foo",
        3
    ])); // Should pass
    
    const c = isValid(toCustom(["string"]));       // Should pass 
    const d = isValid(toCustom([
        1,
        2,
        3
    ])); // should fail
    
    console.log(a, b, c, d); // prints: true true true false
    

    【讨论】:

    • 感谢您的回答!但是,我不是在寻找进行该检查的函数:只是满足要求的类型定义。看来现在不可能了。
    猜你喜欢
    • 2021-12-15
    • 1970-01-01
    • 2017-07-10
    • 2014-04-28
    • 2013-10-04
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    • 1970-01-01
    相关资源
    最近更新 更多