Typescript: 제안 : 유형 부동산 유형

에 만든 2014년 11월 28일  ·  76코멘트  ·  출처: microsoft/TypeScript

동기

많은 자바 스크립트 라이브러리 / 프레임 워크 / 패턴에는 객체의 속성 이름을 기반으로 한 계산이 포함됩니다. 예를 들어 백본 모델, 기능적 변환 pluck , ImmutableJS 는 모두 이러한 메커니즘을 기반으로합니다.

//backbone
var Contact = Backbone.Model.extend({})
var contact = new Contact();
contact.get('name');
contact.set('age', 21);

// ImmutableJS
var map = Immutable.Map({ name: 'François', age: 20 });
map = map.set('age', 21);
map.get('age'); // 21

//pluck
var arr = [{ name: 'François' }, { name: 'Fabien' }];
_.pluck(arr, 'name') // ['François', 'Fabien'];

이 예제에서 API와 기본 유형 제약 조건 간의 관계를 쉽게 이해할 수 있습니다.
백본 모델의 경우 유형의 객체에 대한 일종의 _proxy_ 일뿐입니다.

interface Contact {
  name: string;
  age: number;
}

pluck 의 경우 변환입니다.

T[] => U[]

여기서 U는 T prop 의 속성 유형입니다.

그러나 우리는 TypeScript에서 그러한 관계를 표현할 방법이 없으며 결국 동적 유형으로 끝납니다.

제안 된 해법

제안 된 해결책은 T[prop] 유형에 대한 새로운 구문을 도입하는 것입니다. 여기서 prop 는 반환 값 또는 유형 매개 변수와 같은 유형을 사용하는 함수의 인수입니다.
이 새로운 유형 구문으로 다음 정의를 작성할 수 있습니다.

declare module Backbone {

  class Model<T> {
    get(prop: string): T[prop];
    set(prop: string, value: T[prop]): void;
  }
}

declare module ImmutableJS {
  class Map<T> {
    get(prop: string): T[prop];
    set(prop: string, value: T[prop]): Map<T>;
  }
}

declare function pluck<T>(arr: T[], prop: string): Array<T[prop]>  // or T[prop][] 

이런 식으로 백본 모델을 사용할 때 TypeScript는 getset 호출을 올바르게 유형 검사 할 수 있습니다.

interface Contact {
  name: string;
  age: number;
}
var contact: Backbone.Model<Contact>;

var age = contact.get('age');
contact.set('name', 3) /// error

prop 상수

강제

분명히 상수는 인덱스 유형 ( string , number , Symbol )으로 사용할 수있는 유형이어야합니다.

인덱싱 가능한 사례

Map 정의를 살펴 보겠습니다.

declare module ImmutableJS {
  class Map<T> {
    get(prop: string): T[string];
    set(prop: string, value: T[string]): Map<T>;
  }
}

T 색인이 생성되면지도는 다음 동작을 상속합니다.

var map = new ImmutableJS.Map<{ [index: string]: number}>;

이제 getget(prop: string): number 유형에 대해 있습니다.

질문

이제 _correct_ 동작을 생각하는 데 통증이있는 ​​경우가 있습니다. Map 정의부터 다시 시작하겠습니다.
{ [index: string]: number } 를 유형 매개 변수로 전달하는 대신에
{ [index: number]: number } 컴파일러가 오류를 발생시켜야합니까?

상수 대신에 prop에 대한 동적 표현식과 함께 pluck 사용하는 경우 :

var contactArray: Contact[] = []
function pluckContactArray(prop: string) {
  return _.pluck(myArray, prop);
}

또는 매개 변수로 전달 된 유형의 특성이 아닌 상수와 함께.
컴파일러가 T[prop] 유형을 추론 할 수 없기 때문에 pluck 대한 호출이 오류를 발생 시키면 T[prop]{} 또는 any , 그렇다면 --noImplicitAny 컴파일러가 오류를 발생시켜야합니까?

Fixed Suggestion help wanted

가장 유용한 댓글

@weswigham @mhegazy , 그리고 나는 최근에 이것을 논의하고 있습니다; 우리가 진행하는 모든 발전 사항을 알려 드리며 이것은 단지 아이디어를 프로토 타입 한 것임을 명심하십시오.

현재 아이디어 :

  • Foo 에서 속성 이름의 합집합을 문자열 리터럴 유형으로 가져 오는 keysof Foo 연산자.
  • Foo[K] 유형에 대해 문자열 리터럴 유형 또는 문자열 리터럴 유형의 합집합 인 K Foo[K] 유형을 지정합니다.

이러한 기본 블록에서 문자열 리터럴을 적절한 유형으로 추론해야하는 경우 다음과 같이 작성할 수 있습니다.

function foo<T, K extends keysof T>(obj: T, key: K): T[K] {
    // ...
}

여기서 Kkeysof T 의 하위 유형이됩니다. 즉, 문자열 리터럴 유형 또는 문자열 리터럴 유형의 합집합을 의미합니다. key 매개 변수에 대해 전달하는 모든 것은 해당 리터럴 / 리터럴 조합에 의해 컨텍스트 유형으로 지정되어야하며 단일 문자열 리터럴 유형으로 추론되어야합니다.

예를 들어

interface HelloWorld { hello: any; world: any; }

function foo<K extends keysof HelloWorld>(key: K): K {
    return key;
}

// 'x' has type '"hello"'
let x = foo("hello");

가장 큰 문제는 keysof 종종 작업을 "지연"해야한다는 것입니다. 내가 게시 한 첫 번째 예제에서와 같이 유형 매개 변수에 대한 문제인 유형을 평가하는 방법에 너무 열망합니다 (즉, 실제로 해결하려는 경우는 실제로 어려운 부분입니다 : smile :).

당신에게 모든 업데이트를 제공하기를 바랍니다.

모든 76 댓글

@NoelAbrahams 나는 그것이 # 394의 복제본이라고 생각하지 않습니다. 반대로 두 기능은 다음과 같이 꽤 보완 적입니다.

 class Model<T> {
    get(prop: memberof T): T[prop];
    set(prop:  memberof T, value: T[prop]): void;
  }

이상적 일 것

뿡 빵뀨

contact.set(Math.random() >= 0.5 ? 'age' : 'name', 13)

이 경우 어떻게해야합니까?

내 문제의 마지막 단락에서 나온 것과 거의 비슷합니다. 객관식이 있다고 말했듯이 오류를보고하거나 any 대해 T[prop] any 추론 할 수 있습니다. 두 번째 솔루션이 더 논리적이라고 생각합니다.

훌륭한 제안. 동의하면 유용한 기능이 될 것입니다.

@fdecampredon , 나는 이것이 중복이라고 생각합니다. Danmembertypeof 대한 제안이 포함 된 해당 응답을 참조하십시오.

IMO는이 모든 것이 다소 좁은 사용 사례에 대한 많은 새로운 구문입니다.

@NoelAbrahams 그것은 동일하지 않습니다.

  • memberof T 는 유효한 속성 이름이 T instance 인 문자열 만 될 수있는 인스턴스 유형을 반환합니다.
  • T[prop]prop 인수 / 변수로 표현되는 문자열로 명명 된 T 의 속성 유형을 반환합니다.

brifge가에있다 memberof 의 유형 prop 해야 매개 변수 memberof T .

실제로 타입 메타 데이터를 기반으로 한 타입 추론을위한 더 풍부한 시스템을 갖고 싶습니다. 그러나 이러한 연산자는 memberof 뿐만 아니라 좋은 시작입니다.

이것은 흥미롭고 바람직합니다. TypeScript는 아직 문자열이 많은 프레임 워크에서 잘 작동하지 않으며 이것은 분명히 많은 도움이 될 것입니다.

TypeScript는 문자열이 많은 프레임 워크에서 잘 작동하지 않습니다.

진실. 여전히 이것이 중복 제안이라는 사실을 변경하지 않습니다.

그러나 이것은 분명히 [문자열이 많은 프레임 워크를 입력하는 데] 많은 도움이 될 것입니다.

그것에 대해 잘 모르겠습니다. 다소 단편적이고 위에 설명 된 프록시-객체 패턴에 다소 구체적으로 보입니다. 나는 # 1003 라인을 따라 매직 스트링 문제에 대해보다 전체적인 접근 방식을 선호합니다.

1003은 any 를 getter의 반환 유형으로 제안합니다. 이 제안은 값 유형을 조회하는 방법도 추가하여 추가합니다. 혼합은 다음과 같은 결과를 가져옵니다.

declare module ImmutableJS {
  class Map<T> {
    get(prop: memberof T): T[prop];
    set(prop: memberof T, value: T[prop]): Map<T>;
  }
}

@spion , # 394를 의미 했습니까? 더 자세히 읽으면 다음과 같은 내용이 표시됩니다.

반환 유형에 대해 생각했지만 전체 제안을 너무 크게 만들지 않도록 생략했습니다.

이것은 나의 초기 생각 이었지만 문제가 있습니다. memberof T 유형의 인수가 여러 개있는 경우 membertypeof T 참조하는 인수는 무엇입니까?

get(property: memberof T): membertypeof T;
set(property: memberof T, value: membertypeof T);

이렇게하면 "내가 언급하는 인수"문제가 해결되지만 membertypeof 이름이 잘못되어 속성 이름을 대상으로하는 연산자의 팬이 아닙니다.

get(property: memberof T): membertypeof property;
set(property: memberof T, value: membertypeof property);

나는 이것이 더 잘 작동한다고 생각합니다.

get(property: memberof T is A): A;
set(property: memberof T is A, value: A)

불행히도 마지막 제안이 적절한 잠재력을 가지고 있다고 생각하지만 훌륭한 솔루션이 있는지 확실하지 않습니다.

OK @NoelAbrahams # 394에 이것과 거의 같은 것을 설명하려는 코멘트가있었습니다.
이제 저는 T[prop] 보다이 의견의 다른 명제보다 조금 더 우아하고이 호의 명제는 반성에서 조금 더 나아 간다고 생각합니다.
이러한 이유로 나는 그것이 중복으로 닫혀서는 안된다고 생각합니다.
그러나 나는 문제를 쓴 사람이기 때문에 편견이 있다고 생각합니다.).

@fdecampredon , 더 많은

@NoelAbrahams 죄송합니다, 그 부분을 놓쳤습니다. 물론, 그것들은 거의 동일합니다 (이것은 문제가 될 수도 있고 아닐 수도있는 또 다른 일반 매개 변수를 도입하지 않는 것 같습니다)

Flow를 살펴보면 애드혹 타입의 좁히기보다는 좀 더 강한 타입 시스템과 특수 타입으로 더 우아 할 것 같아요.

