TypeScript에서 주어진 유형의 값을 가진 객체 유형의 키를 가져오는 방법은 무엇입니까?
TypeScript에서 주어진 유형의 값을 가진 객체 유형의 키를 가져오는 방법은 무엇입니까?
나는 값이 문자열인 유형의 키로 구성된 유형을 만들려고 노력해 왔다. 의사 코드에서는 그럴 것이다.
제가 생각할 수 있는 유일한 방법은 두 단계입니다:
// a mapped type that filters out properties that aren't strings via a conditional type
type StringValueKeys<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };
// all keys of the above type
type Key<T> = keyof StringValueKeys<T>;
하지만 TS 컴파일러는 내가 문자열이 아닌 키를 조건부 유형으로 설정하여 필터링했음에도 불구하고 그것은 단순히 동일하다고 말한다.
따라서 다음과 같은 예를 들어 여전히 이를 예로 들 수 있습니다:
interface Thing {
id: string;
price: number;
other: { stuff: boolean };
}
const key: Key<Thing> = 'other';
다른 두 키의 값이 문자열이 아니기 때문에 유일하게 허용되는 값이 실제로가 아닌 경우.
이를 기본적으로 지원하기 위한 기능 요청이 에 있습니다. 그러나 이 기능이 구현되기 전까지는 여러 가지 방법으로 자신만의 버전을 만들 수 있습니다.
한 가지 방법은 와 를 사용하는 것입니다:
type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
그런 다음 속성이 일치하는 키를 꺼냅니다:
const key: KeysMatching<Thing, string> = 'other'; // ERROR!
// '"other"' is not assignable to type '"id"'
상세:
KeysMatching<Thing, string> ➡
{[K in keyof Thing]-?: Thing[K] extends string ? K : never}[keyof Thing] ➡
{
id: string extends string ? 'id' : never;
price: number extends string ? 'number' : never;
other: { stuff: boolean } extends string ? 'other' : never;
}['id'|'price'|'other'] ➡
{ id: 'id', price: never, other: never }['id' | 'price' | 'other'] ➡
'id' | never | never ➡
'id'
당신이 한 일은 다음과 같습니다:
type SetNonStringToNever<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };
정말로 문자열이 아닌 속성을 속성 값으로 바꾸는 것이었습니다. 열쇠에 손을 대지 않았어요. 여러분들은. 이 키는 의 키와 동일합니다. 그것과 가장 큰 차이점은 값이 아니라 키를 선택해야 한다는 것이다.
보충적인 답변:
버전 4.1 이후에는 대체 솔루션에 활용할 수 있습니다(핵심 논리는 jcalz의 논리와 다르지 않음). 소스 유형을 인덱싱하는 데 사용할 때 대상 유형에 할당할 수 있는 유형을 생성하지 않는 키를 필터링하고 나머지 키의 결합을 추출하기만 하면 됩니다:
type KeysWithValsOfType<T,V> = keyof { [ P in keyof T as T[P] extends V ? P : never ] : P };
interface Thing {
id: string;
price: number;
test: number;
other: { stuff: boolean };
}
type keys1 = KeysWithValsOfType<Thing, string>; //id -> ok
type keys2 = KeysWithValsOfType<Thing, number>; //price|test -> ok
에 의해 올바르게 언급된 바와 같이:
둘 다 문자열 키의 결합을 추출할 수 있습니다. 하지만, T 확장 키와 같이 더 복잡한 상황에서 사용해야 할 때는...<T, X> 그러면 TS는 솔루션을 "이해"할 수 없습니다.
위의 유형은 매핑된 유형과 함께 인덱싱되지 않고 대신 사용하기 때문에 컴파일러는 출력 유니언에 의해 인덱싱될 수 있다고 추론할 수 없습니다. 컴파일러가 이에 대해 확실히 하기 위해, 후자를 다음과 교차시킬 수 있다:
type KeysWithValsOfType<T,V> = keyof { [ P in keyof T as T[P] extends V ? P : never ] : P } & keyof T;
function getNumValueC<T, K extends KeysWithValsOfType<T, number>>(thing: T, key: K) {
return thing[key]; //OK
}
다른 사람들이 나와 같은 질문을 할까봐, 나는 리액트에서 유형 추론이 있는 일반 객체 속성으로 인덱싱하기 위해 이와 같은 패턴을 사용하려고 했지만 작동하지 않았다.
function ListWithSum<T>({
data,
value,
}: {
data: T
value: KeysMatching<T, number>
}) {
// 'item[value]' would not have type 'number', causing a type mismatch
const sum = data.reduce((total, item) => total + item[value], 0)
// ...
}
추가 유형 도입:
type PickKeysMatching<T, V> = {
[key in KeysMatching<T, V>]: V
}
그리고 그것을 구속하는 데 사용하면, 나는 안전하게 받침대에 색인을 넣을 수 있고, 유형에 맞게 정확하게 해결할 수 있다.
function ListWithSum<T extends PickKeysMatching<T, number>>({
data,
value,
}: {
data: T
value: KeysMatching<T, number>
}) {
// 'item[value]' is now a 'number'
const sum = data.reduce((total, item) => total + item[value], 0)
return (
<ul>
{data.map((item) => (
<li>{item[value]}</li>
))}
<li><b>Sum: {sum}</b></li>
</ul>
)
}
구성 요소를 사용할 때는 소품에 전달된 키에 대해서도 유형이 확인됩니다. 소품은 속성에 대한 키를 기대하기 때문에 패스하면 has type과 같은 오류가 발생합니다.
type Contract = { title: string; payment: number}
function Example(){
const contracts: Contract[] = [
{ title: 'Walking neighbourhood dogs', payment: 300 },
{ title: 'Built website for client', payment: 2000 },
{ title: 'Mowed parents lawn', payment: 50 },
]
return <ListWithSum data={contracts} value='payment' />
}