{"id":317887,"date":"2020-08-06T07:44:39","date_gmt":"2020-08-06T14:44:39","guid":{"rendered":"https:\/\/css-tricks.com\/?p=317887"},"modified":"2020-08-07T07:40:58","modified_gmt":"2020-08-07T14:40:58","slug":"typescript-minus-typescript","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/typescript-minus-typescript\/","title":{"rendered":"TypeScript, Minus TypeScript"},"content":{"rendered":"\n

Unless you\u2019ve been hiding under a rock the last several years (and let\u2019s face it, hiding under a rock sometimes feels like the right thing to do), you\u2019ve probably heard of and likely used TypeScript<\/a>. TypeScript is a syntactical superset of JavaScript that adds \u2014 as its name suggests \u2014 typing to the web\u2019s favorite scripting language.<\/p>\n\n\n\n

TypeScript is incredibly powerful, but is often difficult to read for beginners and carries the overhead of needing a compilation step before it can run in a browser due to the extra syntax that isn\u2019t valid JavaScript. For many projects this isn\u2019t a problem, but for others this might get in the way of getting work done. Fortunately the TypeScript team has enabled a way to type check vanilla JavaScript using JSDoc<\/a>.<\/p>\n\n\n

Setting up a new project<\/h2>\n\n\n

To get TypeScript up and running in a new project, you\u2019ll need NodeJS and npm<\/a>. Let\u2019s start by creating a new project and running npm init. For the purposes of this article, we are going to be using VShttps:\/\/code.visualstudio.comCode<\/a> as our code editor. Once everything is set up, we\u2019ll need to install TypeScript:<\/p>\n\n\n\n

npm i -D typescript<\/code><\/pre>\n\n\n\n

Once that install is done, we need to tell TypeScript what to do with our code, so let\u2019s create a new file called tsconfig.json<\/code> and add this:<\/p>\n\n\n\n

{\n\u00a0 \"compilerOptions\": {\n\u00a0 \u00a0 \"target\": \"esnext\",\n\u00a0 \u00a0 \"module\": \"esnext\",\n\u00a0 \u00a0 \"moduleResolution\": \"node\",\n\u00a0 \u00a0 \"lib\": [\"es2017\", \"dom\"],\n\u00a0 \u00a0 \"allowJs\": true,\n\u00a0 \u00a0 \"checkJs\": true,\n\u00a0 \u00a0 \"noEmit\": true,\n\u00a0 \u00a0 \"strict\": false,\n\u00a0 \u00a0 \"noImplicitThis\": true,\n\u00a0 \u00a0 \"alwaysStrict\": true,\n\u00a0 \u00a0 \"esModuleInterop\": true\n\u00a0 },\n\u00a0 \"include\": [ \"script\", \"test\" ],\n\u00a0 \"exclude\": [ \"node_modules\" ]\n}<\/code><\/pre>\n\n\n\n

For our purposes, the important lines of this config file are the allowJs<\/code> and checkJs<\/code> options, which are both set to true<\/code>. These tell TypeScript that we want it to evaluate our JavaScript code. We\u2019ve also told TypeScript to check all files inside of a \/script<\/code> directory, so let\u2019s create that and a new file in it called index.js<\/code>.<\/p>\n\n\n

A simple example<\/h3>\n\n\n

Inside our newly-created JavaScript file, let\u2019s make a simple addition function that takes two parameters and adds them together:<\/p>\n\n\n\n

function add(x, y) {\n\u00a0 return x + y;\n}<\/code><\/pre>\n\n\n\n

Fairly simple, right? add(4, 2)<\/code> will return 6<\/code>, but because JavaScript is dynamically-typed<\/a> you could also call add with a string and a number and get some potentially unexpected results:<\/p>\n\n\n\n

add('4', 2); \/\/ returns '42'<\/code><\/pre>\n\n\n\n

That\u2019s less than ideal. Fortunately, we can add some JSDoc annotations to our function to tell users how we expect it to work:<\/p>\n\n\n\n

\/**\n\u00a0* Add two numbers together\n\u00a0* @param {number} x\n\u00a0* @param {number} y\n\u00a0* @return {number}\n\u00a0*\/\nfunction add(x, y) {\n\u00a0 return x + y;\n}<\/code><\/pre>\n\n\n\n

We\u2019ve changed nothing about our code; we\u2019ve simply added a comment to tell users how the function is meant to be used and what value should be expected to return. We\u2019ve done this by utilizing JSDoc\u2019s @param<\/code> and @return<\/code> annotations with types set in curly braces ({}<\/code>).<\/p>\n\n\n\n

Trying to run our incorrect snippet from before throws an error in VS Code:<\/p>\n\n\n\n

\"\"
TypeScript evaluates that a call to add<\/code> is incorrect if one of the arguments is a string.<\/figcaption><\/figure>\n\n\n\n

In the example above, TypeScript is reading our comment and checking it for us. In actual TypeScript, our function now is equivalent of writing:<\/p>\n\n\n\n

\/**\n\u00a0* Add two numbers together\n\u00a0*\/\nfunction add(x: number, y: number): number {\n\u00a0 return x + y;\n}<\/code><\/pre>\n\n\n\n

Just like we used the number<\/code> type, we have access to dozens of built-in types with JSDoc, including string, object, Array as well as plenty of others, like HTMLElement<\/code>, MutationRecord<\/code> and more.<\/p>\n\n\n\n

One added benefit of using JSDoc annotations over TypeScript\u2019s proprietary syntax is that it provides developers an opportunity to provide additional metadata around arguments or type definitions by providing those inline (hopefully encouraging positive habits of self-documenting our code).<\/p>\n\n\n\n

We can also tell TypeScript that instances of certain objects might have expectations. A WeakMap<\/code>, for instance, is a built-in JavaScript object that creates a mapping between any object and any other piece of data. This second piece of data can be anything by default, but if we want our WeakMap<\/code> instance to only take a string as the value, we can tell TypeScript what we want:<\/p>\n\n\n\n

\/** @type {WeakMap<object>, string} *\/\nconst metadata = new WeakMap();\n\u2028\nconst object = {};\nconst otherObject = {};\n\u2028\nmetadata.set(object, 42);\nmetadata.set(otherObject, 'Hello world');<\/code><\/pre>\n\n\n\n

This throws an error when we try to set our data to 42<\/code> because it is not a string.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n

Defining our own types<\/strong><\/h3>\n\n\n

Just like TypeScript, JSDoc allows us to define  and work with our own types. Let\u2019s create a new type called Person<\/code> that has name<\/code>, age<\/code> and hobby<\/code> properties. Here\u2019s how that looks in TypeScript:<\/p>\n\n\n\n

interface Person {\n\u00a0 name: string;\n\u00a0 age: number;\n\u00a0 hobby?: string;\n}<\/code><\/pre>\n\n\n\n

In JSDoc, our type would be the following:<\/p>\n\n\n\n

\/**\n\u00a0* @typedef Person\n\u00a0* @property {string} name - The person's name\n\u00a0* @property {number} age - The person's age\n\u00a0* @property {string} [hobby] - An optional hobby\n\u00a0*\/<\/code><\/pre>\n\n\n\n

We can use the @typedef<\/code> tag to define our type\u2019s name<\/code>. Let\u2019s define an interface called Person<\/code> with required  name<\/code> (a string)) and age<\/code> (a number) properties, plus a third, optional property called hobby<\/code> (a string). To define these properties, we use @property<\/code> (or the shorthand @prop<\/code> key) inside our comment.<\/p>\n\n\n\n

When we choose to apply the Person<\/code> type to a new object using the @type<\/code> comment, we get type checking and autocomplete when writing our code. Not only that, we\u2019ll also be told when our object doesn\u2019t adhere to the contract we\u2019ve defined in our file:<\/p>\n\n\n\n

\"Screenshot<\/figure>\n\n\n\n

Now, completing the object will clear the error:<\/p>\n\n\n\n

\"\"
Our object now adheres to the Person<\/code> interface defined above<\/figcaption><\/figure>\n\n\n\n

Sometimes, however, we don\u2019t want a full-fledged object for a type. For example, we might want to provide a limited set of possible options. In this case, we can take advantage of something called a union type<\/a>:<\/p>\n\n\n\n

\/**\n\u00a0* @typedef {'cat'|'dog'|'fish'} Pet\n\u00a0*\/\n\u2028\n\/**\n\u00a0* @typedef Person\n\u00a0* @property {string} name - The person's name\n\u00a0* @property {number} age - The person's age\n\u00a0* @property {string} [hobby] - An optional hobby\n\u00a0* @property {Pet} [pet] - The person's pet\n\u00a0*\/<\/code><\/pre>\n\n\n\n

In this example, we have defined a union type called Pet<\/code> that can be any of the possible options of 'cat'<\/code>, 'dog'<\/code> or 'fish'<\/code>. Any other animals in our area are not allowed as pets, so if caleb<\/code> above tried to adopt a 'kangaroo'<\/code> into his household, we would get an error:<\/p>\n\n\n\n

\/** @type {Person} *\/\nconst caleb = {\n  name: 'Caleb Williams',\n  age: 33,\n  hobby: 'Running',\n  pet: 'kangaroo'\n};<\/code><\/pre>\n\n\n\n
\"Screenshot<\/figure>\n\n\n\n

This same technique can be utilized to mix various types in a function:<\/p>\n\n\n\n

\/**\n\u00a0* @typedef {'lizard'|'bird'|'spider'} ExoticPet\n\u00a0*\/\n\u2028\n\/**\n\u00a0* @typedef Person\n\u00a0* @property {string} name - The person's name\n\u00a0* @property {number} age - The person's age\n\u00a0* @property {string} [hobby] - An optional hobby\n\u00a0* @property {Pet|ExoticPet} [pet] - The person's pet\n\u00a0*\/<\/code><\/pre>\n\n\n\n

Now our person type can have either a Pet<\/code> or an ExoticPet<\/code>.<\/p>\n\n\n

Working with generics<\/h3>\n\n\n

There could be times when we don\u2019t want hard and fast types, but a little more flexibility while still writing consistent, strongly-typed code. Enter generic types<\/a>. The classic example of a generic function is the identity function, which takes an argument and returns it back to the user. In TypeScript, that looks like this:<\/p>\n\n\n\n

function identity<T>(target: T): T {\n\u00a0 return target;\n}<\/code><\/pre>\n\n\n\n

Here, we are defining a new generic type (T<\/code>) and telling the computer and our users that the function will return a value that shares a type with whatever the argument target<\/code> is. This way, we can still pass in a number or a string or an HTMLElement<\/code> and have the assurance that the returned value is also of that same type.<\/p>\n\n\n\n

The same thing is possible using the JSDoc notation using the @template<\/code> annotation:<\/p>\n\n\n\n

\/**\n\u00a0* @template T\n\u00a0* @param {T} target\n\u00a0* @return {T}\n\u00a0*\/\nfunction identity(target) {\n\u00a0 return x;\n}<\/code><\/pre>\n\n\n\n

Generics are a complex topic, but for more detailed documentation on how to utilize them in JSDoc, including examples, you can read the Google Closure Compiler page on the topic<\/a>.<\/p>\n\n\n

Type casting<\/h3>\n\n\n

While strong typing is often very helpful, you may find that TypeScript\u2019s built-in expectations don\u2019t quite work for your use case. In that sort of instance, we might need to cast an object to a new type. One instance of when this might be necessary is when working with event listeners.<\/p>\n\n\n\n

In TypeScript, all event listeners take a function as a callback where the first argument is an object of type Event<\/code>, which has a property, target, that is an EventTarget<\/code>. This is<\/em> the correct type per the DOM standard, but oftentimes the bit of information we want out of the event\u2019s target doesn\u2019t exist on EventTarget<\/code> \u2014 such as the value property that exists on HTMLInputElement.prototype<\/code>. That makes the following code invalid:<\/p>\n\n\n\n

document.querySelector('input').addEventListener(event => {\n\u00a0 console.log(event.target.value);\n};<\/code><\/pre>\n\n\n\n

TypeScript will complain that the property value<\/code> doesn\u2019t exist on EventTarget<\/code> even though we, as developers, know fully well that an <input><\/code> does have a value<\/code>.<\/p>\n\n\n\n

\"A<\/figure>\n\n\n\n

In order for us to tell TypeScript that we know event.target<\/code> will be an HTMLInputElement<\/code>, we must cast the object\u2019s type:<\/p>\n\n\n\n

document.getElementById('input').addEventListener('input', event => {\n\u00a0 console.log(\/** @type {HTMLInputElement} *\/(event.target).value);\n});<\/code><\/pre>\n\n\n\n

Wrapping event.target<\/code> in parenthesis will set it apart from the call to value<\/code>. Adding the type before the parenthesis will tell TypeScript we mean that the event.target<\/code> is something different than what it ordinarily expects.<\/p>\n\n\n\n

\"Screenshot<\/figure>\n\n\n\n

And if a particular object is being problematic, we can always tell TypeScript an object is @type {any}<\/code> to ignore error messages, although this is generally considered bad practice depsite being useful in a pinch.<\/p>\n\n\n

Wrapping up<\/h3>\n\n\n

TypeScript is an incredibly powerful tool that many developers are using to streamline their workflow around consistent code standards. While most applications will utilize the built-in compiler, some projects might decide that the extra syntax that TypeScript provides gets in the way. Or perhaps they just feel more comfortable sticking to standards rather than being tied to an expanded syntax. In those cases, developers can still get the benefits of utilizing TypeScript\u2019s type system even while writing vanilla JavaScript.<\/p>\n","protected":false},"excerpt":{"rendered":"

Unless you\u2019ve been hiding under a rock the last several years (and let\u2019s face it, hiding under a rock sometimes feels like the right thing to do), you\u2019ve probably heard of and likely used TypeScript. TypeScript is a syntactical superset of JavaScript that adds \u2014 as its name suggests \u2014 typing to the web\u2019s favorite […]<\/p>\n","protected":false},"author":38145,"featured_media":318809,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[1259],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/08\/typscript-hollow.png?fit=1200%2C600&ssl=1","jetpack-related-posts":[{"id":362478,"url":"https:\/\/css-tricks.com\/the-relevance-of-typescript-in-2022\/","url_meta":{"origin":317887,"position":0},"title":"The Relevance of TypeScript in 2022","date":"January 31, 2022","format":false,"excerpt":"It\u2019s 2022. And the current relevance of TypeScript is undisputed. TypeScript has dominated the front-end developer experience by many, many accounts. By now you likely already know that TypeScript is a superset of JavaScript, building on JavaScript by adding syntax for type declarations, classes, and other object-oriented features with type-checking.\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2022\/01\/typescript-logo.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":331325,"url":"https:\/\/css-tricks.com\/integrating-typescript-with-svelte\/","url_meta":{"origin":317887,"position":1},"title":"Integrating TypeScript with Svelte","date":"December 24, 2020","format":false,"excerpt":"Svelte is one of the newer JavaScript frameworks and it\u2019s rapidly rising in popularity. It\u2019s a template-based framework, but one which allows for arbitrary JavaScript inside the template bindings; it has a superb reactivity story that\u2019s simple, flexible and effective; and as an ahead-of-time (AOT) compiled framework, it has incredibly\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/12\/typescript-svelte-logos.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":362091,"url":"https:\/\/css-tricks.com\/typescript-discriminated-unions\/","url_meta":{"origin":317887,"position":2},"title":"Demystifying TypeScript Discriminated Unions","date":"January 27, 2022","format":false,"excerpt":"TypeScript is a wonderful tool for writing JavaScript that scales. It\u2019s more or less the de facto standard for the web when it comes to large JavaScript projects. As outstanding as it is, there are some tricky pieces for the unaccustomed. One such area is TypeScript discriminated unions. Specifically, given\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2022\/01\/typscript-discriminated-unions.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":337673,"url":"https:\/\/css-tricks.com\/the-deno-company\/","url_meta":{"origin":317887,"position":3},"title":"The Deno Company","date":"April 2, 2021","format":false,"excerpt":"I'm sure a lot of you are paying attention to Deno anyway, the next-gen JavaScript-on-the-sever project from Node creator Ryan Dahl, especially after dropping all these candid regrets about what happened in Node. But perhaps you're paying more attention now that Deno has taken some seed investment and will be\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/04\/demo.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":285982,"url":"https:\/\/css-tricks.com\/creating-reusable-base-classes-in-typescript-with-a-real-life-example\/","url_meta":{"origin":317887,"position":4},"title":"Creating Reusable Base Classes in TypeScript with a Real-Life Example","date":"April 18, 2019","format":false,"excerpt":"Hey CSS-Tricksters! Bryan Hughes was kind enough to take a concept from an existing post he published on converting to TypeScript and take it a few couple steps further in this post to elaborate on creating reusable base classes. While this post doesn\u2019t require reading the other one, it\u2019s certainly\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/04\/typscript-color-swatch.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":254325,"url":"https:\/\/css-tricks.com\/typescript-at-slack\/","url_meta":{"origin":317887,"position":5},"title":"TypeScript at Slack","date":"April 29, 2017","format":false,"excerpt":"An excellent subhead by Felix Rieseberg: How I Learned to Stop Worrying & Trust the Compiler. I'd wager that some of the popularity of SCSS was due to that fact that any valid CSS was valid SCSS, so you could baby step into SCSS on an existing codebase fairly easily.\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"featured_media_src_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/08\/typscript-hollow.png?fit=1024%2C512&ssl=1","_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/317887"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/38145"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=317887"}],"version-history":[{"count":10,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/317887\/revisions"}],"predecessor-version":[{"id":318881,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/317887\/revisions\/318881"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media\/318809"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=317887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=317887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=317887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}