Definitelytyped: React.d.ts en lecture seule<t>en état et accessoires</t>

Créé le 25 janv. 2017  ·  91Commentaires  ·  Source: DefinitelyTyped/DefinitelyTyped

Salut @ericanderson

J'ai beaucoup de problèmes avec ce changement lorsqu'il est utilisé dans la pratique:

Problème 1 : Aller à la définition

Lorsque vous appuyez sur Aller à la définition sur une propriété d'accessoires ou d'état, Typescript n'est pas en mesure de le résoudre.

interface MyComponentProps {
    name: string;
}

export abstract class MyComponent extends React.Component<MyComponentProps , void> {
    myMethood() {
       this.props.name; //<-- Go To definition in name
   }
}

image

Cela a du sens car le membre est généré synthétiquement, mais c'est quand même ennuyeux.

Problème 2 : Hiérarchies de composants (P générique avec contraintes)

Plus important, si vous créez un composant abstrait comme celui-ci :

interface MyBaseProps {
    onChange?: (val: any) => void;
}

export abstract class MyBase<P extends MyBaseProps> extends React.Component<P, void> {
    myMethood() {
        this.props.onChange!(2); //The type is S["onChange"] instead of (val: any) => void and so is not invocable. 
   }
}

TS est capable de montrer qu'il existe une propriété onChange, mais ne peut parfois pas découvrir son type.

image

C'est le changement le plus important car il m'empêche d'avoir des hiérarchies de composants qui partagent des accessoires et des fonctionnalités communs. Cela ressemble à un problème dans le compilateur TS, mais jusqu'à ce qu'il soit corrigé.

Problème 3 : Pas si en lecture seule.

Bien que je convienne que ce changement reflète bien l'intention fonctionnelle de React, il existe des situations valables où vous pouvez modifier l'état de manière impérative, comme dans le constructeur, et aussi si vous modifiez l'état et appelez forceUpdate tout fonctionne correctement.

C# this.state.name = "John"; this.forceUpdate(); //Ok as long as you don't setState afterwards, but calling setState also is annoying with the callback.

Est-ce recommandé ? Non.
Est-ce interdit ? Pas non plus, sinon forceUpdate n'existera pas.

Bien sûr, vous pouvez convertir l'état en S (ou any ) et effectuer le changement, mais s'il s'agit d'un modèle courant, cela devient fastidieux.

Conclusion : Est-ce que ça vaut le coup ?

Je suis triste que la nouvelle fonctionnalité brillante de TS crée plus de problèmes que de solutions dans ce cas, mais je pense honnêtement que c'est le cas ici.

D'un autre côté, le changement de setState est super 👍 , je ne savais pas pour Pick<S,K> .

Commentaire le plus utile

Le problème 3 est à débattre je suppose.

Vous avez raison de dire que vous pouvez _techniquement_ faire l'exemple ci-dessus dans React, mais je dirais certainement que ce n'est pas la façon dont React était censé être utilisé.

Celle-ci peut être décomposée en 3 cas distincts.

Initialisation générique

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  public state: State = {
    bar: 5,
  };
}

Initialisation basée sur les props

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      bar: props.baz,
    };

    // or
    this.setState({
      bar: props.baz,
    });
  }
}

Affectation aléatoire avec forceUpdate

Étant donné que je pense qu'il est préférable de pousser les gens vers la "bonne" chose, vous pouvez facilement contourner ce problème en redéclarant public state :

interface State {
  bar: number;
}

class Foo extends React.Component<{}, State> {
  public state: State;
  public myMethod() {
    this.state.bar = 5;
  }
}

Tous les 91 commentaires

Quelle version de tapuscrit votre studio visuel utilise-t-il ?

@vsaio pour sa

Pour le problème 1, avec TS 2.1.5 et le dernier VSCode, cela fonctionne bien pour moi. Je n'ai pas Windows/VS donc je ne peux pas vérifier là-bas, mais je parierais qu'il y a des mises à jour de vos plugins ou que vous n'êtes pas sur TS 2.1.5

Idem pour le problème 2

VS 2015 avec TS 2.1.5.0

Le problème 3 est à débattre je suppose.

Vous avez raison de dire que vous pouvez _techniquement_ faire l'exemple ci-dessus dans React, mais je dirais certainement que ce n'est pas la façon dont React était censé être utilisé.

Celle-ci peut être décomposée en 3 cas distincts.

Initialisation générique

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  public state: State = {
    bar: 5,
  };
}

Initialisation basée sur les props

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      bar: props.baz,
    };

    // or
    this.setState({
      bar: props.baz,
    });
  }
}

Affectation aléatoire avec forceUpdate

Étant donné que je pense qu'il est préférable de pousser les gens vers la "bonne" chose, vous pouvez facilement contourner ce problème en redéclarant public state :

interface State {
  bar: number;
}

class Foo extends React.Component<{}, State> {
  public state: State;
  public myMethod() {
    this.state.bar = 5;
  }
}

Mes problèmes sont avec la variance des génériques. spécifiquement pour le typage dans la classe qui est typée de manière générique. ci-dessous est un échantillon assez minimal de l'endroit où les choses se décomposent.

class TBaseState {
  public value: string;
}

function globalFunc<T extends Readonly<TBaseState>>(item: T) {
}

class MyComponent<TProps, TState extends TBaseState> extends React.Component<TProps, TState> {
  broken() {
    // typing of this.state is Readonly<TState>
    // this is not assignable to Readonly<TBase>
    globalFunc(this.state);

    // this is a horrible hack to fix the generics variance issue
    globalFunc(this.state as TState as Readonly<TBaseState>);
  }
}

class MyState extends TBaseState {
}

let component: MyComponent<any, MyState>;

// here the typing of component.state is Readonly<MyState>
// this is assignable to Readonly<TBase>
globalFunc(component.state);

je suis en TS 2.1.5.0

image

mais il se peut que dans VS nous ayons une pire expérience TS que dans le code VS ...

pour le problème 1, passez à la définition TS ne fonctionne pas non plus dans VS Code :

interface MyComponentProps {
    name: string;
}

export abstract class MyComponent extends React.Component<MyComponentProps , void> {
    fullName: string;
    myMethood() {
       this.props.name; //<-- doesnt work
       this.fullName; //<-- works
   }
}

pour le problème 2, il est vrai que VS Code se comporte mieux :

image

tandis que VS semble confus :

image

Je pense que pour VSCode et le problème 1, cela fonctionne parce que j'utilise le plugin pour la "Dernière grammaire Typescript et Javascript" qui doit avoir une gestion plus intelligente.

@patsissons c'est un exemple intéressant, même si je pense que c'est plus représentatif d'un bogue dans le tapuscrit que d'un bogue dans le fichier de définition. Par exemple, setState prenait S , ce qui signifiait faire des partiels, nous devions faire des trucs bizarres comme setState({foo:5} as any as State) ou utiliser celui qui prend une fonction. Je ne suis pas sûr que le manque d'expressivité du compilateur rende les typages "mauvais". Je pense que c'est un argument décent pour une modification du README pour marquer ce cas limite.

Avez-vous signalé un problème sur TS ?

