2016-12-06 3 views
0

C'est une question difficile qui, je le sais, est un problème pour beaucoup de programmes (j'aborderai cela à la fin). Je veux créer un setter personnalisé en tapuscrit, mais le type de données de la propriété en cours de définition n'est pas simplement un nombre, une chaîne, mais bien un objet de classe. Cela fonctionne bien - mais si une propriété de l'instance de classe est modifiée, le setter n'est pas appelé. Voici un exemple d'une telle situation:(Typescript) Setter n'est pas appelé sur une sous-propriété d'une instance de classe

//This class contains two properties 
class Point 
{ 
    public x : number; 
    public y : number; 

    constructor(x : number, y : number) { this.x = x; this.y = 0; } 
} 

//How here is another class that contains a Point 
//But it is private and a getter/setter is used 
class PointStore 
{ 
    private _foo : Point; 

    public get foo() : Point { return this._foo; } 

    //Here is the problem, the setter is only called when the whole of foo is changed 
    public set foo(n : Point) { this._foo = n; console.log("Foo has been set!"); } 

    constructor() { this._foo = new Point(0, 0); } 
} 

//Use case 
let bar : PointStore = new PointStore(); 

bar.foo = new Point(10, 10); //Logs "Foo has been set!" 
bar.foo.x = 20; //Doesn't log anything 

Le problème est assez clair de l'exemple, mais je veux simplement dire ceci:

Y at-il de toute façon autour du tout? Parce que je l'ai vu des API telles que Unity3D ils ont choisi de faire leur classe « Point » ont des membres que privés et pour que les données ne peut être réglé par le constructeur par exemple:

//the 'Unity' solution 
transform.position = new Vector2(10, 10); //Okay 
transform.position.x = 20; //Error 

Mais ce n'est pas du tout une solution parfaite au problème, car il rend la programmation avec la classe 'Point' beaucoup plus difficile à partir de ce moment.

Si quelqu'un a un truc pour résoudre ce problème, il serait grandement apprécié.

+0

vous attendez-vous le poseur de 'bar.foo' être invoqué lors de l'exécution de 'bar.foo.x'? –

+0

Je sais que cela n'arrivera pas, mais comment puis-je le faire invoquer le setter? @NitzanTomer –

+0

Non. Un setter est appelé uniquement lors de l'affectation d'une valeur à la propriété, mais ici vous utilisez le getter pour la propriété. Pourquoi est ce que tu veux faire ça? Quel est ton objectif? –

Répondre

0

Le setter ne sera utilisé que si vous affectez une valeur à la propriété. Une façon de contourner cela est en utilisant Object.assign, comme ceci:

bar.foo = new Point(10, 10); 
bar.foo = Object.assign(bar.foo, {x: 20}) 

Vous pouvez aussi aller plus loin:

bar.foo = Object.assign(bar.foo, {x: {z: 20} }) 
+0

Comment cela répond-il à la question OP? –

+0

Il lui permettra de changer les propriétés dans l'instance de classe, à n'importe quel niveau, tout en tirant le setter. – Shai

+0

La partie "aller plus loin" de la réponse est juste pour démontrer comment cela fonctionnera pour des objets plus complexes, pas pertinent spécifiquement pour le cas du PO. – Shai

1

Vous pouvez utiliser a Proxy pour que:

class PointStore { 
    private _foo: Point; 

    constructor() { 
     this.createProxy(new Point(0, 0)); 
    } 

    public get foo(): Point { return this._foo; } 

    public set foo(point: Point) { 
     this.createProxy(point); 
     console.log("Foo has been set!"); 
    } 

    private createProxy(point: Point) { 
     this._foo = new Proxy(point, { 
      set: function (target: Point, property: string, value: any) { 
       target[property] = value; 
       console.log("Foo has been set (using proxy)!"); 
       return true; 
      } 
     }); 
    } 
} 

(code in playground

+0

Haha, ne l'ai pas encore testé mais cela ressemble à la réponse que je cherchais. (Marquera comme réponse une fois que j'ai lu à ce sujet) –

+0

Hmmmm mon compilateur tsc (je compile à ECMA Script 2015 qui est censé soutenir un 'proxy') dit "ne peut pas trouver le nom proxy"? –

+0

Droite. 'Proxy' est une fonction es6, si vous ne ciblez pas es6, il manque dans le fichier de définition. –

0

La grande chose au sujet de tapuscrit est que, 2 types sont compatibles s'ils ont la même forme. Ainsi, en fonction de ce que vous avez décrit, je pense que cela semble correspondre au projet de loi et cela ne cassera pas votre code parce PointStore ici est compatible avec Point classe

class PointStore 
{ 
    private x : number; 
    private y : number; 

    constructor(x: number, y: number) { 
     this.x = x; 
     this.y = y; 
    }; 

    public get x() { return this.point.x; }; 

    public set x(x: number) { 
     // your custom logic here 
     this.point.x = x; 
    }; 

    // setter and getter for other y omitted 

} 
+0

Mais cela casserait la compatibilité avec le reste de mon code de base. Et je ne veux pas capturer l'événement à partir de la classe de point - il en a besoin d'une autre classe permet de dire 'PointStore' @Dummy –

+0

Alors pourquoi ne faites-vous pas quelque chose comme ma réponse éditée? Partout où vous avez besoin de ce traitement personnalisé lorsque la propriété est modifiée, utilisez simplement cette classe – Dummy

+0

Je veux dire que cela fonctionne, mais encore une fois, j'ai besoin de capturer l'événement à partir de la classe 'PointWrapper'. –