개발하자

TypeScript에서 주어진 유형의 값을 가진 객체 유형의 키를 가져오는 방법은 무엇입니까?

Cuire 2023. 6. 14. 10:33
반응형

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';

다른 두 키의 값이 문자열이 아니기 때문에 유일하게 허용되는 값이 실제로가 아닌 경우.

TypeScript 놀이터의 코드 샘플에 대한 링크




이를 기본적으로 지원하기 위한 기능 요청이 에 있습니다. 그러나 이 기능이 구현되기 전까지는 여러 가지 방법으로 자신만의 버전을 만들 수 있습니다.

한 가지 방법은 와 를 사용하는 것입니다:

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' />
}

반응형