casting - multiple - typescript partial
Construye un objeto de funciĆ³n con propiedades en TypeScript (8)
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 (ver 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
Quiero crear un objeto de función, que también tiene algunas propiedades en él. Por ejemplo en JavaScript lo 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, no puedo construirlo, sin requerir un molde. Como:
var f: { (): any; someValue: number; } =
<{ (): any; someValue: number; }>(
function() { }
);
f.someValue = 3;
¿Cómo construirías esto sin un elenco?
Como acceso directo, puede asignar dinámicamente el valor del objeto utilizando el accesoador [''propiedad'']:
var f = function() { }
f[''someValue''] = 3;
Esto evita la verificación de tipo. 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 revise el valor de la propiedad, esto no funcionará.
Entonces, 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;
})();
Básicamente, utiliza un literal de función autoejecutable para "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.
EDITAR: Simplificó el código un poco.
Esto es fácilmente alcanzable ahora (mecanografiado 2.x) con Object.assign(target, source)
ejemplo:
La magia aquí es que Object.assign<T, U>(...)
se escribe para devolver un tipo de intersección de T & U
Hacer cumplir que esto resuelve a una interfaz conocida también es directo:
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: número no asignable a la cadena [] (tipo de retorno)
advertencia: es posible que necesite rellenar Object.assign si se dirige a navegadores más antiguos.
Esto se aparta de la tipificación fuerte, pero puedes hacer
var f: any = function() { }
f.someValue = 3;
si estás tratando de moverte por una escritura opresiva y fuerte como lo era cuando encontré 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.
"Tu JavaScript es un tipo de letra perfectamente válido" se evalúa como falso. (Nota: usando 0.95)
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 esencia mía con la versión de TypeScript / JavaScript de Optional
TypeScript está diseñado para manejar este caso a través de la declaración de fusión :
también puede estar familiarizado con la práctica de JavaScript de crear una función y luego ampliar la función añadiendo propiedades a la función. TypeScript utiliza la fusión de declaraciones para crear definiciones como esta de una manera segura.
La fusión de declaraciones nos permite decir que algo es a la vez una función y un espacio de nombres (módulo interno):
function f() { }
namespace f {
export var someValue = 3;
}
Esto conserva la escritura y nos permite escribir f()
y 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 suele ser un patrón confuso o inesperado en TypeScript, así que trate de evitarlo, pero puede ser necesario al usar o convertir código JS antiguo. Este es uno de los únicos momentos en los que sería apropiado mezclar módulos internos (espacios de nombres) con externos.
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 y las copia sobre un objeto. Devuelve el mismo objeto pero con un nuevo tipo que incluye ambos conjuntos de propiedades, por lo que describe correctamente el comportamiento del 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 asistente de bajo nivel todavía realiza una aserción de tipo, pero es seguro en cuanto al tipo de diseño. Con este asistente 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 el Playground de TypeScript . Tenga en cuenta que hemos obligado a foo
a ser del tipo Foo
, por lo que el resultado de la merge
debe ser un Foo
completo. Entonces, si cambias el nombre de la bar
a bad
, obtienes un error de tipo.
NB . Sin embargo, todavía hay un agujero 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 para merge
, y eso no funcionaría. Entonces, hasta que esto se pueda declarar, debemos atraparlo 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;
}