TypeScript 5.1 bêta

Par:
fredericmazue

ven, 21/04/2023 - 14:16

Microsoft a annoncé la disponibilité de TypeScript 5.1 en version beta. Cette mouture se fait principalement remarquer par les retours implicites pour les fonctions retournant undefined, et par les accesseurs sans types liés.

Retours implicites pour les fonctions retournant undefined

En JavaScript si une fonction se termine sans instruction return, elle renvoie la valeur undefined .

function foo() {
    // no return
}
// x = undefined

let x = foo();

Cependant, dans les versions précédentes de TypeScript, les seules fonctions qui ne pouvaient absolument pas avoir d'instructions de retour étaient les fonctions de type retour void ou any. Cela signifiait que même si vous disiez explicitement "cette fonction retourne undefined", vous étiez obligé d'avoir au moins une instruction de retour.

//  fine - we inferred that 'f1' returns 'void'
function f1() {
    // no returns
}

//  fine - 'void' doesn't need a return statement
function f2(): void {
    // no returns
}

//  fine - 'any' doesn't need a return statement
function
f3(): any {
    // no returns
}

//  error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
function f4(): undefined {
    // no returns
}

Ce qui pouvait être pénible si certaines API s'attendaient à ce qu'une fonction retourne undefined. Car cela impose d'écrire au moins un retour explicite de undefined ou une instruction et d'une annotation explicite.

declare function takesFunction(f: () => undefined): undefined;

//  error!
// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
takesFunction(() => {
    // no returns
});

//  error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
takesFunction((): undefined => {
    // no returns
});

//  error!
// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
takesFunction(() => {
    return;
});

//  works
takesFunction(() => {
    return undefined;
});

//  works
takesFunction((): undefined => {
    return;
});

Pour remédier à cela, tout d'abord TypeScript 5.1 autorise désormais fonction retournant undefiened  à ne pas avoir d'instruction return.

//  Works in TypeScript 5.1!
function f4(): undefined {
    // no returns
}

//  Works in TypeScript 5.1!
takesFunction((): undefined => {
    // no returns
});

Ensuite si une fonction n'a pas d'expression de retour et est transmise à quelque chose qui attend une fonction qui retourne undefined, TypeScript déduit undefined pour le type de retour de cette fonction.

//  Works in TypeScript 5.1!
takesFunction(function f() {
    //                 ^ return type is undefined
 
   // no returns
});

//  Works in TypeScript 5.1!
takesFunction(function f() {
    //                 ^ return type is undefined

    return;
});

Accesseurs sans types liés

TypeScript 4.3 permettait de dire qu'une paire d'accesseurs getet setpouvait spécifier deux types différents.

interface Serializer {
    set value(v: string | number | boolean);
    get value(): string;
}

declare let box: Serializer;

// Allows writing a 'boolean'
box.value = true;

// Comes out as a 'string'
console.log(box.value.toUpperCase());

Ceci avec l'exigence que le type de get soit un sous-type du type de set. Cela signifiait qu'écrire

box.value = box.value;

serait toujours valide.

Cependant, il existe de nombreuses API existantes qui ont des types totalement indépendants entre leurs getters et leurs setters.

TypeScript 5.1 autorise désormais des types complètement indépendants pour les accesseurs, à condition qu'ils aient des annotations de type explicites. Et tandis que cette version de TypeScript ne change pas encore les types pour ces interfaces intégrées, CSSStyleRule peut maintenant être définie de la manière suivante :

interface CSSStyleRule {
    // ...

    /** Always reads as a `CSSStyleDeclaration` */
    get style(): CSSStyleDeclaration;

    /** Can only write a `string` here. */
    set style(newValue: string);

    // ...
}

Cela permet également d'autres modèles comme exiger que les accesseurs set n'acceptent que des données "valides", tout en spécifiant que les accesseurs get peuvent retourner undefined si un état sous-jacent n'a pas encore été initialisé.

class SafeBox {
    #value: string | undefined;

    // Only accepts strings!
    set value(newValue: string) {
    }

    // Must check for 'undefined'!
    get value(): string | undefined {
        return this.#value;
    }
}