Bouw een functieobject met eigenschappen schrijfmachine

stemmen
46

Ik wil een functie object, waar ook enkele eigenschappen hield op te creëren. Bijvoorbeeld in JavaScript zou ik doen:

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

Nu met de schrijfmachine kan ik het type van deze als te beschrijven:

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

Maar ik kan niet echt bouwen, zonder dat er een cast. Zoals:

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

Hoe zou je dit bouwen zonder een cast?

De vraag is gesteld op 07/10/2012 om 07:08
bron van user
In andere talen...                            


9 antwoorden

stemmen
73

Update: Dit antwoord heeft me de beste oplossing in eerdere versies van typoscript, maar er zijn betere opties beschikbaar in de nieuwste versies (zie andere antwoorden).

De geaccepteerde antwoord werkt en in sommige situaties kunnen worden verlangd, maar de keerzijde van het verstrekken van geen soort veiligheid voor de opbouw van het object. Deze techniek zal in ieder geval gooit een type fout als u probeert een ongedefinieerde eigenschap toe te voegen.

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

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

// type error
f.notDeclard = 3
antwoordde op 05/09/2013 om 16:12
bron van user

stemmen
34

Typescript is ontworpen om deze zaak door middel van behandelen verklaring samenvoegen :

kunt u ook bekend als JavaScript praktijk van het maken van een functie en de functie uitstrekkende verder door de toevoeging eigenschappen op het functioneren. Typescript gebruikt verklaring samenvoegen op te bouwen definities als dit in een type-veilige manier.

Verklaring samenvoeging laat ons zeggen dat er iets is zowel een functie en een namespace (intern module):

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

Zo blijven typen en laat ons beiden te schrijven f()en f.someValue. Bij het schrijven van een .d.tsbestand voor de bestaande JavaScript-code gebruiken declare:

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

eigenschappen toevoegen van de functies is vaak een verwarrende patroon of onverwachte schrijfmachine, zodat proberen te vermijden, maar het kan nodig zijn bij het gebruik of het omzetten oudere JS code. Dit is een van de weinige keren passend interne modules (namespaces) mengen met externe zouden zijn.

antwoordde op 28/10/2015 om 13:47
bron van user

stemmen
27

Dit is gemakkelijk haalbaar nu (typoscript 2.x) met Object.assign(target, source)

voorbeeld:

voer image beschrijving hier

De magie is hier dat Object.assign<T, U>(...)wordt getypt om terug te keren een kruising type T & U.

Handhaving van dat dit wordt omgezet in een bekende interface is ook straight-forward:

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

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

Fout: foo: Niet toewijsbaar aan foo: string
error: Niet toewijsbare string [] (terugkerend)

addertje onder het gras: je kan nodig zijn om polyfill Object.assign als targeting oudere browsers.

antwoordde op 25/01/2017 om 13:43
bron van user

stemmen
17

Dus als de eis is om gewoon te bouwen en die functie "f" te dragen zonder een cast, hier is een mogelijke oplossing:

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

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

In wezen, het maakt gebruik van een zelf uitvoeren functie letterlijke tot een object dat die handtekening zal overeenkomen voordat de opdracht is volbracht "te bouwen". De enige weirdness is dat de binnenste verklaring van de functie heeft van het type 'elke' te zijn, anders wordt de compiler schreeuwt dat je het toewijzen bent naar een woning die nog niet bestaat op het object.

EDIT: Vereenvoudigde de code een beetje.

antwoordde op 07/10/2012 om 17:33
bron van user

stemmen
1

Ik kan niet zeggen dat het is zeer eenvoudig, maar het is zeker mogelijk:

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

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

als je nieuwsgierig kreeg dit is van een kern van mijn met de typoscript / JavaScript-versie vanOptional

antwoordde op 07/02/2018 om 23:57
bron van user

stemmen
1

Een bijgewerkte antwoord: sinds de toevoeging van kruising types via &, is het mogelijk om "samen te voegen" twee afgeleide types on the fly.

Hier is een algemeen helper dat de eigenschappen van een object leest fromen kopieert ze over een object onto. Retourneert hetzelfde object ontomaar met een nieuw type dat beide eigenschappen bestaat, zo correct beschrijft de runtime-gedrag:

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

Deze low-level helper nog steeds het uitvoeren van een type bewering, maar het is het type-safe door het ontwerp. Met deze helper in de plaats hebben we een operator die we kunnen gebruiken om het probleem van de OP's op te lossen met volledige typeveiligheid:

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}`)
        }
    }
);

Klik hier om het uit te proberen in de typoscript Playground . Merk op dat we hebben beperkt foovan het type te zijn Foo, dus het resultaat van mergemoet een compleet zijn Foo. Dus als je een andere naam geven barom baddan krijg je een soort fout.

NB Er is nog steeds een soort gat hier, echter. Typescript voorziet niet in een manier om een parameter type beperken tot "geen functie" te zijn. Dus je zou kunnen raken in de war en geef uw functie als het tweede argument aan merge, en dat zou niet werken. Dus totdat dit kan worden verklaard, moeten we hem te vangen op runtime:

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;
}
antwoordde op 10/03/2016 om 19:14
bron van user

stemmen
1

Als een snelkoppeling, kunt u op dynamische wijze het object waarde toe met behulp van de [ 'eigendom'] accessor:

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

Dit omzeilt de typecontrole. Echter, het is redelijk veilig, want je moet opzettelijk toegang tot de woning op dezelfde manier:

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

Echter, als je echt wilt dat de controle van het type van de waarde van onroerend goed, dit zal niet werken.

antwoordde op 11/10/2012 om 05:38
bron van user

stemmen
0

Oude vraag, maar voor de versies van typoscript te beginnen met 3.1, kunt u gewoon doen de woning opdracht als u zou doen in plain JS, zo lang als u een functie aangifte of het gebruik constsleutelwoord voor de variabele:

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'"

Reference en online voorbeeld .

antwoordde op 26/11/2018 om 21:38
bron van user

stemmen
0

Deze vertrekt vanaf sterke typen, maar u kunt doen

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

als je probeert om rond onderdrukkende sterke typering krijgen zoals ik was toen ik deze vraag gevonden. Helaas is dit een zaak typoscript mislukt op volkomen geldig JavaScript dus je moet je Typescript vertellen om zich terug te trekken.

"Je JavaScript is volkomen geldig Typescript" false oplevert. (Let op: het gebruik van 0,95)

antwoordde op 21/02/2014 om 16:59
bron van user

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more