使用reactjs和typescript的typesafe select onChange事件


72

我已经弄清楚了如何使用事件的丑陋转换将事件处理程序绑定到SELECT元素上。

是否可以以类型安全的方式检索值而无需强制转换为任何值?

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>
        );
    }
}

2
类型安全是什么意思?
弗朗索瓦·理查德

我想这意味着要通过打字稿进行编译,并检查所有变量分配在其类型上是否正确。我们谈论的是打字稿,当然不是javascript
Cyril Gandon

Answers:


67

我尝试使用,React.FormEvent<HTMLSelectElement>但是即使EventTarget在代码中没有可见的代码,也导致了编辑器错误:

类型'EventTarget'的值不存在属性'value'

然后我更改React.FormEventReact.ChangeEvent,它有所帮助:

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

3
这应该是最简单的方法,如果我访问e.target.value,FormEvent总是显示错误
Capaj

62

由于将我的类型升级为反应0.14.43(我不确定确切何时引入),因此React.FormEvent类型现在是通用的,这消除了强制转换的需要。

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>
        );
    }
}

15
从2016年12月开始,必须使用 event.currentTarget.value 来从表单中获取数据。
Martin Majewski

1
戴夫,请考虑更新您的示例。
亚历克斯

2
@MartinMajewski我已编辑使用currentTarget(joequery.me/code/event-target-vs-event-currenttarget-30-seconds是一个很好的解释)
davestevens

12

更新:React的官方类型定义已经有一段时间将事件类型作为通用类型了,因此您现在已经进行了完整的编译时检查,而这个答案已经过时了。


是否可以以类型安全的方式检索值而无需强制转换为任何值?

是。如果确定处理程序附加到的元素,则可以执行以下操作:

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

现场演示

TypeScript编译器将允许此类型断言,因为HTMLSelectElementEventTarget。之后,它应该是类型安全的,因为您知道e.target是一个HTMLSelectElement,因为您刚刚将事件处理程序附加到了它。

但是,为了保证类型安全(在这种情况下,在重构时是相关的),还需要检查实际的运行时类型:

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

我想这比强制转换要好,但是您仍在使用类型断言,而这正是我试图避免的事情。
davestevens

是的,但是由于您完全可以确定e.targetHTMLSelectElement,因此此类型声明是安全的,并且所有后续的类型检查本质上也是安全的,例如target.value。如果要使其绝对防弹,也可以在运行时检查其类型,并抛出TypeErrorife.target类型不正确的信息。这将永远不会发生,但是会增加一层额外的保证类型安全的层。
约翰·威斯

stackoverflow.com/questions/260626/what-is-type-safe我通过typesafe理解的是类型检查是由编译器强加的。这意味着没有类型声明,也不依赖于运行时异常。
davestevens's

1
我之前评论过,事件处理程序应该是公共的,而不是私有的。从那以后,我意识到这是不正确的。即使我们在运行时有私有方法(希望将来会使用),私有方法在这里仍然可以正常工作,因为React不需要直接调用私有方法,而只需调用您要传递的arrow函数作为onChange道具。
马特·布朗

9

最简单的方法是将类型添加到接收值的变量中,如下所示:

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

或者,您也可以像这样投射value属性event.target

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

编辑:

最后,您可以定义EventTarget.value单独.d.ts文件中的内容。但是,该类型必须与其他地方使用的类型兼容,并且any无论如何您最终都会再次使用。

全球公司

interface EventTarget {
    value: any;
}

3
我希望彻底摆脱“任何人”的角色。
davestevens,2015年

感谢您的想法,但这仍然无法回答我的问题。我认为答案是需要修改react的.d.ts,以便SELECT元素的onChange的签名使用新的SelectFormEvent。否则,正如您所指出的,它需要强制转换。我想我可以将其封装在MYSELECT标记中。
davestevens,2015年

1
好吧,别忘了回答并接受您的回答:)自我回答
thinkrepo 2015年

更好的添加方法EventTarget.value是增加全局eventTarget,可以通过任何.ts文件完成此操作:declare global { interface EventTarget { value: any; }}
JasonS 2016年

8

有用:

type HtmlEvent = React.ChangeEvent<HTMLSelectElement>

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

5

在我的情况下,onChange事件的类型为React.ChangeEvent:

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

4

JSX

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

打字稿

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

3

据我所知,这目前是不可能的-始终需要强制转换。

为了使其成为可能,需要修改react的.d.ts,以便SELECT元素的onChange的签名使用新的SelectFormEvent。新的事件类型将公开目标,该目标公开值。然后,代码可以是类型安全的。

否则,总是需要对任何对象进行强制转换。

我可以将所有内容封装在MYSELECT标记中。


0

除了@thoughtrepo的答案:

在React中没有确定类型的事件之前,为输入控件提供特殊的目标接口可能会很有用:

export interface FormControlEventTarget extends EventTarget{
    value: string;
}

然后在将代码转换为这种类型以适合获得 IntelliSense支持的位置:

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

 (event.target as FormControlEventTarget).value;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.