예를 들어 get(prop: string): Contact[prop] 에서 의미하는 것은 일련의 가능한 오버로딩 일뿐입니다.

interface Map {
  get(prop : string) : Contact[prop];
}

// is morally equivalent to 

interface Map {
  get(prop : "name") : string;
  get(prop : "age") : number;
}

& 유형 연산자 (교차 유형)가 있다고 가정하면 다음과 같은 유형입니다.

interface Map {
   get : (prop : "name") => string & (prop : "age") => number;
}

이제 특수 처리 ( [prop] 없음)없이 유형 표현식에서만 비 일반적인 케이스를 번역 했으므로 매개 변수 문제를 해결할 수 있습니다.

아이디어는 유형 매개 변수에서이 유형을 다소 생성하는 것입니다. 유형 검사기를 암시하면서 유형 표현식 (특수 구문 없음)으로 만 생성 된 유형을 표현하기 위해 특수 더미 제네릭 유형 $MapProperties , $Name$Value 를 정의 할 수 있습니다. 뭔가를해야한다는 것.

class Map<T> {
   get : $MapProperties<T, (prop : $Name) => $Value>
   set : $MapProperties<T, (prop : $Name, val : $Value) => void>
}

복잡하고 템플릿에 가까워 보이거나 가난한 사람의 인간 의존 유형으로 보일 수 있지만 누군가 유형이 값에 의존하기를 원할 때 피할 수 없습니다.

이것이 유용한 또 다른 영역은 유형이 지정된 객체의 속성을 반복하는 것입니다.

interface Env {
 // pretend this is an actually interesting type
};

var actions = {
  action1: function (env: Env, x: number) : void {},
  action2: function (env: Env, y: string) : void {}
};

// actions has type { action1: (Env, number) => void; action2: (Env, string) => void; }
var env : Env = {};
var boundActions = {};
for (var action in actions) {
  boundActions[action] = actions[action].bind(null, env);
}

// boundActions should have type { action1: (number) => void; action2: (string) => void; }

이러한 유형은 최소한 이론적으로 추론 할 수 있어야하지만 ( for 루프의 결과를 추론 할 수있는 충분한 유형 정보가 있습니다), 아마 상당히 늘어날 것입니다.

다음 버전의 반응은 해당 접근 방식의 큰 이점이 될 것입니다. https://github.com/facebook/react/issues/3398을 참조

https://github.com/Microsoft/TypeScript/issues/1295#issuecomment -64944856과 같이 문자열이 문자열 리터럴이 아닌 식으로 제공되는 경우이 기능은 중지 문제로 인해 빠르게 분해됩니다. 그러나 ES6 Symbol 문제 (# 2012) 학습을 사용하여이 기본 버전을 구현할 수 있습니까?

승인되었습니다.

우리는 구문에 대한 느낌을 얻기 위해 실험적인 브랜치에서 이것을 시도하고 싶을 것입니다.

어떤 버전의 제안이 구현 될지 궁금하십니까? 즉, T[prop] 가 호출 사이트에서 평가되고 구체적인 유형으로 열심히 대체 될 것입니까? 아니면 새로운 형식의 유형 변수가 될까요?

# 3779에 정의 된대로보다 일반적이고 덜 자세한 구문에 의존해야한다고 생각합니다.

interface Map<T> {
  get<A>(prop: $Member<T,A>): A;
  set<A>(prop: $Member<T,A>, value: A): Map<T>;
}

아니면 A의 유형을 추론 할 수 없습니까?

정상적인 솔루션을 기다리는 동안 TS와 ImmutableJS를 더 쉽게 통합 할 수있는 작은 코드 생성 도구를 만들었습니다. https://www.npmjs.com/package/tsimmutable. 매우 간단하지만 대부분의 사용 사례에서 작동 할 것이라고 생각합니다. 누군가를 도울 것입니다.

또한 멤버 유형이있는 솔루션은 ImmutableJS에서 작동하지 않을 수 있습니다.

interface Profile {
  firstName: string 
}

interface User {
  profile: Profile  
}

let a: Map<User> = fromJS(/* ... */);
a.get('profile') // Type will be Profile, but the real type is Map<Profile>!

@ s-panferov 다음과 같이 작동 할 수 있습니다.

interface ImmutableMap<T> {
    get<A extends boolean | number | string>(key : string) : A;
    get<A extends {}>(key : string) : ImmutableMap<A>;
    get<E, A extends Array<any>>(key : string) : ImmutableList<E>;
}

interface Profile {

}

interface User {
    name : string;
    profile : Profile;
}

var map : ImmutableMap<User>;

var name = map.get<string>('name'); // string
var profile = map.get<Profile>('profile'); // ImmutableMap<Profile>

이것은 DOM 노드 또는 Date 객체를 제외하지 않지만 변경 불가능한 구조에서 전혀 사용하도록 허용해서는 안됩니다. https://github.com/facebook/immutable-js/wiki/Converting-from-JS-objects

최상의 해결 방법에서 점차적으로 작업

먼저 현재 사용 가능한 최상의 해결 방법으로 시작한 다음 점차적으로 해결하는 것이 유용하다고 생각합니다.

프리미티브

function getProperty<T extends object>(container: T; propertyName: string) {
    return container[propertyName];
}

이제 반환 값이 대상 속성의 유형을 갖기를 원하므로 다른 일반 유형 매개 변수를 해당 유형에 추가 할 수 있습니다.

function getProperty<T extends object, P>(container: T; propertyName: string) {
    return <P> container[propertyName];
}

따라서 클래스 사용 사례는 다음과 같습니다.

class C {
    member: number;
    static member: string;
}

let instance = new C();
let result = getProperty<C, typeof instance.member>(instance, "member");

그리고 resultnumber 유형을 올바르게 수신합니다.

그러나 호출에서 '멤버'에 대한 중복 참조가있는 것 같습니다. 하나는 유형 매개 변수에 있고 다른 하나는 항상 대상 속성 이름의 문자열 표현을 수신하는 _literal_ 문자열입니다. 이 제안의 아이디어는이 두 가지가 문자열 표현 만받는 단일 유형 매개 변수로 통합 될 수 있다는 것입니다.

따라서 여기에서 문자열이 _generic 매개 변수 _ 역할도하고 이것이 작동하려면 리터럴로 전달되어야한다는 점을 관찰해야합니다 (다른 경우에는 런타임 리플렉션 형식을 사용할 수없는 경우 해당 값을 자동으로 무시할 수 있음). 따라서 의미를 명확하게하려면 일반 매개 변수와 문자열 사이의 관계를 나타내는 방법이 있어야합니다.

function getProperty<T extends object, PName: string = propertyName>(container: T; propertyName: string) {
    return <T[PName]> container[propertyName];
}

이제 호출은 다음과 같습니다.

let instance = new C();
let result = getProperty<C>(instance, "member");

내부적으로 다음과 같이 해결됩니다.

let result = getProperty<C, "member">(instance, "member");

그러나 C 에는 인스턴스 및 member 라는 정적 속성이 모두 포함되어 있으므로 상위 종류의 일반 표현식 T[PName] 는 모호합니다. 여기 의도는 인스턴스의 속성에 적용하는 대부분이기 때문에, 같은 솔루션이 제안 typeon 연산자는 또한 의미 향상시킬 수있는 내부적으로 사용될 수 T[PName] 대표하는 일부에 의해 해석됩니다 _value reference_, 반드시 유형일 필요는 없습니다.

function getProperty<T extends object, PName: string = propertyName>(container: T; propertyName: string) {
    return <typeon T[PName]> container[propertyName];
}

( T 이 호환되는 인터페이스 유형 인 경우에도 작동합니다. typeon 인터페이스를 지원하므로)

정적 속성을 얻으려면 생성자 자체를 사용하여 함수를 호출하고 생성자 유형을 typeof C 로 전달해야합니다.

let result = getProperty<typeof C>(C, "member"); // Note it is called with the constructor object

(내부적으로 typeon (typeof C)typeof C 되므로 작동합니다)
result 은 이제 string 유형을 올바르게 수신합니다.

추가 유형의 속성 식별자를 지원하기 위해 다음과 같이 다시 작성할 수 있습니다.

type PropertyIdentifier = string|number|Symbol;

function getProperty<T extends object, PName: PropertyIdentifier = propertyName>(container: T; propertyName: PropertyIdentifier) {
    return <typeon T[PName]> container[propertyName];
}

따라서이를 지원하는 데 필요한 여러 가지 기능이 있습니다.

  • 리터럴 값이 일반 매개 변수로 전달되도록 허용합니다.
  • 이러한 리터럴이 함수 인수의 값을 받도록 허용합니다.
  • 더 높은 종류의 제네릭 형식을 허용합니다.
  • 고급 제네릭을 통해 _generic_ 유형의 리터럴 제네릭 매개 변수를 통해 속성 유형을 참조 할 수 있습니다.

문제 재검토

이제 원래 해결 방법으로 돌아가 보겠습니다.

function getProperty<T extends object, P>(container: T; propertyName: string) {
    return <P> container[propertyName];
}

이 해결 방법의 한 가지 장점은 직접 속성뿐 아니라 중첩 된 개체의 속성을 참조하여보다 복잡한 경로 이름을 지원하도록 쉽게 확장 할 수 있다는 것입니다.

function getPath<T extends object, P>(container: T; path: string) {
    ... more complex code here ...

    return <P> resultValue;
}

그리고 지금:

class C {
    a: {
        b: {
            c: string[];
        }
    }
}
let instance = new C();
let result = getPath<C, typeof instance.a.b.c>(instance, "a.b.c");

result 는 여기서 string[] 유형을 올바르게 가져옵니다.

문제는 중첩 된 경로도 지원되어야한다는 것입니다. 입력 할 때 자동 완성 기능을 사용할 수 없다면이 기능이 정말 유용할까요?

이것은 다른 방향으로 작동하는 다른 솔루션이 가능할 수 있음을 시사합니다. 원래 해결 방법으로 돌아 가기 :

function getProperty<T extends object, P>(container: T; propertyName: string) {
    return <P> container[propertyName];
}

이것을 다른 방향에서 보면 타입 참조 자체를 가져 와서 문자열로 변환 할 수 있습니다.

function getProperty<T extends object, P>(container: T; propertyName: string = @typeReferencePathOf(P)) {
    return <P> container[propertyName];
}

@typeReferencePathOf 는 개념 상 nameOf 와 유사하지만 일반 매개 변수에 적용됩니다. 수신 된 유형 참조 typeof instance.member (또는 typeon C.member )를 취하고 속성 경로 member 추출한 다음 컴파일시 리터럴 문자열 "member" 로 변환합니다. 시각. 덜 전문화 된 보완 @typeReferenceOf 는 완전한 문자열 "typeof instance.member" 해석됩니다.

