【问题标题】:Returning the strict item-type of an array-property by a given key and an object通过给定键和对象返回数组属性的严格项类型
【发布时间】:2022-01-26 19:57:46
【问题描述】:

我正在尝试提供一个帮助器/包装器函数 (get_item),它应该返回一个对象属性的单个项目,但不知何故我无法完成正确的输入。

// without typings
function get_item(foo, key, index) {
    const array = foo[key];
    // check if the index exists
    if (index in array === false) throw new Error("Item " + index + " not found for " + key);
    return array[index];
}
// usage example 
const item = get_item({bar:[0,1,2]}, 'bar', 0);

一旦我尝试推断arrayArrayItemType,我的尝试似乎失败了。

简单地通过给定的key返回array的类型是没有问题的,如here所示。

但除此之外,我知道,我的 foo-object 中只有 Array 类型的属性,并且想要返回我通过 key 选择的数组中单个项目的类型,但这失败了,因为 typescript 在使用索引运算符访问数组时似乎忘记了它与key 属性的关系。

Typescript 4.5.4 Playground (same code as below)

/**
 * I want to get the item at position 'index' 
 * from the properties with name 'key'
 * of the structure 'Foo'
 */
function get_item<
    KEY extends keyof Foo,  // "zoo" | "bar"
>(foo: Foo, key: KEY, index:number)
    : ArrayItemType<Foo[KEY]> // determines the type of a single element for the property 'key' beeing number|string
{
    const array: Foo[KEY] = foo[key];   // this can be Array<number> or Array<string> depending on 'key'
    // check if the index exists
    if (index in array === false) throw new Error("Item " + index + " not found for " + key);
    const item = array[index];          // at this point the type seems to have collapsed to number|string,
                                        // discarding the fact that we know the exact type of 'array' for a given 'key'
    return item;    // Error
                    // Type 'string | number' is not assignable to type 'ArrayItemType<Foo[KEY]>'. 
                    // Type 'string' is not assignable to type 'ArrayItemType<Foo[KEY]>'.
}

type ArrayItemType<ARRAY> = ARRAY extends Array<infer ITEM> ? ITEM : never;

/**
 * Some interface with a few properties of type Array<any>
 */
interface Foo {
    bar : Array<number>;
    zoo : Array<string>;
}

const foo : Foo = {
    bar: [0,1],
    zoo: ["a", "b"],
};
// determines correct types here, this is the way i want it
const number_item :number = get_item(foo, 'bar', 1);
const string_item :string = get_item(foo, 'zoo', 1);

当然添加as ArrayItemType&lt;Foo[KEY]&gt; 确实可以解决问题, 但我想将这些类型转换保持在最低限度,特别是当我不明白为什么它不应该工作时(这让我想,我在某处误解了我的代码)。

那么,我错过了什么吗?
这不应该是不可能的吗?
还是KEY extends keyof Foo 的问题再次出现,允许的值比我知道的要多?

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    你可以这样做:

    TS Playground

    /** Throws if value is undefined */
    function getItem <
      K extends PropertyKey,
      T extends Record<K, readonly unknown[]>,
      I extends number,
    >(obj: T, key: K, index: I): T[K][I] extends undefined ? never : T[K][I] {
      const arr = obj[key];
      if (!Array.isArray(arr)) throw new Error(`Property "${key}" not found`);
      const value = arr[index];
      if (typeof value === 'undefined') throw new Error(`Value not found at index ${index}`);
      return value;
    }
    
    getItem({bar: [2, 4, 6]}, 'bar', 0); // number
    getItem({bar: [2, 4, 6] as const}, 'bar', 0); // 2
    
    interface Foo {
      bar: number[];
      zoo: string[];
    }
    
    const foo: Foo = {
      bar: [0, 1],
      zoo: ['a', 'b'],
    };
    
    getItem(foo, 'bar', 1); // number
    getItem(foo, 'zoo', 1); // string
    getItem({hello: ['world'] as const}, 'hello', 1); // never, will throw
    

    但是,请注意 TypeScript 中索引签名和数组的性质:

    getItem(foo, 'zoo', 2); // string but actually never because it will throw
    

    【讨论】:

    • 啊,所以我实际上只是缺少使用通用索引类型。使用I extends numberFoo[KEY][I] 作为返回类型就是我想要的行为方式。虽然我仍然不太确定为什么有必要这样做。
    猜你喜欢
    • 2022-01-01
    • 1970-01-01
    • 2015-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 2022-01-01
    • 1970-01-01
    相关资源
    最近更新 更多