developer tip

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

optionbox 2020. 11. 19. 08:05
반응형

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

Adding properties to functions is often a confusing or unexpected pattern in TypeScript, so try to avoid it, but it can be necessary when using or converting older JS code. This is one of the only times it would be appropriate to mix internal modules (namespaces) with external.


As a shortcut, you can dynamically assign the object value using the ['property'] accessor:

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

This bypasses the type checking. However, it is pretty safe because you have to intentionally access the property the same way:

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

However, if you really want the type checking for the property value, this won't work.


An updated answer: since the addition of intersection types via &, it is possible to "merge" two inferred types on the fly.

Here's a general helper that reads the properties of some object from and copies them over an object onto. It returns the same object onto but with a new type that includes both sets of properties, so correctly describing the runtime behaviour:

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

This low-level helper does still perform a type-assertion, but it is type-safe by design. With this helper in place, we have an operator that we can use to solve the OP's problem with full type safety:

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}`)
        }
    }
);

Click here to try it out in the TypeScript Playground. Note that we have constrained foo to be of type Foo, so the result of merge has to be a complete Foo. So if you rename bar to bad then you get a type error.

NB There is still one type hole here, however. TypeScript doesn't provide a way to constrain a type parameter to be "not a function". So you could get confused and pass your function as the second argument to merge, and that wouldn't work. So until this can be declared, we have to catch it at runtime:

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

I can't say that it's very straightforward but it's definitely possible:

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

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

if you got curious this is from a gist of mine with the TypeScript/JavaScript version of Optional


Old question, but for versions of TypeScript starting with 3.1, you can simply do the property assignment as you would in plain JS, as long as you use a function declaration or the const keyword for your variable:

function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"

Reference and online example.


이것은 강력한 타이핑에서 출발하지만 할 수 있습니다.

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

이 질문을 발견했을 때와 같이 억압적인 강력한 타이핑을 피하려는 경우. 슬프게도 이것은 TypeScript가 완벽하게 유효한 JavaScript에서 실패하는 경우이므로 TypeScript에 백 오프하도록 지시해야합니다.

"당신 JavaScript는 완벽하게 유효한 TypeScript입니다"는 거짓으로 평가됩니다. (참고 : 0.95 사용)

참고 URL : https://stackoverflow.com/questions/12766528/build-a-function-object-with-properties-in-typescript

반응형