TypeScript é uma linguagem verdadeiramente bonita. Seu arsenal tem tudo o que é necessário para o desenvolvimento de alta qualidade. E, de repente, alguém está familiarizado com esboços de sexo dramático com JavaScript, então eu entenderei. O TypeScript possui várias suposições, sintaxe inesperada e construções deliciosas que enfatizam sua beleza, forma e preenchimento com um novo significado. Hoje estamos falando sobre eles, sobre essas suposições, sobre a magia das expressões. Quem se importa, seja bem-vindo.
Um pouco de verdade
N1 verdadeiro.
Na maioria dos projetos e descobertas inesperadas descritas abaixo, primeiro peguei meu olho nas páginas do Stack Overflow, no github ou foram inventadas por mim. E só então ocorreu - tudo isso é
aqui ou
aqui . Portanto, peço que você trate com entendimento antecipadamente se as descobertas declaradas lhe parecerem banais.
N2 verdadeiro.
O valor prático de alguns projetos é 0.
N3 verdadeiro.
Os exemplos foram testados no tsc versão 3.4.5 e no destino es5. Apenas no caso, sob a configuração do spoiler
tsconfig.json{
"CompilerOptions": {
"OutFile": "./target/result.js",
"Módulo": "amd",
"Alvo": "es5",
"Declaração": true,
"NoImplicitAny": true,
"NoImplicitReturns": true,
"StrictNullChecks": true,
"StrictPropertyInitialization": true,
"ExperimentalDecorators": true,
"EmitDecoratorMetadata": true,
"PreserveConstEnums": true,
"NoResolve": true,
"SourceMap": true,
"InlineSources": true
}
"Incluir": [
"./src"
]
}
Implementação e herança
Localizar : na seção de implementos, você pode especificar interfaces, tipos e
classes . Estamos interessados no último. Detalhes
aquiabstract 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; }
Acho que os desenvolvedores do TypeScript cuidaram dos 'contratos estritos' executados através da palavra-chave class Além disso, as classes não precisam ser abstratas.
Localizar : expressões são permitidas na seção extends.
Detalhes Se fizer uma pergunta - se é possível herdar de 2 classes, a resposta formal é não. Mas se você quer dizer exportar funcionalidade - sim.
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__
Encontre : um anônimo
profundo e ao mesmo tempo sem sentido.
const lass = class extends class extends class extends class extends class {} {} {} {} {};
E quem escreverá a palavra classe com 4 se estende no exemplo acima?
Se sim // tslint:disable const Class = class Class extends class Class extends class Class extends class Class extends class Class {} {} {} {} {};
E mais?
Assim // tslint:disable const lass = class Class<Class> extends class Class extends class Class extends class Class extends class Class {} {} {} {} {};
Bem, você entendeu - é apenas classe!
Ponto de exclamação - operador e modificador ilimitados
Se você não usar as configurações de compilação strictNullChecks e strictPropertyInitialization,
provavelmente o conhecimento sobre o ponto de exclamação passou perto de você ... Além do objetivo principal, mais 2 funções são atribuídas a ele.
Localizar : Ponto de exclamação como
operador de asserção não nulaEste operador permite acessar o campo da estrutura, que pode ser nulo sem verificar se é nulo. Um exemplo com uma explicação:
// --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" }; } }
Total, esse operador permite remover verificações desnecessárias de nulo no código, se tivermos certeza ...
Localizar : Ponto de exclamação como
modificador de asserção de atribuição definidaEsse modificador nos permitirá inicializar a propriedade da classe posteriormente, em algum lugar do código, com a opção de compilação strictPropertyInitialization ativada. Um exemplo com uma explicação:
// --strictPropertyInitialization class Field { foo!: number; // ^ // Notice this '!' modifier. // This is the "definite assignment assertion" constructor() { this.initialize(); } initialize() { this.foo = 0; // ^ } }
Mas todo esse mini-cálculo sobre o ponto de exclamação não faria sentido sem um momento de humor.
Pergunta: Você acha que a seguinte expressão será compilada?
// --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
Todo mundo que escreve tipos complexos descobre muitas coisas interessantes. Então eu tive sorte.
Localizar : um subtipo pode ser referenciado pelo nome de um campo do tipo principal.
type Person = { id: string; name: string; address: { city: string; street: string; house: string; } }; type Address = Person["address"];
Quando você escreve os tipos, essa abordagem de declaração dificilmente faz sentido. Mas acontece que um tipo vem de uma biblioteca externa, mas um subtipo não.
Um truque de subtipo também pode ser usado para melhorar a legibilidade do código. Imagine que você tenha uma classe base com um tipo genérico do qual as classes herdam. O exemplo abaixo ilustra o que foi dito.
class BaseDialog<In, Out> { show(params: In): Out {/** . return ... */ } } // - class PersonDialogOld extends BaseDialog<Person[], string> {/** */} // class PersonDialog extends BaseDialog<Person[], Person["id"]> {/** */}
Localizar : com a ajuda do sistema de tipos TypeScript, é possível obter um conjunto combinatório de tipos gerados com cobertura da funcionalidade desejada. Difícil dizer, eu sei. Pensei nessa formulação por um longo tempo. Vou mostrar um exemplo do modelo do Builder, como um dos mais famosos. Imagine que você precisa criar um determinado objeto usando esse padrão de design.
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();
Ainda não observe o método de criação redundante, o construtor privado e, geralmente, o uso desse modelo em ts. Concentre-se na cadeia de chamadas. A idéia é que os métodos chamados sejam usados estritamente uma vez. E seu IDE também deve estar ciente disso. Em outras palavras, após chamar qualquer método na instância do construtor, esse método deve ser excluído da lista de métodos disponíveis. Para alcançar essa funcionalidade, o tipo NarrowCallside nos ajudará.
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
Localizar : usando o sistema de tipos TypeScript, você pode controlar a sequência de chamadas especificando uma ordem estrita. No exemplo abaixo, usando o tipo DirectCallside, demonstramos isso.
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
Essas são todas as minhas descobertas interessantes sobre o TypeScript hoje. Obrigado a todos pela atenção e até breve.