web-dev-qa-db-fra.com

TypeScript: 'super' doit être appelé avant de pouvoir accéder à 'this' dans le constructeur d'une classe dérivée

J'ai déjà vu cette question passer plusieurs fois auparavant, mais je pense que ma question concerne davantage une approche architecturale de cette approche.
Dans TypeScript, il n'est pas possible d'utiliser le mot clé this avant d'appeler super (sur une classe qui s'étend à partir d'une autre classe).
Mais que se passe-t-il si vous devez faire quelque chose comme dans l'exemple ci-dessous?
(Juste pour clarifier les choses: je crée un cycle de vie de composant pour une bibliothèque d’interface utilisateur, alors j’ai l’impression que j’ai vraiment besoin de faire quelque chose comme ça, et je n'arrive pas à penser à une autre façon de résoudre ce problème. ).

Code

Ce que j'aimerais faire est la suivante:

class Person 
{
    public firstName: string;

    constructor()
    {
        this.scream();
    }

    protected scream(): void
    {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        this.lastName = lastName;
        super(firstName);
    }

    protected scream(): void
    {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

Problème

Le constructeur de la classe parente, 'Person', appelle une méthode protégée.
La classe enfant 'Employee' souhaite utiliser son propre paramètre (this.lastName) lors du remplacement de cette méthode protégée.
Mais le code ci-dessus renvoie l'erreur (dans Webstorm au moins):
"" super "doit être appelé avant d'accéder à" this "dans le constructeur d'une classe dérivée"

Solution possible

A) Echangez this.lastName = lastName avec l'appel super

class Employee extends Person
{
    ...

    constructor(firstName: string, lastName: string)
    {
        super(firstName);
        this.lastName = lastName;
    }

    ...
}

=> Le problème ici est que this.lastName sera undefined dans la méthode scream() de la classe 'Employee'.

B)
Utilisez setTimeout(callback, 0). De cette façon, la méthode this.scream() sera appelée plus tard.

class Person 
{
    ...

    constructor()
    {
        setTimeout(() => this.scream(), 0);
    }

    ...
}

=> Mais ça me semble être un très mauvais bidouillage.

C)
N'appelez pas this.scream()de l'intérieur de la classe Person, mais appelez-le depuis le consommateur.

const employee: Employee = new Employee();
employee.scream();

=> Mais évidemment ce n’est pas toujours ce que vous voulez.

Question

  • Est-ce que je fais une bêtise ici?
  • Existe-t-il de meilleurs moyens d’arranger mon code afin que je n’aie pas besoin de le faire?
  • Existe-t-il un moyen de contourner cette erreur?
9
dotdotcommadot

Est-ce que je fais une bêtise ici?

Oui, vous l'êtes. Comme iberbeu l'a dit dans son commentaire, un constructeur ne devrait jamais rien faire qui n'ait à voir avec la construction l'objet. C'est un cas de mauvaise pratique pouvant conduire à toutes sortes de comportements inattendus.

Y at-il de meilleures façons d'arranger mon code afin que je n'ai pas besoin de le faire?

Utiliser la solution que vous avez fournie dans votre optionCest la solution.

Y a-t-il un moyen de contourner cette erreur?

Cela dépend de ce que vous voulez réellement faire. La manière normale de faire les choses est illustrée par vous-même dans votre optionC. Si le problème que vous rencontrez est lié à l’instanciation d’objets complexes, vous pouvez rechercher des modèles de constructeur/d’usine. Mais si vous voulez réellement que les constructeurs aient do quelque chose, vous le faites simplement mal; les constructeurs ne sont pas obligés d'effectuer des actions, ils sont là pour construire des objets et rien d'autre.

3
Nypan

Une autre solution que j'ai finalement trouvée, en plus de celles fournies par @iberbeu et @Nypan, consiste à ajouter une méthode intermédiaire initProps() juste avant l'appel de scream():

class Person 
{
    public firstName: string;

    constructor(firstName: string, props?: any)
    {
        this.firstName = firstName;
        this.initProps(props);
        this.scream();
    }

    protected initProps(props: any): void
    {
    }

    protected scream(): void
    {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        super(firstName, {lastName});
    }

    protected initProps(props: any): void
    {
        this.lastName = props.lastName;
    }

    protected scream(): void
    {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

Bien que je pense que les deux ont fait la force et que je devrais plutôt utiliser un modèle d'usine.

3
dotdotcommadot

Comme je l'ai écrit dans mon commentaire et @Nypan dans sa réponse, vous devriez éviter de faire cela . Quoi qu’il en soit, une possibilité pourrait être de remplacer la méthode scream dans Child et d’appeler une nouvelle méthode. Regardez le code suivant

class Person 
{
    public firstName: string;

    constructor() {
        this.scream();
    }

    protected scream(): void {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        super(firstName);
        this.lastName = lastName;
        this.screamOVerriden();
    }

    protected scream(): void {
        // do nothing
    }

    protected screamOverriden(): void {
        console.log(this.firstName + ' ' + this.lastName);
    }

}

Je ne recommande toujours pas de faire cela, mais si vous dites que vous en avez vraiment besoin et que vous ne vous souciez pas de le faire correctement, cela pourrait être une solution

1
iberbeu