New TypeScript features for enhanced usability

TypeScript, in many respects, is no longer more like a programming language, but a powerful tool for linting and documenting code that helps you write better JavaScript programs.

One of TypeScript's most notable strengths is its support for some of the latest features described in the ECMAScript specification. When a developer upgrades to a new version of TypeScript, it means that he has new JavaScript features. Moreover, the use of these features does not mean potential compatibility issues. TypeScript, in addition to introducing the latest JavaScript features, is also notable for the fact that the creators of the language constantly present to the TS-programmer community something new, designed to increase the usability. This includes, for example, auxiliary tools for code refactoring, tools for renaming entities and for finding places where they are used in programs.



The material, the translation of which we publish today, will discuss some interesting fresh features of TypeScript. For a complete list of TypeScript innovations, take a look here .

Immutable objects and arrays


In order to make immutable arrays used in the form of ordinary variables and function parameters during compilation, in TypeScript, you can use auxiliary types Readonlyand ReadonlyArray. However, their use can cause a feeling of heterogeneity in type annotation, especially when declaring arrays using characters []after specifying a type. TypeScript 3.4 introduces a new way to label parameters that are read-only arrays. Immediately there appeared a new way of declaring variables that should be immutable.

Improving usability with read-only parameters


When declaring the parameters of functions that you need to work with as read-only arrays, you can now use the keyword readonly. In the following example, the signatures of the two methods are identical:

function foo(s: ReadonlyArray<string>) { /* ... */ }
 
function foo(s: readonly string[]) { /* ... */ }

In both cases, any attempt to modify the array (for example, using its method push) will result in an error. This innovation eliminates the need for an auxiliary generic type, which means that code is easier to read. Object types can also be labeled as read-only entities, but they still need a helper type Readonly.

Improving the usability of immutable variables using the as const construct


The type of a variable declared using a keyword constcannot be changed. This concept exists in JavaScript. It is also adopted in TypeScript for the sake of organizing more stringent work with types. But when working with object data types, such as objects or arrays, it turns out that such structures are not truly immutable. Using a keyword constmeans that a specific instance of an object or array will remain unchanged when working with a constant, however the contents of this object or array can be easily changed. For example, without violating the rules for working with const entities, you can add new values โ€‹โ€‹to the array using the method push, you can change the values โ€‹โ€‹of the properties of objects.

Using ReadonlyandReadonlyArrayyou can tell TypeScript that the system should treat entities as if they were truly immutable. This means that every time an attempt is made in the code to change such an entity, an error message will be issued.

interface Person { 
  name: string; 
}
 
const person = { 
  name: 'Will' 
} as Readonly<Person>;
person.name = 'Diana'; // !

In TypeScript 3.4, among other innovations, the concept of const assertion (constant statement), which provides for the use of a construct, appeared as const. This is a simplified method for declaring constants containing immutable objects and arrays. Such declarations are built by adding as consta constant to the end of the declaration. This method has an additional advantage, which is that when you use it, you do not need to explicitly specify the type in the statement as const.

const person = { 
        name: 'Will' 
} as const;
 
person.name = 'Diana'; // !
 
//      as const
const array = [1, 2, 3] as const
array.push(4); // !

Helper type Omit


There are several helper types in TypeScript that make it easy to map existing types to new ones, or conditionally set a type based on other types.

The auxiliary type Partialallows marking all properties of the object as optional. Before the release of TypeScript 3.5, as it turned out, I constantly used one interesting mechanism in my projects. This is the same as the use of the auxiliary type now allows to achieve Omit. This type, as its name implies, allows you to exclude something from other types. Omitaccepts the type and combination of keys, and then returns a new type from which the properties described by the keys are excluded. Gone are the days when I had to use Pickit Excludefor the independent implementation of functionalityOmit.

//     TypeScript 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
 
interface A { 
    propA?: string; 
    propB?: string; 
    propC?: string; 
}
 
type B = Omit<A, 'propA' | 'propC'>; 
const b: B = { propA: 'hi' }; // ;

New JavaScript Features Supported by TypeScript


When suggestions for new JavaScript features reach 4 stages of coordination, they are considered to be part of the next version of the language. True, this does not mean that such capabilities can be immediately used in JavaScript, since their support should be implemented in appropriate environments. The application should have access to such opportunities wherever its normal operation is supposed.

Support for new JavaScript features is regularly added to the TypeScript compiler. Typically, code that implements these features can be converted to JavaScript code that is compatible with all browsers that support the project build goal specified in tsconfig.json.

โ– Check for null and undefined


JavaScript developers are familiar with the concept of truth and falsity. When checking for the truth can be identified 6 values, which are always false: 0, null, undefined, ยซยป, NaN, and, of course, false. Most often, the developer just needs to find out if the value is true or false, but in some cases you just need to find out if the value being investigated is a real value nullor undefined. For example, in the case if it is necessary to distinguish between code 0and undefined:

//  ||     ,  index  0
const getValueOrOne = (x?: number) => index || 1
getValueOrOne(0); // 1 <-- 

