typsicher Wählen Sie das Ereignis onChange mit reactjs und typescript aus

71

Ich habe herausgefunden, wie man einen Ereignishandler an ein SELECT-Element bindet, indem ich eine hässliche Besetzung des Ereignisses mit einem beliebigen Element verwende.

Ist es möglich, den Wert typsicher abzurufen, ohne ihn zu übertragen?

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>
        );
    }
}
davestevens
quelle
2
Was bedeutet es typsicher?
François Richard
Ich denke, es bedeutet, durch Typoskript kompiliert zu werden und zu überprüfen, ob alle Variablenzuweisungen von ihrem Typ in Ordnung sind. Wir sprechen natürlich über Typoskript, nicht über Javascript
Cyril Gandon

Antworten:

66

Ich habe versucht, zu verwenden, React.FormEvent<HTMLSelectElement>aber es führte zu einem Fehler im Editor, obwohl EventTargetim Code nichts sichtbar ist:

Die Eigenschaft 'Wert' ist für den Wert vom Typ 'EventTarget' nicht vorhanden.

Dann wechselte ich React.FormEventzu React.ChangeEventund es half:

private changeName(event: React.ChangeEvent<HTMLSelectElement>) {
    event.preventDefault();
    this.props.actions.changeName(event.target.value);
}
Sergei Basharov
quelle
3
Dies sollte die oberste Antwort sein, FormEvent zeigt immer Fehler an, wenn ich auf e.target.value
Capaj
62

Seit dem Upgrade meiner Eingaben auf die Reaktion 0.14.43 (ich bin mir nicht sicher, wann dies genau eingeführt wurde) ist der Typ React.FormEvent jetzt generisch und macht eine Besetzung überflüssig.

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>
        );
    }
}
davestevens
quelle
15
Ab Dezember 2016 muss man verwenden event.currentTarget.value , um die Daten aus dem Formular zu erhalten.
Martin Majewski
1
Dave, erwäge, dein Beispiel zu aktualisieren.
Alex
2
@ MartinMajewski Ich habe bearbeitet, um currentTarget zu verwenden ( joequery.me/code/event-target-vs-event-currenttarget-30-seconds ist eine gute Erklärung)
davestevens
12

Update: Die offiziellen Typdefinitionen für React enthalten seit einiger Zeit Ereignistypen als generische Typen, sodass Sie jetzt die vollständige Überprüfung der Kompilierungszeit durchführen können und diese Antwort veraltet ist.


Ist es möglich, den Wert typsicher abzurufen, ohne ihn zu übertragen?

Ja. Wenn Sie sich über das Element sicher sind, an das Ihr Handler angehängt ist, können Sie Folgendes tun:

<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'
}

Live-Demo

Der TypeScript-Compiler lässt diese Typzusicherung zu , da ein HTMLSelectElement ein EventTarget ist . Danach sollte es typsicher sein, da Sie wissen, dass e.target ein HTMLSelectElement ist , weil Sie gerade Ihren Ereignishandler daran angehängt haben.

Um jedoch die Typensicherheit zu gewährleisten (die in diesem Fall beim Refactoring relevant ist), muss auch der tatsächliche Laufzeittyp überprüft werden:

