TypeScript es un lenguaje realmente hermoso. Su arsenal tiene todo lo necesario para un desarrollo de alta calidad. Y si de repente, alguien está familiarizado con los bocetos dramáticos sexuales con JavaScript, entonces lo entenderé. TypeScript tiene una serie de suposiciones, sintaxis inesperada y diseños encantadores que enfatizan su belleza, forma y lo llenan de un nuevo significado. Hoy estamos hablando de ellos, de estos supuestos, de la magia de las expresiones. A quién le importa, bienvenido.
Un poco de verdad
Verdadero N1.
La mayoría de los diseños y hallazgos inesperados que se describen a continuación, primero me llamó la atención en las páginas de Stack Overflow, github o fueron inventados por mí mismo. Y solo entonces amaneció: todo esto está
aquí o
aquí . Por lo tanto, le pido que trate con anticipación si los hallazgos declarados le parecen triviales.
Verdadero N2.
El valor práctico de algunos diseños es 0.
Verdadero N3.
Los ejemplos se probaron con tsc versión 3.4.5 y target es5. Por si acaso, bajo la configuración de spoiler
tsconfig.json{
"CompilerOptions": {
"OutFile": "./target/result.js",
"Módulo": "amd",
"Target": "es5",
"Declaración": cierto,
"NoImplicitAny": verdadero,
"NoImplicitReturns": verdadero,
"StrictNullChecks": verdadero,
"StrictPropertyInitialization": verdadero,
"Decoradores experimentales": cierto,
"EmitDecoratorMetadata": verdadero,
"PreserveConstEnums": verdadero,
"NoResolve": cierto,
"SourceMap": verdadero,
"InlineSources": verdadero
},
"Incluir": [
"./src"
]
}
Implementación y Herencia
Buscar : en la sección de implementos puede especificar interfaces, tipos y
clases . Estamos interesados en lo último. Detalles
aquíabstract class ClassA { abstract getA(): string; } abstract class ClassB { abstract getB(): string; } // , tsc abstract class ClassC implements ClassA, ClassB { // ^ , implements . abstract getA(): string; abstract getB(): string; }
Creo que los desarrolladores de TypeScript se encargaron de los 'contratos estrictos' ejecutados a través de la palabra clave de clase. Además, las clases no tienen que ser abstractas.
Buscar : se permiten expresiones en la sección de extensiones.
Detalles Si hacer una pregunta, si es posible heredar de 2 clases, entonces la respuesta formal es no. Pero si te refieres a exportar funcionalidad, sí.
class One { one = "__one__"; getOne(): string { return "one"; } } class Two { two = "__two__"; getTwo(): string { return "two"; } } // , : IDE ( ) . class BothTogether extends mix(One, Two) { // ^ , extends info(): string { return "BothTogether: " + this.getOne() + " and " + this.getTwo() + ", one: " + this.one + ", two: " + this.two; // ^ IDE ^ } } type FaceType<T> = { [K in keyof T]: T[K]; }; type Constructor<T> = { // prototype: T & {[key: string]: any}; new(): T; }; // TODO: , . function mix<O, T, Mix = O & T>(o: Constructor<O>, t: Constructor<T>): FaceType<Mix> & Constructor<Mix> { function MixinClass(...args: any) { o.apply(this, args); t.apply(this, args); } const ignoreNamesFilter = (name: string) => ["constructor"].indexOf(name) === -1; [o, t].forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).filter(ignoreNamesFilter).forEach(name => { MixinClass.prototype[name] = baseCtor.prototype[name]; }); }); return MixinClass as any; } const bt = new BothTogether(); window.console.log(bt.info()); // >> BothTogether: one and two, one: __one__, two: __two__
Buscar : un anónimo
profundo y al mismo tiempo sin sentido.
const lass = class extends class extends class extends class extends class {} {} {} {} {};
¿Y quién escribirá la palabra clase con 4 extensiones en el ejemplo anterior?
Si es asi // tslint:disable const Class = class Class extends class Class extends class Class extends class Class extends class Class {} {} {} {} {};
Y mas?
Como este // tslint:disable const lass = class Class<Class> extends class Class extends class Class extends class Class extends class Class {} {} {} {} {};
Bueno, lo entiendes, ¡es solo clase!
Signo de exclamación: operador y modificador ilimitados
Si no utiliza la configuración de compilación estrictaNulidadCheques y estrictaPropertiaInicialización,
entonces lo más probable es que el conocimiento sobre el signo de exclamación pasó cerca de usted ... Además del propósito principal, se le asignan 2 roles más.
Buscar : signo de exclamación como
operador de aserción no nuloEste operador le permite acceder al campo de estructura, que puede ser nulo sin verificarlo. Un ejemplo con una explicación:
// --strictNullChecks type OptType = { maybe?: { data: string; }; }; // ... function process(optType: OptType) { completeOptFields(optType); // , completeOptFields . window.console.log(optType.maybe!.data); // ^ - , null // !, tsc: Object is possibly 'undefined' } function completeOptFields(optType: OptType) { if (!optType.maybe) { optType.maybe = { data: "some default info" }; } }
En total, este operador le permite eliminar cheques innecesarios para nulos en el código, si estamos seguros ...
Buscar : signo de exclamación como
modificador de aserción de asignación definidaEste modificador nos permitirá inicializar la propiedad de clase más adelante, en algún lugar del código, con la opción de compilación estrictaPropertyInitialization activada. Un ejemplo con una explicación:
// --strictPropertyInitialization class Field { foo!: number; // ^ // Notice this '!' modifier. // This is the "definite assignment assertion" constructor() { this.initialize(); } initialize() { this.foo = 0; // ^ } }
Pero todo este mini cálculo sobre el signo de exclamación no tendría sentido sin un momento de humor.
Pregunta: ¿Crees que se compilará la siguiente expresión?
// --strictNullChecks type OptType = { maybe?: { data: string; }; }; function process(optType: OptType) { if (!!!optType.maybe!!!) { window.console.log("Just for fun"); } window.console.log(optType.maybe!!!!.data); }
Tipos
Todos los que escriben tipos complejos descubren muchas cosas interesantes. Entonces tuve suerte.
Buscar : se puede hacer referencia a un subtipo por el nombre de un campo del tipo principal.
type Person = { id: string; name: string; address: { city: string; street: string; house: string; } }; type Address = Person["address"];
Cuando escribe tipos usted mismo, este enfoque de declaración apenas tiene sentido. Pero sucede que un tipo proviene de una biblioteca externa, pero un subtipo no.
Un truco de subtipo también se puede utilizar para mejorar la legibilidad del código. Imagine que tiene una clase base con un tipo genérico del que las clases heredan. El siguiente ejemplo ilustra lo que se ha dicho.
class BaseDialog<In, Out> { show(params: In): Out {/** . return ... */ } } // - class PersonDialogOld extends BaseDialog<Person[], string> {/** */} // class PersonDialog extends BaseDialog<Person[], Person["id"]> {/** */}
Buscar : con la ayuda del sistema de tipos TypeScript es posible lograr un conjunto combinatorio de tipos generados con cobertura de la funcionalidad deseada. Difícil de decir, lo sé. Pensé en esta formulación durante mucho tiempo. Te mostraré un ejemplo de la plantilla Builder, como una de las más famosas. Imagine que necesita construir un determinado objeto utilizando este patrón de diseño.
class SimpleBuilder { private constructor() {} static create(): SimpleBuilder { return new SimpleBuilder(); } firstName(firstName: string): this { return this; } lastName(lastName: string): this { return this; } middleName(midleName: string): this { return this; } build(): string { return "what you needs"; } } const builder = SimpleBuilder.create(); // . const result = builder.firstName("F").lastName("L").middleName("M").build();
No mire aún el método de creación redundante, el constructor privado, y en general el uso de esta plantilla en ts. Centrarse en la cadena de llamadas. La idea es que los métodos llamados deben usarse estrictamente 1 vez. Y su IDE también debe ser consciente de esto. En otras palabras, después de llamar a cualquier método en la instancia del constructor, este método debe excluirse de la lista de los disponibles. Para lograr dicha funcionalidad, el tipo NarrowCallside nos ayudará.
type ExcludeMethod<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; type NarrowCallside<T> = { [P in keyof T]: T[P] extends (...args: any) => T ? ReturnType<T[P]> extends T ? (...args: Parameters<T[P]>) => NarrowCallside<ExcludeMethod<T, P>> : T[P] : T[P]; }; class SimpleBuilder { private constructor() {} static create(): NarrowCallside<SimpleBuilder> { return new SimpleBuilder(); } firstName(firstName: string): this { return this; } lastName(lastName: string): this { return this; } middleName(midleName: string): this { return this; } build(): string { return "what you needs"; } } const builder = SimpleBuilder.create(); const result = builder.firstName("F") // ^ - .lastName("L") // ^ - lastName, middleName build .middleName("M") // ^ - middleName build .build(); // ^ - build
Buscar : utilizando el sistema de tipos TypeScript, puede controlar la secuencia de llamadas especificando un orden estricto. En el siguiente ejemplo, usando el tipo DirectCallside, lo demostramos.
type FilterKeys<T> = ({[P in keyof T]: T[P] extends (...args: any) => any ? ReturnType<T[P]> extends never ? never : P : never })[keyof T]; type FilterMethods<T> = Pick<T, FilterKeys<T>>; type BaseDirectCallside<T, Direct extends any[]> = FilterMethods<{ [Key in keyof T]: T[Key] extends ((...args: any) => T) ? ((..._: Direct) => any) extends ((_: infer First, ..._1: infer Next) => any) ? First extends Key ? (...args: Parameters<T[Key]>) => BaseDirectCallside<T, Next> : never : never : T[Key] }>; type DirectCallside<T, P extends Array<keyof T>> = BaseDirectCallside<T, P>; class StrongBuilder { private constructor() {} static create(): DirectCallside<StrongBuilder, ["firstName", "lastName", "middleName"]> { return new StrongBuilder() as any; } firstName(firstName: string): this { return this; } lastName(lastName: string): this { return this; } middleName(midleName: string): this { return this; } build(): string { return "what you needs"; } } const sBuilder = StrongBuilder.create(); const sResult = sBuilder.firstName("F") // ^ - firstName build .lastName("L") // ^ - lastName build .middleName("M") // ^ - middleName build .build(); // ^ - build
Total
Estos son todos mis hallazgos interesantes en TypeScript hoy. Gracias a todos por su atención y hasta pronto.