This code will work by setting xto the value written to index, in all cases except those when the value indexis equal 0. In order for this code to work correctly in any situation, it needs to be rewritten using a more complex test scheme to find out the real type of value.

//   ,    
const getValueOrOne = (x?: number) => index !== null && index !== undefined ? : 1
getValueOrOne(0); // 0

Now the code works, but it requires the use of more complex checks. The new operator for checking the value on nulland undefined(it looks like two question marks - ??) simplifies such checks by returning the value located in its left part, if it is not equal to nulland undefined. Otherwise, it returns what is on its right side.

// !
const getValueOrOne = (x?: number) => index ?? 1
getValueOrOne(0); // 0
getValueOrOne(2); // 2
getValueOrOne(); // 1

โ– Optional sequences


Another new JavaScript feature available in TypeScript 3.7 is the operator for organizing optional sequences ( ?.). I first met such an operator in the Groovy programming language. Since then, I wanted it to appear in JavaScript as well. This operator allows you to organize access to the embedded properties of objects without the need for constant checking of their existence. If, when accessing a property, this operator encounters a value undefined, it will simply return this value without throwing an error TypeError.

//    
const value = foo && foo.bar && foo.bar.baz;
 
//    
const value = foo?.bar?.baz;

The optional sequence operator combined with the operator of checking the values โ€‹โ€‹on nulland undefinedgives the developer even more possibilities, allowing, for example, to write to the variable either the value of some nested property of the object, or, if such a property does not exist, some standard value. Here's what it looks like:

const value = foo?.bar?.baz ?? 'default value';

โ– Private class fields


TypeScript, since the advent of this language, has its own concept of private class fields declared with an access modifier private. This concept appeared in TypeScript even before classes were described in the ECMAScript standard. But in TypeScript, this concept refers to the mechanisms that work during code compilation. The compiler will throw an error if the private field of the class is accessed not from the classโ€™s own methods. Now in JavaScript there is an opportunity to declare private properties and methods of a class. But this feature is both semantically and syntactically different from what still existed in TypeScript.

Private fields in JavaScript are not declared using an access modifier private. Instead, they are declared by putting a symbol at the beginning of their names #.

class Fan 
    #on = false
    private name = 'fan';
 
    turnOn() { 
        this.#on = true
    }
   isTurnedOn() { 
        return this.#on; 
    }
}
 
const fan = new Fan(); 
fan.isTurnedOn(); // false  
fan.turnOn(); 
fan.isTurnedOn(); // true
 
fan.on; //  
fan.#on; // 
fan.name; //   ,    JS

JavaScript now supports private fields, the proposal for private methods is in the third stage of approval. Currently, the modifier privateand the character #in the field name cannot be used together. Both approaches can come in handy during development, and which one to choose depends on the programmer. Here's a podcast that discusses the new syntax for declaring private fields.

โ–Using the await keyword at the top level of code


Asynchronous programming mechanisms greatly expand the capabilities of JavaScript and TypeScript. At first promises appeared in this area, then - a design async/awaitthat allows you to write cleaner asynchronous code.

One of the cases where promises are used, and not, async/awaitis an asynchronous method call outside the asynchronous function. For example, at the top level of the module or application code. As a workaround in this situation, you can propose creating an asynchronous immediately called function expression (IIFE, Immediately Invoked Function Expression) and executing asynchronous code inside such an expression.

(async () => { 
    const response = await fetch('https://api.github.com/users/sitepen'); 
    const data = await response.json(); 
    console.log(`Check out the blog at ${data.blog}`); 
})();

TypeScript now supports JavaScript's ability to use keywords awaitat the top level of code. This means that await can be used outside of the functions declared with the keyword async. This is very good at writing compact and clear code. True, the expressions awaitat the top level of the code are criticized for the fact that they can slow down the loading of modules and create a situation in which a certain module can slow down the loading of the entire application, since the system has to wait for the completion of the asynchronous operation, and then execute all the code of the module.

const response = await fetch('https://api.github.com/users/sitepen'); 
const data = await response.json();
 
export default { ...data };

Enhanced TypeScript experimentation environment


This cannot be called a new TypeScript feature, but given that we are talking about TypeScript as a tool, TypeScript Playground can be called an effective tool for quickly checking any TypeScript constructs and viewing the JavaScript code into which these constructs turn into. Most of the examples here are tested specifically in TypeScript Playground. Now this environment supports the ability to select a specific version of TypeScript (including support for beta versions). It includes several examples that will help beginners get started with TypeScript.

Summary


TypeScript is a tool that helps you write better and more expressive JavaScript code. TypeScript helper tools make it easy to solve complex tasks, such as refactoring and renaming entities, which are much more complicated in ordinary JavaScript. TypeScript is constantly introducing new mechanisms, such as Omitand as const. In the language, one can observe a continuous improvement in support of complex types. TypeScript quickly implements the latest JavaScript features. That is why many people choose TypeScript, perceiving it as a tool, language and ecosystem.

Dear readers! What new TypeScript features do you find most interesting?


All Articles