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

时间:2018-03-29 08:04:27

标签: typescript generics typescript2.8 conditional-types

我试图在打字稿2.8中找到新的conditional types

例如,我有一些带有数组属性的对象,在我的流程中必须只有一个元素,我想得到这个值。 这是code I thought should work,它正确地只允许传递相关属性,但我无法弄清楚如何指定返回类型。我收到以下编译错误:

  

类型number不能用于索引类型Pick<T, { [K in keyof T]: T[K] extends any[] ? K : never; }[keyof T]>[K]

ns的类型推断为number|string而不是number nstring s }}

代码如下:

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)

1 个答案:

答案 0 :(得分:3)

您可以使用一些变通方法来实现此功能。

修复报告的错误:

要强制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。它没有详细记录,但在某些特定情况下TypeScript infers literal types而不是扩展到更一般的类型(因此当1被推断为1而不是number时,或'a'被推断为'a'而不是string时)。约束keyof ArrayProperties<T>显然不会触发此非扩展,因此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>;

现在一切都是真的。

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