如何以IntelliSense知道工作的方式将对象键的值映射到TypeScript中的同一对象,如下例所示?
setStretchLastSection
答案 0 :(得分:3)
您可以执行以下操作:
export function getByName<
TName extends string,
TObj extends {__name: TName},
>(arr: TObj[]): Record<TName, TObj> {
return arr.reduce((acc, obj) => {
return {
...acc,
[obj.__name]: obj,
};
}, {} as Partial<Record<TName, TObj>>) as Record<TName, TObj>;
}
编辑:上面的解决方案有两个问题:
string
的键(表示obj.nonExistingKey.qux
将通过)obj.foo.qux
,它仍认为{__name: 'foo', qux: 'value'}
没问题。要解决问题1,我们可以传递数组as const
:
function getByName<TObj extends {__name: string}>(arr: readonly TObj[]) {
return arr.reduce((acc, obj) => {
return {
...acc,
[obj.__name]: obj,
};
}, {}) as Record<TObj['__name'], TObj>;
}
const obj = getByName([
{ __name: 'foo', baz: 'foobar' },
{ __name: 'bar', qux: 'quux' },
] as const);
obj.nonExistingKey.quuz // not ok NOW!
但是,我不知道泛型问题2的解决方案。如果这对您确实很重要,则可以实施some type guards。
答案 1 :(得分:2)
棘手的部分是尝试维护属性名称和该属性的特定接口之间的关系。理想情况下,属性baz
将存在于键foo
上,而不存在于键bar
上。
我知道它是半工作的,但是只有当您使用__name: 'foo' as const
表示此名称的类型仅是文字字符串'foo'
时,它才有效。否则,打字稿会将每个名称的类型视为string
,并且特定名称和特定属性之间的关联会丢失。
// standard util to get element type of an array
type Unpack<T> = T extends (infer U)[] ? U : never;
type KeyedByName<U extends {__name: string}[]> = {
[K in Unpack<U>['__name']]: Extract<Unpack<U>, {__name: K}>
}
在KeyedByName
中,我们说一个键的值为只能是其__name
属性与该键的类型匹配的数组的元素。但是,如果密钥类型仅为string
,则根本不会缩小。
如果我们使用'foo' as const
表示法,则返回类型KeyedByName
变得非常具体。
const inputsConst = [
{ __name: 'foo' as const, baz: 'foobar' },
{ __name: 'bar' as const, qux: 'quux' },
];
type K1 = KeyedByName<typeof inputsConst>
评估为
type K1 = {
foo: {
__name: "foo";
baz: string;
qux?: undefined;
};
bar: {
__name: "bar";
qux: string;
baz?: undefined;
};
}
我们知道某些属性是必需的,而其他属性则不存在(只能是undefined
)。
const checkK1 = ( obj: K1 ) => {
const fooName: string = obj.foo.__name // ok
const fooBaz: string = obj.foo.baz // required to be string
const fooQux: undefined = obj.foo.qux // can access, but will always be undefined because it doesn't exist
const fooQuuz = obj.foo.quuz // error
const barName: string = obj.bar.__name // ok
const barQux: string = obj.bar.qux // required to be string
const barBaz: undefined = obj.bar.baz // can access, but will always be undefined because it doesn't exist
const barQuuz = obj.bar.quuz // error
}
但是,在不使用foo as const
的情况下,此类型与@gurisko的答案中的Record
相比并没有更具体,因为打字稿将'foo'
和'bar'
的类型都视为{{ 1}},因此它们是等效的。
string
评估为:
const inputsPlain = [
{ __name: 'foo', baz: 'foobar' },
{ __name: 'bar', qux: 'quux' },
];
type K2 = KeyedByName<typeof inputsPlain>
所有属性都视为可选属性,无论它们来自type K2 = {
[x: string]: {
__name: string;
baz: string;
qux?: undefined;
} | {
__name: string;
qux: string;
baz?: undefined;
};
}
还是foo
。
bar
答案 2 :(得分:1)
以下答案很大程度上基于@Linda Paiste's answer。
区别在于,我不是在使用普通对象,而是在使用类作为替代对象。引用琳达:
在
KeyedByName
中,我们说一个键的值只能是其__name属性与该键的类型匹配的数组元素。但是,如果键类型只是字符串,则根本不会缩小。
我相信通过使用类,TypeScript在解压缩类型时会采用一种更有效的方法来推断类型,因为它似乎使用映射到类本身的__types
属性对对象进行类型检查。它没有将简单的对象组合为一个对象,而是有效地推断出正确的类型,从而导致KeyedByName
解决以下问题:
class Foo {
get __name(): 'foo' { return 'foo'; }
constructor(public baz: string) {}
}
class Bar {
public readonly __name: 'bar' = 'bar';
constructor(public qux: string) {}
}
type ObjectsByName = KeyedByName<typeof inputs>;
ObjectsByName
的计算结果为:
type ObjectsByName = {
foo: Foo;
bar: Bar;
}