Hello everyone who does not forget to look at our blog and traditionally non-working time of the day!
Long ago, in our
publication of November 13, 2015, you convinced us to wait for the finishing of Angular 2 and publish a book about it. We are seriously going to take up such a project in the very near future, but for now we are offering to read a detailed answer to the question in the title of this post.
Angular 2 is written in TypeScript. In this article I will explain why this decision was made. In addition, I will share my own experience with TypeScript; what it is to write and refactor code.
I have TypeScript to my taste, but you may not.')
Yes, Angular 2 is written in TypeScript, but applications in Angular 2 can be written without it. The framework interacts well with ES5, ES6 and Dart.
In TypeScript - a great toolkitThe main advantage of TypeScript is its tooling.
It provides sophisticated auto-completion, navigation and refactoring. In large projects, such tools are almost indispensable . You don’t always decide by yourself to change the code permanently, the entire code base is raw, and any large refactoring becomes risky and costly.
TypeScript is not the only typed language that is compiled into JavaScript. There are other languages ​​with more stringent type systems that theoretically should provide absolutely phenomenal tools. But in practice in most of them you will find almost nothing except the compiler.
The fact is that the elaboration of a rich toolkit should be a priority goal from the very first day - this is exactly the goal set by the TypeScript team . That is why language services were created here that can be used in editors for type checking and autocompletion. If you were wondering where such a large number of editors came from with excellent support for TypeScript - it is in language services.
Intellectual input (intellisense) and the simplest refactoring (for example, renaming a character) drastically change the process of writing and, especially, code refactoring. Although this indicator is difficult to measure, it seems to me that the refactorings, which were spent several days before, are now done in a few hours.
Yes, TypeScript significantly optimizes code editing, but preparing for development with it is more difficult than, say, taking and throwing an ES5 script on a page. In addition, you lose JavaScript tools to analyze source code (for example, JSHint), but they usually have adequate replacements.
TypeScript is a superset of JavaScriptSince TypeScript is a superset of JavaScript, you do not need to radically rewrite code when migrating to this language. This can be done gradually, module by module.
Just take the module, rename the
.js
files in it to
.ts
, and then gradually add type annotations. Finished with the module - go to the next. When the entire code base is typed, you can start messing around with the compiler settings, making them stricter.
The whole process may take some time, but when we migrated Angular 2 to TypeScript, in the process we managed to not only develop new functions, but also fixed bugs.
In TypeScript, abstractions become apparent.Good design is well defined interfaces. And to express the idea of ​​an interface is much easier in a language that interfaces support.Suppose there is an application for buying books. Books in it are acquired in two ways: a registered user can do this through a graphical interface, and others through an external system, which connects to the application through an API.

