2012-10-07 60 views
39

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

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

Ahora a máquina de escribir que puede describir el tipo de esto como:

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

Sin embargo no puedo realmente construirlo, sin que se requiera un yeso. Por ejemplo:

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

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

+1

Ésta no es una respuesta directa a su pregunta, pero para cualquiera que quiera para construir concisamente un objeto de función con propiedades, y está bien con el casting, el [operador Object-Spread] (https://blog.mariusschulz.com/2016/12/23/typescript-2-1-object-rest-and -spread) parece hacer el truco: 'var f: {(): any; someValue: number; } = <{(): cualquiera; someValue: number; }> { ... ((= = "Hola"), someValue: 3 }; '. – Jonathan

Respuesta

14

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

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

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

En esencia, se utiliza una función de ejecutar el self literal a "construir" un objeto que coincidirá con esa firma antes de que se realice la tarea. La única rareza es que la declaración interna de la función debe ser del tipo 'cualquiera', de lo contrario, el compilador grita que está asignando una propiedad que todavía no existe en el objeto.

EDIT: Simplified the code an bit.

+2

Por lo que puedo entender, esto no comprueba realmente el tipo, por lo que .someValue podría ser esencialmente cualquier cosa. – shabunc

1

Como atajo, puede asignar dinámicamente el valor del objeto utilizando el [ 'propiedad'] de acceso:

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

Esto no pasa por la comprobación de tipos. Sin embargo, es bastante seguro ya que hay que acceder intencionalmente 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 de comprobación de valor de la propiedad, esto no va a funcionar.

68

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

La respuesta aceptada funciona y puede ser necesaria en algunas situaciones, pero tiene la desventaja de no proporcionar ningún tipo de seguridad para construir el objeto. Esta técnica arrojará al menos 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 
+3

esto es increíble, thx – peter

+1

También es más fácil de leer –

+1

No entiendo por qué la línea 'var f' no causa un error, ya que en ese momento no existe la propiedad' someValue'. –

0

Esto se aparta de tipado fuerte, pero se puede hacer

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

si usted está tratando de obtener la tipificación fuerte alrededor opresivo como estaba cuando me encontré con esta pregunta. Tristemente, este es un caso en el que TypeScript falla en JavaScript perfectamente válido, por lo que debe decirle a TypeScript que se desactive.

"JavaScript es un tipo de letra perfectamente válido" se evalúa como falso. (Nota: usando 0.95)

28

mecanografiado está diseñado para manejar este caso a través declaration merging:

you may also be familiar with JavaScript practice of creating a function and then extending the function further by adding properties onto the function. TypeScript uses declaration merging to build up definitions like this in a type-safe way.

Declaración fusión nos permite afirmar que algo es a la vez una función y un (módulo interno de espacio de nombres):

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

Este conserva la escritura y nos permite escribir f() y f.someValue. Al escribir un archivo .d.ts para el código JavaScript existente, utilice declare:

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

Adición de propiedades a las funciones es a menudo un patrón confuso o inesperado a máquina de escribir, así que trate de evitarlo, pero puede ser necesario cuando se utiliza o la conversión de más edad Código JS Este es uno de los únicos momentos en los que sería apropiado mezclar módulos internos (espacios de nombres) con externos.

+0

Upvote para mencionar los módulos ambientales en la respuesta. Este es un caso muy típico al convertir o anotar módulos JS existentes. ¡Exactamente lo que estoy buscando! – Nipheris

+0

Encantador. Si desea utilizar esto con módulos ES6, puede hacer '' 'función hello() {..}' '' '' 'namespace hello { export const value = 5; } '' ' ' '' export default hello; '' ' IMO esto es mucho más limpio que Object.assign o hacks de tiempo de ejecución similares. Sin tiempo de ejecución, sin aserciones de tipo, sin nada. – skrebbel

+0

Esta respuesta es genial, pero ¿cómo se puede adjuntar un símbolo como Symbol.iterator a una función? – kzh

1

Una respuesta actualizada: desde la adición de los 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. Se devuelve el mismo objeto onto pero con un nuevo tipo que incluye dos conjuntos de propiedades, que describe tan correctamente el comportamiento de 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 no todavía realizar un tipo-afirmación, pero es de tipo seguro por diseño. Con esta ayuda en su lugar, tenemos un operador que podemos utilizar para resolver el problema de la OP con la 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}`) 
     } 
    } 
); 

Click here to try it out in the TypeScript Playground. Tenga en cuenta que hemos restringido foo para que sea del tipo Foo, por lo que el resultado de merge tiene que ser un Foo completo. Por lo tanto, si cambia el nombre a bar por bad, aparece un error de tipo.

NB Sin embargo, todavía hay un orificio de tipo aquí. TypeScript no proporciona una forma de restringir un parámetro de tipo para que sea "no una función". Entonces podría confundirse y pasar su función como el segundo argumento al merge, y eso no funcionaría. Así que hasta que esto puede ser declarada, tenemos que cogerlo 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; 
} 
21

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

ejemplo:

enter image description here

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

Hacer cumplir que esto se resuelve con una interfaz conocida es también recta de avance:

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:number not assignable to foo:string
Error: number not assignable to string[] (return type)

advertencia: puede que tenga que polyfill Object.assign si apuntan a los navegadores antiguos.

+0

Fantástico, gracias. A partir de febrero de 2017, esta es la mejor respuesta (para usuarios de Typescript 2.x). –

Cuestiones relacionadas