Typescript : 매핑 된 유형의 인덱스 서명

Nov 23 2020

어떻게 형이 걸릴 수 { 'k': number, [s: string]: any }와 추상적 인 이상 'k'number? 해당 유형 TT<'k', number>제공 하는 유형 별칭을 갖고 싶습니다 .


다음 예를 고려하십시오.

function f(x: { 'k': number, [s: string]: any }) {}                           // ok
type T_no_params = { 'k': number, [s: string]: any };                         // ok
type T_key_only<k extends string> = { [a in k]: number };                     // ok
type T_value_only<V> = { 'k': V, [s: string]: any};                           // ok
type T_key_and_index<k extends string, V> = { [a in k]: V, [s: string]: any };// ?
  • { 'k': number, [s: string]: any}함수의 매개 변수 유형으로 직접 사용하면 f작동합니다.
  • -alias 작업 [s: string]: any에서 색인 된 부분 사용type
  • k extends stringin type-alias 사용 도 작동합니다.
  • 결합하면 k extends string과를 [s: string]: any동일에 type-alias, 나는 구문 분석 오류 수 (심지어 의미 론적 오류를, 심지어는 유효한 구문이 될 것 같지 않습니다).

여기에서 작동하는 것 같습니다.

type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V }

그러나 여기에서는 왜 추가 속성에 대해 불평하지 않는지 이해할 수 없습니다 (오른쪽에있는 유형은 추가 속성이있는 &개체를 허용하지 않아야 함).


편집 :

&집합 이론적 교차점과 유사하게 동작해야하는 교차 연산자 인이 답변에서 여러 번 언급되었습니다 . 그러나 다음 예제에서 보여주는 것처럼 추가 속성을 처리 할 때는 그렇지 않습니다.

function f(x: {a: number}){};
function g(y: {b: number}){};
function h(z: {a: number} & {b: number}){};

f({a: 42, b: 58});  // does not compile. {a: 42, b: 58} is not of type {a: number}
g({a: 42, b: 58});  // does not compile. {a: 42, b: 58} is not of type {b: number}
h({a: 42, b: 58});  // compiles!

이 예 {a: 42, b: 58}에서는가 type {a: number}도 아니고 type도 아닌 것처럼 보이지만 {b: number}어떻게 든 교차점에서 끝납니다 {a: number} & {b: number}. 그것은 집합 이론적 교차가 작동하는 방식이 아닙니다.

그것이 바로 제 &제안이 저에게 의심스러워 보이는 이유 입니다. 누군가 매핑 된 유형과 "교차하는"유형 { [s: string]: any }이 유형을 더 작게 만드는 대신 "더 크게"만들 수 있는 방법을 자세히 설명해 주시면 감사하겠습니다 .


나는 질문을 보았다

  • Typescript의 매핑 된 유형에 대한 색인 서명
  • 매핑 된 유형에 대한 인덱스 서명을 추가하는 방법

그러나 그것들은 비슷한 이름을 가지고 있음에도 불구하고 직접적인 관련이있는 것처럼 보이지 않았습니다.

답변

1 arturgrzesiak Dec 01 2020 at 18:58

type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V }당신이 추구하는 유형을 정의하는 올바른 방법입니다. 그러나 알아야 할 한 가지는 ( deprecated flag : keyofStringsOnly ) :

keyof 유형 연산자는 문자열을 반환 | 문자열 인덱스 서명이있는 유형에 적용될 때 문자열 대신 숫자입니다.

인덱스를 string유형이 아닌 유형 으로 제한하는 방법을 모르겠습니다 string | number. 실제로 인덱스 number액세스를 허용 하는 string것은 Javascript가 작동하는 방식 (항상 숫자를 문자열화할 수 있음)과 일치하기 때문에 합리적인 것으로 보입니다. 반면에 문자열 값으로 숫자 인덱스에 안전하게 액세스 할 수 없습니다.


&이 항상 가능한 값의 설정 제한합니다 (또는 잎을 변경하지만, 확장하지 않음) - 타입 연산자는 이론적 교점을 설정하는 유사하게 작동합니다. 귀하의 경우 유형은 문자열과 유사하지 않은 키를 색인으로 제외합니다. 정확하게 unique symbol는 인덱스로 제외 합니다.

