其他 - Angular: Why TypeScript?

优质
小牛编辑
131浏览
2023-12-01

Victor Savkin is a co-founder of nrwl.io, providing Angular consulting to enterprise teams. He was previously on the Angular core team at Google, and built the dependency injection, change detection, forms, and router modules.


Angular 2 is written in TypeScript. In this article I will talk about why we made the decision. I’ll also share my experience of working with TypeScript: how it affects the way I write and refactor my code.

I like TypeScript, but you don’t have to

Even though Angular 2 is written in TypeScript, you don’t have to use it to write Angular 2 applications. The framework also works great with ES5, ES6, and Dart.

TypeScript Has Great Tools

The biggest selling point of TypeScript is tooling. It provides advanced autocompletion, navigation, and refactoring. Having such tools is almost a requirement for large projects. Without them the fear changing the code puts the code base in a semi-read-only state, and makes large-scale refactorings very risky and costly.

TypeScript is not the only typed language that compiles to JavaScript. There are other languages with stronger type systems that in theory can provide absolutely phenomenal tooling. But in practice most of them do not have anything other than a compiler. This is because building rich dev tools has to be an explicit goal from day one, which it has been for the TypeScript team. That is why they built language services that can be used by editors to provide type checking and autocompletion. If you have wondered why there are so many editors with great TypeScript supports, the answer is the language services.

The fact that intellisense and basic refactorings (e.g., rename a symbol) are reliable makes a huge impact on the process of writing and especially refactoring code. Although it is hard to measure, I feel that the refactorings that would have taken a few days before now can be done in less than a day.

While TypeScript greatly improves the code editing experience, it makes the dev setup more complex, especially comparing to dropping an ES5 script on a page. In addition, you cannot use tools analyzing JavaScript source code (e.g., JSHint), but there are usually adequate replacements.

TypeScript is a Superset of JavaScript

Since TypeScript is a superset of JavaScript, you don’t need to go through a big rewrite to migrate to it. You can do it gradually, one module at a time.

Just pick a module, rename the .js files into .ts, then incrementally add type annotations. When you are done with this module, pick the next one. Once the whole code base is typed, you can start tweaking the compiler settings to make it more strict.

This process can take some time, but it was not a big problem for Angular 2, when we were migrating to TypeScript. Doing it gradually allowed us to keep developing new functionality and fixing bugs during the transition.

TypeScript Makes Abstractions Explicit

A good design is all about well-defined interfaces. And it is much easier to express the idea of an interface in a language that supports them.

For instance, imagine a book-selling application where a purchase can be made by either a registered user through the UI or by an external system through some sort of an API.

It is hard, just by looking at the code to tell what objects can play the role of a purchaser, and what methods this role has. We do not know for sure, and we will not get much help from our tools. We have to infer this information manually, which is slow and error-prone.

Now, compare it with a version where we explicitly define the Purchaser interface.

The typed version clearly states that we have the Purchaser interface, and the User and ExternalSystem classes implement it. So TypeScript interfaces allow us to define abstractions/protocols/roles.

It is important to realize that TypeScript does not force us to introduce extra abstractions. The Purchaser abstraction is present in the JavaScript version of the code, but it is not explicitly defined.

In a statically-typed language, boundaries between subsystems are defined using interfaces. Since JavaScript lacks interfaces, boundaries are not well expressed in plain JavaScript. Not being able to clearly see the boundaries, developers start depending on concrete types instead of abstract interfaces, which leads to tight coupling.

My experience of working on Angular 2 before and after our transition to TypeScript reinforced this belief. Defining an interface forces me to think about the API boundaries, helps me define the public interfaces of subsystems, and exposes incidental coupling.

TypeScript Makes Code Easier to Read and Understand

Yes, I know it does not seem intuitive. Let me try to illustrate what I mean with an example. Let’s look at this function jQuery.ajax(). What kind of information can we get from its signature?

The only thing we can tell for sure is that the function takes two arguments. We can guess the types. Maybe the first one is a string and the second one is a configuration object. But it is just a guess, and we might be wrong. We have no idea what options go into the settings object (neither their names nor their types), or what this function returns.

There is no way we can call this function without checking the source code or the documentation. Checking the source code is not a good option — the point of having functions and classes is to be able to use them without knowing how they are implemented. In other words, we should rely on their interfaces, not on their implementation. We can check the documentation, but it is not the best developer experience — it takes additional time, and the docs are often out-of-date.

So although it is easy to read jQuery.ajax(url, settings), to really understand how to call this function we need to either read its implementation or its docs.

Now, contrast it with a typed version.

It gives us a lot more information.

  • The first argument of this function is a string.
  • The function returns a JQueryXHR object, and we can see its properties and functions.

