2017-05-11 1 views
1

Je suis en train de jouer avec des décorateurs de Typescript et ils semblent se comporter différemment de ce à quoi je m'attendais lorsqu'ils sont utilisés avec l'héritage de classe.Décorateurs dactylographiés avec héritage

Supposons que j'ai le code suivant:

class A { 
    @f() 
    propA; 
} 

class B extends A { 
    @f() 
    propB; 
} 

class C extends A { 
    @f() 
    propC; 
} 

function f() { 
    return (target, key) => { 
     if (!target.test) target.test = []; 
     target.test.push(key); 
    }; 
} 

let b = new B(); 
let c = new C(); 

console.log(b['test'], c['test']); 

qui sort:

[ 'propA', 'propB', 'propC' ] [ 'propA', 'propB', 'propC' ] 

Bien que je m'y attendais:

[ 'propA', 'propB' ] [ 'propA', 'propC' ] 

Ainsi, il semble que target.test est partagée entre A, B et C. Et ma compréhension de ce qui se passe ici est la suivante:

  1. Puisque B étend A, new B() déclenche l'instanciation d'une première, ce qui déclenche l'évaluation de f pour A. Comme target.test est défini, il est initialisé.
  2. f est ensuite évalué pour B, et comme il étend A, A est instancié en premier. Donc, à ce moment, target.test (target étant B) les références test définies pour A. Donc, nous poussons propB en elle. À ce stade, les choses se passent comme prévu.
  3. Identique à l'étape 2, mais pour C. Cette fois, lorsque C évalue le décorateur, je m'attendrais à ce qu'il ait un nouvel objet pour test, différent de celui défini pour B. Mais le journal me prouve mal.

Quelqu'un peut-il me expliquer pourquoi cela se passe (1) et comment pourrais-je mettre en œuvre f tel que A et B ont test séparées propriétés?

Je suppose que vous appelez cela un décorateur "spécifique à l'instance"?

Répondre

0

Très bien, donc après avoir passé quelques heures à jouer et à faire des recherches sur le web, j'ai eu une version de travail. Je ne comprends pas pourquoi cela fonctionne, alors s'il vous plaît pardonnez le manque d'explication. La clé est d'utiliser Object.getOwnPropertyDescriptor(target, 'test') == null au lieu de !target.test pour vérifier la présence de la propriété test.

Si vous utilisez:

function f() { 
    return (target, key) => { 
     if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = []; 
     target.test.push(key); 
    }; 
} 

la console affiche:

[ 'propB' ] [ 'propC' ] 

Ce qui est presque ce que je veux. Maintenant, le tableau est spécifique à chaque instance. Mais cela signifie que 'propA' est absent du tableau, puisqu'il est défini dans A. Nous devons donc accéder à la cible parente et obtenir la propriété à partir de là. Cela m'a pris un certain temps pour comprendre, mais vous pouvez l'obtenir avec Object.getPrototypeOf(target).

La solution finale est:

function f() { 
    return (target, key) => { 
     if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = []; 
     target.test.push(key); 

     /* 
     * Since target is now specific to, append properties defined in parent. 
     */ 
     let parentTarget = Object.getPrototypeOf(target); 
     let parentData = parentTarget.test; 
     if (parentData) { 
      parentData.forEach(val => { 
       if (target.test.find(v => v == val) == null) target.test.push(val); 
      }); 
     } 
    }; 
} 

qui délivre en sortie

[ 'propB', 'propA' ] [ 'propC', 'propA' ] 

quelqu'un mai qui comprend pourquoi cela fonctionne tout ce qui précède ne me éclaire pas.

1

Je pense que c'est parce que la classe B est créée que le prototype de A est copié avec toutes ses propriétés personnalisées (comme références).

J'ai utilisé une solution légèrement modifiée, les coutures pour résoudre un problème plus naturel avec les doublons si la classe C ne possède pas de décorateurs.

Toujours pas sûr si cela est la meilleure façon de traiter de tels cas:



    function foo(target, key) { 
     let 
      ctor = target.constructor; 

     if (!Object.getOwnPropertyDescriptor(ctor, "props")) { 
      if (ctor.props) 
       ctor.props = [...ctor.props]; 
      else 
       ctor.props = []; 
     } 

     ctor.props.push(key); 
    } 

    abstract class A { 
     @foo 
     propA = 0; 
    } 

    class B extends A { 
     @foo 
     propB = 0; 
    } 

    class C extends A { 
     @foo 
     propC = 0; 
    } 

0

@ user5365075 je suis tombé sur exactement le même problème lorsque vous travaillez avec des décorateurs de méthode, et votre solution a fonctionné.

Voici un décorateur je me sers dans un de mes paquets (avec un objet au lieu d'un tableau):

export function property(options) { 
    return (target, name) => { 
     // Note: This is a workaround due to a similar bug described here: 
     // https://stackoverflow.com/questions/43912168/typescript-decorators-with-inheritance 

     if (!Object.getOwnPropertyDescriptor(target, '_sqtMetadata')) { 
      target._sqtMetadata = {} 
     } 

     if (target._sqtMetadata.properties) { 
      target._sqtMetadata.properties[name] = options.type 
     } else { 
      target._sqtMetadata.properties = { [name]: options.type } 
     } 

     const parentTarget = Object.getPrototypeOf(target) 
     const parentData = parentTarget._sqtMetadata 

     if (parentData) { 
      if (parentData.properties) { 
       Object.keys(parentData.properties).forEach((key) => { 
        if (!target._sqtMetadata.properties[key]) { 
         target._sqtMetadata.properties[key] = parentData.properties[key] 
        } 
       }) 
      } 
     } 
    } 
} 

Je peux confirmer le même comportement existe pour les décorateurs de classe aussi bien.