그래서 지금:

getProperty<C, typeof instance.subObject>(instance);

해결 방법 :

getProperty<C, typeof instance.subObject>(instance, "subObject");

getPath 대한 유사한 구현 :

getPath<C, typeof instance.a.b.c>(instance);

해결 방법 :

getPath<C, typeof instance.a.b.c>(instance, "a.b.c");

@typeReferencePathOf(P) 는 기본값으로 만 설정되어 있습니다. 필요한 경우 인수를 수동으로 지정할 수 있습니다.

getPath<C, SomeTypeWhichIsNotAPath>(instance, "member.someSubMember.AnotherSubmember.data");

두 번째 유형 매개 변수가 제공되지 않으면 @typeReferencePathOf()undefined 또는 빈 문자열 "" 해석 될 수 있습니다. 유형이 제공되었지만 내부 경로가없는 경우 "" 됩니다.

이 접근 방식의 장점 :

  • 고급 제네릭이 필요하지 않습니다.
  • 리터럴을 일반 매개 변수로 허용 할 필요가 없습니다.
  • 어떤 식 으로든 제네릭을 확장 할 필요가 없습니다.
  • typeof 표현식을 입력 할 때 자동 완성을 허용하고 컴파일 타임에 문자열 표현에서 변환 할 필요없이 기존 유형 검사 메커니즘을 사용하여 유효성을 검사합니다.
  • 제네릭 클래스 내에서도 사용할 수 있습니다.
  • typeon 필요하지 않습니다 (하지만 지원 가능).
  • 다른 시나리오에 적용 할 수 있습니다.

+1

좋은! 나는 이미 같은 문제를 다루기 위해 큰 제안을 쓰기 시작했지만이 토론을 찾았으므로 여기서 토론합시다.

제출하지 않은 문제에서 발췌 한 내용은 다음과 같습니다.

질문 : 다음 함수가 사용되어야하는 컨텍스트에서 사용할 수 있도록 .d.ts에서 어떻게 선언되어야합니까?

function mapValues(obj, fn) {
      return Object.keys(obj)
          .map(key => ({key, value: fn(obj[key], key)}))
          .reduce((res, {key, value}) => (res[key] = value, res), {})
}

이 함수 (약간의 변형 포함)는 거의 모든 일반 "유틸리티"라이브러리에서 찾을 수 있습니다.

mapValues 의 두 가지 고유 한 사용 사례가 있습니다.

ㅏ. _Object-as-a-_ _Dictionary _ 여기서 속성 이름은 동적이고 값은 동일한 유형이며 함수는 일반적으로

var obj = {'a': 1, 'b': 2, 'c': 3};
var res = mapValues(x, val => val * 5); // {a: 5, b: 10, c: 15}
console.log(res['a']) // 5

비. _Object-as-a-_ _Record _ 여기서 각 속성에는 고유 한 유형이 있고 함수는 매개 변수 적으로 다형성 (구현에 따라)

var obj = {a: 123, b: "Hello", c: true};
var res = mapValues(p, val => [val]); // {a: [123], b: ["Hello"], c: [true]}
console.log(res.a[0].toFixed(2)) // "123.00"

현재 TypeScript에서 mapValues 사용할 수있는 가장 좋은 도구는 다음과 같습니다.

declare function mapValues<T1, T2>(
    obj: {[key: string]: T1},
    fn: (arg: T1, key: string) => T2
): {[key: string]: T2};

이 시그니처가 _Object-as-a-_ _Dictionary _ 유스 케이스와 완벽하게 일치하는 것을 확인하는 것은 어렵지 않지만 _Object-as-a-_에 적용될 때 유형 추론의 다소 놀라운 1 결과를 생성합니다. _ 기록 _은 의도였다.

{p1: T1, p2: T2, ...pn: Tn} 유형은 모든 유형을 병합하고 레코드 구조에 대한 모든 정보를 효과적으로 삭제하는 {[key: string]: T1 | T2 | ... | Tn } 로 강제 변환됩니다.

  • 정확하고 예상되는 2 가 괜찮을 것으로 예상되는 console.log(res.a[0].toFixed(2)) 는 컴파일러에 의해 거부됩니다.
  • 허용되는 console.log((<number>res['a'][0]).toFixed(2)) 에는 제어되지 않고 오류가 발생하기 쉬운 두 위치, 즉 유형 변환 및 임의 속성 이름이 있습니다.

1, 2 — ES에서 TS로 마이그레이션하는 프로그래머 용


이 문제를 해결하기위한 가능한 단계 (및 다른 문제도 해결)

1. 문자열 리터럴 유형의 도움으로 가변 레코드 도입
type Numbers<p extends string> =  { ...p: number };
type NumbersOpt<p extends string> = {...p?: number };
type ABC = "a" | "b" | "c";
type abc = Numbers<ABC> // abc =  {a: number, b: number, c: number} 
type abcOpt = NumbersOpt<ABC> // abcOpt =  {a?: number, b?:number, c?: number}

function toFixedAll<p extends string>(obj: {...p: number}, precision):{...p: string}  {
      var result: {...p: string} = {} as any;
      Object.keys(obj).forEach((p:p) => {
           result[p] = obj[p].toFixed(precision);
      });
      return result;
}

var test = toFixedAll({x:5, y:7}, 3); // { x: "5.00", y: "6.00" },  p inferred as "x"|"y"
console.log(test.y.length) // 4   test.y: string
2. '문자열'을 확장하도록 제한되는 다른 형식 형식으로 형식 형식을 첨자 할 수 있도록 허용

예:

declare function mapValues<p extends string, T1[p], T2[p]>(
     obj:{...p: T1[p]}, fn:(arg: T1[p]) => T2[p]
): {...p: T2[p]};
3. 형식 유형의 구조 해제 허용

예:

class C<Array<T>> {
     x: T;
}   

var v1: C<string[]>;   // v1.x: string

세 단계를 모두 함께 작성하면

declare module Backbone {

  class Model<{...p: T[p]}> {
    get(prop: p): T[p];
    set(prop: p, value: T[p]): void;
  }
}

declare module ImmutableJS {
  class Map<{...p: T[p]}> {
    get(prop: p): T[p];
    set(prop: p, value: T[p]): this;
  }
}

declare function pluck<p extends string, T[p]>(
    arr: Array<{...p:T[p]}>, prop: p
): Array<T[p]> 

구문이 @fdecampredon이 제안한 것보다 더 장황하다는 것을 알고 있습니다.
하지만 원래 제안으로 mapValues 또는 combineReducers 의 유형을 표현하는 방법을 상상할 수 없습니다.

function combineReducers(reducers) {
  return (state, action) => mapValues(reducers, (reducer, key) => reducer(state[key], action))
}

내 제안으로 선언은 다음과 같습니다.

declare function combineReducers<p extends string, S[p]>(
    reducers: { ...p: (state: S[p], action: Action) => S[p] }
): (state: { ...p: S[p] }, action: Action) => { ...p: S[p] };

원래 제안으로 표현할 수 있습니까?

@spion , @fdecampredon ?

@Artazor 확실히 개선되었습니다. 이제이 기능의 벤치 마크 사용 사례를 표현하기에 충분할 것 같습니다.

user.iduser.namenumberstring 포함하는 데이터베이스 열 유형이라고 가정합니다. 즉, Column<number>Column<string>

다음을 취하는 함수 select 의 유형을 작성하십시오.

select({id: user.id, name: user.name})

Query<{id: number; name: string}> 반환합니다.

select<T>({...p: Column<T[p]>}):Query<T>

그래도 어떻게 확인하고 추론할지 모르겠습니다. 유형 T가 존재하지 않기 때문에 문제가있는 것 같습니다. 반환 유형을 구조화 된 형식으로 표현해야합니까? 즉

select<T>({...p: Column<T[p]>}):Query<{...p:T[p]}>

@Artazor 가 묻는 것을 잊었습니다. p extends string 매개 변수가 정말 필요합니까?

이것으로 충분하지 않습니까?

select<T[p]>({...p: Column<T[p]>}):Query<{...p:T[p]}>

{...p:Column<T[p]>} 모든 유형 변수 T [p]

편집 : 또 다른 질문, 이것이 정확성을 어떻게 확인합니까?

@spion 저는 당신의 관점을 가지고 있습니다. 당신은 저를 오해했습니다 (하지만 그것은 제 잘못입니다). T[p] 저는 완전히 다른 것을 의미했습니다. 그리고 당신은 p extends string 보게 될 것입니다

데이터 구조에 대한 네 가지 사용 사례를 살펴 보겠습니다 : List , Tuple , DictionaryRecord . 이를 _ 개념적 데이터 구조 _라고 부르고 다음 속성으로 설명합니다.

개념적 데이터 구조액세스
ㅏ. 위치
(번호)
비. 키
(끈)
개수
항목
1. 변수
(각 항목에 대해 동일한 역할)
명부사전
2. 고정
(각 항목은 고유 한 역할을합니다)
튜플기록

자바 스크립트에서는, 이러한 네 개의 케이스는 저장 형태 모의 일 : Array - A1A2, 및 대한 Object - B1B2에 대한.

_ 참고 1 _. 솔직히 말해서, TupleArray 의해 뒷받침된다고 말하는 것은 옳지 않습니다. 유일한 "참"튜플 유형은 arguments 객체의 유형이기 때문입니다. Array 가 아닙니다. 그럼에도 불구하고, 그것들은 특히 구조화와 메타 프로그래밍의 문을 여는 Function.prototype.apply 의 맥락에서 어느 정도 상호 교환이 가능합니다. TypeScript는 또한 튜플 모델링을 위해 배열을 활용합니다.

_ 참고 2 _. 임의의 키가 포함 된 사전의 전체 개념이 ES6 Map 형식으로 수십 년 후 도입되었지만, Brendan Eich는 Record 및 제한된 Dictionary ( 문자열 키만 해당) Object ( obj.propobj["prop"] 와 동일 함)의 동일한 후드 아래에서 전체 언어에서 가장 논란이 많은 요소가되었습니다 (제 생각에는).
그것은 자바 스크립트 의미론의 저주이자 축복입니다. 그것은 반사를 사소하게 만들고 프로그래머가 거의 제로의 정신적 비용으로 _programming_과 _meta-programming_ 레벨 사이를 자유롭게 전환하도록 장려합니다 (알지 못해도!). 스크립팅 언어로서 JavaScript 성공의 필수적인 부분이라고 생각합니다.

이제 TypeScript가이 이상한 의미론에 대한 유형을 표현하는 방법을 제공 할 때입니다. _programming_ 수준에서 모든 것이 괜찮다고 생각하면 :