Donc, ce changement de nos jours casse tous les VS et désactive Go To Definition dans tous les codes VS sauf si vous avez un plug-in ...

Il y a aussi l'argument de l'exhaustivité. Il y a des millions d'API qui sont censées être en lecture seule et qui ne le sont pas de nos jours, juste dans React.d.ts

 interface ComponentLifecycle<P, S> {
        componentWillMount?(): void;
        componentDidMount?(): void;
        componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
        shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: Readonly<any>): boolean;
        componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: Readonly<any>): void;
        componentDidUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>, prevContext: Readonly<any>): void;
        componentWillUnmount?(): void;
    }

Je pense que readonly devrait être utilisé pour 'freeze' ou 'Inmmutable.js' et non pour la longue queue de réflexions qui ne sont pas destinées à être modifiées, comme les objets d'événement, par exemple.

Je n'ai pas déposé, je viens de moderniser mon code aujourd'hui pour gérer les nouveaux types Readonly<T> , c'est un cas que j'ai rencontré et pour lequel je n'avais pas de solution correctement typée. Allez-y et déposez un problème, je serai occupé la majeure partie du reste de la journée aujourd'hui avec le code de correctif.

Ah oui, je savais que j'en manquais. @olmobrutall Si nous gardons les changements d'état que j'ai introduits pour marquer en lecture seule, je conviens que ces méthodes doivent être mises à jour. J'ai l'impression que nous avons besoin d'un consensus sur la bonne chose à faire en premier.

Quant aux pauses VS, je ne sais pas ce qui va bien. Les types doivent-ils être retenus parce que certains outils ne sont pas à jour ?

@patsissons Vous pouvez toujours fournir vos propres saisies pour réagir pour l'instant si vous voulez attendre de voir comment cela se passe avant de mettre à jour tout votre code. https://ericlanderson.com/using-custom-typescript-definitions-with-ts-2-x-3121db84015d#.ftlkojwnb

d'après notre expérience, VS est toujours un peu en retrait. Notre boutique utilise vscode pour effectuer tout développement actif de dactylographie et VS est davantage utilisé pour simplement corriger les fichiers de code ou pour que les développeurs non dactylographiés examinent le code, pas nécessairement pour le développement actif.

@ericanderson le hack n'est pas si mal pour l'instant, je dois juste vanner Readonly<T> pour obtenir mon T qui est assignable au Readonly<Base> .

On parle de 'react.d.ts', cette déclaration de membre unique est massivement utilisée. Je pense qu'il vaut la peine de se retenir jusqu'à ce que VS soit prêt.

De plus, comme 50% des types dans le monde sont censés être en lecture seule, comme les objets que vous obtenez des API, je ne pense pas que nous ayons besoin d'annoter cela.

Je pense que Readonly devrait être utilisé pour les objets qui ont été explicitement convertis pour avoir des propriétés get-only. Comme geler.

@olmobrutall Readonly est nouveau, donc la meilleure pratique exacte n'est pas vraiment définie. Personnellement, je préférerais que tout déclare qu'il faut Readonly<> de choses pour aider à signifier qu'il ne le fera pas muter. De même, React ne s'attend pas à ce que vous modifiiez state en dehors de setState et donc ce changement garantit que les accidents n'introduisent pas de bogues, ce qui est l'un des principaux avantages de l'utilisation de TypeScript sur javascript.

Si les performances étaient plus cohérentes entre les navigateurs pour Object.freeze , j'imagine que les gens de React commenceraient à geler après setState .

Quel est le but de forceUpdate alors?

Je suis curieux de connaître les réflexions des autres sur la façon dont DefinitelyTyped devrait fonctionner en ce qui concerne les mises à jour des outils ainsi que la philosophie de Readonly sur réagir (et d'autres bibliothèques dont l'intention est que vous ne modifiez pas certains objets).

cc/ @johnnyreilly @vsaio @pspeter3 pour des réflexions sur réagir spécifiquement et d'autres réflexions en général
cc/ @andy-ms @mhegazy pour des réflexions sur la façon dont DefinitelyTyped devrait procéder philosophiquement pour les mises à jour des outils et l'utilisation zélée de Readonly

@olmobrutall, nous utilisons forceUpdate pour mettre en file d'attente un rendu côté réaction, piloté par des événements observables côté état.

MISE À JOUR :
Je vais clarifier un peu notre scénario pour qu'il ne soit pas mal compris. Nos objets d'état sont des objets immuables de longue durée (donc Readonly<T> est en fait très approprié pour nous). Ces objets d'état contiennent plusieurs flux observables rxjs qui se dirigent vers une observable de notification appelée stateChanged . Les composants React surveillent cet observable pour les événements et canalisent ces événements dans un appel à forceUpdate (après l'anti-rebond). En effet, notre état mutable vit dans l'état, mais l'état lui-même et les membres qui existent dans l'état sont tous immuables. Ce n'est certainement pas le cas d'utilisation standard de React, mais c'est un flux très similaire. Nous avons simplement des objets d'état intelligents qui savent comment informer les composants lorsqu'un nouveau rendu est requis.

@ericanderson, le principal problème est que ces définitions de type souffrent de problèmes de SemVer. Étant donné que les versions de définition de type sont largement liées à leurs versions de module respectives, nous nous retrouvons avec une modification mineure de la version qui entraîne des changements de définition de type, ce qui signifie que nous devons épingler les versions @types dans notre package.json fichier.

@olmobrutall De la documentation de réagir :

Normalement, vous devriez essayer d'éviter toutes les utilisations de forceUpdate() et lire uniquement à partir de this.props et this.state dans render().

Le guide de réaction vous indique en fait de ne pas mettre à jour l'état directement : https://facebook.github.io/react/docs/state-and-lifecycle.html#do-not-modify-state-directly

forceUpdate , comme je l'ai lu, sert uniquement à forcer votre composant à se mettre à jour lorsque votre composant est basé sur des données _pas_ dans vos accessoires ou votre état.

@patsissons Je me trompe peut-être, mais je pense que SemVer est conçu pour être rétrocompatible avec les API et les intentions sémantiques. Ce n'est pas parce que vous utilisez une bibliothèque d'une manière non prévue (conformément à la documentation) que la bibliothèque doit continuer à prendre en charge lesdites utilisations non prévues. Les auteurs de la bibliothèque sont bien dans SemVer pour changer la sémantique qui était incorrecte mais qui s'est avérée être utilisée par certaines personnes.

Cela dit, peut-être Readonly<> à state est un changement trop important, mais supposons un instant que ce soit le bon changement. Quand devrait-il être publié dans DefinitelyTyped ? Votre code devra toujours être modifié une fois que vous aurez obtenu la mise à jour qui marquera finalement state comme Readonly<> .

Je ne sais toujours pas ce qui est juste à propos de l'application de Readonly<> à state , ce qui rend difficile le débat sur le semver, l'outillage ou quoi que ce soit d'autre. Mon instinct était que c'était juste. Les personnes qui ont examiné le changement ne l'ont jamais soulevé comme un problème. Cela semble conforme à l'intention de l'équipe React.

