Typescript:由同一对象的其他属性引用属性的类型



请参阅以下代码:

interface X {
a: string;
b: number;
c: boolean;
}
type A = {
prop: keyof X;
value: X[keyof X];
}
const a: A = { prop: 'a', value: 'a' }; // ok
const b: A = { prop: 'a', value: 1 }; // not ok, value should be string, because X['a'] is string
const c: A = { prop: 'b', value: 1 }; // ok
const d: A = { prop: 'c', value: 1 }; // not ok, value should be boolean, because X['c'] is string

这里,如果prop"a"number"b"boolean"c",我希望.value属性的类型是string,但在所有情况下都是string|number|boolean,因为keyof X可以针对type A的每次使用引用不同的密钥。如何让它两次引用同一属性,而不让它显式地将其输入到A的泛型参数中?

我觉得我应该用infer她,但我不确定怎么用,而且我可能在那里走错了路。

您希望Akeyof X中每个K{prop: K; value: X[K]}的并集,如下所示:

type A = {
prop: "a";
value: string;
} | {
prop: "b";
value: number;
} | {
prop: "c";
value: boolean;
};

在该并集的每个元素中,prop类型和value类型之间都有关联,这禁止您从X:的不同成员分配propvalue类型

const a: A = { prop: 'a', value: 'a' }; // ok
const b: A = { prop: 'a', value: 1 }; // error
const c: A = { prop: 'b', value: 1 }; // ok
const d: A = { prop: 'c', value: 1 }; // error

您还可以通过以下几种方式让编译器以编程方式为您计算此值,例如构建一个立即索引到的映射类型:

type A = { [K in keyof X]-?: {
prop: K;
value: X[K];
} }[keyof X];

可以通过IntelliSense验证A的上述定义是等效的,但现在如果修改XA将自动更新。

游乐场链接到代码

如果没有泛型,我不相信你能做到这一点。现在是:

type A<K extends keyof X> = {
prop: K;
value: X[K];
}

好消息是,当您为显式给定类型的变量赋值时,只需要指定泛型参数,如下所示:

const a: A<'a'> = { prop: 'a', value: 'a' };

而且,您的代码中可能永远不会有这种需求。例如,如果您使用此泛型类型指定函数,则不需要显式指定泛型参数即可使其工作:

function fn<K extends keyof X>(a: A<K>) {
// something
}
fn({ prop: 'a', value: 'a' }); // ok
fn({ prop: 'a', value: 1 }); // not ok, value should be string, because X['a'] is string
fn({ prop: 'b', value: 1 }); // ok
fn({ prop: 'c', value: 1 }); // not ok, value should be boolean, because X['c'] is string

游乐场链接

A的最终目标就是这种类型。(与第一个答案相同(

type A = {
prop: "a";
value: string;
} | {
prop: "b";
value: number;
} | {
prop: "c";
value: boolean;
};

为了制造这种类型,

  1. 定义PropValuePairOfX

    type PropValuePairOfX<K extends keyof X> = { prop: K; value: X[K] };
    
  2. 定义PropValuePairMapOfX

    type PropValuePairMapOfX = { [K in keyof X]: PropValuePairOfX<K> };
    // {
    //   a: {prop: 'a', value: string},
    //   b: {prop: 'b', value: number},
    //   c: {prop: 'c', value: boolean}
    // }
    
  3. 最后用PropValuePairsOfX提取PropValuePairMapOfX的值作为UnionType。

    // This type is the same as A
    type PropValuePairsOfX = PropValuePairMap[keyof X];
    //  {prop: 'a', value: string} | {prop: 'b', value: number} | {prop: 'c', value: boolean}
    //  This type is the same as our goal type 😆
    const a: PropValuePairsOfX = { prop: 'a', value: 'a' }; // ok
    const b: PropValuePairsOfX = { prop: 'a', value: 1 }; // not ok, value should be string, because X['a'] is string
    const c: PropValuePairsOfX = { prop: 'b', value: 1 }; // ok
    const d: PropValuePairsOfX = { prop: 'c', value: 1 }; // not ok, value should be boolean, because X['c'] is string
    
  4. (选项(通过使X成为泛型参数类型,我们可以使其成为更通用的类型。

    interface X {
    a: string;
    b: number;
    c: boolean;
    }
    type PropValuePair<T, K extends keyof T> = { prop: K; value: T[K] };
    type PropValuePairMap<T> = { [K in keyof T]: PropValuePair<T, K> };
    type PropValuePairsUnion<T> = PropValuePairMap<T>[keyof T];
    const validA: PropValuePairsUnion<X> = { prop: 'a', value: 'a' }; // ok
    const invalidA: PropValuePairsUnion<X> = { prop: 'a', value: 0 }; // ng
    

最新更新