The typed signature is certainly longer than the untyped one, but :string, :JQueryAjaxSettings, and JQueryXHR are not clutter. They are important documentation that improves the comprehensibility of the code. We can understand the code to a much greater degree without having to dive into the implementation or reading the docs. My personal experience is that I can read the typed code faster because types provide more context to understand the code. But if any of the readers can find a study on how types affect code readability, please leave a comment.

Does TypeScript Limit Expressiveness?

Dynamically-typed languages have inferior tooling, but they are more malleable and expressive. I think using TypeScript makes your code more rigid, but to a much lesser degree than people think. Let me show you what I mean. Let’s say I use ImmutableJS to define the Person record.

How do we type the record? Let’s start with defining an interface called Person:

If we try to do the following:

the TypeScript compiler will complain. It does not know that PersonRecord is actually compatible with Person because PersonRecord is created reflectively. Some of you with the FP background are probably saying: “If only TypeScript had dependent types!” But it does not. TypeScript’s type system is not the most advanced one. But its goal is different. It is not here to prove that the program is 100% correct. It is about giving you more information and enable greater tooling. So it is OK to take shortcuts when the type system is not flexible enough. So we can just cast the created record, as follows:

The typed example:

The reason why it works is because the type system is structural. As long as the created object has the right fields — name and age — we are good.

You need to embrace the mindset that it is OK to take shortcuts when working with TypeScript. Only then you will find using the language enjoyable. For instance, don’t try to add types to some funky metaprogramming code — most likely you won’t be able to express it statically. Type everything around that code, and tell the typechecker to ignore the funky bit. In this case you will not lose a lot of expressiveness, and the bulk of your code will remain toolable and analyzable.

This is similar to trying to get 100% unit test code coverage. Whereas getting 95% is usually not that difficult, getting 100% can be challenging, and may negatively affect the architecture of your application.

The optional type system also preserves the JavaScript development workflow. Large parts of your application’s code base can be “broken”, but you can still run it. TypeScript will keep generating JavaScript, even when the type checker complains. This is extremely useful during development.

Why TypeScript?

There are a lot of options available to frontend devs today: ES5, ES6 (Babel), TypeScript, Dart, PureScript, Elm, etc.. So why TypeScript?

Let’s start with ES5. ES5 has one significant advantage over TypeScript: it does not require a transpiler. This allows you to keep your build setup simple. You do not need to set up file watchers, transpile code, generate source maps. It just works.

ES6 requires a transpiler, so the build setup will not be much different from TypeScript. But it is a standard, which means that every single editor and build tool either supports ES6 or will support it. This is a weaker argument that it used to be as most editors at this point have excellent TypeScript support.

Elm and PureScript are elegant languages with powerful type systems that can prove a lot more about your program than TypeScript can. The code written in Elm and PureScript can be a lot terser than similar code written in ES5.

最后更新:

类似资料

  • 小编在整理当前开源书籍项目的时候,发现项目有部分章节内容没有编排进summary.md里面,但是小编又不知道该将这些内容放到哪里。所以,直接建了个其他的章节,把这些内容放这里来。 Worker节点负载均衡 强制平衡 强行移除节点 Manager节点配置静态IP地址 分布式manager节点 Manager节点的故障排查 监控Swarm健康 备份 检查Swarm中service的状态

  • Task Scheduling  任务 Angular: Why TypeScript? 测试组件 回顾 Reducers 和纯函数 Redux和组件架构 配置应用以使用Redux 插件 使用RxJS操作符实现更多高级特性 与其他框架比较 Angular 2中的Web辅助功能 无障碍Web应用的关键

  • 其他 如果需要 CSS Hacks,需详细注明解决什么问题。 尽量避免使用 IE 中的 CSS filters。 font-weight普通字重使用normal,加粗使用bold。大部分字体只有两个字重,所以 不建议使用容易混淆的数值表示方法。 如无特别精确的要求,推荐使用不带单位的line-height,这样当前元素的行高只与自身font-size成比例关系,使排版更加灵活。例如line-hei

  • 其他 总是开启 ruby -w 选项,以编写安全的代码。 避免使用哈希作为可选参数。这个方法是不是做太多事了?(对象构造器除外) 避免单个方法的长度超过 10 行(不计入空行)。理想上,大部分方法应当不超过 5 行。 避免参数列表数目多于三或四个。 如果你真的需要“全局”方法,将它们添加到 Kernel 并设为私有。 使用模块实例变量而不是全局变量。 # 差 $foo_bar = 1 # 好 mo

  • .gitignore HTML5 Boilerplate 引入了一个基础性的、项目级的 .gitignore。主要用来让源代码忽略对项目中特定文件和目录的管理。在不同的开发环境使用不同的忽略列表,将会大有裨益。 特定系统和特定编辑器的文件应该使用 “global ignore” ,从而忽略系统中所有库对相关文件的依赖。 比如,在希望全局忽略的 HOME 目录,将如下内容放入 .gitignore