Je suis heureux de m'en remettre à l'un des critiques pour réagir dans DefinitelyTyped (je les ai tous mis en copie ci-dessus).

Donc, l'Observable change l'état et forcez-vous la mise à jour ? Donc, changer l'état de manière impérative est en quelque sorte autorisé.

Je pense que l'endroit où Readonly doit être utilisé n'est pas défini à 100%. Mais faut-il commencer par une propriété controversée et massivement utilisée avant que les outils ne soient prêts ?

Je suis tout à fait fortement typé, je maintiens 6 projets tous avec strictNullChecks, mais les avantages ici sont bien moindres que les problèmes qu'il produit actuellement.

@ericanderson Je pense que SemVer2 est conçu pour autoriser les déclarations de version de nœud telles que ^15.0.0 et s'attendre à ce que toutes les mises à niveau mineures ou correctives (c'est-à-dire 15.0.1 ou 15.1.0 ) du module soient transparentes ou au moins rétrocompatible d'un point de vue externe. Toute mise à niveau majeure ( 16.0.0 ) nécessiterait un ajustement de la déclaration de version pour apporter le changement. Cela empêche essentiellement les changements de rupture d'être introduits dans le système de frappe. Mais actuellement, les versions de définition de type ne peuvent pas s'écarter de leur version majeure de version de module respective (par convention), ce qui entraîne cette discontinuité.

La version courte est que les définitions de type peuvent avoir des changements de rupture introduits sans que le module lui-même ne change du tout, et les changements de rupture nécessitent un changement de version majeur.

Mais vous ne ferez pas de PR en supprimant forceUpdate, n'est-ce pas ?

Ensuite, si forceUpdate est là, le changement d'état doit impérativement être là aussi.

En théorie, vous devriez utiliser un graphe d'état profond et immuable, mais très souvent, changer impérativement l'état est très bien et tous les développeurs n'adhèrent pas à l'idée de structures de données persistantes.

Heureusement, React permet une voie de secours et rend possible des modifications directes de l'état, allons-nous interdire aux développeurs TS d'emprunter cette voie ? N'est-ce pas trop paternaliste ?

Vue.js par exemple favorise les changements impératifs, je ne serai pas surpris si cela influence React.

De plus, je lisais un article de blog d'un auteur de React il n'y a pas longtemps (je m'en souviens) encourageant l'utilisation de React sans toute la cérémonie Redux.

Sinon, ma position est de publier les définitions de types dès que possible, ma seule préoccupation est l'automatisation. étant donné l'état actuel de la définition de type, la gestion des versions d'une génération successive sans modification de la source peut échouer. Ces types d'échecs de CI non déterministes sont troublants. Personne ne veut voir sa construction se casser parce qu'il a poussé un changement et ensuite découvrir que sa main de changement n'a rien à voir avec la rupture de la construction.

Après avoir dormi dessus, mes opinions personnelles sont les suivantes :

  • Une bonne pratique consisterait à utiliser des verrous de fil ou de npm, de sorte que vous n'auriez pas de surprise à moins que vous n'ayez d'abord mis à niveau localement.
  • Rendre l'état readonly est la façon dont React est destiné à être utilisé. La documentation et les exemples le confirment.
  • Le flux de travail où vous ne souhaitez pas utiliser setState se trouve dans votre base de code et dans vos propres droits. Vous avez raison de dire que React fournit forceUpdate, mais son utilisation est destinée à provoquer un rendu lorsque vous êtes en dehors du cas d'utilisation prévu. Ainsi, si vous ne voulez pas utiliser l'état comme prévu, c'est bien, mais à ce stade, vous n'avez pas besoin d'utiliser la variable d'instance state. En fait, vous pouvez simplement utiliser des variables privées régulières.
  • Oui, ce projet dépend de beaucoup de gens, et pourtant jusqu'à présent, ce sont les deux seules plaintes qui me font penser que ce n'est pas un problème répandu. De plus, le problème soulevé à propos de la fonction globale peut simplement être réécrit pour prendre le générique différemment (voir le problème TypeScript lié)

Compte tenu des réflexions ci-dessus ainsi que des solutions de contournement pour les applications React non standard, je pense que Readonly est correct et que le seul changement nécessaire pour être complet est de mettre à jour les méthodes de cycle de vie en conséquence.

Je suis d'accord que ces changements ont tout à fait du sens, mon cas d'utilisation qui causait des problèmes était un cas de coin très unique qui devrait rarement être rencontré. En adaptant ma base de code à des accessoires et à un état en lecture seule, j'ai détecté des problèmes de frappe autrement indétectables. Il ne fait aucun doute que la publication des modifications était le bon choix.

Patsisson, utilisant VS, VS Code ou tout autre éditeur ?

Mon point est que si React favorise une approche fonctionnelle, il permet un flux de travail de type jeu vidéo où vous apportez impérativement des modifications au monde (état) puis effectuez un rendu (forceUpdate). Cette approche est maintenant interdite dans TS. Genre de fonc-damentalisme :)

Je réfléchirai à des alternatives pour rendre mon écosystème actuel viable alors...

Erreur de lancement du problème 2 uniquement avec strictNullChecks .

[TS] Cannot invoke an expression whose type lacks a call signature. Type '((val: any) => void) | undefined' has no compatible call signatures.

+1 J'utilise strictNullChecks

@ericanderson ?

Comme indiqué ci-dessus, cela est lié à l'outillage, qui est clairement en dehors du champ d'application de DT. Si vous êtes sur VSCode et installez l'aperçu du prochain analyseur TS, je n'ai pas vu ce problème avec strictNullChecks lorsque j'écrivais l'un des éléments ci-dessus.

Je n'ai pas de fenêtres, donc je ne peux pas parler à VS proprement dit.

ce patch Pick a cassé les suggestions sous VSCode. lorsque vous faites this.setState({ | }) (Ctrl + Espace), rien n'est affiché, même si l'état est clairement défini et en utilisant Partial<State> car setState peut définir l'état des membres de manière sélective

À mon humble avis, le code correct devrait être setState(state: Partial<S>, callback?: () => any): void;

Comme indiqué dans la branche pull request d'origine, nous avons commencé avec Partial. Cependant, si votre objet d'état est :

État de l'interface {
foo : chaîne ;
}

Ensuite, avec partial, vous pouvez faire ce qui suit :

setState({foo: undefined});

Ce qui est clairement faux.

Je suis désolé - mais encore une fois à propos de Readonly

React n'est pas seulement Redux et setState. React est également mobx et d'autres modèles observables, où l'attribution de propriétés d'état est la CARACTÉRISTIQUE PRINCIPALE . Le changement discuté tue complètement l'utilisation de mobx avec le tapuscrit.

Alors pourquoi ajouter le comportement qui n'existe pas dans le code de réaction d'origine au fichier .d.ts ? .d.ts doit être le reflet de la bibliothèque d'origine mais pas l'enseignement du bon style de codage, selon le point de vue de l'auteur !

@lezious , j'ai bien peur de ne pas comprendre votre position. Pouvez-vous fournir un exemple de code et ce qui est cassé dans les typages par rapport à l'échantillon ? Merci!