if (!(target instanceof HTMLSelectElement))
{
    throw new TypeError("Expected a HTMLSelectElement.");
}
John Weisz
quelle
Ich nehme an, das ist besser als das Casting für irgendeinen, aber Sie verwenden immer noch die Typzusicherung, und das habe ich versucht zu vermeiden.
Davevestens
Ja, aber da Sie völlig sicher sind, dass dies e.targeteine ist HTMLSelectElement, ist diese Typzusicherung sicher , und alle nachfolgenden Typprüfungen sind von Natur aus sicher, z target.value. Wenn Sie es absolut kugelsicher machen möchten, können Sie auch eine Laufzeitprüfung für seinen Typ durchführen und ein TypeErrorif auslösen, das e.targetnicht vom richtigen Typ ist. Dies wird eigentlich nie passieren, aber es fügt eine zusätzliche Schicht garantierter Typensicherheit hinzu.
John Weisz
stackoverflow.com/questions/260626/what-is-type-safe Was ich unter typesafe verstehe, ist, dass die Typprüfung vom Compiler auferlegt wird. Das bedeutet keine Typzusicherungen und nicht abhängig von Laufzeitausnahmen.
Davevestens
1
Ich habe zuvor kommentiert, dass der Ereignishandler öffentlich und nicht privat sein sollte. Ich habe seitdem festgestellt, dass das falsch war. Selbst wenn wir zur Laufzeit private Methoden hätten (was wir hoffentlich in Zukunft tun werden ), würde eine private Methode hier immer noch gut funktionieren, da React die private Methode nicht direkt aufrufen muss, sondern nur die Pfeilfunktion, die Sie übergeben als die onChangeStütze.
Matt Browne
9

Am einfachsten ist es, der Variablen, die den Wert empfängt, einen Typ hinzuzufügen:

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

Oder Sie könnten die valueImmobilie genauso wie event.targetfolgt besetzen:

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

Bearbeiten:

Zuletzt können Sie definieren, was EventTarget.valuesich in einer separaten .d.tsDatei befindet. Der Typ muss jedoch dort kompatibel sein, wo er an anderer Stelle verwendet wird, und Sie werden ihn anyohnehin wieder verwenden.

globals.d.ts

interface EventTarget {
    value: any;
}
Gedankenrepo
quelle
3
Ich hatte gehofft, die "wie jede" Besetzung komplett loszuwerden.
Davevestens
Vielen Dank für Ihre Gedanken, aber das beantwortet meine Frage immer noch nicht. Ich denke, die Antwort ist, dass die .d.ts von react geändert werden müssten, damit die Signatur des onChange eines SELECT-Elements ein neues SelectFormEvent verwendet. Ansonsten braucht es, wie Sie betonen, eine Besetzung. Ich denke, ich könnte das in ein MYSELECT-Tag einkapseln.
Davevestens
1
Okay, vergiss nicht zu antworten und akzeptiere deine Antwort :) Selbstantwort
nachdenklich
Eine bessere Möglichkeit zum Hinzufügen EventTarget.valuebesteht darin, das globale eventTarget zu erweitern, was über eine beliebige .ts-Datei erfolgen kann:declare global { interface EventTarget { value: any; }}
JasonS
8

Es klappt:

type HtmlEvent = React.ChangeEvent<HTMLSelectElement>

const onChange: React.EventHandler<HtmlEvent> = 
   (event: HtmlEvent) => { 
       console.log(event.target.value) 
   }
Петр Графинов
quelle
5

In meinem Fall wurde das onChange-Ereignis als React.ChangeEvent eingegeben:

onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => {
           console.warn('onChange TextInput value: ' + e.target.value);
           } 
         }
Jackkobec
quelle
4

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 });
}
Marco Lackovic
quelle
3

Soweit ich das beurteilen kann, ist dies derzeit nicht möglich - eine Besetzung ist immer erforderlich.

Um dies zu ermöglichen, müssten die .d.ts von react so geändert werden, dass die Signatur von onChange eines SELECT-Elements ein neues SelectFormEvent verwendet. Der neue Ereignistyp würde das Ziel verfügbar machen, wodurch der Wert verfügbar gemacht wird. Dann könnte der Code typsicher sein.

Andernfalls ist immer eine Besetzung erforderlich.

Ich könnte das alles in einem MYSELECT-Tag zusammenfassen.

davestevens
quelle
0

Zusätzlich zu der Antwort von @oughtrepo:

Bis wir in React keine definitiv eingegebenen Ereignisse haben, kann es nützlich sein, eine spezielle Zielschnittstelle für Eingabesteuerelemente zu haben:

export interface FormControlEventTarget extends EventTarget{
    value: string;
}

Und dann in Ihrem Code, der in diesen Typ umgewandelt wurde, wo IntelliSense- Unterstützung angebracht ist:

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

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