Build a function object with properties in TypeScript

后端 未结 9 1261
南旧
南旧 2020-11-28 06:00

I want to create a function object, which also has some properties held on it. For example in JavaScript I would do:

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


        
相关标签:
9条回答
  • 2020-11-28 06:23

    So if the requirement is to simply build and assign that function to "f" without a cast, here is a possible solution:

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

    Essentially, it uses a self executing function literal to "construct" an object that will match that signature before the assignment is done. The only weirdness is that the inner declaration of the function needs to be of type 'any', otherwise the compiler cries that you're assigning to a property which does not exist on the object yet.

    EDIT: Simplified the code a bit.

    0 讨论(0)
  • 2020-11-28 06:29

    Update: This answer was the best solution in earlier versions of TypeScript, but there are better options available in newer versions (see other answers).

    The accepted answer works and might be required in some situations, but have the downside of providing no type safety for building up the object. This technique will at least throw a type error if you attempt to add an undefined property.

    interface F { (): any; someValue: number; }
    
    var f = <F>function () { }
    f.someValue = 3
    
    // type error
    f.notDeclard = 3
    
    0 讨论(0)
  • 2020-11-28 06:30

    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.

    0 讨论(0)
  • 2020-11-28 06:32

    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;
    }
    
    0 讨论(0)
  • 2020-11-28 06:33

    This departs from strong typing, but you can do

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

    if you are trying to get around oppressive strong typing like I was when I found this question. Sadly this is a case TypeScript fails on perfectly valid JavaScript so you have to you tell TypeScript to back off.

    "You JavaScript is perfectly valid TypeScript" evaluates to false. (Note: using 0.95)

    0 讨论(0)
  • 2020-11-28 06:36

    This is easily achievable now (typescript 2.x) with Object.assign(target, source)

    example:

    The magic here is that Object.assign<T, U>(t: T, u: U) is typed to return the intersection T & U.

    Enforcing that this resolves to a known interface is also straight-forward. For example:

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

    which errors due to incompatible typing:

    Error: foo:number not assignable to foo:string
    Error: number not assignable to string[] (return type)

    caveat: you may need to polyfill Object.assign if targeting older browsers.

    0 讨论(0)
提交回复
热议问题