仅接受数组值键的函数(并推导 return 类型)

Function that accepts only keys of array values (and deduces return type)

我正在尝试理解打字稿 2.8 中的新 conditional types

例如,我有一些具有数组属性的对象,在我的流中必须只有一个元素,我想获得这个值。 这是 code I thought should work,它正确地只允许传入相关属性,但我不知道如何指定 return 类型。我收到以下编译错误:

Type number cannot be used to index type Pick<T, { [K in keyof T]: T[K] extends any[] ? K : never; }[keyof T]>[K].

并且 ns 的类型被推断为 number|string 而不是 nnumberstrings.

代码如下:

type ArrayProperties<T> = Pick<T, {
    [K in keyof T]: T[K] extends Array<any>? K : never
}[keyof T]>;

const obj = {
    a: 4,
    n: [2],
    s: ["plonk"]
};

// Compilation error on next line
function single<T, K extends keyof ArrayProperties<T>>(t: T, k: K): ArrayProperties<T>[K][number] {
    const val = t[k];
    if (!Array.isArray(val))
        throw new Error(`Expected ${k} to be an array`);

    if (val.length !== 1) 
        throw new Error(`Expected exactly one ${k}`);

    return val[0];
}

const n = single(obj, "n"); // 'n' should be of type 'number'
const s = single(obj, "s"); // 's' should be of type 'string'
const a = single(obj, "a"); // Should fail to compile (OK)

您可以使用一些解决方法来实现此目的。

修复报告的错误:

要强制 TypeScript 编译器在无法验证 属性 存在时查找 属性,您可以将键类型与已知键相交。像这样:

type ForceLookup<T, K> = T[K & keyof T]; // no error

所以你可以改变

ArrayProperties<T>[K][number]

ForceLookup<ArrayProperties<T>[K],number>

让我们确保它有效:

type N = ForceLookup<ArrayProperties<typeof obj>["n"],number>; // number ✔️
type S = ForceLookup<ArrayProperties<typeof obj>["s"],number>; // string ✔️

ns 推断更窄的类型:

问题是 K 没有被推断为字符串文字。要提示编译器应该尽可能为类型参数推断字符串文字,您可以添加约束 extends string。它不是 well-documented,但在某些特定情况下 TypeScript infers literal types 而不是扩大到更一般的类型(因此当 1 被推断为 1 而不是 number,或者当 'a' 被推断为 'a' 而不是 string 时)。约束 keyof ArrayProperties<T> 显然不会触发此 non-widening,因此在所有情况下 K 都扩大到 keyof ArrayProperties<T>。这是 K 的解决方法:

K extends string & keyof ArrayProperties<T>

让我们看看实际效果:

declare function single<T, K extends string & keyof ArrayProperties<T>>(
  t: T, k: K): ForceLookup<ArrayProperties<T>[K],number>;
const n = single(obj, "n"); // number ✔️
const s = single(obj, "s"); // string ✔️
const a = single(obj, "a"); // still error ✔️

全部完成!


好吧,我想在这里做一些简化。 ArrayProperties<T>[K] 可以减少到 T[K],对于任何你可以实际使用的 K。所以你得到:

declare function single<T, K extends string & keyof ArrayProperties<T>>(
  t: T, k: K): ForceLookup<T[K],number>;

现在全部完成。


希望对您有所帮助。祝你好运!