TypeScript:要求两个数组的长度相同?

IT技术 javascript reactjs typescript
2021-04-26 16:22:11

说我正在做Tab+Panel调用的组件TabsPanels我想确保我得到了相同数量的TabPanel组件,如下所示:

type TabsPanelsProps = {
  tabs: Tab[];
  panels: Panel[];
}
<TabsPanels
  tabs={[<Tab/>, <Tab/>]}
  panels={[<Panel/>]} // Error: tabs.length and panels.length do not match
/>

有没有办法做到这一点?如果有一些效用函数,比如

PropsAreEqual<T, K1, K2, P>
where
T = type
K1 = key 1
K2 = key 2
P = the property to be equal

这显然很糟糕,但你明白我在说什么。那么你可以做

PropsAreEqual<TabsPanelsProps, 'tabs', 'panels', 'length'>
4个回答

我看到一些关于推断数组文字长度的问题的答案。问题在于,当您将数组文字传递给函数时,编译器通常会将其扩展为数组,并且不会将其解释为固定长度的元组。这通常是您想要的;通常数组会改变长度。当您希望编译器将其[1, 2]视为一对而不是数组时,您可以给编译器一个提示:

function requireTwoSameLengthArrays<
    T extends readonly [] | readonly any[]
>(t: T, u: { [K in keyof T]: any }): void { }

请注意,泛型类型参数T泛型约束是空元组类型[]和数组类型的联合any[](不用担心readonly; 这个修饰符使函数更通用,而不是更具体,因为它string[]可以赋值给readonly string[]而不是反之亦然。)在联合中拥有空元组类型不会改变可以成为的东西种类T(之后所有,any[]已经包括空元组[])。但它确实给编译器一个提示,即需要元组类型。

所以编译器会推断[1, 2]as[number, number]而不是 as number[]


检查上面的签名,您会看到u参数是映射的数组/元组类型如果T是元组,{[K in keyof T]: any}则是与 长度相同的元组T

让我们看看它的实际效果:

requireTwoSameLengthArrays([1, 2], [3, 4]); // okay
requireTwoSameLengthArrays([1, 2], [3]); // error! property 1 is missing in [number]!
requireTwoSameLengthArrays([1, 2], [3, 4, 5]); // error! length is incompatible!

万岁!


请注意,如果编译器已经忘记了元组的长度,这将不起作用:

const oops = [1, 2]; // number[]
requireTwoSameLengthArrays(oops, [1, 2, 3]); // okay because both are of unknown length

的类型oops被推断为number[],并将其传递给requireTwoSameLengthArrays()无法撤消该推断。太晚了。如果您希望编译器只拒绝完全未知长度的数组,您可以这样做:

function requireTwoSameLengthTuples<
    T extends (readonly [] | readonly any[]) & (
        number extends T["length"] ? readonly [] : unknown
    )>(t: T, u: { [K in keyof T]: any }): void { }

这是更丑陋的,但它所做的是检查是否T有长度number而不是某些特定的数字文字如果是这样,它会通过要求一个空元组来阻止匹配。这有点奇怪,但它有效:

requireTwoSameLengthTuples([1, 2], [3, 4]); // okay
requireTwoSameLengthTuples([1, 2], [3]); // error! [number] not [any, any]
requireTwoSameLengthTuples([1, 2], [3, 4, 5]); // error! ]number, number, number]

requireTwoSameLengthTuples(oops, [1, 2, 3]); // error on oops!
// Types of property 'length' are incompatible.

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

Playground 链接到代码

这可以通过要求消费者将长度泛型类型传递给函数来实现

function same<T extends number>(
  nums: (readonly number[] & { readonly length: T }),
  strings: (readonly string[] & { readonly length: T })
) { }

same<2>(
  [3, 4] as const,
  ['1', '4'] as const
)

唯一的限制是您需要传入<2>or else typescript足以推断泛型numberfor N,您还需要声明所有参数as const以使它们通过类型擦除丢失元组长度

为了让它在react渲染函数中工作,你需要做一些额外的一些丑陋的 TypeScript 条件类型

这是给你的部分答案:

type ArrayOfFixedLength<T extends any, N extends number> = readonly T[] & { length: N }; 

const a1: ArrayOfFixedLength<number, 2> = [1] as const; 
const a2: ArrayOfFixedLength<number, 2> = [1, 2] as const; 


function myFunction<N extends number>(array1: ArrayOfFixedLength<any, N >, array2: ArrayOfFixedLength<any, N>) {
return true; 
}

myFunction<3>([1, 2, 3] as const, [2, 3, 4] as const); 
myFunction<2>([1, 2] as const, [1, 2, 3] as const);

// However, if you don't specify the array length, 
// It fails to error
myFunction([1, 2, 3] as const, [2, 3, 4] as const); 
myFunction([1, 2] as const, [1, 2, 3] as const);

操场

解释

因为我们本质上是使用length数组属性来检查它们是否相等,所以需要将数组视为不可变的,因此使用readonlyandas const关键字。

我们所做的是声明我们有一个数组,并且该数组的长度始终为 N。

现在,我不明白这段代码是为什么myFunction<3>需要通用参数 in 我不知道为什么传入的参数没有推断出 N 的值。

现在,在您的情况下,您最好在运行时检查数组长度并抛出错误。请记住,typescript是为了开发人员的体验,通常是在错误发生之前捕获错误,但在这种情况下,执行运行时错误可能是捕获此类错误的最快和最简单的方法。

迭代@jcalz 答案从 @cancerbero获得灵感,我发现了一个稍微不同的策略,它在我的情况下效果很好:

type Tuple<T> = readonly T[] | readonly [];
type TupleOfLength<T, Length extends number> = Length extends 0 ? readonly [] :
    readonly [T, ...T[]] & { readonly length: Length };

type LengthOfTuple<Tuple extends readonly any[]> = number extends Tuple['length'] ? never : Tuple['length'];

function requireTwoSameLengthTuples<T, FirstArray extends Tuple<T>>(
    firstArray: FirstArray,
    secondArray: TupleOfLength<T, LengthOfTuple<FirstArray>>
) {
    // Do your processing
}

requireTwoSameLengthTuples([1, 2], []); // fails
requireTwoSameLengthTuples([1, 2], [3]); // fails
requireTwoSameLengthTuples([1], [2, 3]); // fails
requireTwoSameLengthTuples([1, 2], [3, 4]); // works

const array1 = [1, 2];
const array2 = [3, 4];
requireTwoSameLengthTuples(array1, array2); // fails too

typescript游乐场