3
votes

Pourquoi le type n'a-t-il aucun chevauchement lorsqu'il est enum?

J'essaie de comprendre pourquoi TypeScript signale que ma condition sera toujours fausse en raison de l'absence de chevauchement de type entre Action.UP | Action.DOWN et Action.LEFT dans cette situation ( lien du terrain de jeu ):

class Component<S> {
    public state: S;
    public render() {}
}

enum Action {
    UP,
    DOWN,
    LEFT,
    RIGHT,
}

interface State {
    action: Action;
}

const initialAction: Action.UP | Action.DOWN = Action.UP;
class MyComponent extends Component<State> {
    public state = {
        action: initialAction,
    }

    public render() {
        // Why is action not of type Action as declared in the interface?
        const isLateral = this.state.action === Action.LEFT;
    }
}

Si this.state.action n'a qu'une valeur initiale de type Action.UP | Action.DOWN , mais est déclaré en tant que type Action dans l'interface, pourquoi ne puis-je pas le comparer à Action.LEFT ? Si je peux réattribuer à l'avenir this.state.action à LEFT ou RIGHT , pourquoi la condition est-elle toujours fausse? P >


10 commentaires

Pourquoi ne pas définir state dans le constructeur au lieu de le redéfinir dans la classe enfant?


@ShaunLuttin C'était censé être un MVCE qui se moque de l'apparence de React puisque c'est ce que j'utilise.


Bon ... même ainsi, pourquoi redéfinir la propriété state alors qu'elle est déjà définie dans la classe Component de React?


@ShaunLuttin Pardonnez-moi (je suis nouveau dans TypeScript), mais TS fait-il la distinction entre une propriété de classe et sa valeur initiale? J'avais l'impression que je pourrais éviter le passe-partout du constructeur en attribuant la propriété de classe (comme avec les propriétés de classe proposées par ECMAScript).


Oui. TypeScript fait la distinction entre une propriété de classe et sa valeur initiale. Vous pouvez affecter la propriété de classe en même temps que vous définissez la propriété de classe. Si vous faites cela dans une classe enfant, vous redéfinissez la propriété en plus de définir sa valeur initiale.


Ce que vous faisiez au départ équivaut à ceci: typescriptlang.org/ jouer /…


De plus, j'ai ajouté une citation à ma réponse qui montre que l'utilisation du constructeur pour définir l'état initial est ce que la documentation React recommande.


@ShaunLuttin D'accord, alors y a-t-il une raison de ne pas faire public state: MyState = {…} car je suis habitué à la syntaxe ES à part le fait que je dois le saisir explicitement? De cette façon, je n'aurai pas besoin du constructeur.


La définition de l'état initial en redéfinissant la propriété state de la classe parent Component peut être problématique de manière surprenante. J'ai bien peur de ne pas en savoir assez sur React pour savoir quelles surprises pourraient nous attendre lorsque nous ne suivons pas la documentation.


@ShaunLuttin D'accord, merci pour l'explication.


3 Réponses :


0
votes

Vous devez taper l'état public à State comme ceci:

public state: State = {
    action: initialAction,
}

Sinon, Typescript en déduira que state.action est du même type que initialAction .

Pour répondre à la question dans votre commentaire

( lien du terrain de jeu )

Vous avez redéfini la valeur et le type de state dans votre classe enfant lorsque vous définissez comme une nouvelle propriété.

Mon lien illustre cela. J'ai ajouté une nouvelle propriété sur la classe parente appelée count avec un type number . Si je le définis dans ma classe enfant comme une nouvelle propriété, il l'inférera comme any et peut donc être défini sur une chaîne dans render () .

Cependant, vous pouvez voir comment j'ai géré votre propriété state d'origine. Pour utiliser le type d'état du parent, vous pouvez appeler super () dans le constructeur, puis définir this.state.action = initialAction; .

De cette façon, lorsque vous appelez render () , il recherchera le type Action sur this.state.action , et non le nouveau défini Action.UP | Action.DOWN


1 commentaires

D'accord, cela a du sens. Mais pourquoi dois-je à nouveau spécifier si la classe Component définit public state: S ?



2
votes

Réponse à "Pourquoi?"

Pourquoi l'action n'est-elle pas de type Action comme déclaré dans l'interface?

Il est d'un type différent car la classe enfant redéfinit la propriété state et lui donne un type plus étroit que dans la classe parent. Si vous faites cela par conception, vous pouvez accéder à l'interface plus large de la classe parente en effectuant un cast dans render () .

class MyComponent extends Component<State> {

    constructor() { 
        super();
        this.state = {
            action: initialAction,
        }
    }

    public render() {
        const isLateral = this.state.action === Action.LEFT;
    }
}

Approche recommandée

Vous pouvez également faire ce que La documentation du composant React dit:

... si votre composant a besoin d'utiliser l'état local, attribuez l'état initial à this.state directement dans le constructeur:

Autrement dit, ne redéfinissez pas la propriété state de la classe parente; à la place, utilisez la propriété state déjà définie que extend vous donne. Définissez sa valeur initiale dans le constructeur.

const isLateral = (this as Component<State>).state.action === Action.LEFT;

0 commentaires

0
votes

dans notre cas, nous avons oublié {} car const A: React.FC = (first, second, ... rest) devait être const A: React.FC = ({premier, deuxième, ... reste}) . C'est horrible :)


0 commentaires