프로그래밍 수준의 유형액세스
ㅏ. 위치
(번호)
비. 키
(끈)
개수
항목
1. 변수
(각 항목에 대해 동일한 역할)
티[]{[키 : 문자열] : T}
2. 고정
(각 항목은 고유 한 역할을합니다)
[T1, T2, ...]{키 1 : T1, 키 2 : T2, ...}

그러나 _meta-programming_ 수준으로 전환하면 _programming_ 수준에서 고정 된 것들이 갑자기 변수가됩니다! 그리고 여기서 우리는 갑자기 튜플과 함수 시그니처 사이의 관계를 인식하고 실제로 메타 프로그래밍 요구의 작은 (그러나 매우 중요한) 부분만을 다루는 # 5453 (Variadic Kinds)와 같은 것을 제안합니다. 매개 변수를 나머지 인수 유형에 추가합니다.

     function f<T>(a: number, ...args:T) { ... }

    f<[string,boolean]>(1, "A", true);

# 6018도 구현되는 경우 Function 클래스는 다음과 같을 수 있습니다.

declare class Function<This, TArgs, TRes> {
        This::(...args: TArgs): TRes;
        call(self: This, ...args: TArgs): TRes;
        apply(self: This, args: TArgs): TRes;
        // bind needs also formal pattern matching:
        bind<[...TPartial, ...TCurried] = TArgs>(
           self: This, ...args: TPartial): Function<{}, TCurried, TRes> 
}

훌륭하지만 어쨌든 불완전합니다.

예를 들어, 다음 함수에 올바른 유형을 할당하고 싶다고 가정 해보십시오.

function extractAndWrapAll(...args) {
     return args.map(x => [x]);
}

// wrapAll(1,"A",true) === [[1],["A"],[true]]

제안 된 가변 유형으로는 서명 유형을 변환 할 수 없습니다. 여기에 더 강력한 것이 필요합니다. 실제로 유형에 대해 작동 할 수있는 컴파일 타임 함수를 갖는 것이 바람직합니다 (Facebook의 Flow에서와 같이). 하지만 TypeScript의 유형 시스템이 다음 사소한 업데이트에서 사용자 영역을 깨뜨릴 큰 위험없이 최종 프로그래머에게 노출 될 수있을만큼 안정적 일 때만 가능할 것이라고 확신합니다. 따라서 우리는 완전한 메타 프로그래밍 지원보다 덜 급진적이면서도 입증 된 문제를 처리 할 수있는 것이 필요합니다.

이 문제를 해결하기 위해 _object signature_ 개념을 소개하고자합니다. 대략 둘 중 하나입니다

  1. 객체의 속성 이름 집합 또는
  2. 사전의 키 세트, 또는
  3. 배열의 숫자 인덱스 세트
  4. 튜플의 숫자 위치 세트.

이상적으로는 정수 리터럴 유형과 문자열 리터럴 유형을 사용하여 다음과 같은 배열 또는 튜플의 서명을 나타내는 것이 좋습니다.

type ZeroToFive = 0 | 1 | 2 | 3 | 4 | 5;
// or
type ZeroToFive = 0 .. 5;   // ZeroToFive extends number (!)

정수 리터럴에 대한 규칙은 문자열 리터럴 규칙과 일치합니다.

그런 다음 객체 rest / spread 구문과 정확히 일치하는 서명 추상화 구문을 도입 할 수 있습니다.

{...Props: T }

한 가지 중요한 예외가 있습니다. 여기서 Props 는 범용 수량 자 아래에 바인딩 된 식별자입니다.

<Props extends string> {...Props: T }  // every property has type T
<Index extends number> {...Index: T }  // every item has type T  
// the same as T[]

따라서 유형 어휘 범위에 도입되고이 범위의 어느 곳에서나 유형의 이름으로 사용할 수 있습니다. 그러나 그 사용법은 두 가지입니다. rest / spread 속성 이름 대신 사용되는 경우 독립형 유형을 사용할 때 객체 서명에 대한 추상화를 나타내며 문자열 (또는 각각 숫자)의 하위 유형을 나타냅니다.

declare class Object {
      static keys<p extends string, q extends p>(object{...q: {}}): p[];
}

그리고 여기에 가장 정교한 부분이 있습니다 : _Key Dependent Types_

우리는 특별한 타입 구조를 소개합니다 : T for Prop (당신을 혼란스럽게하는 T [Prop]보다는이 구문을 사용하고 싶습니다) 여기서 Prop은 객체의 서명 추상화를 보유하는 타입 변수의 이름입니다. 예를 들어 <Prop extends string, T for Prop> 유형 어휘 범위 내로 개의 정규 타입 컨덕터는 PropT 가 공지되는 경우 각각의 특정 값에 대해 pProp 자체 유형 T 입니다.

Props 속성을 가진 객체가 어딘가에 있고 해당 유형이 T 라고 말하지 않습니다! 두 유형 사이의 기능적 종속성 만 소개합니다. 유형 T는 유형 Props의 구성원과 상관 관계가 있으며 그게 전부입니다!

그것은 우리에게 다음과 같은 것을 쓸 수있는 가능성을줍니다.

function unwrap<P extends string, T for P>(obj:{...P: Maybe<T>}): Maybe<{...P: T}> {
  ...
}

unwrap({a:{value:1}, b:{value:"A"}, c:{value: true}}) === { a: 1, b: "A", c: true }
// here actual parameters will be inferred as 
unwrap<"a"|"b"|"c", {a: number, b: string, c: boolean}>

그러나 두 번째 매개 변수는 객체가 아니라 유형에 대한 식별자의 추상 맵으로 취급됩니다. 이 관점에서 T for P 는 P가 숫자의 하위 유형일 때 튜플 유형의 추상 시퀀스를 나타내는 데 사용할 수 있습니다 (

T{...P: .... T .... } 내부 어딘가에서 사용되면 해당 맵의 특정 유형을 정확히 하나 나타냅니다.

이것이 나의 주요 아이디어입니다.
질문, 생각, 비판을 간절히 기다리고 있습니다 .-)

맞습니다. extends string 는 한 경우에는 배열 (및 가변 인수)을 고려하고 다른 경우에는 문자열 상수 (유형)를 고려하는 것입니다. 단!

우리는 어딘가에 Props 속성을 가진 객체가 있고 그 유형이 T라고 말하지 않습니다! 두 유형 사이의 기능적 종속성 만 소개합니다. 유형 T는 유형 Props의 구성원과 상관 관계가 있으며 그게 전부입니다!

그 뜻이 아니라, 유형이 p로 색인 된 유형 사전 인 T [p]이기 때문에 더 의미가 있습니다. 그게 좋은 직관이라면 나는 그것을 유지할 것입니다.

전반적으로 구문에 약간의 작업이 필요할 수 있지만 일반적인 아이디어는 멋져 보입니다.

가변 인수에 대해 unwrap 를 쓸 수 있습니까?

편집 : 신경 쓰지 마십시오. 제안 된 가변 종류 확장이 이것을 해결한다는 것을 깨달았습니다.

모두들 안녕,
내 문제 중 하나를 해결하는 방법을 헷갈 리고이 토론을 찾았습니다.
문제는 :
RpcManager.call(command:Command):Promise<T> 메서드가 있으며 사용법은 다음과 같습니다.

RpcManager.call(new GetBalance(123)).then((result) => {
 // here I want that result would have a type.
});

해결책은 다음과 같습니다.

interface Command<T> {
    responseType:T;
}

class GetBalance implements Command<number> {
    responseType: number; // somehow this should be avoided. maybe Command should be abstract class.
    constructor(userId:number) {}
}

class RpcManager {
    static call(command:Command):Promise<typeof command.responseType> {
    }
}

or:

class RpcManager {
    static call<T>(command:Command<T>):Promise<T> {
    }
}

이것에 대한 어떤 생각?

@ antanas-arvasevicius이 예제의 마지막 코드 블록은 원하는 작업을 수행해야합니다.

특정 작업을 수행하는 방법에 대한 질문이 더 많은 것 같습니다. 컴파일러 버그를 발견했다고 생각되면 Stack Overflow를 사용하거나 문제를 제출하십시오.

안녕, 라이언, 응답 해주셔서 감사합니다.
마지막 코드 블록을 시도했지만 작동하지 않습니다.

빠른 데모 :

interface Command<T> { }
class MyCommand implements Command<{status:string}> { }
class RPC { static call<T>(command:Command<T>):T { return; } }

let response = RPC.call(new MyCommand());
console.log(response.status);

//output: error TS2339: Property 'status' does not exist on type '{}'.
//tested with: Version 1.9.0-dev.20160222

Stack Overflow를 사용하지 않아서 미안하지만이 문제와 관련이 있다고 생각했습니다. :)
이 주제에 대한 새로운 문제를 열어야합니까?

사용되지 않은 제네릭 형식 매개 변수는 추론이 작동하지 않도록합니다. 일반적으로 타입 선언에 사용하지 않는 타입 매개 변수는 의미가 없으므로 _never_ 있어야합니다. T 를 소비하면 모든 것이 작동합니다.

interface Command<T> { foo: T }
class MyCommand implements Command<{status:string}> { foo: { status: string; } }
class RPC { static call<T>(command:Command<T>):T { return; } }

let response = RPC.call(new MyCommand());
console.log(response.status);

놀랍습니다! 감사합니다!
유형 매개 변수가 제네릭 유형 내에 배치 될 수 있다고 생각하지 않았습니다.
TS가 추출합니다.
2016 년 2 월 22 일 11:56 PM에 "Ryan Cavanaugh" [email protected] 작성했습니다.

사용되지 않은 제네릭 형식 매개 변수는 추론이 작동하지 않도록합니다. 에
일반적으로 유형에 사용되지 않은 유형 매개 변수가 _ 절대 _ 있어야합니다.
무의미한 선언. T를 섭취하면 모든 것이
공장:

인터페이스 명령{foo : T} class MyCommand는 Command <{status : string}>를 구현합니다. {foo : {status : string; }} class RPC {정적 호출(명령 : 명령) : T {반환; }}
let response = RPC.call (new MyCommand ());
console.log (response.status);


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/1295#issuecomment -187404245
.

@ antanas-arvasevicius RPC 스타일 API를 만드는 경우 유용한 문서가 있습니다. https://github.com/alm-tools/alm/blob/master/docs/contributing/ASYNC.md : rose :

위의 접근 방식은 다음과 같습니다.

  • 매우 복잡합니다.
  • 코드에서 문자열 사용을 다루지 마십시오. string 는 "모든 참조 찾기"및 리팩토링 (예 : 이름 바꾸기)이 가능하지 않습니다.
  • 일부는 "1 단계"속성 만 지원하고 더 복잡한 표현식은 지원하지 않습니다 (일부 프레임 워크에서 지원됨).

