添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
着急的跑步鞋  ·  Fix 'Binding Element ...·  2 周前    · 
强悍的核桃  ·  phpoffice/phpword - ...·  3 周前    · 
温柔的拐杖  ·  customer_v2·  4 周前    · 
憨厚的水煮鱼  ·  WPF 判断USB插拔·  2 月前    · 
爱热闹的蚂蚁  ·  no-undef - ESLint - ...·  2 月前    · 
文武双全的松鼠  ·  Iframe – how to ...·  3 月前    · 

Put simply, TypeScript is a superset of JavaScript. Think of it as JavaScript with additional annotations and static type checking.

TypeScript transpiles down to JavaScript, so any browser that runs JavaScript can run code written in TypeScript. TypeScript can also target older versions of JavaScript. This lets you use modern JavaScript features like classes, arrow functions, let/const , and template strings while targeting browsers that don’t yet support these things.

Additionally, TypeScript’s static checking makes entire classes of defects impossible, which is something I feel very strongly about .

With that brief introduction, let’s meet the app we’ll be migrating to TypeScript.

The Sample Application

We’ll be working with a simple JavaScript application that we’ll migrate to TypeScript.

The code is available on GitHub in its initial JavaScript state (with a few bugs) and its finished TypeScript state . If you’d like to play with the final fixed version in your browser, it is available online .

The app is a simple test case manager where the user types in the name of a test case and adds it to the list. Test cases can then be marked as passed, failed, or deleted.

Here we define a TestCase class that serves as our primary (and only) entity in this application. We have a collection of testCases defined in line 1 that holds the current state. At line 20, we add a startup event handler that generates the initial application data and calls out to the function to update the test cases.

Pretty simple, though it does contain at least one bug (see if you can find it before I point it out later).

Rendering JavaScript Code

Now, let’s look at our list rendering code. It’s not pretty since we’re not using a templating engine or a fancy single page application framework like Angular, Vue, or React.

The code here is relatively self-explanatory and clears out the list of items, then adds each item to the list. I never said it was efficient, but it works for a demo.

Like the last, this chunk contains at least one bug.

Event Handling JavaScript Code

The final chunk of code handles events from the user.

This specifically handles button clicks and adding items to the list.

And, again, there’s at least one bug in this chunk.

What’s Wrong with the Code?

So, what’s wrong here? Well, I’ve observed the following problems:

  • It’s impossible to fail or delete the initial test data.
  • It’s impossible to fail any added test
  • If you could delete all items, the add item label wouldn’t show up

Where the bugs are isn’t the point. The point is: each one of these bugs would have been caught by TypeScript.

So, with that introduction, let’s start converting this to TypeScript. In the process, we’ll be forced to fix each one of these defects and wind up with code that can’t break in the same way again.

Installing TypeScript

If you have not already installed TypeScript, you will need to install Node Package Manager (NPM) before you get started. I recommend installing the Long Term Support (LTS) version, but your needs may be different.

Once NPM is installed, go to your command line and execute the following command: npm i -g typescript

This will i nstall TypeScript g lobally on your machine and allow you to use tsc , the T ype S cript C ompiler. As you can see, although the term for converting TypeScript code to JavaScript is transpiling , people tend to say compiler and compilation. Just be aware that you may see it either way – including in this article.

With this complete, you now have everything you need in order to work with TypeScript. You don’t need a specific editor to work with TypeScript, so use whatever you like. I prefer to work with WebStorm when working with TypeScript code, but VS Code is a very popular (and free) alternative.

Next, we’ll get set up with using TypeScript in our project.

Compiling our Project as a TypeScript Project

Initializing TypeScript

Open a command line and navigate into your project directory, then run the following:

tsc --init

You should get a message indicating that tsconfig.json was created.

You can open up the file and take a look if you want. Most of this file is commented out, but I actually love that. TypeScript gives you a good configuration file that tells you all the things you can add in or customize.

Now, if you navigate up to the project directory and run tsc you should see TypeScript displaying a number of errors related to your file:

Next, we’ll address an issue in the addTestCase method. Here, TypeScript is complaining that HTMLElement doesn’t have a value field. True, but the actual element we’re pulling is a text box, which shows up as an HTMLInputElement . Because of this, we can add a type assertion to tell the compiler that the element is a more specific type.

The modified code looks like this:

const textBox = <HTMLInputElement>document.getElementById('txtTestName');

Important Note: TypeScript’s checks are at compile time, not in the actual runtime code. The concept here is to identify bugs at compile time and leave the runtime code unmodified.

