在映射对象的情况下,打字稿保持顺序或返回值类型

Vel*_*dan 0 iteration mapping types typescript

我正在研究 at 对象的简单映射器。它应该接受对象属性的数组并返回这些属性值的数组。它工作正常,但对于 Typescript,我无法保存返回值的类型顺序。

这是例子

import { createStore, Store as FriendsStore } from "./mobx_react_lite_store";
import { createAccountsStore, AccountsStoreType } from "./accountsStoreLite";

type RootObj = {
    friendsStore: FriendsStore,
    accountsStore: AccountsStoreType
};

const rootStore: RootObj = {
    friendsStore: friendsStore,
    accountsStore: accountsStore
};

function pickStores<T, K extends keyof T>(rootStore: T, keys: K[]): T[K][] {
    return keys.map((key: K) => {
        const store = rootStore[key];

        if (!store) {
            throw new Error(`Can't find the store with name ${key} in rootStore. Myabe yo've forgottent
            to add this store to the rootStore.`);
        }

        return store;
    }) 
}
Run Code Online (Sandbox Code Playgroud)

当我尝试仅用一家商店来调用它时,它对前任来说就像一个魅力

// Works fine. It shows that accountsStore has AccountsStoreType
const [ accountsStore ] = pickStores(rootStore, ["accountsStore"]);

// Works fine. It shows that accountsStore has friendsStore type
const [ friendsStore ] = pickStores(rootStore, ["friendsStore"]);
Run Code Online (Sandbox Code Playgroud)

但是当我尝试用 2 个存储调用它时,返回的类型不保持某些顺序。所以friendsStore可能是friendsStore 或AccountsStoreType。同样对于accountsStore

const [ friendsStore, accountsStore ] = pickStores(rootStore, ["friendsStore", "accountsStore"]);
Run Code Online (Sandbox Code Playgroud)

这是截图

是否可以为每个被破坏的财产保存正确的类型?谢谢你的帮助。

UPDATE lukasgeiter提供了正确的答案,但 Typescript 有一些有趣的技巧。我调查了它们,查找了一些文档等,以下是了解这些功能的有用资源:

希望此信息对某人有所帮助。

关于类型

允许从对象和交集类型派生

关于查找类型

关于映射类型

luk*_*ter 7

为了解决这个问题,我们必须弄清楚两件事......

1. 元组

为了能够使用数组中元素的各个类型,keys我们需要 TypeScript 将其视为元组:

当前 TypeScript 将参数的类型推断keys为:

("friendsStore" | "accountsStore")[]
Run Code Online (Sandbox Code Playgroud)

然而,我们想要的是这样的类型:

["friendsStore", "accountsStore"]
Run Code Online (Sandbox Code Playgroud)

为了实现这一点,有几种解决方案:

铸件

最简单但也是最详细的选项是将数组转换为元组:

<["friendsStore", "accountsStore"]>["friendsStore", "accountsStore"]
Run Code Online (Sandbox Code Playgroud)

常量

或者,我们也可以将数组转换为const. 这会将其转换为readonly元组:

<const>["friendsStore", "accountsStore"] // -> readonly ["friendsStore", "accountsStore"]
Run Code Online (Sandbox Code Playgroud)

剩余参数

如果我们更改keys为剩余参数,TypeScript 会将其类型推断为元组。继续阅读以查看其实际效果。

2. 映射类型

使用正确类型的元组,我们可以构造一个返回类型,它将键映射到以下类型T

{ [I in keyof K]: T[keyof T & K[I]] }
Run Code Online (Sandbox Code Playgroud)
  • [I in keyof K]循环遍历数组的元素keysI是索引而不是元素本身。
  • K[I]获取元素的类型
  • T[keyof T & K[I]]K[I]在 上查找属性的类型T。我必须添加keyof T &才能满足编译器的要求。如果丢失它会抱怨 that K[I]is not a key of T,即使我们知道情况总是如此。


完整代码

我创建了两个版本的代码,其中一个版本应该适用。一种使用剩余参数,另一种使用const强制转换。

不幸的是我不得不any在函数内部引入强制转换。也许可以将其转换为更具体的内容,但这是我能找到的最好的。

带剩余参数

请注意由于剩余参数而导致我们调用函数的方式发生了变化

function pickStores<T, K extends (keyof T)[]>(rootStore: T, ...keys: K): { [I in keyof K]: T[keyof T & K[I]] } {
    return keys.map((key: keyof T) => {
        // ...
    })  as any;
}

const [ friendsStore, accountsStore ] = pickStores(rootStore, "friendsStore", "accountsStore");
Run Code Online (Sandbox Code Playgroud)

与常量演员

function pickStores<T, K extends readonly (keyof T)[]>(rootStore: T, keys: K): { [I in keyof K]: T[keyof T & K[I]] } {
    return keys.map((key: keyof T) => {
        // ...
    })  as any;
}

const [ friendsStore, accountsStore ] = pickStores(rootStore, ["friendsStore", "accountsStore"] as const);
Run Code Online (Sandbox Code Playgroud)