【问题标题】:Function that accepts only keys of array values (and deduces return type)仅接受数组值键的函数(并推断返回类型)
【发布时间】:2018-09-08 02:35:46
【问题描述】:

我正在尝试在 typescript 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]

并且nsns 的类型被推断为number|string 而不是numberstring

代码如下:

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)

【问题讨论】:

  • 你不能使用ArrayProperties&lt;T&gt;[K][number]作为返回类型,因为打字稿没有检查数组属性中元素的数据类型,例如。 s: ["plonk"] 这里的返回类型是 string 而不是数字
  • 关于 n,s 的类型,它作为联合类型返回,而不是单个类型。您可以使用 ArrayProperties&lt;T&gt;[K][any] 或简单地使用 ArrayProperties&lt;T&gt;[K] 它应该编译
  • @Niladri 我认为打字稿应该推断出每个K 的类型,因此如果用"n" 调用,那么ArrayProperties&lt;T&gt;["n"] 应该是一个数字数组,ArrayProperty&lt;T&gt;["n"][number] 应该是number跨度>

标签: typescript generics typescript2.8 conditional-types


【解决方案1】:

您可以使用一些解决方法来使其正常工作。

修复报告的错误:

要强制 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&lt;T&gt; 显然不会触发这种不扩大,所以K 在所有情况下都扩大到keyof ArrayProperties&lt;T&gt;。这是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 ✔️

全部完成!


好吧,我会在这里做一些简化。对于您可以实际使用的任何KArrayProperties&lt;T&gt;[K] 可以简化为T[K]。所以你得到:

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

现在一切都完成了。


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

【讨论】:

  • 我在度假,现在才验证您的代码是否有效……我不得不说这不太直观。谢谢你教育我!
猜你喜欢
  • 1970-01-01
  • 2019-01-07
  • 1970-01-01
  • 2020-08-06
  • 2015-10-20
  • 1970-01-01
  • 2021-06-16
  • 1970-01-01
  • 2018-03-03
相关资源
最近更新 更多