navigationend - TypeScript e inicializadores de campo
router events subscribe angular 4 (9)
A continuación se muestra una solución que combina una aplicación más corta de Object.assign
para Object.assign
más de cerca el patrón original de C#
.
Pero primero, revisemos las técnicas ofrecidas hasta ahora, que incluyen:
- Copiar constructores que aceptan un objeto y aplicarlo a
Object.assign
- Un astuto truco
Partial<T>
dentro del constructor de copia - Uso de "casting" contra un POJO
- Aprovechamiento de
Object.create
lugar deObject.assign
Por supuesto, cada uno tiene sus pros / contras. La modificación de una clase de destino para crear un constructor de copia no siempre es una opción. Y "casting" pierde cualquier función asociada con el tipo de objetivo. Object.create
parece menos atractivo ya que requiere un mapa de descriptor de propiedad bastante detallado.
La respuesta más corta, de propósito general
Entonces, aquí hay otro enfoque que es algo más simple, mantiene la definición de tipo y los prototipos de función asociados, y modela más de cerca el patrón de C#
deseado:
const john = Object.assign( new Person(), {
name: "John",
age: 29,
address: "Earth"
});
Eso es. La única adición sobre el patrón C#
es Object.assign
junto con 2 paréntesis y una coma. Consulte el siguiente ejemplo de trabajo para confirmar que mantiene los prototipos de función del tipo. No se requieren constructores ni trucos inteligentes.
Ejemplo de trabajo
Este ejemplo muestra cómo inicializar un objeto usando una aproximación de un inicializador de campo C#
:
class Person {
name: string = '''';
address: string = '''';
age: number = 0;
aboutMe() {
return `Hi, I''m ${this.name}, aged ${this.age} and from ${this.address}`;
}
}
// typescript field initializer (maintains "type" definition)
const john = Object.assign( new Person(), {
name: "John",
age: 29,
address: "Earth"
});
// initialized object maintains aboutMe() function prototype
console.log( john.aboutMe() );
Cómo iniciar una nueva clase en TS
de tal manera (ejemplo en C#
para mostrar lo que quiero):
// ... some code before
return new MyClass { Field1 = "ASD", Field2 = "QWE" };
// ... some code after
SOLUCIÓN:
Sintaxis de JavaScript
clásica:
return { Field1: "ASD", Field2: "QWE" };
En algunos escenarios, puede ser aceptable usar Object.create
. La referencia de Mozilla incluye un relleno múltiple si necesita compatibilidad con la parte posterior o si desea desplegar su propia función de inicialización.
Aplicado a tu ejemplo:
Object.create(Person.prototype, {
''Field1'': { value: ''ASD'' },
''Field2'': { value: ''QWE'' }
});
Escenarios útiles
- Pruebas unitarias
- Declaración en línea
En mi caso, encontré esto útil en pruebas unitarias por dos razones:
- Al probar las expectativas, a menudo quiero crear un objeto delgado como una expectativa
- Los marcos de prueba unitaria (como Jasmine) pueden comparar el prototipo del objeto (
__proto__
) y fallar la prueba. Por ejemplo:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails
La salida de la falla de prueba de la unidad no dará una pista sobre lo que no coincide.
- En pruebas unitarias puedo ser selectivo sobre qué navegadores apoyo
Finalmente, la solución propuesta en http://typescript.codeplex.com/workitem/334 no es compatible con la declaración en línea json-style. Por ejemplo, lo siguiente no compila:
var o = {
m: MyClass: { Field1:"ASD" }
};
Estaría más inclinado a hacerlo de esta manera, usando (opcionalmente) propiedades automáticas y valores predeterminados. No ha sugerido que los dos campos sean parte de una estructura de datos, así que es por eso que elegí esta opción.
Puede tener las propiedades en la clase y luego asignarlas de la manera habitual. Y, obviamente, pueden o no ser necesarios, así que eso es otra cosa también. Es solo que este es un azúcar sintáctico tan agradable.
class MyClass{
constructor(public Field1:string = "", public Field2:string = "")
{
// other constructor stuff
}
}
var myClass = new MyClass("ASD", "QWE");
alert(myClass.Field1); // voila! statement completion on these properties
Existe un problema en el codeplex de TypeScript que describe esto: Soporte para inicializadores de objetos .
Como se dijo, puede hacer esto usando interfaces en TypeScript en lugar de clases:
interface Name {
first: string;
last: string;
}
class Person {
name: Name;
age: number;
}
var bob: Person = {
name: {
first: "Bob",
last: "Smith",
},
age: 35,
};
La forma más fácil de hacerlo es con el tipo de conversión.
return <MyClass>{ Field1: "ASD", Field2: "QWE" };
Puede afectar un objeto anónimo creado en su tipo de clase. Bonus : en visual studio, te beneficias de intellisense de esta manera :)
var anInstance: AClass = <AClass> {
Property1: "Value",
Property2: "Value",
PropertyBoolean: true,
PropertyNumber: 1
};
Editar:
ADVERTENCIA Si la clase tiene métodos, la instancia de su clase no los obtendrá y el constructor no se ejecutará.
Esta solución solo debe usarse con la interfaz . Por ejemplo, use esta solución para almacenar el modelo como Objeto antiguo simple.
interface IClass {
Property1: string;
Property2: string;
PropertyBoolean: boolean;
PropertyNumber: number;
}
var anObject: IClass = <IClass> {
Property1: "Value",
Property2: "Value",
PropertyBoolean: true,
PropertyNumber: 1
};
Si está utilizando una versión anterior de mecanografiado <2.1, entonces puede usar algo similar a lo siguiente, que básicamente es la conversión de cualquier objeto escrito a máquina:
const typedProduct = <Product>{
code: <string>product.sku
};
Sugiero un enfoque que no requiere Typescript 2.1:
class Person {
public name: string;
public address?: string;
public age: number;
public constructor(init:Person) {
Object.assign(this, init);
}
public someFunc() {
// todo
}
}
let person = new Person(<Person>{ age:20, name:"John" });
person.someFunc();
puntos clave:
- No se requiere de Typecript 2.1, no se requiere
Partial<T>
- Admite funciones (en comparación con la aserción de tipo simple que no admite funciones)
Actualizado 07/12/2016: Typescript 2.1 introduce tipos mapeados y proporciona Partial<T>
, que le permite hacer esto ....
class Person {
public name: string = "default"
public address: string = "default"
public age: number = 0;
public constructor(init?:Partial<Person>) {
Object.assign(this, init);
}
}
let persons = [
new Person(),
new Person({}),
new Person({name:"John"}),
new Person({address:"Earth"}),
new Person({age:20, address:"Earth", name:"John"}),
];
Respuesta Original:
Mi enfoque es definir una variable de fields
separada que pases al constructor. El truco es redefinir todos los campos de clase para este inicializador como opcional. Cuando se crea el objeto (con sus valores predeterminados), simplemente asigna el objeto inicializador a this
;
export class Person {
public name: string = "default"
public address: string = "default"
public age: number = 0;
public constructor(
fields?: {
name?: string,
address?: string,
age?: number
}) {
if (fields) Object.assign(this, fields);
}
}
o hacerlo manualmente (un poco más seguro):
if (fields) {
this.name = fields.name || this.name;
this.address = fields.address || this.address;
this.age = fields.age || this.age;
}
uso:
let persons = [
new Person(),
new Person({name:"Joe"}),
new Person({
name:"Joe",
address:"planet Earth"
}),
new Person({
age:5,
address:"planet Earth",
name:"Joe"
}),
new Person(new Person({name:"Joe"})) //shallow clone
];
y salida de la consola:
Person { name: ''default'', address: ''default'', age: 0 }
Person { name: ''Joe'', address: ''default'', age: 0 }
Person { name: ''Joe'', address: ''planet Earth'', age: 0 }
Person { name: ''Joe'', address: ''planet Earth'', age: 5 }
Person { name: ''Joe'', address: ''default'', age: 0 }
Esto le proporciona seguridad básica e inicialización de propiedad, pero es opcional y puede estar fuera de servicio. Si no pasas un campo, obtienes los valores predeterminados de la clase.
También puede mezclarlo con los parámetros de constructor necesarios: pegue los fields
al final.
Creo que lo más cerca del estilo de C # que va a obtener ( se rechazó la sintaxis de field-init ). Preferiría el inicializador de campo correcto, pero no parece que vaya a suceder todavía.
Para comparar, si usa el enfoque de conversión, su objeto inicializador debe tener TODOS los campos para el tipo al que está enviando, además de no obtener ninguna función (o derivación) específica de clase creada por la clase misma.