web-dev-qa-db-fra.com

Construire un objet fonction avec des propriétés dans TypeScript

Je souhaite créer un objet fonction contenant également certaines propriétés. Par exemple en JavaScript je ferais:

var f = function() { }
f.someValue = 3;

Maintenant, dans TypeScript, je peux décrire le type de ceci comme suit:

var f: { (): any; someValue: number; };

Cependant, je ne peux pas réellement le construire, sans avoir besoin d'un casting. Tel que:

var f: { (): any; someValue: number; } =
    <{ (): any; someValue: number; }>(
        function() { }
    );
f.someValue = 3;

Comment construiriez-vous cela sans casting?

56
JL235

Donc, si l'exigence est simplement de construire et d'affecter cette fonction à "f" sans transtypage, voici une solution possible:

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

Il utilise essentiellement un littéral de fonction à exécution automatique pour "construire" un objet qui correspondra à cette signature avant la fin de l'affectation. Le seul inconvénient est que la déclaration interne de la fonction doit être de type 'any', sinon le compilateur crie que vous affectez une propriété qui n'existe pas encore sur l'objet.

EDIT: Un peu simplifié le code.

18
nxn

Mise à jour: cette réponse était la meilleure solution dans les versions antérieures de TypeScript, mais de meilleures options sont disponibles dans les versions plus récentes (voir les autres réponses).

La réponse acceptée fonctionne et peut être requise dans certaines situations, mais présente l'inconvénient de ne fournir aucune sécurité de type pour la construction de l'objet. Cette technique générera au moins une erreur de type si vous essayez d’ajouter une propriété non définie.

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3
85
Greg Weber

Ceci est facilement réalisable maintenant (TypeScript 2.x) avec Object.assign(target, source)

exemple: 

 enter image description here

La magie ici est que Object.assign<T, U>(t: T, u: U) est tapé pour renvoyer le intersectionT & U

Faire en sorte que cela résolve une interface connue est également simple. Par exemple: 

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
); 

quelles erreurs sont dues à une saisie incompatible: 

Erreur: toto: numéro non assignable à toto: chaîne 
Erreur: numéro non assignable à la chaîne [] (type de retour)

mise en garde: vous devrez peut-être polyfill Object.assign si vous ciblez des navigateurs plus anciens. 

45
Meirion Hughes

TypeScript est conçu pour gérer ce cas via déclaration fusioning :

vous pouvez également être familiarisé avec la pratique JavaScript consistant à créer une fonction, puis à l'étendre davantage en ajoutant des propriétés à la fonction. TypeScript utilise la fusion de déclarations pour construire des définitions comme celle-ci d'une manière sûre pour le type. 

La fusion de déclarations nous permet de dire que quelque chose est à la fois une fonction et un espace de noms (module interne):

function f() { }
namespace f {
    export var someValue = 3;
}

Cela préserve la frappe et nous permet d’écrire à la fois f() et f.someValue. Lorsque vous écrivez un fichier .d.ts pour le code JavaScript existant, utilisez declare:

declare function f(): void;
declare namespace f {
    export var someValue: number;
}

L'ajout de propriétés à des fonctions est souvent un motif confus ou inattendu dans TypeScript. Essayez donc de l'éviter, mais cela peut être nécessaire lors de l'utilisation ou de la conversion de code JS plus ancien. C’est l’un des rares cas où il conviendrait de mélanger des modules internes (espaces de noms) avec des modules externes.

40
mk.

En tant que raccourci, vous pouvez affecter dynamiquement la valeur de l'objet à l'aide de l'accesseur ['property']:

var f = function() { }
f['someValue'] = 3;

Cela contourne la vérification de type. Cependant, il est assez sûr car vous devez accéder intentionnellement à la propriété de la même manière:

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

Toutefois, si vous souhaitez vraiment que le type vérifie la valeur de la propriété, cela ne fonctionnera pas.

2
Rick Love

Une réponse mise à jour: depuis l'ajout de types d'intersection via &, il est possible de "fusionner" deux types inférés à la volée.

Voici un assistant général qui lit les propriétés d'un objet from et les copie sur un objet onto. Il retourne le même objet onto mais avec un nouveau type qui inclut les deux jeux de propriétés, décrivant donc correctement le comportement à l'exécution:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

Cet assistant de bas niveau effectue toujours une assertion de type, mais il est de type sécurisé par nature. Avec cette aide en place, nous avons un opérateur que nous pouvons utiliser pour résoudre le problème du PO avec une sécurité de type complète:

interface Foo {
    (message: string): void;
    bar(count: number): void;
}

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), {
        bar(count: number) {
            console.log(`bar was passed ${count}`)
        }
    }
);

Cliquez ici pour l'essayer dans le TypeScript Playground . Notez que nous avons contraint foo à être de type Foo. Le résultat de merge doit donc être un Foo complet. Donc, si vous renommez bar en bad, vous obtenez une erreur de type.

NB Il y a toujours un type trou ici. TypeScript ne fournit pas un moyen de contraindre un paramètre de type à "pas une fonction". Donc, vous pourriez vous embrouiller et passer votre fonction comme second argument à merge, et cela ne fonctionnerait pas. Donc, jusqu'à ce que cela puisse être déclaré, nous devons l'attraper au moment de l'exécution:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    if (typeof from !== "object" || from instanceof Array) {
        throw new Error("merge: 'from' must be an ordinary object");
    }
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}
1
Daniel Earwicker

Je ne peux pas dire que c'est très simple mais c'est tout à fait possible:

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

si vous êtes curieux ceci provient d'un de mes Gist avec la version TypeScript/JavaScript de Optional

1
thiagoh

Ancienne question, mais pour les versions de TypeScript commençant par 3.1, vous pouvez simplement faire l'affectation de propriété comme vous le feriez dans JS simple, tant que vous utilisez une déclaration de fonction ou le mot clé const pour votre variable:

function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"

Référence et exemple en ligne .

0
Geo1088

Cela diffère du dactylographie fort, mais vous pouvez le faire

var f: any = function() { }
f.someValue = 3;

si vous essayez de contourner un typage fort oppressant comme je l'étais quand j'ai trouvé cette question. Malheureusement, il s'agit d'un cas où TypeScript échoue avec un code JavaScript parfaitement valide. Vous devez donc dire à TypeScript de revenir en arrière.

"Votre JavaScript est parfaitement valide TypeScript" est évalué à false. (Note: en utilisant 0.95)

0
WillSeitz