다음은 C # 표현식 트리에서 영감을 얻은 또 다른 아이디어입니다. 이것은 대략적인 생각 일 뿐이며 완전히 생각한 것은 없습니다! 구문은 끔찍합니다. 나는 이것이 누군가에게 영감을 주는지보고 싶다.

표현식을 나타내는 특별한 종류의 문자열이 있다고 가정합니다.
type Expr<T, U> = string 라고 부르겠습니다.
여기서 T 는 시작 개체 유형이고 U 는 결과 유형입니다.

T 유형의 매개 변수 하나를 취하고 이에 대한 멤버 액세스를 수행하는 람다를 사용하여 Expr<T,U> 의 인스턴스를 만들 수 있다고 가정합니다.
예 : person => person.address.city .
이 경우 전체 람다는 매개 변수에 대한 액세스 권한이 무엇이든 포함하는 문자열로 컴파일됩니다 (이 경우 "address.city" .

대신 Expr<any, any> 로 표시되는 일반 문자열을 사용할 수 있습니다.

이 특별한 Expr 유형을 언어로 사용하면 다음과 같은 작업을 수행 할 수 있습니다.

function pluck<T, U>(array: T[], prop: Expr<T, U>): U[];

let numbers = pluck([{x: 1}, {x: 2}], p => p.x);  // number[]
// compiles to:
// let numbers = pluck([..], "x");

이것은 기본적으로 C #에서 사용되는 표현식의 제한된 형태입니다.
이것이 다듬어지고 흥미로운 곳으로 이어질 수 있다고 생각하십니까?

트윗 담아 가기

_ ( @ jods4- 여기에 귀하의 제안에 회신하지 않아서 죄송합니다. 댓글을 통해 '매장'되지 않았 으면 좋겠습니다) _

이 기능의 이름 ( '유형 속성 유형')이 매우 혼란스럽고 이해하기 매우 어렵다고 생각합니다. 여기에 설명 된 개념이 무엇인지, 그리고 그것이 처음에 무엇을 의미하는지조차 이해하기가 매우 어려웠습니다!

첫째, 모든 유형에 속성이있는 것은 아닙니다! undefinednull 은 그렇지 않습니다 (유형 시스템에 대한 최근 추가 사항 일 뿐임). number , string , boolean 와 같은 프리미티브는 거의 속성에 의해 인덱싱되지 않습니다 (예 : 2 [ "prop"]? 작동하는 것처럼 보이지만 거의 항상 실수입니다)

문자열 리터럴 값을 통해이 문제 참조 하는 매우 특별한 방법입니다 _.

특정 사용 사례의 맥락을 벗어나 가능한 한 간단하게 설명하고 예시했다면 매우 유익했을 것입니다.

interface MyInterface {
  prop1: number;
  prop2: string;
}

let prop1Name = "prop1";
type Prop1Type = MyInterface[prop1Name]; // Prop1Type is now 'number'

let prop2Name = "prop2";
type Prop2Type = MyInterface[prop2Name]; // Prop2Type is now 'string'

let prop3Name = "prop3";
type NonExistingPropType = MyInterface[prop3Name]; // Compilation error: property 'prop3' does not exist on 'MyInterface'.

let randomString = createRandomString();
type NotAvailablePropType = MyInterface[randomString]; // Compilation error: value of 'randomString' is not known at compile time.

_Edit : 올바르게 구현하려면 컴파일러 변수에 할당 된 문자열이 초기화 된 지점과 형식 표현식에서 참조 된 지점 사이에 변경되지 않았는지 확인

_ 편집 2 : 아마도 이것은 변수와 함께 사용될 때 const 에서만 작동할까요? _

원래 의도가 문자열 리터럴의 매우 특별한 경우에만 속성 참조를 허용하는 것이 었는지 확실하지 않습니다. 예

function func(someString: string): MyInterface[someString] {
  ..
}

let x = func("prop"); // x gets the type of MyInterface.prop

원래 의도 한 것 이상으로 이것을 일반화 했습니까?

함수 인수가 문자열 리터럴이 아닌 경우 즉, 컴파일 타임에 알려지지 않은 경우 어떻게 처리합니까? 예

let x = func(getRandomString()); // What type if inferred for 'x' here?

오류가 발생합니까, 아니면 기본값이 any 입니까?

(추신 : 이것이 실제로 의도 된 경우라면이 문제의 이름 을 문자열 리터럴 함수 또는 메서드 인수

다음은이 기능을 활성화하는 데 필요한 사항을 보여주는 간단한 벤치 마크 예제입니다 (예시에 충분 함).

이 함수의 유형을 작성하십시오.

  • 약속 필드를 포함하는 객체를 취하고
  • 동일한 객체를 반환하지만 모든 필드가 확인되고 각 필드는 적절한 유형을가집니다.
function awaitObject(obj) {
  var result = {};
  var wait = Object.keys(obj)
    .map(key => obj[key].then(val => result[key] = val));
  return Promise.all(wait).then(_ => result)
}

다음 객체에서 호출 될 때 :

var res = awaitObject({a: Promise.resolve(5), b: Promise.resolve("5")})

결과 res{a: number; b: string} 유형이어야합니다.


이 기능 (@Artazor에 의해 완전히 구체화 됨)을 사용하면 서명은 다음과 같습니다.

awaitObject<p, T[p]>(obj: {...p: Promise<T[p]>}):Promise<{...p: T[p]}>

편집 : 위의 반환 유형 수정, Promise<...> 누락 ^^

@spion

예제를 제공 해주셔서 감사합니다. 그러나 여기서는 합리적이고 간결한 사양과 실제 응용 프로그램에서 분리 된 명확한 축소 된 예제 세트를 찾지 못했고 이것이 작동하는 방식에 대한 의미론과 다양한 가장자리 사례를 강조하려고 노력했습니다. 기본적인 것조차 :

function func<T extends object>(name: string): T[name] {
 ...
}
  1. 효과적이고 설명적인 제목을 제공하려는 노력은 거의 없었습니다 ( 'Type Property Type'은 혼란스럽고이 기능에 대한 그다지 설명적인 이름이 아닙니다. 실제로는 특정 유형에 관한 것이 아니라 유형 참조 에 관한 것입니다). 가장 좋은 제목은 _ 문자열 리터럴 함수 또는 메서드 인수에 의한 인터페이스 속성 유형 참조입니다 (이 범위를 오해하지 않았다고 가정).
  2. name 가 컴파일 시간에 알려지지 않았거나 null 또는 정의되지 않은 경우 (예 : func<MyType>(getRandomString()) , func<MyType>(undefined) 에 대한 명확한 언급이 없습니다.
  3. Tnumber 또는 null 와 같은 기본 유형 인 경우 어떤 일이 발생하는지에 대한 명확한 언급이 없습니다.
  4. 이것이 함수에만 적용되는지 여부와 T[name] 를 함수 본문에서 사용할 수 있는지 여부에 대한 명확한 세부 정보는 제공되지 않았습니다. 그리고이 경우 name 가 함수 본문에 재 할당되어 그 값이 컴파일 타임에 더 이상 알려지지 않으면 어떻게 될까요?
  5. 구문의 실제 사양이 없습니다 : 예를 들어 T["propName"] 또는 T[propName] 자체적으로도 작동합니까? 매개 변수 나 변수에 대한 참조가 없으면 유용 할 수 있습니다!
  6. T[name] 가 다른 매개 변수에서 사용될 수 있거나 심지어 함수 범위 밖에서도 사용될 수 있는지에 대한 언급이나 논의가 없습니다.
function func<T extends object>(name: string, val: T[name]) {
 ...
}
type A = { abcd: number };
const name = "abcd";
let x: A[name]; // Type of 'x' resolves to 'number'

_7. 추가 일반 매개 변수 및 typeof 사용하는 비교적 간단한 해결 방법에 대한 실제 논의가 없습니다 (이미 언급되었지만 기능이 이미 승인 된 후 비교적 늦었습니다).

function get<T, V>(obj: T, propName: string): V {
    return obj[propName];
}

type MyType = { abcd: number };
let x: MyType  = { abcd: 12 };

let result = get<MyType, typeof x.abcd>(x, "abcd"); // Type of 'result' is 'number'

결론 : 여기에는 실제 제안이 없으며 사용 사례 데모 만 있습니다. 나는 이것이 그들의 표준에 달할 것이라고 믿지 않았기 때문에 이것이 TypeScript 팀에 의해 받아 들여 지거나 긍정적 인 관심을 받았다는 것에 놀랐습니다. 내가 여기서 약간 거칠게 들릴지 모르지만 (나에게 전형적이지 않은) 내 비판은 개인적이거나 특정 개인을 향한 비판이 없습니다.

메타 프로그래밍 측면에서 그렇게 멀리 나가고 싶습니까?

JS는 동적 언어이며 코드는 임의의 방법으로 객체를 조작 할 수 있습니다.
타입 시스템에서 모델링 할 수있는 것에는 한계가 있으며 TS에서 모델링 할 수없는 함수를 쉽게 생각 해낼 수 있습니다.
문제는 오히려 이동하는 것이 얼마나 합리적이고 유용할까요?

@spion의 마지막 예 ( awaitObject )는 동시에 다음과 같습니다.

  • 언어 관점에서 _ 매우 복잡합니다 ( 여기서
  • 적용 측면에서 _ 매우 좁음 _. 이 예는 특정 제약 조건을 선택하고 잘 일반화되지 않습니다.

BTW @spion 귀하의 예제의 반환 유형을 Promise 반환합니다.

우리는 _.pluck 와 같이 필드를 나타내는 문자열을 사용하는 API를 입력하는 원래 문제와는 거리가 멀습니다.
이러한 API는 일반적이고 그렇게 복잡하지 않기 때문에 지원되어야합니다. 이를 위해 { ...p: T[p] } 와 같은 메타 모델이 필요하지 않습니다.
OP에는 몇 가지 예가 있으며, 이슈 이름에 대한 내 의견에는 Aurelia에서 더 많은
다른 접근 방식으로 이러한 사용 사례를 다룰 수 있습니다. 가능한 아이디어는 위의 의견을 참조하십시오.

@ jods4 는 전혀 좁지 않습니다. 현재 유형 시스템에서 모델링 할 수없는 여러 가지에 사용할 수 있습니다. TS 유형 시스템에서 마지막으로 누락 된 부품 중 하나라고 말하고 싶습니다. 위의 실제 사례는 @Artazor https://github.com/Microsoft/TypeScript/issues/1295#issuecomment -177287714 위의 간단한 것, SQL 쿼리 작성기 "select"예제 등이 있습니다.

이것은 블루 버드로 awaitObject 입니다. 진짜 방법입니다. 이 유형은 현재 쓸모가 없습니다.

보다 일반적인 솔루션이 더 좋습니다. 간단한 경우와 복잡한 경우 모두에서 작동합니다. 저전력 솔루션은 사례의 절반이 부족한 것으로 확인되며 향후보다 일반적인 솔루션을 채택해야하는 경우 호환성 문제를 쉽게 일으킬 수 있습니다.

예, 더 많은 작업이 필요하지만 @Artazor 는 이전에 생각하지 못했던 모든 측면을 분석하고 탐색하는 데 탁월한 작업을 수행했다고 생각합니다. 나는 우리가 원래의 문제에서 벗어난 상태에 머 무르지 않을 것이며, 우리는 그것에 대해 더 잘 이해할 수있을뿐입니다.

더 나은 이름이 필요할 수 있습니다. 시도해 보겠습니다.하지만 일반적으로 이러한 사항을 제대로 이해하지 못합니다. "Object generics"는 어떻습니까?

@spion ,

awaitObject<p,T[p]>(obj: {...p:Promise<T[p]>}): Promise<{...p:T[p]}>

(출력 서명에서 Promise를 놓쳤습니다)
-)

@ jods4 ,
{ ...p: T[p] } 가 해결책 인 실제 문제가 많이 있습니다. 이름 하나 : react + flux / redux

트윗 담아 가기
나는 이것이 정확하게 지정하기가 매우 복잡 해지는 것이 걱정됩니다.

이 문제의 동기는 표류했습니다. 원래는 객체 필드를 나타내는 문자열을 받아들이는 API를 지원하는 것이 었습니다. 이제는 객체를 매우 동적 인 방식으로 맵이나 레코드로 사용하는 API에 대해 주로 논의하고 있습니다. 우리가 하나의 돌로 두 마리의 새를 죽일 수 있다면 나는 그것을 할 수 있습니다.

문자열 문제를 취하는 API로 돌아 가면 T[p] 는 내 의견으로는 완전한 솔루션이 아닙니다 (전에 이유를 말 했음).

_ 정확성을 위해 _ awaitObject 는 Promise가 아닌 속성도 허용해야합니다. 최소한 Bluebird props 는 허용합니다. 이제 우리는

awaitObject<p,T[p]>(obj: { ...p: T[p] | Promise<T[p]> }): Promise<T>

이 표기법이 작동 할 것으로 예상하기 때문에 반환 유형을 Promise<T> 로 변경했습니다.
다른 오버로드가 있습니다. 예를 들어 그러한 객체에 대해 Promise를 취하는 것입니다 (그 서명이 훨씬 더 재미 있습니다). 따라서 { ...p } 표기법은 다른 유형보다 더 나쁜 것으로 간주되어야합니다.

이 모든 것을 지정하는 것은 힘든 일이 될 것입니다. 앞으로 나아가고 싶다면 다음 단계라고 말하고 싶습니다.

@spion @ jods4

이 기능은 제네릭이 아니라 더 높은 종류의 다형성 유형에 관한 것이 아니라는 점을 언급하고 싶었습니다. 이것은 포함 유형 (아래에 설명 된 일부 "고급"확장명 포함)을 통해 속성 유형을 참조하는 단순한 구문으로, 개념 상 typeof 와 그리 멀지 않습니다. 예:

type MyType = { abcd: number };
let y: MyType["abcd"]; // Technically this could also be written as MyType.abcd

이제 비교 :

type MyType = { abcd: number };
let x: MyType;

let y: typeof x.abcd;

typeof 에는 두 가지 주요 차이점이 있습니다. typeof 와 달리 이것은 인스턴스가 아닌 유형 (적어도 원시적이지 않은 것, 여기서 종종 생략되는 사실)에 적용됩니다. 또한 (그리고 이것은 중요한 것입니다) 속성 경로 설명자 및 제네릭으로 리터럴 문자열 상수 (컴파일시 알려야 함) 사용을 지원하도록 확장되었습니다.

const propName = "abcd";
let y: MyType[propName];
// Or with a generic parameter:
let y: T[propName];

그러나 기술적으로 typeof 는 문자열 리터럴도 지원하도록 확장 될 수 있습니다 ( x 에 일반 유형이있는 경우 포함).

let x: MyType;
const propName = "abcd";
let y: typeof x[propName];

이 확장을 사용하면 여기에서 일부 대상 사례를 해결하는 데 사용할 수도 있습니다.

function get<T>(propName: string, obj: T): typeof obj[propName]

그러나 typeof 는 무한한 중첩 수준을 지원하므로 더 강력합니다.

let y: typeof x.prop.childProp.deeperChildProp

그리고 이것은 한 단계 만 진행됩니다. 즉, 다음을 지원할 계획이 없습니다 (내가 아는 한).

let y: MyType["prop"]["childProp"]["deeperChildProp"];
// Or alternatively
let y: MyType["prop.childProp.deeperChildProp"];

이 제안의 범위가 너무 좁다 고 생각합니다 (모호한 수준에서 제안이라고도 부를 수 있다면). 특정 문제를 해결하는 데 도움이 될 수 있으며 (대체 솔루션이있을 수 있음) 많은 사람들이이를 홍보하고 싶어하는 것처럼 보입니다. 그러나 그것은 또한 언어의 귀중한 구문을 소비하고 있습니다. 더 넓은 계획과 디자인 지향적 인 접근 방식이 없이는 현재까지 명확한 사양도없는 것을 서둘러 도입하는 것은 현명하지 않은 것 같습니다.

_ 편집 : 코드 예제에서 몇 가지 명백한 실수 수정 _

typeof 대안에 대해 약간 조사했습니다.

typeof x["abcd"]typeof x[42] 대한 향후 언어 지원 이 승인되었으며 현재 개발중인 # 6606에 속합니다 (작동중인 구현이 있음).

이것은 절반 정도입니다. 이것들이 제자리에 있으면 나머지는 여러 단계에서 수행 할 수 있습니다.

(1) 문자열 리터럴 상수 (또는 숫자 상수-튜플에 유용 할 수 있습니까?)에 대한 지원을 유형 표현식의 속성 지정자로 추가합니다. 예 :

let x: MyType;
const propName = "abcd";
let y: typeof x[propName];

(2) 이러한 지정자를 제네릭 유형에 적용 할 수 있습니다.

let x: T; // Where T should extend 'object'
const propName = "abcd";
let y: typeof x[propName];

(3) 이러한 지정자가 전달되도록 허용하고 실제로 함수 인수를 통해 참조를 "인스턴스화"합니다 ( typeof list[0] 계획되어 있으므로 pluck 와 같은 더 복잡한 경우를 다룰 수 있다고 생각합니다.)

function get<T extends object>(obj: T, propertyName: string): typeof obj[propertyName];
function pluck<T extends object>(list: Array<T>, propertyName: string): Array<typeof list[0][propertyName]>;

( object 유형은 # 1809에서 제안 된 유형입니다)

더 강력하더라도 (예 : typeof obj[propName][nestedPropName] 지원할 수 있음)이 대체 접근 방식이 여기에 설명 된 모든 경우를 포함하지 않을 수 있습니다. 이것에 의해 처리되지 않을 것 같은 예가 있으면 알려주세요. (생각하는 한 가지 시나리오는 객체 인스턴스가 전혀 전달되지 않을 때입니다. 그러나이 시점에서 저에게는 어려운 경우를 상상해보십시오. 가능하다고 생각하지만 그게 필요할 것입니다).

_ 편집 : 코드의 일부 오류 수정 _

뿡 빵뀨
몇 가지 생각 :

  • 이것은 API 정의를 수정하지만 호출자에게는 도움이되지 않습니다. 우리는 여전히 전화 사이트에 nameof 또는 Expression 가 필요합니다.
  • .d.ts 정의에서 작동하지만 일반적으로 TS 함수 / 구현에서 정적으로 추론하거나 검증 할 수 없습니다.

더 많이 생각할수록 Expression<T, U> 는 이러한 종류의 API에 대한 솔루션처럼 보입니다. 호출 사이트 문제, 결과 입력을 수정하고 구현에서 추론 가능 + 검증 가능할 수 있습니다.

뿡뿡

pluck 는 표현식으로 구현 될 수 있다고 위의 이전 주석 방식 에서 언급했습니다. C #에 익숙해졌지만 실제로 실험 해본 적은 없었기 때문에이 시점에서 그것들에 대해 잘 이해하지 못하고 있음을 인정합니다.

어쨌든 TypeScript가 _type argument inference_를 지원한다고 언급하고 싶었으므로 pluck 을 사용하는 대신 map 를 사용하여 동일한 결과를 얻을 수 있으며 일반 매개 변수를 지정할 필요도 없습니다 ( 추론 됨) 전체 유형 검사 및 완료도 제공합니다.

let x = [{name: "John", age: 34}, {name: "Mary", age: 53}];

let result = x.map(obj => obj.name);
// 'result' is ["John", "Mary"] and its type inferred as 'string[]' 

map (데모를 위해 매우 단순화 됨)는 다음과 같이 선언됩니다.

map<U>(mapFunc: (value: T) => U): U[];

이 동일한 추론 패턴을 '트릭'으로 사용하여 임의의 람다 (호출 할 필요도 없음)의 결과를 함수에 전달하고 반환 유형을 설정할 수 있습니다.

function thisIsATrick<T, U>(obj: T, onlyPassedToInferTheReturnType: () => U): U {
   return;
}

let x = {name: "John", age: 34};

let result = thisIsATrick(x, () => x.age) // Result inferred as 'number' 

_ 편집 : 람다로 전달하는 것은 어리석은 것처럼 보일 수 있습니다. 그러나 중첩 된 객체 (예 : x.prop.Childprop )와 같은 더 복잡한 경우에는 정의되지 않은 참조 또는 오류가 발생할 수있는 null 참조가있을 수 있습니다. 반드시 호출되지 않는 람다에 포함하면이를 피할 수 있습니다.

여기에서 설명하는 일부 사용 사례에 대해 잘 알지 못함을 인정합니다. 개인적으로 속성 이름을 문자열로 사용하는 함수를 호출 할 필요가 없다고 느꼈습니다. 일반적으로 유형 인수 인터페이스와 함께 람다 (당신이 설명하는 장점도 유지됨)를 전달하면 많은 일반적인 문제를 해결할 수 있습니다.

내가 typeof 대체 접근법을 제안한 이유는 대부분 동일한 사용 사례를 다루고 사람들이 여기에서 설명하는 것과 동일한 기능을 제공하는 것처럼 보이기 때문입니다. 직접 사용 하시겠습니까? 나는 모르겠다. 실제로 필요하다고 느껴본 적이 없다 (단순히 밑줄과 같은 외부 라이브러리를 거의 사용하지 않고 거의 항상 필요에 따라 유틸리티 함수를 혼자서 개발할 수있다).

@malibuzios 사실, pluck 는 람다 + map 가 내장 된 약간 어리 석습니다.

이 주석에서는 모두 문자열을받는 5 가지 API를 제공합니다. 모두 변경 사항을 관찰하는 데 연결되어 있습니다.

@ jods4 및 기타

# 6606이 완료되면 1 단계 (상수 문자열 리터럴을 속성 지정자로 사용하도록 허용) 만 구현하면 여기에 필요한 기능 중 일부를 얻을 수 있지만 우아하게는 할 수 없습니다 (필요할 수 있음). 상수로 선언 할 속성 이름, 추가 문 추가).

function observeProperty<T, U>(obj: T, propName: string ): Subscriber<U> {
    ....
}

let x = { name: "John", age: 42 };

const propName = "age";
observeProperty<typeof x, typeof x[propName]>(x, propName);

그러나이를 구현하기위한 노력의 양은 2 단계와 3 단계를 구현하는 것보다 훨씬 적을 수 있습니다 (확실하지 않지만 1이 이미 # 6606에 포함되어있을 수 있습니다). 2 단계가 구현되면 x 에 제네릭 유형이있는 경우도 포함됩니다 (하지만 이것이 실제로 필요한지 확실하지 않습니까?).

편집 : 내가 외부 상수를 사용하고 속성 이름을 두 번 쓴 이유는 입력을 줄이는 것뿐만 아니라 이름이 항상 일치하도록하기 위해서 였지만 "이름 바꾸기"및 "모두 찾기"와 같은 도구에서는 여전히 사용할 수 없습니다. 참고 문헌 "은 심각한 단점이라고 생각합니다.

@ jods4 나는 여전히 문자열을 사용하지 않는 더 나은 솔루션을 찾고 있습니다. nameof 및 그 변형으로 무엇을 할 수 있는지 생각해 보겠습니다.

여기 또 다른 아이디어가 있습니다.

문자열 리터럴은 이미 유형으로 지원되므로 이미 다음과 같이 작성할 수 있습니다.

let x: "HELLO"; 

일반 전문화의 한 형태로 함수에 전달 된 리터럴 문자열을 볼 수 있습니다.

(_Edit :이 예제는 s 가 함수 본문에서 변경되지 않도록 수정되었습니다.하지만 const 는 현재 매개 변수 위치에서 지원되지 않습니다 ( readonly 대해 확실하지 않음). 그러나)._) :

function func(const s: string)

s 와 연관된 string 매개 변수 유형은 여기서 암시 적 제네릭으로 볼 수 있습니다 (문자열 리터럴에 특화 될 수 있으므로). 명확성을 위해 더 명시 적으로 작성합니다 (정말 필요한지 확실하지 않지만).

function func<S extends string>(const s: S)
func("TEST");

내부적으로 다음을 전문으로 할 수 있습니다.

function func(s: "TEST")

이제 이것은 속성 이름을 "전달"하는 다른 방법으로 사용할 수 있습니다. 여기서 의미론을 더 잘 포착한다고 생각합니다.

function observeProperty<T, S extends string>(obj: T, const propName: S): Subscriber<T[S]>

x = { name: "John",  age: 33};

observeProperty(x, nameof(x.age))

T[S] 이후 TS 모두 일반 매개 변수입니다. 런타임 및 유형 범위 요소 (예 : T[someString] )를 혼합하는 것보다 유형 위치에 두 유형을 결합하는 것이 더 자연스러워 보입니다.

_ 편집 : 변경 불가능한 문자열 매개 변수 유형을 갖도록 다시 작성된 예제 ._

TS 1.8.7 하고 있고 최신 버전이 아니기 때문에 최신 버전에서는

const x = "Hello";

x 유형은 이미 리터럴 유형 "Hello" , (즉 : x: "Hello" )으로 추론되어 많은 의미가 있습니다 (# 6554 참조).

따라서 매개 변수를 const (또는 readonly 로 정의 할 수있는 방법이 있습니까?) :

function func<S extends string>(const s: S): S

그런 다음 이것이 유지 될 수 있다고 믿습니다.

let result = func("abcd"); // type of 'result' inferred as the literal type "abcd"

이 중 일부는 다소 새롭고 최근 언어 기능에 의존하기 때문에 가능한 한 명확하게 요약하려고합니다.

(1) const (그리고 아마도 readonly ) 문자열 변수가 컴파일 타임에 알려진 값을 받으면 변수는 자동으로 같은 값을 가진 리터럴 유형을받습니다 ( 나는 이것이 1.8.x 에서 발생하지 않는 최근 행동이라고 생각합니다.

const x = "ABCD";

x 유형은 string !가 아니라 "ABCD" x 로 추론됩니다. 예를 들어 x: "ABCD" 있습니다.

(2) readonly 함수 매개 변수가 허용 된 경우, 제네릭 유형의 readonly 매개 변수는 매개 변수가 변경 불가능하므로 인수로 수신 될 때 해당 유형을 문자열 리터럴로 자연스럽게 특수화합니다!

function func<S extends string>(readonly str: S);
func("ABCD");

여기서 Sstring 아니라 "ABCD" 유형으로 확인되었습니다!

그러나 str 매개 변수가 변경 불가능하지 않은 경우 컴파일러는 해당 매개 변수가 함수 본문에 재 할당되지 않는다고 보장 할 수 없으므로 유추 된 유형은 string 입니다.

function func<S extends string>(str: S) {
    str = "DCBA"; // This may happen
}

func("ABCD");

(3) 이것을 활용하고 속성 지정자가 유형에 대한 참조가되도록 원래 제안을 수정할 수 있습니다. 이는 string 의 파생물로 제한되어야합니다 (일부 경우에는 현재 실제로 그렇게 할 수있는 방법은 없지만 런타임 엔터티보다는) 단일 리터럴 유형으로 만 제한하는 것이 적절할 수도 있습니다.

function get<T extends object, S extends string>(obj: T, readonly propName: S): T[S]

TypeScript는 형식 인수 추론을 지원하므로이를 호출 할 때 형식 인수를 명시 적으로 지정할 필요가 없습니다.

let x = { name: "John", age: 42 };

get(x, "age"); // result type is inferred to be 'number'
// or for stronger type safety:
get(x, nameof(x.age)); // result type is inferred to be 'number'

_ 편집 : 일부 맞춤법 및 코드 오류를 수정했습니다 ._
_ 참고 :이 수정 된 접근 방식의 일반화 및 확장 버전은 이제 # 7730에서도 추적됩니다 ._

@Raynos와의 토론에서 나온 유형 속성 유형 (또는 최근에 부르는 "인덱싱 된 제네릭")의 또 다른 사용이 있습니다.

배열에 대한 다음과 같은 일반 검사기를 이미 작성할 수 있습니다.

function tArray<T>(f:(t:any) => t is T) {
    return function (a:any): a is Array<T> {
        if (!Array.isArray(a)) return false;
        for (var k = 0; k < a.length; ++k)
            if (!f(a[k])) return false;
        return true;
    }
}

function tNumber(n:any): n is number {
    return typeof n === 'number'
}
var isArrayOfNumber = tArray(tNumber)

function test(x: {}) {
    if (isArrayOfNumber(x)) {
        return x[x.length - 1].toFixed(2); // this type checks
    }
}

제네릭을 인덱싱하면 객체에 대한 일반 검사기를 작성할 수도 있습니다.

function tObject<p, T[p]>(checker: {...p: (t:any) => t is T[p]}) {
  return function(obj: any): obj is {...p: T[p] } {
    for (var key in checker) if (!checker[key](obj[key])) return false;
    return true;
  }
}

문자열, 숫자, 부울, null 및 undefined에 대한 기본 검사기와 함께 다음과 같이 작성할 수 있습니다.

var isTodoList = tObject({
  items: tArray(tObject({text: tString, completed: tBoolean})),
  showCompleted: tBoolean
})

그리고 올바른 런타임 검사기 _and_ 컴파일 시간 유형 가드로 동시에 결과를 얻습니다. :)

아무도 이것에 대해 아직 작업을 했습니까, 아니면 누구의 레이더에 있습니까? 이것은 lodash 또는 ramda 와 같은 많은 데이터베이스 인터페이스를 포함하여 얼마나 많은 표준 라이브러리가 타이핑을 사용할 수 있는지에 대한 주요 개선이 될 것입니다.

@malibuzios 난 당신이 @Artazor의 제안에 접근하고 생각 :)

우려 사항을 해결하려면 :

function func<S extends string>(readonly str: S): T[str] {
 ...
}

이것은

function func<S extends string, T[S]>(str: S):T[S] { }

이렇게하면 다음과 함께 호출 될 때 이름이 가장 구체적인 유형 (문자열 상수 유형)으로 잠 깁니다.

func("test")

S 유형은 "test" ( string 아님). 따라서 str 에는 다른 값을 다시 할당 할 수 없습니다. 예를 들어 시도한 경우

str = "other"

컴파일러가 오류를 생성 할 수 있습니다 (분산 건전성 문제가없는 경우;))

하나의 속성 유형을 가져 오는 대신 임의의 수퍼 유형 유형을 가져 오는 옵션을 갖고 싶습니다.

그래서 내 제안은 다음 구문을 추가하는 것입니다. T[prop] 대신 T[...props] 구문을 추가하고 싶습니다. 여기서 propsT 의 멤버 배열이어야합니다. 그리고 그 결과 유형의 슈퍼 유형입니다 T 의 회원들과 T 에 정의 된 props .

Node.js를위한 인기있는 ORM 인 Sequelize에서 매우 유용 할 것이라고 생각합니다. 보안상의 이유로 및 성능상의 이유로 사용해야하는 테이블의 속성 만 쿼리하는 것이 좋습니다. 그것은 종종 슈퍼 유형의 유형을 의미합니다.

interface IUser {
    id: string;
    name: string;
    email: string;
    createdAt: string;
    updatedAt: string;
    password: string;
    // ...
}

interface Options<T> {
     attributes: (memberof T)[];
}

interface Model<IInstance> {
     findOne(options: Options<IInstance>): IInstance[...options.attributes];
}

declare namespace DbContext {
   define<T>(): Model<T>;
}

const Users = DbContext.define<IUser>({
   id: { type: DbContext.STRING(50), allowNull: false },
   // ...
});

const user = Users.findOne({
    attributes: ['id', 'email', 'name'],
    where: {
        id: 1,
    }
});

user.id
user.email
user.name

user.password // error
user.createdAt // error
user.updatedAt // error

(제 예에서는 예상되는 연산자 memberoftypeof options.attributes 와 동일한 표현식 options.attributes 포함되어 있지만 typeof 연산자는 유형을 예상하는 위치에 있기 때문에이 경우 중복됩니다.).

아무도 주장하지 않으면 나는 이것을 시작했습니다.

함수 내부의 유형 안전성에 대해 어떻게 생각하십니까? 즉, return 문이 반환 유형에 할당 가능한 것을 반환하는지 확인하세요.

interface A {
     a: string;
}
function f(p: string): A[p] {
    return 'aaa'; // This is string, but can we ensure it is the intended A[p] ?
}

또한 여기에 사용 된 "Property Type"이라는 이름이 약간 잘못된 것 같습니다. 유형에 거의 모든 유형이있는 속성이 있음을 나타냅니다.

"속성 참조 유형"은 어떻습니까?

함수 내부의 형식 안전에 대해 어떻게 생각하십니까?

내 브레인 스토밍 :

let a: A;
function f(p: string): A[p] {
  let x = a[p]; // typeof A[p], only when:
  // 1. p is directly referencing function argument
  // 2. function return type is Property Reference Type

  p = "abc"; // not allowed to assign a new value when p is used on Property Reference Type

  return x; // x is A[p], so okay
}

그리고 반환 줄에서 일반 문자열을 허용하지 않습니다.

@malibuzios 문제는 유추 할 수없는 경우에 대비하여 모든 제네릭을 지정해야한다는 것입니다. 이는 바이러스 성 / 코드 기반을 오염시킬 수 있습니다.

https://github.com/Microsoft/TypeScript/issues/1295#issuecomment -239653337에 대한 TS 팀의 의견이 있습니까?

@RyanCavanaugh @mhegazy 등?

이름에 관해서는이 기능 (적어도 @Artazor가 제안한 형식)을 "Indexed Generics"라고 부르기 시작했습니다.

이 문제에 대한 다른 각도의 해결책이 될 수 있습니다. 이미 가져 왔는지 모르겠지만 긴 실입니다. 문자열 일반 제안을 개발하여 인덱싱 서명을 확장 할 수 있습니다. 인덱서 유형에 문자열 리터럴을 사용할 수 있으므로이 값을 동등하게 만들 수 있습니다 (현재는 그렇지 않음).

interface A1 {
    a: number;
    b: boolean;
}
interface A2 {
    [index: "a"]: number;
    [index: "b"]: boolean;
}

그래서 우리는 다음과 같이 쓸 수 있습니다.

declare function pluck<P, T extends { [indexer: P]: R; }, R>(obj: T, p: P): R;

고려해야 할 몇 가지 사항이 있습니다.

  • P 는 문자열 리터럴 만 될 수 있음을 나타내는 방법은 무엇입니까?

    • 문자열은 기술적으로 모든 문자열 리터럴을 확장하므로 P extends string 은 (는) 그다지 이념적이지 않습니다.

    • P super string 제약 (# 7265, # 6613, https://github.com/Microsoft/TypeScript/issues/6613#issuecomment-175314703) 허용에 대한 다른 논의가 진행 중입니다.

    • 우리가 정말로 이것에 대해 신경을 써야합니까? Tstring s 또는 number 인덱서가있는 경우 인덱서 유형에 허용되는 모든 것을 사용할 수 있습니다. Pstring 또는 number 있습니다.

  • 현재 "something" 를 두 번째 인수로 전달하면 string 유형이됩니다.

    • 문자열 리터럴 # 10195가 답이 될 수 있습니다.

    • 또는 TS는 허용되는 경우보다 구체적인 P 사용해야한다고 추론 할 수 있습니다.

  • 인덱서는 암시 적으로 선택 사항입니다. { [i: string]: number /* | undefined */ }

    • 도메인에 undefined 을 (를) 정말로 원하지 않는 경우 시행하는 방법은 무엇입니까?

  • P , TR 사이의 모든 관계에 대한 자동 유형 추론이 작동하도록하는 열쇠입니다.

@weswigham @mhegazy , 그리고 나는 최근에 이것을 논의하고 있습니다; 우리가 진행하는 모든 발전 사항을 알려 드리며 이것은 단지 아이디어를 프로토 타입 한 것임을 명심하십시오.

현재 아이디어 :

  • Foo 에서 속성 이름의 합집합을 문자열 리터럴 유형으로 가져 오는 keysof Foo 연산자.
  • Foo[K] 유형에 대해 문자열 리터럴 유형 또는 문자열 리터럴 유형의 합집합 인 K Foo[K] 유형을 지정합니다.

이러한 기본 블록에서 문자열 리터럴을 적절한 유형으로 추론해야하는 경우 다음과 같이 작성할 수 있습니다.

function foo<T, K extends keysof T>(obj: T, key: K): T[K] {
    // ...
}

여기서 Kkeysof T 의 하위 유형이됩니다. 즉, 문자열 리터럴 유형 또는 문자열 리터럴 유형의 합집합을 의미합니다. key 매개 변수에 대해 전달하는 모든 것은 해당 리터럴 / 리터럴 조합에 의해 컨텍스트 유형으로 지정되어야하며 단일 문자열 리터럴 유형으로 추론되어야합니다.

예를 들어

interface HelloWorld { hello: any; world: any; }

function foo<K extends keysof HelloWorld>(key: K): K {
    return key;
}

// 'x' has type '"hello"'
let x = foo("hello");

가장 큰 문제는 keysof 종종 작업을 "지연"해야한다는 것입니다. 내가 게시 한 첫 번째 예제에서와 같이 유형 매개 변수에 대한 문제인 유형을 평가하는 방법에 너무 열망합니다 (즉, 실제로 해결하려는 경우는 실제로 어려운 부분입니다 : smile :).

당신에게 모든 업데이트를 제공하기를 바랍니다.

@DanielRosenwasser 업데이트 @weswighamkeysof 연산자에 대한 PR을 제출 한 것을 보았 으므로이 문제를 여러분에게 전달하는 것이 더 좋습니다.

왜 원래 제안 된 구문에서 벗어나기로 결정했는지 궁금합니다.

function get(prop: string): T[prop];

keysof ?

T[prop] 은 덜 일반적이며 많은 인터레이스 기계가 필요합니다. 여기에 하나의 큰 문제는 당신도의 문자 내용과 관련 거라고 어떻게 prop 의 속성 이름에 T . 나는 당신이 무엇을할지 완전히 확신하지 못합니다. 암시 적 유형 매개 변수를 추가 하시겠습니까? 상황에 맞는 입력 동작을 변경해야합니까? 서명에 특별한 것을 추가해야합니까?

대답은 아마도 그 모든 것에 예일 것입니다. 내 직감이 더 간단하고 별개의 두 개념을 사용하고 거기에서 구축하는 것이 더 낫다고 말했기 때문에 우리를 그로부터 멀어지게했습니다. 단점은 특정 경우에 약간 더 많은 상용구가 있다는 것입니다.

이런 종류의 패턴을 사용하는 새로운 라이브러리가 있고 그 보일러 플레이트가 TypeScript로 작성하기 어렵게 만든다면, 우리는 그것을 고려해야 할 것입니다. 그러나 전반적으로이 기능은 주로 도서관 소비자에게 서비스를 제공하기위한 것입니다. 사용 사이트는 어쨌든 여기서 이점을 얻을 수있는 곳이기 때문입니다.

@DanielRosenwasser 간신히 토끼 구멍 아래로 갔다. @SaschaNaz 아이디어를 구현하는 keysof 은 중복이라고 생각합니다. T[p] 이미 pT 의 리터럴 소품 중 하나 여야한다고 이미 관련시킵니다.

내 대략적인 구현은 PropertyReferencedType 라는 새로운 유형을 도입하는 것이 었습니다.

export interface PropertyReferencedType extends Type {
        property: Symbol;
        targetType: ObjectType;
}

PropertyReferencedType 의 반환 유형으로 선언 된 함수를 입력하거나 PropertyReferencedType 를 참조하는 함수를 입력 할 때 : ElementAccessExpression 의 유형은 다음을 참조하는 속성으로 증가됩니다. 액세스 된 속성의 기호입니다.

export interface Type {
        flags: TypeFlags;                // Flags
        /* <strong i="20">@internal</strong> */ id: number;      // Unique ID
        //...
        referencedProperty: Symbol; // referenced property
}

따라서 참조 된 속성 기호가있는 유형은 PropertyReferencedType 할당 할 수 있습니다. 확인하는 동안 referencedPropertyT[p] p 와 일치해야합니다. 또한 요소 액세스 식의 부모 유형을 T 할당 할 수 있어야합니다. 그리고 일을 더 쉽게하기 위해 p 역시 const 여야합니다.

새로운 유형 PropertyReferencedType 는 함수 내에 "미해결 유형"으로 만 존재합니다. 호출 사이트에서 p 유형을 해결해야합니다.

interface A { a: string }
declare function getProp(p: string): A[p]
getProp('a'); // string

PropertyReferencedType 함수 할당을 통해서만 전파되고 호출 식을 통해 전파 될 수 없습니다. PropertyReferencedType 는 반환 유형이 T[p] 함수의 본문을 확인하는 데 도움이되는 임시 유형일 뿐이 기 때문입니다

keysofT[K] 유형 연산자를 도입하면 다음과 같이 사용할 수 있음을 의미합니다.

interface A {
  a: number;
  b: string;
}
type AK = keysof A; // "a" | "b"
type AV = A[AK]; // number | string ?
type AA = A["a"]; // number ?
type AB = A["b"]; // string ?
type AC = A["c"]; // error?
type AN = A[number]; // error?

type X1 = keysof { [index: string]: number; }; // string ?
type X2 = keysof { [index: string]: number; [index: number]: string; }; // string | number ?

@DanielRosenwasser 는 귀하의 예가 내

function foo<T, K extends keysof T>(obj: T, key: K): T[K] {
    // ...
}
// same as ?
function foo<K, V, T extends { [k: K]: V; }>(obj: T, key: K): V {
    // ...
}

Underscore의 _.pick 서명이 어떻게 작성되는지 모르겠습니다.

o2 = _.pick(o1, 'p1', 'p2');

pick(Object, ...props: String[]) : WHAT GOES HERE;

@rtm https://github.com/Microsoft/TypeScript/issues/1295#issuecomment -234724380에서 제안했습니다. 이 문제와 관련이 있더라도 새로운 문제를 여는 것이 더 나을 수 있습니다.

이제 # 11929에서 구현이 가능합니다.

이 페이지가 도움이 되었나요?
0 / 5 - 0 등급