Definitelytyped: Sur l'utilisation de Pick pour le setState de @types/react

Créé le 25 juil. 2017  ·  53Commentaires  ·  Source: DefinitelyTyped/DefinitelyTyped

Je comprends que Pick été utilisé pour le type de setState car le retour de undefined pour une clé qui ne devrait pas être indéfinie entraînerait la définition de la clé sur non définie par React.

Cependant, l'utilisation de Pick cause d'autres problèmes. D'une part, la saisie semi-automatique du service de compilation devient inutile, car elle utilise le résultat de Pick pour la saisie semi-automatique, et lorsque vous demandez des complétions, le résultat de Pick ne contient pas encore la clé que vous pouvez souhaitez effectuer une saisie semi-automatique. Mais les problèmes sont particulièrement graves lors de l'écriture de setState avec un argument de rappel :

  1. La liste des clés est dérivée de votre première instruction return ; si vous ne retournez pas une clé particulière dans votre instruction return, vous ne pouvez pas non plus la lire dans l'argument sans forcer la liste des clés à se réinitialiser à never . Plusieurs déclarations de retour peuvent être difficiles à écrire si elles renvoient des clés différentes, surtout si vous avez un retour indéfini quelque part (par exemple if (state.busy) { return } ).

    • Cela peut être contourné en utilisant toujours l'entrée dans un spread (par exemple this.setState(input => ({ ...input, count: +input.count + 1 })) ) mais c'est redondant et une désoptimisation, en particulier pour les états plus grands, car setState transmettra la valeur de retour du rappel à Object.assign .

  2. Si, pour une raison quelconque, le type que vous retournez n'est pas compatible avec le type d'entrée, le Pick choisira never pour ses clés, et la fonction sera autorisée à retourner _n'importe quoi_. Même les clés qui coïncident avec une clé existante autorisent effectivement any comme valeur -- si cela ne convient pas, ce n'est tout simplement pas Pick ed, et est traité comme une propriété excédentaire pour {} , qui n'est pas vérifié.
  3. Si never est choisi comme argument générique, pour l'une des raisons énumérées ci-dessus, un argument de rappel peut en fait être traité comme un argument de la forme objet de setState ; cela provoque la saisie des arguments du rappel any au lieu de {} . Je ne sais pas pourquoi ce n'est pas une erreur implicite.
interface State {
  count: string // (for demonstration purposes)
}

class Counter extends React.Component<{}, State> {
  readonly state: Readonly<State> = {
    count: '0'
  }

  render () {
    return React.createElement('span', { onClick: this.clicked }, this.state.count)
  }

  private readonly clicked = () => {
    this.setState(input => ({
      count: +input.count + 1 // not a type error
      // the setState<never>(input: Pick<State, never>) overload is being used
    }))
  }
}

Pour résumer, alors que l'utilisation de Pick , malgré quelques inconvénients, permet de détecter les erreurs de type dans la forme sans rappel de setState , elle est totalement contre-productive dans la forme de rappel ; où non seulement il ne fait pas la tâche prévue d'interdire undefined mais désactive également toute vérification de type sur les entrées ou les sorties du rappel.

Peut-être devrait-il être modifié, au moins pour le formulaire de rappel, en Partial et espérer que les utilisateurs sachent ne pas renvoyer les valeurs undefined , comme cela se faisait dans les anciennes définitions.

Commentaire le plus utile

Le récent "correctif" me pose maintenant des problèmes avec plusieurs instructions de retour dans un rappel setState() .

Typescript : 2.6.2 avec toutes les options "strict" activées à l'exception de "strictFunctionTypes"
types/réagissent : 16.0.30

Exemple de code :

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(): void {
        this.setState((prevState) => {
            if (prevState.a) {
                return {
                    b: true
                };
            }

            return {
                a: true
            };
        });
    }
}

Erreur du compilateur :

error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
  Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
    Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
      Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.