Aucun problème:

c'est ma classe d'état

class UserInfoBlockState  
{
    <strong i="7">@observable</strong>                  <- this is mobx way to declare state
    public updating: boolean;
    <strong i="8">@observable</strong> 
    public deleted: boolean;
}

et c'est mon composant

<strong i="12">@observer</strong>       <-- this is mobx way to make component react to state change
export class UserPanel extends React.Component<IUserInfoBlockProps, UserInfoBlockState>
{
   ......
     private updateUser()
    {
        this.state.updating = true;
        UsersAPI.update(this.props.user)
       .then(() =>
            {
                this.state.updating = false;      <--- this is the mobx way to work with the state
            }
        ).catch(() =>
            {
                this.showErrror("Server error");
                this.state.updating = false;
            });
    }
   ....

}

et maintenant nous (notre entreprise avec un énorme projet écrit sur react + mobx) avons mis à jour le DT et React au début du nouveau cercle de publication et ... 3000+ erreurs de compilation "la propriété est en lecture seule". Wow. Qu'est-ce que vous me suggérez de faire - réécrire tout le projet en redux, ne jamais mettre à jour react.d.ts ou toujours conserver et prendre en charge la version fourchue?

@mweststrate , veuillez vérifier ceci.

@Iezious J'apprécie votre position et je vous demande de vous calmer. J'essaie de travailler avec toi. Cela n'a rien à voir avec Redux, mais du pur React.

