web-dev-qa-db-fra.com

typesafe sélectionner un événement onChange en utilisant reactjs et typescript

J'ai compris comment attacher un gestionnaire d'événement sur un élément SELECT en utilisant une distribution laide de l'événement en un.

Est-il possible de récupérer la valeur de manière sécurisée sans transtyper?

import React = require('react');

interface ITestState {
    selectedValue: string;
}

export class Test extends React.Component<{}, ITestState> {

    constructor() {
        super();
        this.state = { selectedValue: "A" };
    }

    change(event: React.FormEvent) {
        console.log("Test.change");
        console.log(event.target); // in chrome => <select class="form-control" id="searchType" data-reactid=".0.0.0.0.3.1">...</select>

        // Use cast to any works but is not type safe
        var unsafeSearchTypeValue = ((event.target) as any).value;

        console.log(unsafeSearchTypeValue); // in chrome => B

        this.setState({
            selectedValue: unsafeSearchTypeValue
        });
    }

    render() {
        return (
            <div>
                <label htmlFor="searchType">Safe</label>
                <select className="form-control" id="searchType" onChange={ e => this.change(e) } value={ this.state.selectedValue }>
                    <option value="A">A</option>
                    <option value="B">B</option>
                </select>
                <h1>{this.state.selectedValue}</h1>
            </div>
        );
    }
}
36
davestevens

Depuis la mise à niveau de mes saisies pour réagir à 0.14.43 (je ne sais pas exactement quand cela a été introduit), le type React.FormEvent est maintenant générique, ce qui supprime la nécessité d'un transtypage.

import React = require('react');

interface ITestState {
    selectedValue: string;
}

export class Test extends React.Component<{}, ITestState> {

    constructor() {
        super();
        this.state = { selectedValue: "A" };
    }

    change(event: React.FormEvent<HTMLSelectElement>) {
        // No longer need to cast to any - hooray for react!
        var safeSearchTypeValue: string = event.currentTarget.value;

        console.log(safeSearchTypeValue); // in chrome => B

        this.setState({
            selectedValue: safeSearchTypeValue
        });
    }

    render() {
        return (
            <div>
                <label htmlFor="searchType">Safe</label>
                <select className="form-control" id="searchType" onChange={ e => this.change(e) } value={ this.state.selectedValue }>
                    <option value="A">A</option>
                    <option value="B">B</option>
                </select>
                <h1>{this.state.selectedValue}</h1>
            </div>
        );
    }
}
47
davestevens

J'ai essayé d'utiliser React.FormEvent<HTMLSelectElement> mais cela a entraîné une erreur dans l'éditeur, même s'il n'y a pas de EventTarget visible dans le code:

La propriété 'valeur' ​​n'existe pas sur la valeur de type 'EventTarget'

Ensuite, j'ai changé React.FormEvent en React.ChangeEvent et cela a aidé:

private changeName(event: React.ChangeEvent<HTMLSelectElement>) {
    event.preventDefault();
    this.props.actions.changeName(event.target.value);
}
25
Sergei Basharov

Mise à jour: les définitions de types officielles de React incluent les types d'événements en tant que types génériques depuis un certain temps. Vous disposez donc maintenant d'une vérification complète au moment de la compilation et cette réponse est obsolète.


Est-il possible de récupérer la valeur de manière sécurisée sans transtyper?

Oui. Si vous êtes certain de l'élément auquel votre gestionnaire est attaché, vous pouvez faire:

<select onChange={ e => this.selectChangeHandler(e) }>
    ...
</select>
private selectChangeHandler(e: React.FormEvent)
{
    var target = e.target as HTMLSelectElement;
    var intval: number = target.value; // Error: 'string' not assignable to 'number'
}

Démo en direct

Le compilateur TypeScript autorisera cette assertion de type, car un HTMLSelectElement est un EventTarget. Après cela, il devrait être sûr pour le type, car vous savez que e.target est un HTMLSelectElement, parce que vous venez d'y attacher votre gestionnaire d'événements.

Cependant, pour garantir type-safety (qui, dans ce cas, est pertinent lors du refactoring), il est également nécessaire de vérifier le type d'exécution réel:

if (!(target instanceof HTMLSelectElement))
{
    throw new TypeError("Expected a HTMLSelectElement.");
}
12
John Weisz

Le moyen le plus simple consiste à ajouter un type à la variable qui reçoit la valeur, comme ceci:

var value: string = (event.target as any).value;

Ou vous pouvez convertir la propriété value ainsi que event.target comme ceci:

var value = ((event.target as any).value as string);

Modifier:

Enfin, vous pouvez définir ce que EventTarget.value se trouve dans un fichier .d.ts séparé. Cependant, le type devra être compatible là où il est utilisé ailleurs, et vous finirez par utiliser any de toute façon.

globals.d.ts

interface EventTarget {
    value: any;
}
11
thoughtrepo

Ça marche: 

type HtmlEvent = React.ChangeEvent<HTMLSelectElement>

const onChange: React.EventHandler<HtmlEvent> = 
   (event: HtmlEvent) => { 
       console.log(event.target.value) 
   }

Autant que je sache, cela n’est actuellement pas possible - un casting est toujours nécessaire.

Pour que cela soit possible, il faudrait que les fichiers .d.ts de react soient modifiés de sorte que la signature du onChange d’un élément SELECT utilise un nouveau SelectFormEvent. Le nouveau type d'événement exposerait la cible, ce qui expose la valeur. Ensuite, le code pourrait être typesafe.

Sinon, il y aura toujours le besoin d'un casting pour tout.

Je pourrais encapsuler tout cela dans une balise MYSELECT.

3
davestevens

JSX:

<select value={ this.state.foo } onChange={this.handleFooChange}>
    <option value="A">A</option>
    <option value="B">B</option>
</select>

TypeScript :

private handleFooChange = (event: React.FormEvent<HTMLSelectElement>) => {
    const element = event.target as HTMLSelectElement;
    this.setState({ foo: element.value });
}
0
Marco Lackovic

En plus de la réponse de @ Thingtrepo:

Jusqu'à ce que nous n'ayons pas clairement défini les événements dans React, il peut être utile de disposer d'une interface cible spéciale pour les contrôles d'entrée:

export interface FormControlEventTarget extends EventTarget{
    value: string;
}

Et ensuite, dans votre code transtypé vers ce type, il est approprié d’avoir IntelliSense support:

 import {FormControlEventTarget} from "your.helper.library"

 (event.target as FormControlEventTarget).value;
0
Artru