As you can see, the role of both classes in this case is the “buyer”. Despite the importance of the “buyer” role in this application, it is clearly not expressed in the code. There is no
purchaser.js
file. Therefore, someone can change the code and not even notice that such a role exists.
function processPurchase(purchaser, details){ } class User { } class ExternalSystem { }
If you simply view the code, it is difficult to say which objects can act as a buyer. Surely we do not know, and our tools are not particularly help us in this. Such information has to be fished manually - and this is a slow matter, fraught with errors.
And here is the version in which we explicitly define the
Purchaser
interface.
interface Purchaser {id: int; bankAccount: Account;} class User implements Purchaser {} class ExternalSystem implements Purchaser {}
In the typed version, it is clearly indicated that we have the
Purchaser
interface, and the
User
and
ExternalSystem
classes implement it. So TypeScript interfaces allow you to define abstractions / protocols / roles.
It is important to understand that TypeScript does not force us to add unnecessary abstractions . Abstraction "Buyer" is in the JavaScript code, just there it is clearly not defined.
In a statically typed language, the boundaries between subsystems are defined using interfaces. Since there are no interfaces in JavaScript, defining borders in pure JavaScript is more difficult. If the boundaries for the developer are not obvious, then it depends on the concrete types, and not on the abstract interfaces, which provokes strong binding .
From experience with Angular 2 before and after the transition to TypeScript, this belief has only strengthened. Defining the interface, I have to think over the boundaries of the API, it helps me to outline the public interfaces of the subsystems and immediately identify the binding, if it happens by chance.
TypeScript makes it easier to read and understand codeYes, I know that at first glance it does not seem so. Then consider an example. Take the
jQuery.ajax()
function. What information is clear from her signature?
All that can be said for sure - this function takes two arguments. Types can try to guess. Perhaps, first comes the line, and after it - the configuration object. But this is just a version, perhaps we are mistaken. We do not imagine what options can be in the settings object (neither their names, nor types), we do not know what this function returns.
It is not known how to call this function, it is necessary to check with the source code or documentation. Checking the source code is not the best option; what's the use of functions and classes, if it is not clear how they are implemented. In other words, you need to rely on their interfaces, and not on the implementation. You can check the documentation, but the developers will confirm that this is a thankless job - time is spent on checking, and the documentation itself is often irrelevant.
So, it is
jQuery.ajax(url, settings)
to read
jQuery.ajax(url, settings)
, but in order to understand how to call this function, you need to read in either its implementation or the documentation.
Now compare with the typed version.
ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR; interface JQueryAjaxSettings { async?: boolean; cache?: boolean; contentType?: any; headers?: { [key: string]: any; };
This version is much more informative:
- The first argument to this function is a string.
- The
settings
argument is optional. We see all the parameters that can be passed to the function - not only their names, but also their types. - The function returns a
JQueryXHR
object, we see its properties and functions.
A typed signature is definitely longer than untyped, but
:string
:JQueryAjaxSettings
and
JQueryXHR
are not garbage. This is an important "documentation", thanks to which the code is easier to read. You can understand the code much deeper, without going into the implementation or reading of documents. Personally, I read typed code faster, because types are a context that helps you understand the code. But, if someone from the readers finds a study on how types affect readability, please leave a link in the comments.
One of the important differences between TypeScript and many other languages ​​compiled in JavaScript is that type annotations are optional, and jQuery.ajax (url, settings) is a real valid TypeScript. So, types in TypeScript can be compared not with a switch, but with an adjustment dial. If you think that the code is trivial, and it can be read without type annotations - do not use them.
Use types only when they are useful .
TypeScript limits expressiveness?Tools in languages ​​with dynamic typing are so-so, but they are more plastic and expressive. I think with TypeScript your code will become slower, but to a much lesser extent than it might seem. Now I will explain. Suppose I use ImmutableJS to define a
Person
record.
const PersonRecord = Record({name:null, age:null}); function createPerson(name, age) { return new PersonRecord({name, age}); } const p = createPerson("Jim", 44); expect(p.name).toEqual("Jim");
How to typify such a record? First, let's define an interface called Person:
interface Person { name: string, age: number };
If we are trying to do this:
function createPerson(name: string, age: number): Person { return new PersonRecord({name, age}); }
then the TypeScript compiler swears. He does not know that PersonRecord is actually compatible with Person, because PersonRecord was created reflexively. Some readers familiar with the OP may say: “Oh, if there were dependent types in TypeScript!” But they are not here. TypeScript's type system is not the most advanced. But our goal is different: not to prove that the program is 100% correct, but to provide you with more detailed information and better tools. Therefore, it is quite possible to cut corners if the type system is not very flexible.
The created record can be easily brought, like this:
function createPerson(name: string, age: number): Person { return <any>new PersonRecord({name, age}); }
Typed example:
interface Person { name: string, age: number }; const PersonRecord = Record({name:null, age:null}); function createPerson(name: string, age: number): Person { return <any>new PersonRecord({name, age}); } const p = createPerson("Jim", 44); expect(p.name).toEqual("Jim");
This works because the type system is structural. If the created object has the necessary fields - name and age - then everything is in order.
It is necessary to get used to that when working with TypeScript, “cut corners” is normal. Only then will you be pleased to deal with this language. For example, do not try to add types to some fancy code for metaprogramming - most likely, you simply cannot express it statically. Conjugate this code and order the type checking system to ignore the artsy part. In this case, you hardly lose any expressiveness, but the bulk of the code will remain convenient for processing and analysis.
The situation resembles an attempt to provide one hundred percent coverage with unit tests. 95% is usually done without problems, but to achieve 100% is already a task, and this coverage can adversely affect the architecture of the entire application.
With the optional type system, the JavaScript development cycle is also preserved . Large fragments of the code base may be “broken”, but you can still run them. TypeScript will still generate javascript even if the type-checking system is not satisfied. During development, this is extremely convenient.
Why typeScript?Today, front-end vendors have a wide choice of development tools: ES5, ES6 (Babel), TypeScript, Dart, PureScript, Elm, etc. Why typeScript?
Let's start with ES5. ES5 has one major advantage over TypeScript: no transpiler is required here. Therefore, the entire development is easy to organize. There is no need to set up file watchers, transpose code, generate code maps. Everything just works.
In ES6, a transpiler is needed, so the assembly itself will be organized roughly as in TypeScript. But this is a standard, meaning that one and all editors or build tools either support ES6 or will support it. Currently, most TypeScript editors are already well supported.
Elm and PureScript are beautiful languages ​​with powerful type systems that can give your program much more than TypeScript. The code on Elm and PureScript can be much more concise than on ES5.
Each of these options has its advantages and disadvantages, but it seems to me that TypeScript is the golden mean and is perfect for most projects. TypeScript has 95% of the virtues of good statically typed languages, and brings these advantages to the JavaScript ecosystem. The feeling is almost the same as you write in ES6: you use the same standard library, the same third-party libraries, idioms and many familiar tools (for example, the “Development” section in Chrome). You get a lot of everything, without leaving the usual JavaScript ecosystem.