Typescript가 함수 매개 변수를 처리하는 방식에서 혼란이 올 수 있다고 생각합니다. 명시 적으로 정의 된 매개 변수로 함수를 호출하는 것은 매개 변수를 변수로 전달하는 것과는 다르게 작동합니다. 두 경우 모두 Typescript는 모든 매개 변수가 올바른 구조 / 모양인지 확인하지만 후자의 경우 추가 소품을 허용하지 않습니다.


개념을 설명하는 코드 :

type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V };
type WithNumber = HasKeyValue<"n", number>;
const x: WithNumber = {
  n: 1
};

type T = keyof typeof x; // string | number
x[0] = 2; // ok - number is a string-like index
const s = Symbol("s");
x[s] = "2"; // error: cannot access via symbol

interface N {
  n: number;
}

function fn(p: N) {
  return p.n;
}

const p1 = {
  n: 1
};

const p2 = {
  n: 2,
  s: "2"
};

fn(p1); // ok - exact match
fn(p2); // ok - structural matching: { n: number } present;  additional props ignored
fn({ n: 0, s: "s" }); // error: additional props not ignore when called explictily
fn({}); // error: n is missing

편집하다

객체 리터럴-명시 적으로 어떤 모양의 객체를 만드는 const p: { a: number} = { a: 42 }것은 Typescript에 의해 특별한 방식으로 처리됩니다. 일반적인 구조적 추론과는 반대로 유형이 정확히 일치해야합니다. 솔직히 말해서, 안전하지 않은 추가 캐스트가없는 추가 속성은 어쨌든 액세스 할 수 없기 때문에 이치에 맞습니다.

[...] 그러나 TypeScript는이 코드에 버그가있을 수 있다는 입장을 취합니다. 객체 리터럴은 다른 변수에 할당하거나 인수로 전달할 때 특별한 처리를 받고 과도한 속성 검사를받습니다. 객체 리터럴에 "대상 유형"에없는 속성이 있으면 오류가 발생합니다. [...] 이러한 검사를 처리하는 마지막 방법 중 하나는 약간 의외 일 수 있지만 객체를 다른 변수에 할당하는 것입니다.

TS 핸드북

이 오류를 해결하는 또 다른 옵션은 ...와 교차하는 것입니다 { [prop: string]: any }.

추가 코드 :

function f(x: { a: number }) {}
function g(y: { b: number }) {}
function h(z: { a: number } & { b: number }) {}

f({ a: 42, b: 58 } as { a: number }); // compiles - cast possible, but `b` inaccessible anyway
g({ a: 42 } as { b: number }); // does not compile - incorrect cast; Conversion of type '{ a: number; }' to type '{ b: number; }' may be a mistake
h({ a: 42, b: 58 }); // compiles!

const p = {
  a: 42,
  b: 58
};

f(p); // compiles - regular structural typing
g(p); // compiles - regular structural typing
h(p); // compiles - regular structural typing

const i: { a: number } = { a: 42, b: 58 }; // error: not exact match
f(i); // compiles
g(i); // error
h(i); // error
1 artcorpse Nov 30 2020 at 18:05

교차로 연산자에 대한 추론 방법이 있습니다. 도움이 될 수 있습니다.

type Intersection = { a: string } & { b: number }

당신이 읽을 수있는 Intersection"속성이있는 객체로 a유형 string 속성 b유형을 number". 이 간단한 유형도 설명합니다.

type Simple = { a: string; b: number }

그리고 두 가지 유형이 호환됩니다. 거의 모든 용도로 하나를 다른 것으로 바꿀 수 있습니다.

이것이 HasKeyValue정의하려는 유형과 실제로 동일한 이유를 설명하기를 바랍니다 .

T_key_and_index작동하지 않는 이유 는 첫 번째 부분 인 [a in k]: V은 매핑 된 유형을 정의하고 매핑 된 유형 의 정의에서 추가 속성을 가질 수 없기 때문입니다. 매핑 된 유형에 추가 속성을 추가해야하는 경우를 사용하여 유형 교차를& 만들 수 있습니다 .