Construir un objeto de función con propiedades en TypeScript


Quiero crear un objeto de función, que también tiene algunas propiedades retenidas en él. Por ejemplo, en JavaScript yo haría:

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

Ahora en TypeScript puedo describir el tipo de esto como:

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

Sin embargo, en realidad no puedo construirlo, sin requerir un molde. Tales como:

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

¿Cómo construirías esto sin un molde?

Author: Jonathan, 2012-10-07

8 answers

Así que si el requisito es simplemente construir y asignar esa función a" f " sin un molde, aquí hay una posible solución:

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

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

Esencialmente, utiliza un literal de función autoejecutable para "construir" un objeto que coincidirá con esa firma antes de que se realice la asignación. La única rareza es que la declaración interna de la función necesita ser de tipo 'any', de lo contrario el compilador grita que estás asignando a una propiedad que no existe en el objeto todavía.

EDITAR: Simplificó un poco el código.

 17
Author: nxn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-07 17:05:41

Actualización: Esta respuesta fue la mejor solución en versiones anteriores de TypeScript, pero hay mejores opciones disponibles en versiones más recientes (ver otras respuestas).

La respuesta aceptada funciona y puede ser requerida en algunas situaciones, pero tiene la desventaja de no proporcionar seguridad de tipo para la construcción del objeto. Esta técnica al menos arrojará un error de tipo si intenta agregar una propiedad indefinida.

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

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

// type error
f.notDeclard = 3
 81
Author: Greg Weber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-28 20:20:30

TypeScript está diseñado para manejar este caso a través de fusión de declaraciones:

También puede estar familiarizado con la práctica de JavaScript de crear una función y luego extender la función añadiendo propiedades a la función. TypeScript usa la fusión de declaraciones para construir definiciones como esta de una manera segura.

La fusión de declaraciones nos permite decir que algo es tanto una función como un espacio de nombres (interno module):

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

Esto preserva la escritura y nos permite escribir tanto f() como f.someValue. Al escribir un archivo .d.ts para el código JavaScript existente, use declare:

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

Agregar propiedades a las funciones es a menudo un patrón confuso o inesperado en TypeScript, así que trate de evitarlo, pero puede ser necesario al usar o convertir código JS anterior. Esta es una de las pocas veces que sería apropiado mezclar módulos internos (espacios de nombres) con externos.

 38
Author: mk.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-06 16:42:25

Esto es fácilmente alcanzable ahora (typescript 2.x) con Object.assign(target, source)

Ejemplo:

introduzca la descripción de la imagen aquí

La magia aquí es que Object.assign<T, U>(...) se escribe para devolver un intersección tipo de T & U.

Hacer que esto resuelva a una interfaz conocida también es sencillo:

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

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

Error: foo: número no asignable a foo: cadena
Error: number not assignable to string [] (return type)

advertencia: es posible que necesite polyfill Objeto.asignar si se dirige a navegadores más antiguos.

 32
Author: Meirion Hughes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-16 07:57:36

Como atajo, puede asignar dinámicamente el valor del objeto utilizando el accessor ['property']:

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

Esto evita la comprobación de tipos. Sin embargo, es bastante seguro porque tienes que acceder intencionalmente a la propiedad de la misma manera:

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

Sin embargo, si realmente desea que el tipo verifique el valor de la propiedad, esto no funcionará.

 2
Author: Rick Love,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-11 04:38:37

Una respuesta actualizada: desde la adición de tipos de intersección a través de &, es posible "fusionar" dos tipos inferidos sobre la marcha.

Aquí hay un ayudante general que lee las propiedades de algún objeto from y las copia sobre un objeto onto. Devuelve el mismo objeto onto pero con un nuevo tipo que incluye ambos conjuntos de propiedades, describiendo correctamente el comportamiento en tiempo de ejecución:

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

Este ayudante de bajo nivel todavía realiza una afirmación de tipo, pero es seguro de tipo al diseño. Con este ayudante en su lugar, tenemos un operador que podemos usar para resolver el problema del OP con seguridad de tipo completo:

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

Haga clic aquí para probarlo en TypeScript Playground . Tenga en cuenta que hemos restringido foo a ser de tipo Foo, por lo que el resultado de merge tiene que ser un Foo completo. Así que si cambias el nombre bar a bad entonces obtienes un error de tipo.

NB Todavía hay un agujero de tipo aquí, sin embargo. TypeScript no proporciona una manera de restringir un escriba el parámetro para que sea "no una función". Así que podrías confundirte y pasar tu función como segundo argumento a merge, y eso no funcionaría. Así que hasta que esto se pueda declarar, tenemos que capturarlo en tiempo de ejecución:

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;
}
 1
Author: Daniel Earwicker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-10 19:14:36

No puedo decir que sea muy sencillo, pero definitivamente es posible:

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

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

Si tienes curiosidad esto es de una idea mía con la versión TypeScript/JavaScript de Optional

 1
Author: thiagoh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-08 00:10:03

Esto se aparta de la escritura fuerte, pero puedes hacer

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

Si usted está tratando de conseguir alrededor de escribir fuerte opresivo como yo estaba cuando encontré esta pregunta. Lamentablemente, este es un caso en el que TypeScript falla en JavaScript perfectamente válido, por lo que debe decirle a TypeScript que retroceda.

"You JavaScript is perfectly valid TypeScript" se evalúa como false. (Nota: utilizando 0.95)

 0
Author: WillSeitz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-21 15:59:26