522         this.setState((prevState) => {
                          ~~~~~~~~~~~~~~~~

Tous les 53 commentaires

Merci pour vos commentaires. C'est un cas très intéressant que vous évoquez.

Je dois réfléchir un peu aux implications avant de m'engager dans une opinion.

À un moment donné, @ahejlsberg a voulu traiter l' | undefined . Ainsi, foo?: string signifierait que foo n'est pas défini ou qu'il s'agit d'une chaîne.

Lors de la lecture d'une valeur de propriété, la différence n'est pas pertinente 99,9% du temps, mais pour les écritures, en particulier dans le cas de Partial<> , la distinction est extrêmement importante.

Malheureusement, il s'agit d'un changement de langue de rupture, nous devons donc attendre la version 3.0 ou l'avoir derrière un drapeau.

Si on avait dit changement, Partial<> devient extrêmement utile à beaucoup au lieu de mon dogme actuel qui est de rejeter son utilisation à vue.

@ahejlsberg Je sais que tu es un homme occupé, serait-ce difficile à mettre en œuvre ? tel que indéfini n'est pas une valeur assignable implicite ?

D'accord, après avoir passé un peu de temps ce matin à réfléchir au problème, je vois quelques "solutions" au problème que vous proposez, chacune avec des effets secondaires assez importants.

1. Remplacez l'optionnalité ( interface State { foo?: string } ) par une chaîne ou non définie.

Exemple:

interface State {
  foo: string;
  bar: string;
}

const bleh: Partial<State> = { foo: "hi" }; // OKAY
const bleh: Partial<State> = { foo: undefined; } // BREAK

Cela résoudrait techniquement le problème et nous permettrait d'utiliser Partial<> , au détriment du cas 98% étant plus difficile. Cela brise le monde et ne serait donc pas vraiment possible avant la 3.x et généralement, très peu de bibliothèques/cas d'utilisation se soucient réellement de la distinction entre non défini et non défini.

2. Il suffit de passer en partiel

Exemple:

interface State {
  foo: string;
  bar: string;
}

setState(prevState => {foo: "hi"}); // OKAY
setState(prevState => {foo: undefined}); // OKAY BUT BAD!

3. Ne rien faire

Exemple:

interface State {
  foo: string;
  bar: string;
}

// The following errors out because {foo: ""} can't be assigned to Pick<State, "foo" | "bar">
// Same with {bar: "" }
setState((prevState) => {
  if (randomThing) {
    return { foo: "" };
  }
  return { bar: "" };
});

// A work around for the few places where people need this type of thing, which works
setState((prevState) => {
  if (randomThing) {
    return { foo: "" } as State
  }
  return { bar: "" } as State
});

// This is fine because the following is still an error
const a = {oops: ""} as State

4. Ajouter une logique imbriquée aux types littéraux qui sont joints

À l'heure actuelle, lorsque nous avons plusieurs chemins de retour, le compilateur rassemble toutes les clés potentielles en un seul Pick<State, "foo" | "bar"> géant.

Cependant, un changement rétrocompatible serait de permettre le regroupement des littéraux, comme Pick<State, ("foo") | ("bar")> ou dans un cas plus complexe : Pick<State, ("foo" | "bar") | ("baz")>

L'absence de parenthèses suggère que ce n'est qu'un seul ensemble de valeurs qui est la fonctionnalité existante.

Maintenant, lorsque nous essayons de lancer {foo: number} en Pick<State, ("foo") | ("bar")> , nous pouvons réussir. Idem avec {bar: number} .

Pick<State, ("foo") | ("bar")> est également lançable en Partial<State> et en {foo: number} | {bar: number} .

Ma conclusion personnelle

(1) et (2) ne sont tout simplement pas faisables. L'un introduit de la complexité pour presque tout le monde, quoi qu'il arrive, et l'autre aide les gens à produire du code qui se compile mais qui est clairement incorrect.

(3) est complètement utilisable aujourd'hui et bien que je ne me souvienne pas pourquoi j'ai ajouté | S comme valeur de retour potentielle à la fonction dans setState , je ne vois aucune autre raison de le faire.

(4) pourrait éviter la solution de contournement dans (3) mais c'est aux développeurs de TypeScript et peut-être que si nous attirons suffisamment l'attention de

Cela me laisse penser que les types sont plus corrects aujourd'hui que si nous les changions.

Le problème avec l'approche "ne rien faire" est que le compilateur ne se comporte pas comme vous le décrivez dans le cas où vous commettez une véritable erreur de type. Si je modifie votre exemple pour, par exemple, définir la valeur sur 0 au lieu d'une chaîne vide :

interface State {
  foo: string;
  bar: string;
}

// The following does not error at all because the compiler picks `Pick<State, never>`
// The function overload of `setState` is not used -- the object overload is, and it
// accepts the function as it is accepting anything (`{}`).
setState((prevState) => {
  if (randomThing) {
    return { foo: 0 };
  }
  return { bar: 0 };
});

Je vois maintenant. Retour à la planche à dessin. Je vais voir si nous pouvons créer une solution intelligente à cela. Si nous ne pouvons pas en trouver une, nous devons évaluer la solution Partial<> vous proposez. La grande chose à considérer est de savoir si un undefined inattendu sur une valeur serait plus courant/ennuyeux qu'un mauvais type inattendu.

Pour clarifier, nous avons deux surcharges pour cela...

setState<K extends keyof S>(f: (prevState: Readonly<S>, props: P) => Pick<S, K>, callback?: () => any): void;
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;

Définir l'un ou les deux sur Partial<S> au lieu de Pick<S, K> résout pas votre problème. Il passe toujours à la version de l'objet, qui accepte le mauvais type pour une raison quelconque :

```ts
État de l'interface {
barre : chaîne ;
foo : nombre ;
}

la classe Foo étend React.Component<{}, State> {
blabla public () {
this.setState((prevState) => ({bar: 1})); // Pas d'erreur :/
}
}```

Pourrions-nous en quelque sorte le forcer à rejeter une fonction en utilisant un extends object
contrainte?
Le vendredi 28 juillet 2017 à 0h00, Eric Anderson [email protected] a écrit :

Pour clarifier, nous avons deux surcharges pour cela...

setState(f: (prevState: Readonly , props: P) => Pick , callback?: () => any): void;setState(état : Pick , callback ? : () => any) : void ;

Définir l'un ou les deux sur Partial au lieu de Pickne résout pas votre problème.

État de l'interface {
barre : chaîne ;
foo : nombre ;
}
la classe Foo étend React.Component<{}, State> {
blabla public () {
this.setState((prevState) => ({bar: 1})); // Pas d'erreur :/
}
}```

-
Vous recevez ceci parce que vous avez créé le fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-318388471 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAEdfeEJ_Ejfana14fRII1OZuS7qTuyuks5sSKX3gaJpZM4OiDrc
.

Nous avons essayé cela. Les fonctions sont des objets.

Eric L Anderson
Envoyé de mon iPhone

Je suis venu ici pour signaler un problème connexe, à savoir que la refactorisation du type d'état est interrompue avec ce changement.

Par exemple, voir https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx - si vous l'ouvrez dans VS Code et F2 pour refactoriser/renommer something à la ligne 6 (https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx#L6), il mettra à jour cette ligne et https://github.com/tomduncalf/react-types-issue/ blob/master/Test.tsx#L14 , mais manquera l'utilisation de cela dans l'appel setState à https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx #L20

Le seul moyen que j'ai trouvé pour le faire fonctionner correctement est de taper explicitement mon objet as IState lors de l'appel de setState , ce qui est un peu gênant et assez facile à oublier.

Je ne connais pas exactement la raison d'être du changement, mais il semble avoir rompu la sécurité de type de manière assez importante lorsque vous travaillez avec l'état, ce serait donc formidable s'il y avait un moyen de le résoudre!

Merci,
À M

Oui, encore une fois, je pense qu'il serait préférable d'utiliser Partial car il conserve mieux ces informations de relation.

On peut soutenir que Pick n'est pas correctement refactorisé est une limitation/un bug du code de refactorisation qui pourrait être amélioré, mais je ne crois toujours pas que Pick fournit la sécurité de type que https:// github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318385945 mentions. Alors que Partial permettrait undefinedundefined n'est pas censé être, et l'utilisateur doit alors faire attention à ne pas le faire, Pick permet n'importe quoi parce tout type non conforme à l'interface provoquerait, au lieu d'une erreur de type, Pick pour générer une interface vide à la place, qui accepte n'importe quoi.

Quant à https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318388471, cela pourrait-il être un bogue dans le vérificateur de propriétés en excès ? Ou cela a-t-il été testé avant que le vérificateur de propriétés en excès ne soit rendu plus strict en 2.3 ou 2.4 (j'ai oublié) ?

Un autre problème que je viens de remarquer ici est que si un composant n'a pas de type d'état (c'est-à-dire un seul paramètre de type à React.Component ), ou si le type d'état est vide ( {} ), Typescript autorisera toujours appels à setState sans générer d'erreur, ce qui me semble incorrect - voir https://github.com/tomduncalf/react-types-issue/blob/master/Test2.tsx

La solution utilisant Partial me semble préférable - je pourrais essayer de le patcher moi-même et voir comment cela fonctionne.

Merci,
À M

Salut tout le monde!
Je ne sais pas pourquoi, mais si je joins des déclarations setState à une seule déclaration :

setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => Pick<S, K>) | Pick<S, K>, callback?: () => any): void;

Cela fonctionne comme prévu pour moi:

import * as React from 'react';

export class Comp extends React.Component<{}, { foo: boolean, bar: boolean }> {
  public render() {
    this.handleSomething();
    return null;
  }

  private handleSomething = () => {
    this.setState({ foo: '' }); // Type '""' is not assignable to type 'boolean'.
    this.setState({ foo: true }); // ok!
    this.setState({ foo: true, bar: true }); // ok!
    this.setState({}); // ok!
    this.setState({ foo: true, foo2: true }); // Object literal may only specify
    // known properties, and 'foo2' does not exist in type
    this.setState(() => ({ foo: '' })); // Property 'foo' is missing in type '() => { foo: string; }'.
    this.setState(() => ({ foo: true })); // ok!
    this.setState(() => ({ foo: true, bar: true })); // ok!
    this.setState(() => ({ foo: true, foo2: true })); // Property 'foo' is missing in type
    // '() => { foo: true; foo2: boolean; }'
    this.setState(() => ({ foo: '', foo2: true })); // Property 'foo' is missing in
    // type '() => { foo: string; foo2: boolean; }'.
    this.setState(() => ({ })); // ok!
  };
}

Cela peut-il changer suffisamment pour résoudre le problème d'origine ?

@mctep Belle trouvaille.

Et si je prends ce que vous avez fait et que je l'étends un tout petit peu, pour qu'il soit Partial<S> & Pick<S, K> au lieu de Pick<S, K> par endroits, intellisense vous suggère des noms de clés. Malheureusement, les noms de clés indiquent que la valeur de la propriété est "quelque chose | non défini" mais lorsque vous la compilez, il sait mieux :

declare class Component<P, S> {
    setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>)) | (Partial<S> & Pick<S, K>), callback?: () => any): void;
}

interface State {
    foo: number;
    bar: string;
    baz?: string;
}

class Foo extends Component<{}, State> {
    constructor() {
        super();
        this.setState(() => { // error
            return {
                foo: undefined
            }
        });
        this.setState({ // error
            foo: undefined
        })
        this.setState({
            foo: 5,
            bar: "hi",
            baz: undefined
        })
    }
}

Je vais mettre ce changement plus tard aujourd'hui

Le récent "correctif" me pose maintenant des problèmes avec plusieurs instructions de retour dans un rappel setState() .

Typescript : 2.6.2 avec toutes les options "strict" activées à l'exception de "strictFunctionTypes"
types/réagissent : 16.0.30

Exemple de code :

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(): void {
        this.setState((prevState) => {
            if (prevState.a) {
                return {
                    b: true
                };
            }

            return {
                a: true
            };
        });
    }
}

Erreur du compilateur :

error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
  Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
    Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
      Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.

522         this.setState((prevState) => {
                          ~~~~~~~~~~~~~~~~

Voir également le problème décrit par @UselessPickles à la suite de ce changement.

Je suis assez certain que cela a toujours été un problème.

Je suis plus qu'à peu près certain que cela n'a pas toujours été un problème. J'ai un projet avec plusieurs déclarations de retour dans les rappels setState() qui se compilent sans problème depuis plus de 2 mois. J'ai suivi les mises à niveau des dépendances NPM toutes les 2 semaines environ, et cette erreur du compilateur vient de commencer à se produire pour moi aujourd'hui après la mise à niveau vers la dernière version de types/react. La version précédente ne produisait pas cette erreur de compilateur.

C'est un problème depuis le passage de Partial à Pick. La solution de contournement serait de donner la liste des clés que vous avez l'intention de retourner au paramètre générique de setState , mais vous êtes alors obligé de toujours retourner toutes les clés...

Ce que je fais dans ces cas, c'est soit je retourne toujours toutes les clés, les clés non modifiées étant définies sur key: prevState.key , soit en retournant un spread avec prevState ( { ...prevState, newKey: newValue } ).

Peut-être que j'ai en fait un cas limite plus spécifique dans mon code qui fonctionnait auparavant par coïncidence ? Un exemple réel de mon projet ressemble plus à ceci, où il renvoie soit un objet vide (pour ne changer aucun état), soit un objet non vide :

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(newValue: boolean): void {
        this.setState((prevState) => {
            if (prevState.a === newValue) {
                // do nothing if there's no change
                return { };
            }

            return {
                a: newValue,
                // force "b" to false if we're changing "a"
                b: false
            };
        });
    }
}

return null et return undefined sont également des valeurs de retour parfaitement acceptables pour ne pas changer d'état (elles passeront par Object.assign et ne modifieront donc pas this.state ).

Me rappelle que la signature actuelle ne permet ni l'un ni l'autre. Peut-être que null devrait être autorisé comme valeur de retour, au moins, car ce n'est pas du tout quelque chose qui peut résulter d'un oubli accidentel de return .


Quoi qu'il en soit, dans les cas où la signature est ambiguë, TypeScript semble sélectionner uniquement la première instruction return dans l'ordre source et l'utiliser pour dériver les paramètres génériques. Il semble pouvoir fusionner les types pour les génériques simples (par exemple Array ou Promise ), mais il ne les fusionne jamais si le type contextuel est un type mappé comme Pick .

Je constate des régressions avec la version sans fonction avec les types les plus récents. En particulier, le type d'argument "state" lors du passage d'un argument est passé de :

setState( état: Pick , callback?: () => any): void;

à:

state : ((prevState : Readonly\ , props : P) => (Pick & Partial\ )) |

https://github.com/DefinitelyTyped/DefinitelyTyped/commit/62c2219a6ed6dc34ea969b8c2c87a41d31002660#diff -96b72df8b13a8a590e4f160cbc51f40c

L'ajout de & Partial<S> semble casser des choses qui fonctionnaient auparavant.

Voici une reproduction minimale :

export abstract class ComponentBaseClass<P, S = {}> extends React.Component<P, S & { baseProp: string }>
{
    foo()
    {
        this.setState( { baseProp: 'foobar' } );
    }
}

cela échoue avec l'erreur :

(429,18): Argument de type '{ baseProp: "foobar"; }' n'est pas assignable au paramètre de type '((prevState: Readonly Type '{ baseProp: "foobar"; }' n'est pas assignable au type 'Pick & Type partiel '{ baseProp: "foobar"; }' n'est pas assignable à tapez 'Partiel

Changer le type d'état de S &{ baseProp: string } à seulement { baseProp: string } fera également disparaître l'erreur (bien que cela brisera les classes réelles qui spécifient le type S).

C'est intéressant. Je suis heureux d'annuler la partie partielle et de sélectionner la partie du changement

Je dirai que ce que vous signalez ressemble à un bogue dans TS, en particulier :

Tapez '{ baseProp: "foobar" ; }' n'est pas assignable au type 'Partiel

Bien. Peut-être que TS ne peut pas savoir ce qui se passe car il n'y a pas de relation entre S et baseProp.

On pourrait passer un S de type { baseProp:number } auquel cas il est correct que vous ne puissiez pas affecter de chaîne à baseProp.

Peut-être que si S étendait la version baseProp ?

J'avais déjà essayé quelque chose comme ça :

interface BaseState_t
{
    baseProp: string
}

export abstract class ComponentBaseClass<P, S extends BaseState_t> extends React.Component<P, S >
{
    foo()
    {
        this.state.baseProp;
        this.setState( { baseProp: 'foobar' } );
    }
}

alors que l'accès est ok, l'appel setState a la même erreur :

Argument de type '{ baseProp: "foobar"; }' n'est pas assignable au paramètre de type '((prevState: Readonly\ , props: P) => Pick & Partial\ ) |
}' n'est pas assignable au type 'Pick & Partial\ '.Tapez '{ baseProp: "foobar" ;

Ce n'est probablement pas un bon moyen de le structurer - après tout, une classe enfant pourrait déclarer des variables d'état qui entrent en collision avec des variables de la classe de base. Donc, le tapuscrit a peut-être raison de se plaindre qu'il ne peut pas être sûr de ce qui va se passer là-bas. Il est étrange qu'il n'y ait aucun problème pour accéder à ces accessoires.

Hmm. Cela ressemble définitivement à un bogue dans TS maintenant.

Je vais jouer avec ça aujourd'hui et éventuellement sortir le & Partial.

Je vais également ajouter ceci comme cas de test et essayer une autre idée pour rendre intellisense heureux

Salut,
Même problème ici...

export interface ISomeComponent {
    field1: string;
    field2: string;
}

interface SomeComponentState {
    field: string;
}

export class SomeComponent<
    TProps extends ISomeComponent = ISomeComponent,
    TState extends SomeComponentState = SomeComponentState> extends React.Component<TProps, TState>{

    doSomething() {
        this.setState({ field: 'test' });
    }

    render() {
        return (
            <div onClick={this.doSomething.bind(this)}>
                {this.state.field}
            </div>
        );
    }
}

Erreur dans setState :
TS2345 (TS) Argument of type '{ field: "test"; }' is not assignable to parameter of type '((prevState: Readonly<TState>, props: TProps) => Pick<TState, "field"> & Partial<TState>) | (Pick...'. Type '{ field: "test"; }' is not assignable to type 'Pick<TState, "field"> & Partial<TState>'. Type '{ field: "test"; }' is not assignable to type 'Partial<TState>'.

L'erreur vient de commencer à se produire après ce changement. Dans la version 16.0.10, cela fonctionnait bien.

Typescript a plusieurs problèmes liés, ils les ont fermés comme fonctionnant comme prévu :

https://github.com/Microsoft/TypeScript/issues/19388

J'ai fait un aperçu de quelques exemples ici : https://gist.github.com/afarnsworth-valve/93d1096d1410b0f2efb2c94f86de9c84

Bien que le comportement semble toujours étrange. En particulier, vous pouvez affecter une variable typée comme classe de base sans transtypage, puis effectuer les mêmes appels avec celle-ci sans problème. Ce problème semble suggérer que l'affectation et la comparaison sont deux choses différentes.

Je ne vous ai pas oublié les gars. Réparera bientôt

Le PR référencé devrait résoudre ce problème. J'ai également ajouté des tests qui devraient protéger contre la rupture de ce boîtier de bord à nouveau.

@ericanderson Merci pour la réponse rapide :)

Le nouveau correctif a-t-il des limitations/inconvénients ?

Aucun à ma connaissance actuellement. J'ai pu garder intellisense (en fait mieux qu'avant, car il résout certains cas limites).

Pour élaborer, la solution & Partial<S> consistait à tromper intellisense pour révéler des paramètres possibles, mais il l'a fait en déclarant qu'ils sont X | undefined . Cela échouerait bien sûr à compiler, mais c'était un peu déroutant. Obtenir le | S corrige l'intellisense afin qu'il suggère tous les bons paramètres et maintenant il n'affiche pas de faux type.

J'ai essayé d'ajouter null comme valeur de retour possible du rappel setState (ce que vous pouvez retourner à l'état, vous ne voulez changer aucun état, sans faire de return; également valide), mais cela a plutôt fait abandonner complètement l'inférence de type et choisir never comme clés 😢

Pour l'instant, dans les rappels où je veux réellement ignorer l'état de mise à jour, j'ai utilisé return null! . Le type never de ce retour fait que TypeScript ignore le retour pour l'inférence de type générique.

Salut les gars...

Le dernier commit a résolu mon problème.
Merci pour la réponse rapide :)

Quelle version a le correctif? Est-il publié sur npm ? Je rencontre le cas où la version de rappel de setState me dit qu'il y a une incompatibilité de propriété dans la valeur de retour, tout comme @UselessPickles trouvé.

Devrait être bon sur les derniers @types/react

Je l'ai corrigé pour les séries 15 et 16

import produce from 'immer';

interface IComponentState
{
    numberList: number[];
}

export class HomeComponent extends React.Component<ComponentProps, IComponentState>

L'ancienne définition de type de React.

// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
    state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S)) | (Pick<S, K> | S),
    callback?: () => void
): void;

..produit cette erreur :

screen shot 2018-05-18 at 2 36 44 pm

J'ai essayé cette proposition :

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Intellisense peut désormais détecter la propriété à partir de l'état de React. Cependant, la propriété détectée est maintenant perçue comme possibly undefined .

screen shot 2018-05-18 at 2 37 32 pm

Devrait utiliser l'opérateur d'assertion null même si numberList n'est ni indéfini ni nullable :

screen shot 2018-05-18 at 2 38 51 pm

Je vais rester sur l'ancienne définition de type jusqu'à ce que la détection de type soit améliorée. En attendant, je vais juste indiquer explicitement le type sur le paramètre générique de immer produire. produce<IComponentState> est plus facile à raisonner que list! .

screen shot 2018-05-18 at 2 51 43 pm

Votre première erreur est que vous ne retournez rien. Ce n'est pas ainsi que fonctionne setState.

Cela fonctionne même lorsqu'il ne renvoie pas de variable dans produire. Je viens de suivre l'exemple ici (non TypeScript):

https://github.com/mweststrate/immer

onBirthDayClick2 = () => {
    this.setState(
        produce(draft => {
            draft.user.age += 1
            // no need to return draft
        })
    )
}

La seule chose qui empêche TypeScript d'exécuter ce code est qu'il a un type déduit à tort de la définition de type React. La définition de type signale une erreur de numberList does not exist on type Pick<IComponentState, never> . Juste capable de faire disparaître l'erreur de compilation en passant explicitement le type sur le paramètre générique de produire, c'est-à-dire produce<IComponentState> .

J'ai même essayé de renvoyer la variable dans produire et de voir si cela aiderait la définition de type React à déduire le type (un problème de poussin et d'œuf), mais il n'y a toujours aucun moyen pour la définition de type React de détecter le type correct de l'état. Par conséquent, l'intellisens pour le brouillon n'apparaît pas :

screen shot 2018-05-18 at 10 38 04 pm

Ou peut-être que j'ai une mauvaise attente du compilateur :) Le compilateur ne peut pas créer un type pour la variable de brouillon basé sur le type de setState car le compilateur traite le code de l'intérieur vers l'extérieur. Cependant, la définition de type suggérée m'a fait penser que le compilateur peut traiter le code de l'extérieur vers l'intérieur, qu'il peut choisir le meilleur type qu'il peut passer du code externe ( setState ) au code interne ( produce ).

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Avec la définition de type ci-dessus, le compilateur peut détecter que le brouillon a une propriété numberList . Il le détecte comme possibly undefined cependant :

image

Après avoir bricolé davantage, j'ai rendu le compilateur capable de transmettre le type de l'état au brouillon de produire en ajoutant le S à la définition de type de setState :

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K> & S))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Le code se compile maintenant :)

screen shot 2018-05-18 at 11 21 03 pm

Votre erreur n'est pas avec setState, votre erreur est à l'intérieur de produire. Quelle est votre définition de type pour les produits ?

Mon PR ci-dessus a une erreur sur le script de test de DefinitelyTyped, je n'ai pas testé localement . Je le teste donc localement maintenant.

Voici la définition du type immer/Produce.

/**
 * Immer takes a state, and runs a function against it.
 * That function can freely mutate the state, as it will create copies-on-write.
 * This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
 *
 * If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
 * any time it is called with the current state.
 *
 * <strong i="7">@param</strong> currentState - the state to start with
 * <strong i="8">@param</strong> recipe - function that receives a proxy of the current state as first argument and which can be freely modified
 * <strong i="9">@param</strong> initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
 * <strong i="10">@returns</strong> The next state: a new state, or the current state if nothing was modified
 */
export default function<S = any>(
    currentState: S,
    recipe: (this: S, draftState: S) => void | S
): S

// curried invocations with default initial state
// 0 additional arguments
export default function<S = any>(
    recipe: (this: S, draftState: S) => void | S,
    initialState: S
): (currentState: S | undefined) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
    recipe: (this: S, draftState: S, a: A) => void | S,
    initialState: S
): (currentState: S | undefined, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
    recipe: (this: S, draftState: S, a: A, b: B) => void | S,
    initialState: S
): (currentState: S | undefined, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
    recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S,
    initialState: S
): (currentState: S | undefined, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
    recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S,
    initialState: S
): (currentState: S | undefined, ...extraArgs: any[]) => S

// curried invocations without default initial state
// 0 additional arguments
export default function<S = any>(
    recipe: (this: S, draftState: S) => void | S
): (currentState: S) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
    recipe: (this: S, draftState: S, a: A) => void | S
): (currentState: S, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
    recipe: (this: S, draftState: S, a: A, b: B) => void | S
): (currentState: S, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
    recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S
): (currentState: S, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
    recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S
): (currentState: S, ...extraArgs: any[]) => S
/**
 * Automatically freezes any state trees generated by immer.
 * This protects against accidental modifications of the state tree outside of an immer function.
 * This comes with a performance impact, so it is recommended to disable this option in production.
 * It is by default enabled.
 */
export function setAutoFreeze(autoFreeze: boolean): void

/**
 * Manually override whether proxies should be used.
 * By default done by using feature detection
 */
export function setUseProxies(useProxies: boolean): void

@ericanderson pourriez-vous m'indiquer la discussion sur la raison pour laquelle Pick est utilisé au lieu de Partial ? Cela m'a causé des heures de chagrin (en utilisant tout simplement setState(obj) , pas la version de rappel), et pour l'instant je vais avec this.setState(newState as State) comme solution de contournement. Je veux juste comprendre pourquoi ça a été changé, parce qu'il doit me manquer quelque chose.

Bonjour @ericanderson ,

J'ai un problème avec la dernière définition.

Mon cas d'utilisation est brièvement comme ceci:

interface AppState {
  valueA: string;
  valueB: string;
  // ... something else
} 
export default class App extends React.Component <{}, AppState> {
  onValueAChange (e:React.ChangeEvent<HTMLInputElement>) {
    const newState: Partial<AppState> = {valueA: e.target.value}
    if (this.shouldUpdateValueB()) {
      newState.valueB = e.target.value;
    }
    this.setState(newState); // <-- this leads to a compiling error
  }
  // ... other methods
}

Le message d'erreur est comme :

Argument of type 'Partial<AppState>' is not assignable to parameter of type 'AppState | ((prevState: Readonly<AppState>, props: {}) => AppState | Pick<AppState, "valueA" | "v...'.
  Type 'Partial<AppState>' is not assignable to type 'Pick<AppState, "valueA" | "valueB" | "somethingElse">'.
    Types of property 'valueA' are incompatible.
      Type 'string | undefined' is not assignable to type 'string'.
        Type 'undefined' is not assignable to type 'string'.

Il semble que Partial<AppState> ne soit pas compatible avec la signature de setState . Bien sûr, je peux résoudre ce problème par une assertion de type comme

this.setState(newState as Pick<AppState, 'valueA' | 'valueB'>)

mais ce n'est pas idéal, car :
1) cette syntaxe est très verbeuse
2) plus important encore, l'assertion de type pourrait violer mes données réelles. Par exemple, newState as Pick<AppState, 'somethingElse'> réussit également le contrôle, bien qu'il ne corresponde pas à mes données.

je pense que partieldevrait en quelque sorte compatible avec Pick, depuis Partiellechoisit juste un nombre incertain de clés de T. Je ne suis pas sûr que ma compréhension soit exacte. Quoi qu'il en soit l'utilisation idéale de mon point de vue devrait être que je puisse passer la variable typée Partialdirectement dans setState.

Pourriez-vous avoir l'amabilité d'examiner ma suggestion ou de signaler mon malentendu s'il y a lieu ? Merci!

C'est un très vieux changement tout d'abord. Donc, à moins que quelqu'un d'autre n'ait récemment changé cela, vous vous trompez probablement d'arbre.

Cela dit. Partial autorise des valeurs indéfinies.

const a : Partial<{foo: string}> = { foo: undefined }

a est valide mais il est clair que le résultat de cette mise à jour de votre état met votre état avec foo indéfini même si vous avez déclaré que c'est impossible.

Par conséquent, un partiel n'est pas assignable à un Pick. Et Pick est la bonne réponse pour s'assurer que vos types ne mentent pas

J'ai l'impression que ne pas permettre :

setState((prevState) => {
  if (prevState.xyz) {
    return { foo: "" };
  }
  return { bar: "" };
});

est hyper restrictif.

La solution de contournement de @Kovensky est la seule solution de contournement sensée que je connaisse, mais toujours pénible à écrire.

Y a-t-il quelque chose qui puisse être fait pour soutenir ce modèle (je dirais) assez courant ?

La seule chose à faire est de supprimer typesafety

Quelqu'un peut-il expliquer le raisonnement pour Pick<S, K> | S | null ?

        setState<K extends keyof S>(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;

Tbh, je ne sais même pas pourquoi la signature ci-dessus fonctionne même pour les mises à jour d'état partielles car K est défini comme keyof S donc Pick<S, K> recrée essentiellement S ?

Partial<S> | null ne devrait-il pas faire le travail aussi ?

        setState(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Partial<S> | null)) | (Partial<S> | null),
            callback?: () => void
        ): void;

Quelqu'un peut-il expliquer...

Il a été expliqué clairement quelques réponses.

setState((prevState) => {
  if (prevState.xyz) {
    return { foo: "" };
  }
  return { bar: "" };
});

est hyper restrictif.

La solution de contournement de @Kovensky est la seule solution de contournement sensée que je connaisse, mais toujours pénible à écrire.

Je viens de rencontrer ce problème exact, mais je ne vois aucune référence à Kovensky dans le fil (peut-être que quelqu'un a changé son nom d'utilisateur ?). Quelqu'un peut-il m'indiquer la solution de contournement actuellement recommandée

@ timrobinson33 Ce commentaire explique la solution de contournement https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351884578

Et c'est une bonne chose que nous n'ayons plus à nous soucier de ça avec des crochets 🙂

@ timrobinson33 Ce commentaire explique la solution de contournement #18365 (commentaire)

Merci beaucoup pour cela. En fin de compte, je pensais que mon code avait l'air plus agréable que plusieurs petits appels setState avec des instructions If dehors d'eux, même si cela signifie que certains chemins appelleront setState plus d'une fois.

Je suppose que c'est en fait similaire à la façon dont nous travaillons avec les crochets, considérant l'état comme plusieurs petites choses que nous mettons à jour indépendamment.

Cette page vous a été utile?
0 / 5 - 0 notes
bleepcoder.com utilise des informations sous licence publique GitHub pour fournir aux développeurs du monde entier des solutions à leurs problèmes. Nous ne sommes pas affiliés à GitHub, Inc. ni à aucun développeur qui utilise GitHub pour ses projets. Nous n'hébergeons aucune des vidéos ou images sur nos serveurs. Tous les droits appartiennent à leurs propriétaires respectifs.
Source pour cette page: Source

Langages de programmation populaires
Projets GitHub populaires
Plus de projets GitHub

© 2024 bleepcoder.com - Contact
Made with in the Dominican Republic.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.