What is Typescript?
Introduction about TypeScripts
How's it going, everybody? Cody, I'm here on staff with
Learning Views. For those of you who are part of the program or people who are
just coming to join us to talk, we're an accelerated web development program.
If you'd like to try and participate in the community as much as possible,
we're honored to have JavaScript Ola here and James to tell us a little bit
more about TypeScript and doing it. We're about to program them. Oh, it still
will talk too, but about four there. James, take it away.
All right, We talked a bit about what that means. Before we dive in, I just kind of want to poll the crowd a little bit and just ask: Who here has used TypeScript before? One person. Okay, who here has never even heard of TypeScript before? A couple more. Okay, perfect. Great.
So one thing I really want to point out about TypeScript is
that it's a really beloved technology. It's up-and-coming. Here is the Stack
Overflow 2017 Developer Survey, and you can see that TypeScript came in as the
number three most loved developer language. I think that's pretty cool; it kind
of speaks volumes about what people are interested in doing and what tools they
are interested in using. You don't see many jobs requesting TypeScript
developers a lot of the time—the jobs are kind of behind the trend. But I think
that's going to catch up. I think TypeScript is definitely something you should
keep your eyes on and it's worth learning, especially if you're into
JavaScript.
What is TypeScript?
So, what is TypeScript? TypeScript is a typed superset of
JavaScript. This means that all valid JavaScript is valid TypeScript, but not
the inverse. TypeScript adds extra syntax and semantics to give you optional
static typing while you're developing, and then it compiles it down into plain
JavaScript. A kind of visual representation of this is right here: Everything
that's JavaScript is TypeScript. That means that anywhere you can use
JavaScript—like Electron, Node, or maybe jQuery in your browser—you can use
TypeScript as well. In my personal opinion, anything JavaScript can do,
TypeScript can do a little bit better.
So why would you want to use TypeScript? The main reason, as
I said, is that it adds optional static typing. I really want to emphasize the
"optional" here because you can use a compiler file called
`tsconfig.json` to specify how strict you want the compiler to be. If you want
really rigid code, you can make it that way, or if you want it to be a little
bit looser because you're more familiar with vanilla JavaScript principles,
that's fine too.
There are also object-oriented principles involved in
TypeScript. I come from a Java and C# background, so this comes really
naturally to me and I think it's pretty great. A lot of people don't
necessarily agree, and that's fine—you don't have to use it this way. You can
still use it procedurally, just like regular JavaScript. Developer features
like really great IntelliSense and compiler warnings make the development
process extremely easy. It's very pleasant to develop in TypeScript. One thing
I do want to note, though, is even when you have compiler warnings in your
TypeScript code, you can still compile it down into your vanilla JavaScript.
That's just because no matter what, it should be able to provide a file that it
can run. But if the compiler is telling you that there are problems, you should
probably be listening. Don't trust yourself; the compiler is smarter than you
for sure. In such cases, you might want to say, "Well, I know that I'm
really trying to do this, but if your compiler is yelling at you, you might
want to abstract or refactor your code in some way."
How Does TypeScript Works?
So how does it work? The general principle is that you write
in TypeScript, you use the TSC compiler tool to compile it down to vanilla
JavaScript, and then you run it in whatever environment you're using. You could
be using Electron or Node, like I mentioned, and that means that if you're
using an `app.ts` file for TypeScript...
Once it's compiled, it becomes an `app.js` file and you can
run that and set that as your entry point into your Node web app. So here's an
example. These are a few of the commands you might want to use to get started.
First, you have to install TypeScript, obviously. So that's just an `npm
install`. Then you'll create a project directory, enter it, and do this command
`tsc --init`. This creates your `tsconfig.json`, so this is where all of your
compiler rules are going to be stored. Every time you compile your TypeScript,
it's going to check this file, check which rules you have turned on and off,
and adjust accordingly. It's actually also dynamic, so while you're typing,
it's checking this file and if you violate any of these rules, you'll get a red
squiggly underline to let you know that, hey, this is not going to behave well
with our compiler.
Examples of TypeScripts
Here's an example of what some TypeScript code looks like.
You can see that it's almost the exact same as regular JavaScript in this
example, except for one thing. Right next to the parameter `val` in our
function, we have a type annotation. This type annotation is saying this can be
anything; this can be any type. This is good; it kind of works a bit like
vanilla JavaScript variables that can just be anything. Normally, when we have
`any`, we need to do a little bit of type checking. Right now, all we're doing
is `console.log` the given parameter, so we can really pass anything into it.
This is just to show you what it compiles down into. You can see here that once
we've run `tsc` on our file, `app.ts` or whatever it may be, the type
annotation is removed because it’s not valid JavaScript syntax. If we had `:
any` here in a vanilla JavaScript file, that would just hit an error and the
function wouldn’t run. So the type annotations and all of the TypeScript
features are to benefit you, the developer, and they really enhance the
development experience. Also, this is good to note because all the extra code
you might be writing isn’t going to affect your actual code base. Your
deployment to your web server is going to be the size of your JavaScript, not
your TypeScript.
Let's talk about the default types involved in the language.
These are the main ones: `boolean`, `number`, `string`, `any`, `array`, and
`tuple`. `boolean`, `number`, and `string` behave exactly how you would expect
them to. A `number` doesn’t have to be any specific type; it can be decimal,
binary, octal, or hexadecimal. The `any` type, like I mentioned before, behaves
exactly like variables in regular JavaScript. `array` is also the same as
regular JavaScript, and `tuple` is a bit of an interesting case because it's
just like arrays except they are strictly ordered sets. So if you define a
tuple as being an array that has one number and then one string, when you
create an instance of that tuple, it has to follow that structure. If you
reverse the types or if you have different types in your array, that's going to
be an error. Again, that error can be ignored; you could still compile the code
and it would work fine, but then you're going to hit issues that you might not
foresee. So again, always just listen to the compiler.
We also have an `enum` type which, if you've used Java or C#
or anything similar, this is basically just a mapping of values to
developer-friendly properties. If you've got a bunch of hex codes for different
colors, you could use `color.red` and it would map to something, like
`#ff0000`. Then there’s also `void`, `null`, and `undefined` which can behave
exactly like `void`, `null`, and `undefined` in vanilla JavaScript. Here’s just
a look at what some of these might look like.
So we have a `boolean`, `number`, and `string`. Again, the
`number` can be a decimal and integer; that doesn’t matter. It supports hex,
binary, and octal as well as decimal. `array` is the same, and here is how you
would define a `tuple`. So remember what I said before: We’re defining this as
being a number in the first space in our array and a string in the second space
in our array. So if we try to add a third element, that’s not going to work. If
we try to add anything that’s not a number and then a string, it’s not going to
work.
So then there’s also, you know, I mean the reason I actually
separated `enum` from the rest is because all of these you instantiate, but
`enum` is kind of like an interface. You just define it and then you can use it
later. So you can say `color.red` in your code somewhere else, but you don’t
say `let variableName = color.red` or something like that.
Coming back a bit to the `any` type, it can be a little bit
dangerous because it’s just like any other variable in JavaScript. And what I
mean by that is let's take a look at this example. Here we can pass anything we
want into this function and it’ll operate exactly how we expect it to. All
we're doing is `console.log` the value, and in JavaScript, you can pass
anything to `console.log`. So this isn’t going to be a problem for any code.
Right here are a few different examples like an integer, `null`, `hello world`,
and then a new `Date` object. So it can support richer, more complex objects as
well. But what if we specify a type? Now we've said, "Well, this function
expects a `Date` type," and you can see the compiler is angry at us, but
the output is the exact same. So the compiler is saying you shouldn't be doing
this, but if we compile it down, it runs and behaves how we’d expect it. But
this can be a bit problematic if you’re operating on anything explicit to that
type. If you use the `any` type in your functions but you're using something
exclusive to a specific type, like the `Date` object, you’re going to have a
bad time.
Here’s a bit more dangerous example. Let's say we’re using
this `val.getUTCFullYear()` in our value function because we know we're passing
a `Date` type. This is fine; the compiler’s not going to yell at us. We don’t
have to do any type checking because that’s what TypeScript's compiler is doing
for you. But if you still use the same function calls down here, if you pass an
integer, `null`, or a string, the results are very different. So in the first
and third example, it’s just `undefined`, but in the second, you can see that
it’s actually going to halt the JavaScript execution, and that could be pretty
critical.
TypeScript lets you define these types using type
annotations for parameters in your functions, and this lets you forego type
checking in your code. You don’t need to say if this value is instantiated and
blah, blah, blah, and that makes it a lot cleaner, a lot easier to maintain.
One thing I wanted to mention before I move on: I mentioned
earlier the great IntelliSense you can get out of TypeScript. In vanilla
JavaScript, at least in Visual Studio Code, when you’re using a `Date` object,
you get all of this anyway. So you don’t need to be using TypeScript
necessarily. But in Visual Studio Code, all of your user-defined types,
interfaces, classes, whatever, all get the same level of methods and
properties, and all of your comments are shown as documentation toward them. So
the IntelliSense is really good; it’s intelligent IntelliSense, and I can't
recommend using VS Code enough if you’re interested in using TypeScript.
Interfaces in TypeScripts
I've mentioned interfaces a little bit before, and they’re
the first main construct in TypeScript. They’re the easiest and the simplest
one, so let’s talk about what that means. Interfaces represent a contract that
an object must fulfill. What that means is if you have an object, it needs to
be of a certain shape. It has to have certain properties to be considered that
type. So here’s an example: We have a `Person` interface with a `name` string.
Now we’ve got this function that expects a `Person` object. Below, we’ve
defined an object that says `name` equals James, but then there’s this extra
property. So your interfaces don’t have to be explicitly fulfilled; they can be
minimally fulfilled. This means that because we’ve minimally fulfilled that
contract with the `name` property on this object, it’s inferred that it’s okay.
It’s the `Person` type and we can use it as such. So down here, when we say
`showName` and just pass this object, it’s not going to yell at us because it
minimally fulfills that contract. So it lets you have any extra properties as
long as you have the requirements defined in the interface.
Interfaces can extend one or more other interfaces, of
course, to make complex objects. So another example would be: We have an
`Animal` and it has a `name`, and then we have a `Dog`. So a `Dog` is a type of
`Animal`. Again, this comes back to object-oriented programming. `Dogs` have a
`breed`, but anything that is a `Dog` will have a `breed` and a `name` because
it’ll be inherited. So down here we’re doing something like what we did before.
Here's a revised and streamlined version of your text with
extra spaces removed from paragraphs:
Instead of defining names and having extra stuff, we're
doing something a little bit different. You can see that `let myDog =
<Dog>{}` is called a type assertion. It's similar to casting in other
languages. We're saying this object is going to be a `Dog`, and we're telling
the compiler this can only be a `Dog` object. This means it has to have exactly
the properties defined in the `Dog` interface. While the contract can be minimally
fulfilled by an anonymous object, it now needs to be explicitly fulfilled by a
non-anonymous object because we're using type assertion to say this is a `Dog`
object. We need to have both `breed` and `name`, nothing more or less.
An interesting part of interfaces in TypeScript is how they
compile down into vanilla JavaScript. As mentioned before, type annotations in
functions disappear when you compile because they're a developer feature.
Similarly, the interface and everything set up in TypeScript disappears in the
compiled code. This is beneficial because you can use interfaces and any
inheritance they provide to create rich objects and data models without
bloating your source code.
I've never used TypeScript with React, but I imagine if the
syntax is a problem in React, you could use the `as` keyword. I don't think
it's deprecated, but if you're using React data models, I'm sure there's a way
to use React and this syntax together. I'll have to look into that for you
because I'm sure there's a method to use React with TypeScript syntax, possibly
with changes when using React libraries or packages.
Classes in TypeScripts
The other major construct in TypeScript is classes. Classes
and interfaces are extremely similar; you can use one where you want to use the
other. JavaScript allows you to use anonymous objects and variables, so you
don’t need to instantiate new variables often, but you can and should whenever
you need to maintain state in your application. Classes and interfaces differ
mainly in that interfaces provide definitions. For example, if you create an
interface for a service provider, it has certain properties and functions, but
it won’t include the actual code inside those functions. An interface only
tells you that a function should exist, but not how it works
In contrast, classes provide concrete implementations of
interfaces. If you have a function like `print`, you need to provide the code
as an implementation and say how `print` works, not just that it exists. So,
classes are used to implement the actual details of an interface. You can use
either an interface or a class based on the context, but generally, I recommend
using interfaces unless you need the features of classes.
Interfaces can extend one or more interfaces, meaning all
properties in one are inherited into the other. Similarly, classes can extend
interfaces, inheriting all properties and methods. Classes can also implement
interfaces, providing concrete implementations of the interface’s properties
and methods. Additionally, classes in TypeScript can implement other classes,
which requires explicitly defining all properties and methods from one class
into another. This might feel a bit wacky, but it’s similar to how it works in
languages like C#.
To get started, create a project directory, go into it, and
start with `npm init` to create your `package.json`. Then run `tsc --init` to
create the `tsconfig.json`, which defines compiler rules. Install Express and
any other packages needed, including type definitions from the `@types`
organization on npm, which provides type definitions for popular packages. Note
that there may be versioning issues with `@types` and packages like Express, so
you might need to install them directly for your vanilla JavaScript to
recognize them while using type definitions for TypeScript
In this example, I wrote the `app.js` file using TypeScript,
but there’s no particular benefit to writing this file in TypeScript. The
benefits come when building data models and backend software. In `app.js`, I've
used `any` type in the type annotations, but let’s talk about linting. Linting
helps find undesirable code that isn't necessarily problematic but could be
less maintainable or problematic in the future. For instance, if there's no
type definition, it can help maintain standards throughout your application.
In my `tsconfig.json`, I can set `noImplicitAny` to enforce
explicit type annotations. By default, everything needs a type annotation, and
if you remove this rule, it won’t enforce explicit types, but the default
behavior is to require them.
So here you can see, instead of TS, the left-hand side of
that error is saying "T is lint," which is an extension I’ve got
installed in VS Code. This indicates that we shouldn’t use the function
keyword, as non-function declarations are forbidden. This helps maintain better
code standards, kind of like linting—finding small issues that aren't critical
but can make code messy. To fix this, we could turn this into an arrow
function, ensuring our code follows a certain standard and remains cleaner and
more maintainable.
Let’s take a look at what we have here. I’ve shown you the
`tsconfig.json`, which contains all your compiler rules. When you run `tsc
--init`, this is what’s provided for you. All the commented-out ones are
default settings, but this file shows all the different options available so
you don’t have to search through documentation. I think that’s a neat feature.
This is your `package.json`, which is quite basic. It
includes your compiler options, and it’s a standard part of a Node app. Your
`launch.json` is a configuration file created for you when you debug your
application, and I'll talk about debugging and mapping files shortly. Then
there's your `tslint.json`. By default, the rules block is empty, but you can
add your own rules here. For example, if I show you some data models I’ve
created for animals, this whole block might turn red because `tslint` prefers
one class per file. If you don’t like that rule, you can ignore it right here.
Source Mapping in TypeScript
One other compiler feature I want to discuss is source
mapping. This is off by default, but you should turn it on. It creates a
mapping file that helps the debugger understand where your TypeScript code
translates into JavaScript code. Although you can't run TypeScript code
directly, the mapping file helps you debug by showing where in your TypeScript
code the JavaScript came from. This is useful if a route isn’t working
properly; you can set a breakpoint, and the debugger can use the map file to
locate the corresponding TypeScript code, providing you with source code
debugging, which is pretty great. Sure, here's your text with the extra spaces
removed from the paragraphs:
So let's just take a look at our application. Something
really simple. Here we go again. We can see that we're just— we've got our
route here that says just go to index whenever the route is requested, and then
if the form gets submitted, we can change the color of the box that our form is
in. So here we've just got the default. We can do blue or green. This is kind
of nothing really interesting, right? This is just showing you the way you can
set up a route and pass data into your Touch application, right into the view.
But let’s do something a little bit more interesting.
The big benefits of TypeScript are interfaces and classes,
and these let you wrap your data into intelligent objects. So let’s say that
you wanted to have a database model built into your TypeScript. You can create
a bunch of models, and like I’ve done here, we’re going to be just using cats
and dogs again. So we can import our cats and dogs and just uncomment this bit
of code right here. Now we’re saying when we submit this form, we’re going to
actually be creating a new instance of this Dog class.
Well, let’s take a look at what that actually is. So here
now we’re using some of the features of TypeScript. We have an interface and
then we have our classes. Notice that I’m not exporting the interface because
there’s no need to make that publicly available. It can be private; it can be
public; it’s going to disappear in your compiled code regardless. But our class
is the one we’re interested in, so we do export that. Here we’re saying every
animal has to have an ID, a name, a URL, as well as a function speak that
returns a string. It doesn’t matter what’s in the function, how it operates, or
what it does— let the class decide that.
The interface is just saying it needs to have one. So now
we’ve got our class Dog, and it implements Animal. We have all of these
different properties in our object, and when we run our constructor, they get
instantiated with the given values. Now here we have our speak function that
was defined up here in our interface, and down below we’re saying, okay, here
is how the speak function actually operates. It’s a concrete implementation.
So we’re doing the exact same thing. Down here we have an ID
and a name and a URL again. Right in here I’m assigning it a value, but if you
were pulling this from a database and you created something new, you would just
pass it values you’ve pulled from the database, manipulate it in some way, and
then be able to save it from the database.
So let’s look at using this. Instead of just running `node
app.js`, let’s say we’re interested in looking at this Dog object. In a real
production environment, you would probably pull something from the database,
and you’re curious to see what your data looks like. Maybe there’s a problem,
and you need to investigate. Certainly! Here’s the cleaned-up version of the
text:
Now we can come into the debugger. I already have this
launched. A JSON file is created, but this gets created for you in VS Code, so
you don't need to worry about it. The very first time you debug, it'll ask you
what kind of environment or configuration you're trying to work with.
Let’s come back to our code. We can run the debugger, and
there we go. When the breakpoint gets hit, we'll be able to debug.
If it didn't work, cancel out, and retry. If it says the
port is in use, just close it and stop. Okay, there we go—listening on port
443.
Now, let's reopen it again. You can see we’ve just got our
boxes; they’re not very interesting. You’re just submitting data through. But
now we can look at our models.
We have the same form, and now we're requesting either a cat
or a dog. This is how you would set up a way to bring data in from your
database and present it in a view. Right now, I'm creating static objects for
the sake of example. But if you had a database connection, you would pull that
information, manipulate it in some way, and then present it to the user in your
view.
So here, if the form submits "dog," we create a
new dog and then run that constructor I showed you. It instantiates all of
those values, and we can render and pass the dog's information to the user. The
same goes for "cat"—we can pass that information once we’ve
instantiated the object.
So it can exist throughout multiple requests and you can
manipulate it in a bunch of different ways and then save it back to your
database. Let’s see what that looks like here. We request a dog. Oh look, now
we’ve hit our breakpoint. This is in TypeScript, so our debugger is using that
mapping file even though we’re running JavaScript to say, “Hey, at this point
they’ve requested to stop at,” and the TypeScript file is somewhere here in the
JavaScript. Now we can say we have this dog. We can look into what it has, and
in this case, it’s pretty obvious because I provided all the values right here.
I just hard-coded them. But if you got it from a database, this would be a lot
more useful because now you can stop and look into what your object actually
has. So now we can just press play because we’ve looked at it and we’re happy
with what it has. We’ve got our view. You can see that we’ve taken data from
our data model, brought it to the view, and we can do the same thing with a
cat. Again, it's a different data object that we’ve created, and we’ve only
instantiated it in this small request scope. But again, it can live longer than
that and can be manipulated across a few different web requests, and you can
store it in a session, for example, things like that. So that’s the basics of
doing the development, and the debugger is an important tool. If you’re not
familiar with using debuggers and you kind of just always use console
statements, it’s a huge quality-of-life improvement when you upgrade and start
using different debuggers. I still know people who work professionally as
developers and they just use console.log for everything and don’t do that.
So that’s about it, and if you want any more information,
Thank you.
ConversionConversion EmoticonEmoticon