Correcting Bad Code

TSC also is complaining about some of our for loops, since we were cheating a little and omitting var syntax for these loops. TypeScript won’t let us cheat anymore, so let’s fix those in updateTestCases and findTestCaseById by putting a const statement in front of the declaration like so:

function findTestCaseById(id) {
    for (const testcase of this.testCases) {
        if (testcase.id === id) return testcase;
    return null;

Fixing the Bugs

Now, by my count, there are two more compilation issues to take care of. Both of these are related to bugs I listed earlier with our JavaScript code. TypeScript won’t allow us to get away with this, thankfully, so let’s get those sorted out.

First of all, we call to showAddItemsPrompt in updateTestCases, but our method is called showAddItemPrompt. This is an obvious issue, and one that could conceivably be caused either by a typo or renaming an existing method but missing a reference. This is easily changed by making sure the names match.

Secondly, failTestCase declares a variable called testCase and then tries to reference it as testcase, which is just never going to work. This is an easy fix where we can make sure the names are consistent.

Referencing our Compiled Code

And, with that, running tsc results in no output – that mean our code compiled without issue!

On top of that, because Logic.ts will automatically transpile into Logic.js, the file our index.html is referencing anyway, that means that we don’t even have to update our HTML.

And so, if we run the application, we can see that we can fail and delete tests again:

This should yield about 16 errors during compile. The vast majority are no impicit any, or TypeScript complaining that it doesn’t know what type things are. Going through and fixing that is somewhat straightforward so I won’t walk through it, but feel free to check my finished result if you get lost.

Beyond that, we see a few instances where TypeScript points out that things could be null. These involve fetching HTML Elements from the page and can be resolved via type assertions:

const list = <HTMLElement>document.getElementById('listTestCases');

The type assertions are acceptable here because we are explicitly choosing to accept the risk of a HTML element’s ID changing causing errors instead of trying to somehow make the app function without required user interface elements. In some cases, the correct choice will be to do a null check, but the extra complexity wasn’t worth it in a case where failing early is likely better for maintainability.

Removing Global State

This leaves us with 5 remaining errors, all of the same type:

'this' implicitly has type 'any' because it does not have a type annotation.

TypeScript is letting us know that it is not amused by our use of this to refer to items in the global scope. In order to fix this (no pun intended), I’m going to wrap our state management logic into a new class:

This generates a number of compiler errors as things now need to refer to methods on the testManager instance or pass in a testManager to other members.

This also exposes a few new problems, including that bug I’ve alluded to a few times.

Specifically, when we create the test data in buildInitialData we’re setting the id to '1' instead of 1. To be more explicit, id is a string and not a number, meaning it will fail any === check (though == checks will pass still). Changing the property initializer to use the number fixes the problem.

Note: This problem also would have been caught without extracting a class if we had declared type assertions around the testcases array earlier.

The remaining errors all have to do with handling the results of findTestCaseById which can return either a TestCase or null in it’s current form.

In TypeScript, this return type can be written explicitly as TestCase | null. We could handle this by throwing an exception instead of returning null if no test case was found, but instead we should probably heed TypeScript’s advice and add null checks.

I’ve glossed over many details, but if you’re confused on something or want to see the final code, it is available in my GitHub repository.

Benefiting from TypeScript

Now, when we run the application, the code works perfectly

There’s been a recent surge of people attacking TypeScript for getting in the way, obfuscating JavaScript, being unnecessary, etc. And sure, maybe TypeScript is overkill for an app of this size, but here’s where I stand on things:

TypeScript is essentially a giant safety net you can use when building JavaScript code. Yes, there’s effort in setting up that safety net, and no, you probably don’t need it for trivial things, but if you’re working on a large project without sufficient test coverage, you need some form of safety net or you’re going to be passing off quality issues to your users.

To my eyes, TypeScript is an incredibly valuable safety net that supports existing and future unit tests and allows QA to focus on business logic errors and usability instead of programming mistakes.

I’ve taken a large JavaScript application and migrated it to TypeScript before to great effect. In the process, I resolved roughly 10 – 20 open bug tickets because TypeScript made the errors glaringly obvious and impossible to ignore.

Even better, this process made the types of errors that had occurred anytime the app was touched impossible to recur.

So, the question is this: What’s your safety net? Are you really willing to let language preferences pass on defects you might miss to your end users?

Matt Eland

Matt Eland Profile Picture

I help people love software engineering, AI, and data science.

Videos