首先,我不确定我的代码出了什么问题,所以如果有更好的标题建议,我会编辑它,谢谢.

我试着用联合类型的"key"来索引一个对象,如果键的类型缩小了,没有问题,但如果类型是联合类型,TS就不满意了,为什么?

我也不理解TS说的错误,那个交叉点类型是从哪里来的?

playground

type FetchedData = {
    A?: {
        A:'A',
        otherField: string
    },
    B?: {
        B:'B',
        otherField: string
    },
    C?: {
        C:'C',
        otherField: string
    },
};

type CachedData = {
    A: {
        A:'A'
    },
    B: {
        B:'B'
    },
    C: {
        C:'C'
    },
};

const cachedData: CachedData = {
    A: {
        A:'A'
    },
    B: {
        B:'B'
    },
    C: {
        C:'C'
    },
}

type Key = keyof FetchedData;

const key: Key = Math.random() > 0.6 ? 'A' : Math.random() > 0.3 ? 'B' : 'C';

const fetchedData: FetchedData = {};

switch (key) {
    case "A" : {
        const cache = cachedData[key]
        fetchedData[key] = {...cache, otherField: 'otherField'}
    }
    // the above is okay, but te below is not, why?
    default : {
        const cache = cachedData[key]
        /* 
            const cache: {
                A: 'A';
            } | {
                B: 'B';
            } | {
                C: 'C';
            }
        */
        fetchedData[key] = {...cache, otherField: 'otherField'}
        /* 
            Type '{ otherField: string; A: "A"; } | { otherField: string; B: "B"; } | { otherField: string; C: "C"; }' is not assignable to type '({ A: "A"; otherField: string; } & { B: "B"; otherField: string; } & { C: "C"; otherField: string; }) | undefined'.
            Type '{ otherField: string; A: "A"; }' is not assignable to type '{ A: "A"; otherField: string; } & { B: "B"; otherField: string; } & { C: "C"; otherField: string; }'.
            Property 'B' is missing in type '{ otherField: string; A: "A"; }' but required in type '{ B: "B"; otherField: string; }'.ts(2322)
        
        */

        // I don't understand why TS says "... not assignable to type '({ A: "A"; otherField: string; } & { B: "B"; otherField: string; } & ..." where does that intersection come from?
    }
}

推荐答案

TypeScrip并不真正支持correlated 100.由于key是并集类型,因此const cache = cachedData[key]fetchedData[key] = {...cache, otherField: 'otherField'}都涉及并集类型.但编译器只是根据它们的types而不是key的标识来独立处理它们.假设您有key1key2,而不是key,这两个并集类型都与key的并集类型相同.您不希望编译器接受fetchedData[key1] = {...cachedData[key2], otherField: 'otherField'},对吗?因为虽然key2key1具有相同的联合类型,但它们可能不是相同的value.仅从类型系统的Angular 来看,无法将该场景与您的代码区分开来.intersection来自编译器,说明无论key1key2是什么,都要确保这样的赋值是安全的.有关引入此交叉行为的PR及其工作原理,请参见microsoft/TypeScript#30769.

这就是问题所在.您的代码可能没有问题,但编译器不能遵循逻辑.


如果您只想做最方便的事情,请接受您比这里的编译器更聪明的事实,并使用type assertion来 suppress 错误:

const cache = cachedData[key]
fetchedData[key] = { ...cache, otherField: 'otherField' } as any

否则:

如果您要使用unions 类型,则需要使用逐个 case 分析,如switch/caseif/else,在对unions 成员进行操作之前将key缩小到单个unions 成员……单一的代码块不会起作用.如果您想使用单个代码块,那么您需要从联合类型切换到generics,从constrained切换到联合.该特定重构技术在microsoft/TypeScript#47109中被描述为支持相关联合的一种方式.

在示例代码中,它可能如下所示.从你的基本CachedData类型开始:

type CachedData = {
    A: { A: 'A' },
    B: { B: 'B' },
    C: { C: 'C' },
};
type Key = keyof CachedData;
const cachedData: CachedData = {
    A: { A: 'A' },
    B: { B: 'B' },
    C: { C: 'C' },
}

然后将FetchedData表示为mapped type而不是CachedData,并在您关心的关键字子集中将其设置为generic:

type FetchedData<K extends Key = Key> =
    { [P in K]?: CachedData[P] & { otherField: string } }
const fetchedData: FetchedData = {};

最后,我们可以将您的单个代码块编写为超过K extends Key的泛型函数体:

function f<K extends Key>(key: K) {
    const cache = cachedData[key];
    // const cache: CachedData[K]
    const _fetchedData: FetchedData<K> = fetchedData;
    const v = { ...cache, otherField: 'otherField' };
    // const v: CachedData[K] & { otherField: string; }
    _fetchedData[key] = v;
}

这是因为cache是泛型类型CachedData[K],而_fetchedData[key]被视为泛型类型CachedData[K] & { otherField: string }.这两种类型都不是联合类型.并且值{...cache, otherField:'otherField'}被视为与_fetchedData[key]相同的类型CachedData[K] & { otherField: string }.

最后,您可以使用联合类型的key作为输入来调用函数:

const key: Key =
    Math.random() > 0.6 ? 'A' : Math.random() > 0.3 ? 'B' : 'C';

f(key); // okay

为了安抚这里的编译器,这是一种相当复杂的做法.您必须重写某些类型,并将某些值复制到适当的类型.就我个人而言,我可能只会使用类型断言,而不会费心重构,除非我真的特别需要更喜欢编译器验证的类型而不是权宜之计.

Playground link to code

Typescript相关问答推荐

动态判断TypScript对象是否具有不带自定义类型保护的键

node—redis:如何在redis timeSeries上查询argmin?

是否可以根据嵌套属性来更改接口中属性的类型?

如何将联合文字类型限制为文字类型之一

从子类构造函数推断父类泛型类型

表单嵌套太深

为什么我的查询钩子返回的结果与浏览器的网络选项卡中看到的结果不同?

使用TypeScrip5强制使用方法参数类型的实验方法修饰符

如何缩小函数的返回类型?

声明遵循映射类型的接口

如何为特定参数定义子路由?

是否将Angular *ngFor和*ngIf迁移到新的v17语法?

创建一个函数,该函数返回一个行为方式与传入函数相同的函数

如何静态键入返回数组1到n的函数

Cypress-验证别名的存在或将别名中的文本与';if';语句中的字符串进行比较

TypeScrip:从嵌套的对象值创建新类型

可以用任意类型实例化,该类型可能与

TypeScript:如何使用泛型和子类型创建泛型默认函数?

如何键入';v型';

为什么 TypeScript 类型条件会影响其分支的结果?