Je ne veux pas que DT bloque un cas d'utilisation qui a fonctionné auparavant, mais je ne pense pas que la façon dont vous décrivez l'utilisation de mobx avec réagir soit cohérente avec la documentation de réaction (ni même la documentation de mobx maintenant que j'ai lu sur ce).

React indique clairement dans la documentation qu'il existe 3 façons d'utiliser correctement l'état, et la toute première est "Ne modifiez pas l'état directement".

Cela m'amène à croire que la façon dont votre base de code fonctionne actuellement risque fort de se casser dans les futures versions de react. En parcourant https://github.com/mobxjs/mobx-react , je ne vois aucune suggestion que vous utilisiez l'état de cette manière. En fait, il semble qu'ils veulent que vous utilisiez des propriétés.

En examinant https://mobx.js.org/getting-started.html et en recherchant "mobx react state", je ne trouve aucune documentation suggérant que vous utilisiez mobx comme vous le faites.

DT est censé transmettre au mieux l'esprit de la bibliothèque sous-jacente et au pire l'implémentation réelle et il est clair que l'achat de réagir et d'étendre le composant signifie respecter le contrat implicite.

Je ne suis pas sûr de ce que je vous suggère de faire. Quelques options que je peux penser à la main:

  1. Une option "bon marché", si vous insistez pour prendre en charge la variable state est de rechercher et de remplacer React.Component par MyComponent et de définir MyComponent comme une sous-classe de React.Component sans les contraintes de lecture seule.
  2. Un autre, basé sur les exemples idiomatiques publiés dans la documentation de mobx, consiste à prendre le temps d'arrêter d'utiliser this.state et d'utiliser simplement des variables sur le React.Component réel. Cela peut être un peu pénible, mais au moins les nouvelles personnes de votre projet pourront voir les modèles dans votre base de code tels qu'ils sont décrits en ligne.
  3. Vous pouvez redéclarer state dans chaque composant si vous voulez continuer à le faire comme vous le faites.
  4. Vous pouvez rechercher et remplacer this.state par this.somethingElse et déclarer manuellement.
  5. Vous pouvez arrêter de prendre des mises à jour pour réagir de DT (et éventuellement à l'avenir de réagir en général en fonction de la façon dont les modifications futures pourraient affecter votre cas d'utilisation).

Si c'était mon projet, je ferais probablement le numéro 2, même si je n'en sais pas assez sur mobx pour en être sûr.

Désolé, je n'ai pas pu être d'accord avec vous (cela ne veut pas dire que les autres ne seront pas d'accord avec vous). J'ai essayé de trouver une raison de revenir sur cette partie, mais je n'arrive pas à monter à bord pour le moment.

Je mentionnerai à nouveau notre stratégie, qui est une application personnalisée des observables RxJs pour piloter les changements d'état React et le rendu qui ressemble étroitement au modèle mobx. Nous utilisons des actions pour recevoir les entrées de la couche de vue (React). Les actions sont synonymes d'une fonction qui consomme des entrées et produit un observable, qui piloterait ensuite d'autres observables d'état. Ce modèle permet à l'état de rester immuable du point de vue de la couche React, car vous n'interrogez que des valeurs observables et exécutez des actions d'état. En interne, votre état peut "muter" lui-même à la suite des actions, car l'état interne n'a pas de contraintes de lecture seule.

Je ne peux pas me calmer. J'ai le projet en production et j'ai besoin de passer énormément de temps d'équipes, et cela signifie de l'argent depuis ce changement.

Et quelle est la raison ? Ce changement reflète-t-il la réalité en réaction ? Non. Il ajoute les restrictions et les comportements qui n'existent pas dans React, la restriction qui est ajoutée d'une manière ou d'une autre simplement parce qu'il pense que c'est juste.

Quel est l'objectif du projet DT ? Décrire les bibliothèques JS aussi précisément que possible, ou décrire notre vision de la bonne utilisation de ces bibliothèques ? Selon le nom "Definitely Typed", c'est le premier. Donc, si cette restriction n'existe pas dans la bibliothèque JS d'origine, elle NE DOIT PAS non plus exister dans DT. C'est mon propos. Où est-ce que je me trompe sur ce point ?

Je comprends que vous soyez frustré, cependant, c'est la façon dont la réaction était censée être utilisée en lisant les docs. Je vois mal comment on devrait rendre DT moins spécifique car votre équipe a abusé de la bibliothèque et violé le contrat implicite.

Montrez-moi une once de documentation publiée par l'équipe de réaction qui suggère que l'état devrait être directement modifiable et je modifierai immédiatement le code.

https://facebook.github.io/react/docs/react-component.html#state

Ne modifiez jamais this.state directement, car appeler setState() par la suite peut remplacer la mutation que vous avez effectuée. Traitez cet état comme s'il était immuable.

Il semble assez clair que react considère que this.state est immuable. React ne considère pas les _properties_ de this.state immuables (ce qui est l'hypothèse redux). Vous êtes libre de faire :

this.state.user.name = "foo";

en réaction idiomatique.

Ma préférence va à la saisie précise de l'API (ce qui signifie dans ce cas Readonly ) et à l'expression de tous les invariants indiqués par l'équipe de réaction.

@ericanderson désolé, je viens seulement de le remarquer. FWIW Je pense que le changement est raisonnable et que l'outillage va rattraper son retard. Au fait, avez-vous entendu dire qu'ils envisageaient de déprécier la surcharge setState qui prend un objet ? L'avenir est le style de réduction setState par tous les comptes.

@amoreland Pas d' accord. Par : https://facebook.github.io/react/docs/state-and-lifecycle.html#do -not-modify-state-directly

Ne pas modifier l'état directement

Par exemple, cela ne restituera pas un composant :

// Wrong
this.state.comment = 'Hello';

À la place, utilisez setState() :

// Correct
this.setState({comment: 'Hello'});

Le seul endroit où vous pouvez assigner this.state est le constructeur.

@johnnyreilly je n'avais pas. C'est intéressant. La source?

Il a été couvert dans l'une des discussions de la récente conférence de réaction. Il est disponible sur YouTube. C'était peut-être le discours de Lin Clark. L'une des premières discussions du jour 1 - couvrant les changements à venir pour réagir à la v16. Fibre etc.

Désolé @gaearon je voulais dire

La raison pour laquelle mobx effectue des changements d'état, c'est parce que les observables pilotent les mises à jour de React, au lieu de _remplacer_ complètement l'état, l'état devient un moteur qui pilote le rendu. donc en faisant quelque chose comme this.state.updating = true; vous faites en fait l'équivalent d'un remplacement d'état, mais à la place l'état est assez intelligent pour déclencher un nouveau rendu en notifiant au composant que l'état est modifié par rapport à son contenu précédent. Je suis d'accord qu'il ne s'agit pas d'une utilisation _conventionnelle_ de React, mais plutôt d'une utilisation beaucoup plus complète et de niveau supérieur de React. Je dirais que l'utilisation conventionnelle de React n'est utile que pour les petits projets avec une poignée de composants. Lorsque vous vous retrouvez avec des centaines de composants, chacun avec plusieurs dizaines de pilotes de mutation réactifs, vous ne pouvez plus utiliser de manière fiable les modifications d'état React conventionnelles (c'est-à-dire, setState) et devez être en mesure de prendre en charge les modifications architecturales qui impliquent l'état _smart_ (ce que mobx fait essentiellement ).

Cela dit, je comprends pourquoi il est contrarié, car les changements de typage affectent une utilisation plus avancée du système React. Les affectations d'état observables ne sont pas techniquement un état _mutant_, mais invoquent plutôt des événements observables pour la valeur RHS. Il se trouve que la syntaxe que mobx a choisie pour émettre ces événements observables entre en conflit avec l'intention expresse de l'état immuable React.

@Iezious si vous avez besoin d'une solution rapide pour ce type de problème, vous pouvez vous en sortir en augmentant les typages React et en refactorisant vos composants pour utiliser une extension des définitions de type React.Component .

import * as React from 'react';
declare module 'react' {
  class MutableStateComponent<P, S> extends React.Component<P, S> {
    state: S;
  }
}
(React as any).MutableStateComponent = React.Component;

et maintenant vous pouvez simplement créer des composants d'état modifiables comme des composants normaux, sauf que leur membre state n'est plus marqué comme readonly .

export class MyComponent extends React.MutableStateComponent<MyProps, MyState> {
  // this.state.name is writable
}

@patsissons oui, c'est exactement la raison.

Je ne suis pas en train de muter l'état, j'utilise des observables mobx, qui appellent setState pour moi, je le fais car mon code de projet ÉNORME semble beaucoup plus clair et compréhensible.

Je sais que je peux faire mon composant, je peux aussi dans mon serveur npm faire un script qui annulera toujours ce changement pour notre entreprise. Je peux utiliser un hack comme "this.state.state.updated" et beaucoup d'autres hacks.

Je veux juste dire que ce changement affecte l'utilisation de modèles observables comme mobx, qui suivent en réalité la manière de réagir, mais maintenant, puisque ce changement ne peut pas être compilé et nécessite des hacks et des solutions de contournement pour fonctionner. Et c'est pourquoi je pense que ce changement n'est pas juste.

peut-être qu'au lieu de ma suggestion MutableStateComponent nous l'appelons plutôt ObservableComponent qui est plus aligné avec le modèle Observable React.

Si vous supprimez Readonly , les personnes qui utilisent les types de réaction avec une réaction régulière (et/ou un certain nombre d'autres systèmes de gestion d'état en dehors de mobx) sont exposées à des erreurs. Je n'utilise pas mobx dans mon énorme projet et j'apprécie les erreurs du compilateur lorsque quelqu'un fait une faute de frappe et utilise accidentellement this.state.foo = bar .

S'il existe un compromis inévitable entre l'utilisation de réaction standard et non standard, les types de réaction standard devraient pencher vers le premier.

De plus, si vous utilisez mobx de manière idiomatique, ce n'est pas un problème.

Si vous supprimez Readonly, les personnes qui utilisent les types de réaction avec une réaction régulière (et/ou un certain nombre d'autres systèmes de gestion d'état en dehors de mobx) sont exposées à des erreurs. Je n'utilise pas mobx dans mon énorme projet et j'apprécie les erreurs du compilateur lorsque quelqu'un fait une faute de frappe et utilise accidentellement this.state.foo = bar.

Donc encore une fois - vous ENSEIGNEZ À ÉCRIRE LE CODE

Ce projet ne consiste pas à apprendre à écrire du code, ce projet consiste à décrire les fonctionnalités existantes des bibliothèques JS. La limitation discutée n'existe pas dans la bibliothèque d'origine et doit être supprimée.

C'est tout.

@patsissons

si vous avez besoin d'une solution rapide pour ce type de problème, vous pouvez vous en sortir en augmentant les typages React et en refactorisant vos composants pour utiliser une extension des définitions de type React.Component.

ce n'est pas correct. Dans le monde de l'entreprise, d'où je viens, il n'y a pas de "solutions miracles". Lorsque quelque chose change dans le SDK, il DOIT être compatible avec les versions antérieures, ou cela prend des années. Il n'y a pas de solution rapide dans le projet de plus de 2 millions de lignes de code. Ce sont des semaines de changements, de tests, de tests de prod A/B, de déploiements pour un grand nombre de personnes. Cela coûte de l'argent réel. Et tout cet énorme effort juste à cause de quelqu'un qui a ajouté un changement non rétrocompatible qui N'EXISTE PAS DANS LA VRAIE BIBLIOTHÈQUE ?

Pourquoi pensez-vous que forceUpdate existe toujours dans la réaction ? Ils parlent sur les confs du bon style, mais font les changements pour empêcher les autres modes d'utilisation. Pourquoi? Comme c'est une grande entreprise et qu'ils savent que les bibliothèques doivent être rétrocompatibles.

@ericanderson a écrit ça

this.state.state.value = 1 

n'est pas non plus légitime de son point de vue. La prochaine fois, il obtiendra l'outil auprès de TS et ajoutera la limitation qui empêchera ce code. Ou faites de la classe finale du composant, ou autre chose simplement parce que "c'est le bon style et empêche les gens de faire des erreurs".

Empêcher les gens de mitakes - c'est la tâche de FB, s'ils veulent le faire, ils peuvent facilement ajouter un proxy et interdire la mutation d'état. Le but de DT est d'ajouter des descriptions, et rien d'autre.

@Iezious

Je pense que le fait est que l'équipe React ne peut pas rendre l'état immuable avec JavaScript, mais s'ils le pouvaient, ils l'auraient fait. TypeScript, d'autre part, peut rendre l'état immuable, c'est pourquoi cette modification a été apportée aux définitions de type. C'était absolument l'intention de l'équipe React d'interdire directement les modifications aux membres de l'état (comme en témoignent leurs documents officiels sur l'utilisation correcte de state ), mais ils n'avaient pas les constructions de langage pour imposer cette contrainte. Cette contrainte n'a jamais été une inconnue, elle est bien documentée depuis le début. Pour ceux d'entre nous qui opèrent en dehors de l'utilisation _conventionnelle_ de React, nous devons au moins adhérer aux recommandations d'utilisation officielles de React. Pour mon équipe, cela signifiait concevoir une solution qui nous permettait de piloter des changements d'état sans muter directement l'état. Cela a été spécifiquement fait pour s'assurer que nous n'allions pas rencontrer de problèmes tels que celui-ci (même si ce changement nous a légèrement affectés, mais uniquement via les signatures de fonction et non la conception fondamentale).

Si la refactorisation n'est pas possible dans votre situation, épinglez @types/react à 15.0.1 avant que la modification ne soit effectuée, ou n'utilisez simplement pas @types/react et conservez à la place votre propre variante du tapez defs et modifiez simplement la saisie state pour Component . Je ne pense vraiment pas que vous réussirez à convaincre qui que ce soit d'annuler le changement.

forceUpdate existe car il est documenté comme le chemin de code recommandé pour piloter le rendu lorsque l'état structurel interne est modifié (ou lorsque render() utilise des données en dehors de l'état qui est modifiable). L'utilisation de forceUpdate est conçue exactement pour le modèle d'utilisation que mon équipe utilise avec React.

L'équipe React PEUT rendre l'état immuable avec JS, c'est facile. Mais pas rétrocompatible.

Encore une fois, c'est :

this.state.state.value = 1 

légitime ou pas ?

Je pense que oui, mais mon avis est déjà clair...

Je ne pense pas que la conversation sur la mutabilité/immuabilité soit _encore_ pertinente. De toute évidence, le bogue dans le compilateur TS (concernant Readonly ) combiné à ce changement rend les composants génériques complètement impossibles à utiliser, cassant beaucoup de code existant. C'est sûrement un cas d'utilisation valide universellement accepté et c'est une raison suffisante pour revenir en arrière pour l'instant ???

interface test1 {
    num: number;
}

function add<T extends test1>(initial: T, add: number) {
    var copy: Readonly<T> = initial;

    //ERROR HERE: [ts] Operator '+' cannot be applied to types 'T["num"]' and 'number'.
    return copy.num + add;
}

Est-ce que quelqu'un sait s'il y a un problème ouvert avec l'équipe Typescript à ce sujet ? Je n'arrive pas à trouver le problème pertinent sur leur tracker.

@caesay Voir Microsoft/TypeScript#15501

je dois init state dans le constructeur, mais tslint affiche "state is readonly"...

constructor(props) {
  super(props);
  this.state = {
     value: props.defaultValue,
  };
}

comment puis-je y remédier.............

Utiliser setState

setState ne fonctionne pas dans le constructeur

Ou considérez componentWillMount w/setState

Merci

Bonjour,

J'ai lu tout le fil mais je ne sais pas s'il est prévu de gérer le scénario @ alanwei0 ?

Je suis entièrement d'accord qu'il est logique d'avoir state comme ReadOnly . Cela étant dit, ne pas pouvoir définir l'état initial dans le constructeur complique un peu les choses car render est appelé avant que componentDidMount ne soit terminé.

@pawelpabich utiliser this.state = { dans le constructeur n'est pas un problème. Vous pouvez affecter des variables readonly dans le constructeur, et Readonly<T> n'empêche pas l'affectation (jamais !).

interface TInterface {
    test: string;
}

class TClass {
    readonly blah: Readonly<TInterface>;
    constructor() {
        this.blah = { test: "constructor" };
    }

    fn = () => {
        this.blah = { test: "fn" };
    }
}

La seule erreur ici sera à l'intérieur fn - pas à cause de Readonly<T> , mais à cause du mot-clé readonly . En fait, la dernière version des typages n'utilise même pas le mot-clé readonly , vous pouvez donc affecter l'état n'importe où, mais ne pas modifier les propriétés à l'intérieur de l'état.

Le problème dont nous parlions ici était un bogue avec le compilateur de typescript qui faisait que les propriétés d'état perdaient leurs types dans les composants hérités. Cela a été corrigé depuis, je crois, et si c'est le cas, ce problème peut être clos.

@caesay désolé, je pensais que c'était le cas dont nous parlons ici. Mon problème est que je ne peux pas définir l'état dans la classe de base. Je suis sur TS 2.4.1. Auriez-vous par hasard l'identifiant du problème pour que je puisse vérifier cela ?

Vous pouvez toujours appeler setState (dans componentWillMount).

@pawelpabich Encore une fois, ce n'est pas un problème :) vous ne pouvez pas attribuer exprès l'état de la classe de base. Comment peux-tu? vous ne connaissez pas le contrat prop utilisé par le composant dérivé.

interface BaseCompState {
    baseState1?: string;
}

class BaseComp<TState extends BaseCompState> extends React.Component<any, TState> {
    constructor(props) {
        super(props);
        this.state = {
            baseState1: "fromBase",
        };
    }
}

interface TopCompState extends BaseCompState {
    topState1?: string;
}

class TopComp extends BaseComp<TopCompState> {
    constructor(props) {
        super(props);
        this.state = {
            baseState1: "fromTop",
            topState1: "fromTop",
        };
    }
}

C'est un exemple simple d'un composant dérivé (accessoires omis, mais même idée). le this.state = dans la classe de base ne peut évidemment pas fonctionner, car il ne sait pas ce qu'est TState . de plus, si cela _fonctionnait_, il écraserait simplement l'état défini par le parent. L'état final serait { baseState1: "fronBase" } . Qu'est-il arrivé à la propriété topState ?

Si vous êtes absolument convaincu que vous devez gérer l'état dans votre composant de base, vous pouvez transmettre l'état du composant dérivé au constructeur du composant de base (comme TState pour que vous puissiez l'assigner) - cela pourrait ressembler à quelque chose comme ce:

interface BaseCompState {
    baseState1?: string;
}

class BaseComp<TState extends BaseCompState> extends React.Component<any, TState> {
    constructor(props, state: TState) {
        super(props);
        this.state = Object.assign({
            baseState1: "fromTop",
        }, state);
    }
}

interface TopCompState extends BaseCompState {
    topState1?: string;
}

class TopComp extends BaseComp<TopCompState> {
    constructor(props) {
        super(props, {
            topState1: "fromTop",
        });
    }
}

Ou encore plus simple, vous pouvez appeler this.setState( depuis votre composant de base (oui, vous pouvez le faire dans le constructeur !)

Il n'y a pas de problème ici.

@caesay Je suis entièrement d'accord que s'il n'y a pas de contraintes, l'affectation n'a pas de sens. Mais le code suivant génère toujours des erreurs de compilation bien que le compilateur dispose de toutes les informations requises pour vérifier que le code est correct.

import * as React from "react";

/* tslint:disable:max-classes-per-file*/

interface BaseProps {
    baseProp: string;
}

interface BaseState {
    baseState: string;
}

class Base<TProps extends BaseProps, TState extends BaseState> extends React.Component<TProps, TState> {
    constructor(props) {
        super(props);

        this.state = {
            baseState: props.baseProp
        };
    }

    render() {
        return <div>{this.state.baseState}</div>;
    }
}

interface DerivedProps extends BaseProps {
    derivedProp: string;
}

interface DerivedState extends BaseState {
    derivedState: string;
}

export class Derived extends Base<DerivedProps, DerivedState> {
    constructor(props) {
        super(props);

        this.state = {
            derivedState: props.derivedProp
        };
    }

    render() {
        return <div>{this.state.derivedState}</div>;
    }
}

les erreurs

webpack: Compiled successfully.
ERROR at Test.tsx(17,9):
TS2322: Type '{ baseState: any; }' is not assignable to type 'Readonly<TState>'.

ERROR at Test.tsx(39,9):
TS2322: Type '{ derivedState: any; }' is not assignable to type 'Readonly<DerivedState>'.
  Property 'baseState' is missing in type '{ derivedState: any; }'.

Version: typescript 2.4.1

Première. Vos accessoires en base dans le constructeur ne sont pas typés. Ainsi props.baseProp est any , qui n'est pas assignable !

Deuxièmement, vos props dans Derived ont le même problème ET il vous manque baseState . Ce qui bien sûr ne fonctionnera pas, indépendamment de Readonly

TProps extends BaseProps ce qui signifie que TProps a au moins les mêmes membres que BaseProps . Alors, comment cela n'est-il pas défini? Je comprends que le compilateur ne soit peut-être pas en mesure de le déduire, mais dire que ce n'est pas défini ne semble pas être correct. La même réflexion peut être appliquée à Derived .

@caesay setState dans le constructeur n'est pas une solution fiable car cette méthode est asynchrone et vous pouvez toujours accéder à render sans définir l'état initial.

La seule solution fiable que je puisse voir est de définir l'état entier dans les classes dérivées. Cela a un inconvénient évident que :

  1. Cela doit être dupliqué dans chaque classe dérivée
  2. Les classes dérivées doivent connaître l'état dont elles ne se soucient pas.

L'exemple que j'ai montré ci-dessus fonctionnerait en C # donc ce serait bien s'il pouvait fonctionner en TypeScript.

  1. ~setState est sûr dans le constructeur~
  2. Dans le cas d'une classe dérivée, le meilleur cas que je puisse voir est d'appeler setState dans votre componentWillMount . Votre base ne sait pas ce qui devrait être dans l'état de son enfant, elle ne peut donc pas obtenir this.state dans une configuration sûre. Cependant, votre sous-classe peut appeler le componentWillMount du parent, puis définir l'état dont elle pense avoir également besoin.
  3. Il existe des fonctionnalités linguistiques dans de nombreuses langues qu'il serait bon d'avoir en tapuscrit. Certains sont réalisables. D'autres ne le sont pas. Quoi qu'il en soit, ce n'est pas un argument en faveur de leur inclusion.
  4. Les erreurs que vous voyez ont un sens. Ils ne suggèrent pas de bogue dans la dactylographie ni les frappes. Dans chaque cas, vous essayez littéralement d'attribuer this.state avec un objet qui ne correspond pas au type attendu.

ÉDITÉ, pour refléter que j'étais incorrect à propos de setState dans le constructeur et pour ajouter des backticks pour la lisibilité.

@ericanderson Je ne pense pas que nous fassions des progrès ici. Je vous ai montré un exemple et j'apprécierais que vous nous concentriez dessus. Sinon, il est difficile d'avoir une discussion.

Re setState il n'est pas sûr à utiliser dans le constructeur. Il ne génère pas d'erreur mais il ne définit pas l'état avant que le rendu ne se produise. J'ai du code qui se brise à cause de cela et les documents React sont très clairs sur le fait qu'il ne doit pas être utilisé là-bas.

@pawelpabich Non, ce n'est pas l'endroit pour avoir cet argument. Vous avez fondamentalement tort dans votre compréhension de la langue. Dans l'exemple que vous avez fourni, vous n'avez pas satisfait au contrat "État" dans SOIT de vos affectations à l'État.

Par exemple, lorsque vous faites

this.state = { baseState: props.baseProp };
// now the state is exactly { baseState: props.baseProp }

L'état est exactement { baseState: props.baseProp } et cela ne satisfait PAS l'exigence de TProps extends BaseProps (parce que nous ne savons pas ce qu'est TProps ! il pourrait y avoir des propriétés).

Après cela, vous faites un devoir _ SÉPARÉ _.

this.state = { derivedState: props.derivedProp };
// now the state is exactly {  derivedState: props.derivedProp }

l'état est maintenant exactement { derivedState: props.derivedProp } (vous avez écrasé votre affectation d'état de base !!) et cela ne satisfait pas DerivedState OR BaseProps.

Vous vous trompez totalement en pensant que cela devrait fonctionner. Si vous avez un problème avec le fonctionnement de base de l'affectation des variables, discutez-en avec les concepteurs de langage dans un nouveau numéro et faites-vous abattre là-bas afin que nous arrêtions de recevoir des notifications ici, s'il vous plaît.

En remarque, vous remplacez ÉGALEMENT votre méthode de base render() , ce qui signifie que votre composant de base ne pourra rien afficher. Si vous êtes convaincu que c'est ce que vous voulez, vous pouvez ajouter un membre protégé getBaseState() et l'appeler à partir du constructeur dérivé lorsque vous définissez l'état (vous n'avez donc pas à dupliquer la logique d'état de base). Mais ce que je pense que vous voulez vraiment, c'est ne pas utiliser du tout de composants dérivés. Essayez de restructurer pour utiliser la composition (où vous avez un objet contenant plusieurs objets enfants). Je pense que vous trouverez qu'il sera beaucoup plus propre.

J'ai à peine envie de m'éloigner de la lecture pour créer un nouveau projet juste pour en débattre avec vous, mais hélas...

Je resterai corrigé à propos setState () dans le constructeur, mais cela ne change pas ce que je ressens à l'utiliser dans componentWillMount .

Exemple de travail de la façon dont cela serait fait:
https://github.com/ericanderson/set-state-example

Plus précisément, index.tsx :

import * as React from "react";
import * as ReactDOM from "react-dom";

/* tslint:disable:max-classes-per-file*/

interface BaseProps {
  baseProp: string;
}

interface BaseState {
  baseState: string;
}

class Base<TProps extends BaseProps, TState extends BaseState> extends React.Component<TProps, TState> {
  public componentWillMount() {
    this.setState({
      baseState: this.props.baseProp,
    });
  }

  public render() {
    return (
      <p>
        <code>this.state.baseState: </code>
        {this.state.baseState}
      </p>
    );
  }
}

interface DerivedProps extends BaseProps {
  derivedProp: string;
}

interface DerivedState extends BaseState {
  derivedState: string;
}

export class Derived extends Base<DerivedProps, DerivedState> {
  public componentWillMount() {
    super.componentWillMount();
    this.setState({
      derivedState: this.props.derivedProp,
    });
  }

  public render() {
    return (
      <div>
        <p>
          <code>this.state.derivedState: </code>
          {this.state.derivedState}
        </p>
        {super.render()}
      </div>
    );
  }
}

ReactDOM.render(<Derived derivedProp="its derived" baseProp="its basic" />, document.getElementById("main"));

@pawelpabich si vous souhaitez implémenter un composant polymorphe avec un état initial, vous devrez rendre votre composant de base abstrait et créer une fonction abstraite getInitialState() (ou sur un thème similaire) à implémenter dans votre classe dérivée. Vous ne voulez affecter l'état qu'une seule fois, soit dans le constructeur, soit avec setState comme @ericanderson l' a montré.

ci-dessous est votre exemple converti en une solution entièrement polymorphe, avec une séparation complète des préoccupations en ce qui concerne la construction de l'état :

interface BaseProps {
  baseProp: string;
}

interface BaseState {
  baseState: string;
}

abstract class Base<TProps extends BaseProps, TState extends BaseState> extends React.Component<TProps, TState> {
  constructor(props: TProps) {
      super(props);

      this.state = this.getInitialState();
  }

  protected abstract getInitialState(): TState;

  protected getBaseState() {
    return this.props.baseProp;
  }

  render() {
      return <div>{this.state.baseState}</div>;
  }
}

interface DerivedProps extends BaseProps {
  derivedProp: string;
}

interface DerivedState extends BaseState {
  derivedState: string;
}

export class Derived extends Base<DerivedProps, DerivedState> {
  getInitialState(): DerivedState {
    return {
      baseState: this.getBaseState(),
      derivedState: this.props.derivedProp,
    };
  }

  render() {
      return <div>{this.state.derivedState}</div>;
  }
}

@patsissons merci !

@caesay J'admets que j'avais tort et que, pour une raison quelconque, je n'ai pas vu que les affectations se substitueraient. Cela étant dit, l'utilisation de CAPS et ! ne m'a pas aidé à sortir de mon trou.

@patsissons et @ericanderson se sont concentrés sur le problème et nous avons maintenant une solution que d'autres peuvent utiliser.

@pawelpabich Je reconnais que mes manières étaient moins que professionnelles - mais c'est compréhensible étant donné que je vous ai donné plusieurs explications, échantillons, etc., et que vous avez choisi de ne pas m'écouter.

alors ce serait simplement écraser l'état défini par le parent.

[_if you want to_] gérer l'état dans votre composant de base, vous pouvez transmettre l'état du composant dérivé au constructeur du composant de base

[_si vous voulez gérer l'état dans votre composant dérivé_] vous pouvez ajouter un membre protégé getBaseState() et l'appeler à partir du constructeur dérivé lorsque vous définissez l'état.

Ce que @patsissons a fait, c'est prendre les commentaires déjà mentionnés ici et fournir un exemple de code - ce qui n'aurait pas dû être nécessaire. Ce n'est pas un stackoverflow, et nous ne fournissons pas non plus souvent d'exemples de code prêts à l'emploi.

Je suis nouveau dans la réaction et le tapuscrit, peut-être que je ne connais pas qch, mais même si l'application se compile sans erreur, avertissement et indice, j'obtiens une erreur d'exécution. Vous trouverez ci-dessous un exemple de composant. J'attribue l'erreur à l'état Readonly . Si l'application fonctionnait avant la modification Readonly , elle a cessé de fonctionner après cette modification et ne génère aucune erreur de compilation.

import * as React from 'react';

export default class HomePage extends React.Component<any, Map<string, string>> {

  public componentWillMount() {
    const map = new Map<string, string>();
    map.set('aKey', 'aValue');
    this.setState(map);
  }

  public render() {

      return (
        <div className="home">
          <div className="greeting">
            Home page: {this.state.get('aKey')} // <-- I get an error here
          </div>
        </div>
      );
  }
}

L'erreur:

homePage.tsx:12 Uncaught TypeError: this.state.get is not a function
    at HomePage.render (homePage.tsx:12)
    at eval (ReactCompositeComponent.js:793)
    at measureLifeCyclePerf (ReactCompositeComponent.js:73)
    at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (

L'état doit toujours être un objet à clé simple, alors définissez plutôt l'état
comme quelque chose comme : { values: Map <string, string> } et lisez
this.state.values.get('aKey')

Op vr 29 sept. 2017 à 09:01 schreef Janusz Białobrzewski <
[email protected]> :

Je suis nouveau pour réagir et dactylographier, peut-être que je ne connais pas qch, mais même
bien que l'application se compile sans erreur, avertissement et indice, j'obtiens un runtime
Erreur. Vous trouverez ci-dessous un exemple de composant. J'attribue l'erreur à Readonly
Etat. Si l'application fonctionnait avant le changement Readonly, alors après cela
changer, il a cessé de fonctionner et il ne donne aucune erreur de temps de compilation.

importer * en tant que React depuis 'react' ;
exporter la classe par défaut HomePage étend React.Component> {

public componentWillMount() {
carte const = nouvelle carte();
map.set('aKey', 'aValue');
this.setState(carte);
}

rendu public() {

  return (
    <div className="home">
      <div className="greeting">
        Home page: {this.state.get('aKey')} // <-- I get an error here
      </div>
    </div>
  );

}
}

L'erreur:

page d'accueil. tsx:12 Erreur de type non interceptée : this.state.get n'est pas une fonction
sur HomePage.render (homePage.tsx:12)
à l'évaluation (ReactCompositeComponent.js:793)
à measureLifeCyclePerf (ReactCompositeComponent.js:73)
à ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/14250#issuecomment-333047367 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABvGhM5hDyRNyUeZuIiGeTZk1N-rfuA4ks5snJW5gaJpZM4LuDWV
.

Merci, mais il semble être un effort inutile de déclarer l'état comme Readonly<S> , car ses membres imbriqués ne sont pas affectés par le Readonly.

Il est possible qu'un jour Readonly s'applique de manière récursive, mais pour l'instant, c'est à vous de vous assurer que vous l'avez bien géré. Dans votre cas, vous devriez vraiment déclarer ReadonlyMap ou

interface State {
    readonly [key: string]: string;
}

ou imbriqué :

interface State {
    map: { readonly [key: string]: string };
}

Nous pouvons l'utiliser pour une lecture approfondie uniquement :

export type DeepReadonly<T> =
  T extends Array<any> ?
  ReadonlyArray<T[0]> :
  T extends Date ?
  T :
  T extends Function ?
  T :
  T extends object ?
  { readonly [P in keyof T]: DeepReadonly<T[P]> } :
  T;

export type Writable<T> =
  T extends ReadonlyArray<any> ?
  Array<WritableObject<T[0]>> :
  T extends Array<any> ?
  Array<WritableObject<T[0]>> :
  WritableObject<T>;

type WritableObject<T> =
  T extends Date ?
  T :
  T extends Function ?
  T :
  T extends object ?
  { -readonly [P in keyof T]: Writable<T[P]> } :
  T;
Cette page vous a été utile?
0 / 5 - 0 notes