[casting] TypeScript의 속성을 사용하여 함수 개체 빌드

몇 가지 속성이있는 함수 개체를 만들고 싶습니다. 예를 들어 JavaScript에서는 다음을 수행합니다.

var f = function() { }
f.someValue = 3;

이제 TypeScript에서이 유형을 다음과 같이 설명 할 수 있습니다.

var f: { (): any; someValue: number; };

그러나 캐스트 없이는 실제로 만들 수 없습니다. 예 :

var f: { (): any; someValue: number; } =
    <{ (): any; someValue: number; }>(
        function() { }
    );
f.someValue = 3;

캐스트없이 어떻게 만들 수 있을까요?



답변

따라서 요구 사항이 단순히 캐스트없이 해당 함수를 “f”에 빌드하고 할당하는 것이라면 가능한 해결책은 다음과 같습니다.

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

기본적으로 자체 실행 함수 리터럴을 사용하여 할당이 완료되기 전에 해당 서명과 일치하는 개체를 “구성”합니다. 유일한 이상한 점은 함수의 내부 선언이 ‘any’유형이어야한다는 것입니다. 그렇지 않으면 컴파일러가 아직 객체에 존재하지 않는 속성에 할당하고 있다고 울었습니다.

편집 : 코드를 약간 단순화했습니다.


답변

업데이트 :이 답변은 이전 버전의 TypeScript에서 최상의 솔루션 이었지만 최신 버전에서 더 나은 옵션을 사용할 수 있습니다 (다른 답변 참조).

허용되는 답변은 작동하며 일부 상황에서 필요할 수 있지만 객체를 구축하는 데 유형 안전을 제공하지 않는다는 단점이 있습니다. 이 기술은 정의되지 않은 속성을 추가하려고하면 최소한 유형 오류를 발생시킵니다.

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3


답변

이것은 이제 쉽게 얻을 수 있습니다 (typescript 2.x) Object.assign(target, source)

예:

여기에 이미지 설명 입력

여기서 마법 Object.assign<T, U>(t: T, u: U)교차로 를 반환하도록 입력 된 것 입니다. T & U 입니다.

이것이 알려진 인터페이스로 해결되도록 강제하는 것도 간단합니다. 예를 들면 :

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
);

호환되지 않는 입력으로 인한 오류 :

오류 : foo : number는 foo : string에 할당 할 수 없습니다.
오류 : number는 string []에 할당 할 수 없습니다 (반환 유형).

주의 : 이전 브라우저를 대상으로하는 경우 Object.assign 을 폴리 필해야 할 수 있습니다 .


답변

TypeScript는 선언 병합을 통해이 경우를 처리하도록 설계되었습니다 .

함수를 만든 다음 함수에 속성을 추가하여 함수를 확장하는 JavaScript 방식에 대해서도 잘 알고있을 수 있습니다. TypeScript는 선언 병합을 사용하여 형식이 안전한 방식으로 이와 같은 정의를 작성합니다.

선언 병합을 통해 무언가가 함수이자 네임 스페이스 (내부 모듈)라고 말할 수 있습니다.

function f() { }
namespace f {
    export var someValue = 3;
}

이것은 타이핑을 보존하고 우리가 f()f.someValue. .d.ts기존 JavaScript 코드 용 파일을 작성할 때 declare다음을 사용하십시오 .

declare function f(): void;
declare namespace f {
    export var someValue: number;
}

함수에 속성을 추가하는 것은 TypeScript에서 종종 혼란 스럽거나 예상치 못한 패턴이므로 피하려고하지만 이전 JS 코드를 사용하거나 변환 할 때 필요할 수 있습니다. 이것은 내부 모듈 (네임 스페이스)을 외부와 혼합하는 것이 적절한 유일한 경우 중 하나입니다.


답변

매우 간단하다고 말할 수는 없지만 확실히 가능합니다.

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

궁금하다면 이것은 TypeScript / JavaScript 버전의 내 요점에서 나온 것입니다.Optional


답변

바로 가기로 [ ‘property’] 접근자를 사용하여 객체 값을 동적으로 할당 할 수 있습니다.

var f = function() { }
f['someValue'] = 3;

이것은 유형 검사를 우회합니다. 그러나 동일한 방식으로 의도적으로 속성에 액세스해야하기 때문에 매우 안전합니다.

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

그러나 실제로 속성 값에 대한 유형 검사를 원하면 작동하지 않습니다.


답변

업데이트 된 답변 : 교차로 유형 추가 이후 & 두 개의 추론 된 유형을 즉석에서 “병합”할 수 있습니다.

다음은 일부 객체의 속성을 읽고 객체 from위에 복사 하는 일반 도우미입니다 onto. 동일한 객체를 반환 onto하지만 두 속성 집합을 모두 포함하는 새 유형을 사용하여 런타임 동작을 올바르게 설명합니다.

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

이 저수준 도우미는 여전히 유형 어설 션을 수행하지만 설계 상 유형 안전합니다. 이 도우미를 사용하면 전체 형식 안전성으로 OP의 문제를 해결하는 데 사용할 수있는 연산자가 있습니다.

interface Foo {
    (message: string): void;
    bar(count: number): void;
}

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), {
        bar(count: number) {
            console.log(`bar was passed ${count}`)
        }
    }
);

TypeScript 플레이 그라운드에서 사용해 보려면 여기를 클릭하십시오 . foo유형 Foo이되도록 제한 했으므로의 결과 merge는 완전해야 Foo합니다. 따라서로 이름 bar을 바꾸면 bad유형 오류가 발생합니다.

NB 그러나 여기에는 여전히 하나의 유형 구멍이 있습니다. TypeScript는 유형 매개 변수를 “함수가 아님”으로 제한하는 방법을 제공하지 않습니다. 따라서 혼란스럽고 함수를에 두 번째 인수로 전달할 수 있으며 merge작동하지 않습니다. 따라서 이것이 선언 될 수있을 때까지 런타임에 포착해야합니다.

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    if (typeof from !== "object" || from instanceof Array) {
        throw new Error("merge: 'from' must be an ordinary object");
    }
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}